set g_spawnshieldtime 1 "number of seconds you are invincible after you spawned, this shield is lost after you fire"
set g_antilag 2 "AntiLag (0 = no AntiLag, 1 = verified client side hit scan, 2 = server side hit scan in the past, 3 = unverified client side hit scan)"
set g_antilag_nudge 0 "don't touch"
- set g_antilag_bullets 1 "Bullets AntiLag (0 = no AntiLag, 1 = server side hit scan in the past) - DO NOT TOUCH (severely changes weapon balance)"
set g_shootfromclient 2 "let client decide if it has the gun left or right; if set to 2, center handedness is allowed; see also cl_gunalign"
set g_shootfromeye 0 "shots are fired from your eye/crosshair; visual gun position can still be influenced by cl_gunalign 1 and 2"
set g_shootfromcenter 0 "weapon gets moved to the center, shots still come from the barrel of your weapon; visual gun position can still be influenced by cl_gunalign 1 and 2"
bind f7 menu_showsandboxtools
+seta menu_monsters_edit_spawn ""
+seta menu_monsters_edit_skin 0
+seta menu_monsters_edit_movetarget 1
+
set g_playerclip_collisions 1 "0 = disable collision testing against playerclips, might be useful on some defrag maps"
set g_botclip_collisions 1 "0 = disable collision testing against botclips, might be useful on some defrag maps"
set g_spawn_useallspawns 0 "use all spawns, e.g. also team spawns in non-teamplay, and all spawns, even enemy spawns, in teamplay"
// respawn delay
set g_respawn_delay 2 "number of seconds you have to wait before you can respawn again"
+ set g_respawn_delay_max 0 "number of seconds you can wait before you're forced to respawn (only effective with g_forced_respawn 1)"
set g_respawn_waves 0 "respawn in waves (every n seconds), intended to decrease overwhelming base attacks"
// overtime
seta menu_slist_showempty 1 "show servers even if they are no empty and have no opponents to play against"
seta menu_slist_modfilter "" // set to either: !modname or modname. modname of = means "same as we are running now".
+ // other serverlist cvars
+ seta menu_slist_categories 1
+ seta menu_slist_categories_onlyifmultiple 1
+ seta menu_slist_purethreshold 0
+ seta menu_slist_modimpurity 0
+ seta menu_slist_recommendations 3
+ seta menu_slist_recommendations_maxping 150
+ seta menu_slist_recommendations_minfreeslots 1
+ seta menu_slist_recommendations_minhumans 0
+ seta menu_slist_recommendations_purethreshold -1
+
+ // serverlist category override cvars
+ seta menu_slist_categories_CAT_FAVORITED_override ""
+ seta menu_slist_categories_CAT_RECOMMENDED_override ""
+ seta menu_slist_categories_CAT_NORMAL_override ""
+ seta menu_slist_categories_CAT_SERVERS_override "CAT_NORMAL"
+ seta menu_slist_categories_CAT_XPM_override "CAT_NORMAL"
+ seta menu_slist_categories_CAT_MODIFIED_override ""
+ seta menu_slist_categories_CAT_OVERKILL_override ""
+ seta menu_slist_categories_CAT_MINSTAGIB_override ""
+ seta menu_slist_categories_CAT_DEFRAG_override ""
+
seta menu_weaponarena ""
seta menu_maxplayers 16 "maxplayers value when the menu starts a game"
exec gamemodes.cfg
exec mutators.cfg
exec notifications.cfg
+exec monsters.cfg
// load console command aliases and settings
exec commands.cfg
alias cl_hook_gamestart_dom
alias cl_hook_gamestart_ctf
alias cl_hook_gamestart_lms
- alias cl_hook_gamestart_arena
alias cl_hook_gamestart_ca
alias cl_hook_gamestart_kh
alias cl_hook_gamestart_ons
alias cl_hook_gamestart_cts
alias cl_hook_gamestart_ka
alias cl_hook_gamestart_ft
+alias cl_hook_gamestart_invasion
alias cl_hook_gameend
alias cl_hook_activeweapon
alias sv_hook_gamestart_dom
alias sv_hook_gamestart_ctf
alias sv_hook_gamestart_lms
- alias sv_hook_gamestart_arena
alias sv_hook_gamestart_ca
alias sv_hook_gamestart_kh
alias sv_hook_gamestart_ons
alias sv_hook_gamestart_cts
alias sv_hook_gamestart_ka
alias sv_hook_gamestart_ft
+alias sv_hook_gamestart_invasion
alias sv_hook_gamerestart
alias sv_hook_gameend
seta leadlimit_override -1 "Lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
seta capturelimit_override -1 "Capture limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
seta captureleadlimit_override -1 "Capture llead imit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
- seta g_arena_point_limit -1 "Arena point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
- seta g_arena_point_leadlimit -1 "Arena point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
seta g_domination_point_limit -1 "Domination point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
seta g_domination_point_leadlimit -1 "Domination point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
seta g_keyhunt_point_limit -1 "Keyhunt point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
seta g_race_laps_limit -1 "Race laps limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
seta g_nexball_goallimit -1 "Nexball goal limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
seta g_nexball_goalleadlimit -1 "Nexball goal lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_invasion_round_limit -1 "Invasion round limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
// =================================
set g_kh_respawn_delay 0
set g_kh_respawn_waves 0
set g_kh_weapon_stay 0
- set g_arena_respawn_delay 0
- set g_arena_respawn_waves 0
- set g_arena_weapon_stay 0
set g_ca_respawn_delay 0
set g_ca_respawn_waves 0
set g_ca_weapon_stay 0
set g_ft_respawn_waves 0
set g_ft_respawn_delay 0
set g_ft_weapon_stay 0
+set g_invasion_respawn_waves 0
+set g_invasion_respawn_delay 0
+set g_invasion_weapon_stay 0
- // =======
- // arena
- // =======
- set g_arena 0 "Arena: many one-on-one rounds are played to find the winner"
- set g_arena_maxspawned 2 "maximum number of players to spawn at once (the rest is spectating, waiting for their turn)"
- set g_arena_roundbased 1 "if disabled, the next player will spawn as soon as someone dies"
- set g_arena_round_timelimit 180
- set g_arena_warmup 5 "time, newly spawned players have to prepare themselves in round based matches"
-
-
// =========
// assault
// =========
set g_ca_teams 0
-
// ==================
// capture the flag
// ==================
set g_ctf_pass_velocity 750 "how fast or far a player can pass the flag"
set g_ctf_allow_vehicle_touch 0 "allow flags to be picked up/captured/returned without even leaving the vehicle"
set g_ctf_allow_vehicle_carry 1 "allow players to hold flags inside a vehicle"
+set g_ctf_allow_monster_touch 0 "allow flags to be returned by monsters"
set g_ctf_shield_max_ratio 0 "shield at most this percentage of a team from the enemy flag (try: 0.4 for 40%)"
set g_ctf_shield_min_negscore 20 "shield the player from the flag if he's got this negative amount of points or less"
set g_race_qualifying_timelimit 0
set g_race_qualifying_timelimit_override -1
set g_race_teams 0 "when 2, 3, or 4, the race is played as a team game (the team members can add up their laps)"
+
+// ==========
+// invasion
+// ==========
+set g_invasion 0 "Invasion: survive against waves of monsters"
+set g_invasion_round_timelimit 120 "maximum time to kill all monsters"
+set g_invasion_warmup 10 "time between waves to prepare for battle"
+set g_invasion_monster_count 10 "number of monsters on first wave (increments)"
+set g_invasion_zombies_only 0 "only spawn zombies"
//registercommand("hud_configure");
//registercommand("hud_save");
//registercommand("menu_action");
-
+
ConsoleCommand_macro_init();
registercvar("hud_usecsqc", "1");
// needs to be done so early because of the constants they create
CALL_ACCUMULATED_FUNCTION(RegisterWeapons);
+ CALL_ACCUMULATED_FUNCTION(RegisterMonsters);
CALL_ACCUMULATED_FUNCTION(RegisterGametypes);
CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
turrets_precache();
Tuba_Precache();
CSQCPlayer_Precache();
-
+
if(autocvar_cl_reticle)
{
if(autocvar_cl_reticle_item_normal) { precache_pic("gfx/reticle_normal"); }
if(autocvar_cl_reticle_item_nex) { precache_pic("gfx/reticle_nex"); }
}
-
+
get_mi_min_max_texcoords(1); // try the CLEVER way first
minimapname = strcat("gfx/", mi_shortname, "_radar.tga");
shortmapname = mi_shortname;
spn_origin_x = ReadShort();
spn_origin_y = ReadShort();
spn_origin_z = ReadShort();
-
+
if(is_new)
{
self.origin = spn_origin;
}
}
else { self.cnt = particleeffectnum("spawn_point_neutral"); }
-
+
self.draw = Spawn_Draw;
}
}
// this way the server can disable the sending of
// spawn origin or such to clients if wanted.
float entnum = ReadByte();
-
+
if(entnum)
{
self.origin_x = ReadShort();
}
}
}
-
+
// local spawn actions
if(is_new && (!entnum || (entnum == player_localentnum)))
{
button_zoom = FALSE;
}
}
-
+
//print(sprintf("Ent_ReadSpawnEvent(is_new = %d); origin = %s, entnum = %d, localentnum = %d\n", is_new, vtos(self.origin), entnum, player_localentnum));
}
case ENT_CLIENT_GAUNTLET: Ent_ReadHook(bIsNewEntity, ENT_CLIENT_GAUNTLET); break;
case ENT_CLIENT_ACCURACY: Ent_ReadAccuracy(); break;
case ENT_CLIENT_AUXILIARYXHAIR: Net_AuXair2(bIsNewEntity); break;
- case ENT_CLIENT_TURRET: ent_turret(); break;
+ case ENT_CLIENT_TURRET: ent_turret(); break;
case ENT_CLIENT_MODEL: CSQCModel_Read(bIsNewEntity); break;
- case ENT_CLIENT_ITEM: ItemRead(bIsNewEntity); break;
+ case ENT_CLIENT_ITEM: ItemRead(bIsNewEntity); break;
case ENT_CLIENT_BUMBLE_RAYGUN: bumble_raygun_read(bIsNewEntity); break;
case ENT_CLIENT_SPAWNPOINT: Ent_ReadSpawnPoint(bIsNewEntity); break;
case ENT_CLIENT_SPAWNEVENT: Ent_ReadSpawnEvent(bIsNewEntity); break;
else if(autocvar_cl_spawnzoom && zoomin_effect)
{
float spawnzoomfactor = bound(1, autocvar_cl_spawnzoom_factor, 16);
-
- current_viewzoom += (autocvar_cl_spawnzoom_speed * (spawnzoomfactor - current_viewzoom) * drawframetime);
+
+ current_viewzoom += (autocvar_cl_spawnzoom_speed * (spawnzoomfactor - current_viewzoom) * drawframetime);
current_viewzoom = bound(1 / spawnzoomfactor, current_viewzoom, 1);
if(current_viewzoom == 1) { zoomin_effect = 0; }
}
setsensitivityscale(pow(current_viewzoom, 1 - zoomsensitivity));
else
setsensitivityscale(1);
-
+
makevectors(view_angles);
if(autocvar_cl_velocityzoom && autocvar_cl_velocityzoom_type) // _type = 0 disables velocity zoom too
case 1: default: curspeed = vlen(v); break;
}
}
-
+
velocityzoom = bound(0, drawframetime / max(0.000000001, autocvar_cl_velocityzoom_time), 1); // speed at which the zoom adapts to player velocity
avgspeed = avgspeed * (1 - velocityzoom) + (curspeed / autocvar_cl_velocityzoom_speed) * velocityzoom;
velocityzoom = exp(float2range11(avgspeed * -autocvar_cl_velocityzoom / 1) * 1);
-
+
//print(ftos(avgspeed), " avgspeed, ", ftos(curspeed), " curspeed, ", ftos(velocityzoom), " return\n"); // for debugging
}
else
R_PolygonVertex(autocvar_vid_conheight * '0 1 0', tc_01, rgb, a);
R_EndPolygon();
}
-
+
// Draw the aiming reticle for weapons that use it
// reticle_type is changed to the item we are zooming / aiming with, to decide which reticle to use
// It must be a persisted float for fading out to work properly (you let go of the zoom button for
reticle_type = 1; // normal zoom
else if((activeweapon == WEP_NEX) && button_attack2)
reticle_type = 2; // nex zoom
-
+
if(reticle_type && autocvar_cl_reticle)
{
if(autocvar_cl_reticle_stretch)
}
}
}
-
+
float e1 = (autocvar_hud_postprocessing_maxbluralpha != 0);
float e2 = (autocvar_hud_powerup != 0);
if(autocvar_hud_postprocessing && (e1 || e2)) // TODO: Remove this code and re-do the postprocess handling in the engine, where it properly belongs.
old_bluralpha = 0;
}
- // edge detection postprocess handling done second (used by hud_powerup)
+ // edge detection postprocess handling done second (used by hud_powerup)
float sharpen_intensity = 0, strength_finished = getstatf(STAT_STRENGTH_FINISHED), invincible_finished = getstatf(STAT_INVINCIBLE_FINISHED);
if (strength_finished - time > 0) { sharpen_intensity += (strength_finished - time); }
if (invincible_finished - time > 0) { sharpen_intensity += (invincible_finished - time); }
-
+
sharpen_intensity = bound(0, ((getstati(STAT_HEALTH) > 0) ? sharpen_intensity : 0), 5); // Check to see if player is alive (if not, set 0) - also bound to fade out starting at 5 seconds.
-
+
if(autocvar_hud_powerup && sharpen_intensity > 0)
{
if(sharpen_intensity != old_sharpen_intensity) // reduce cvar_set spam as much as possible
{
if(time - hit_time < MAX_TIME_DIFF) // don't play the sound if it's too old.
sound(world, CH_INFO, "misc/hit.wav", VOL_BASE, ATTEN_NONE);
-
+
nextsound_hit_time = time + autocvar_cl_hitsound_antispam_time;
}
typehit_time = getstatf(STAT_TYPEHIT_TIME);
- if(typehit_time > nextsound_typehit_time)
+ if(typehit_time > nextsound_typehit_time)
{
if(time - typehit_time < MAX_TIME_DIFF) // don't play the sound if it's too old.
sound(world, CH_INFO, "misc/typehit.wav", VOL_BASE, ATTEN_NONE);
-
+
nextsound_typehit_time = time + autocvar_cl_hitsound_antispam_time;
}
//else
{
- if(gametype == MAPINFO_TYPE_FREEZETAG)
+ if(getstati(STAT_FROZEN))
+ drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
+ if(getstatf(STAT_REVIVE_PROGRESS))
{
- if(getstati(STAT_FROZEN))
- drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
- if(getstatf(STAT_REVIVE_PROGRESS))
- {
- DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
- drawstring_aspect(eY * 0.64 * vid_conheight, _("Revival progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL);
- }
+ DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
+ drawstring_aspect(eY * 0.64 * vid_conheight, _("Revival progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL);
}
if(autocvar_r_letterbox == 0)
CSQC_common_hud();
// crosshair goes VERY LAST
- if(!scoreboard_active && !camera_active && intermission != 2 && spectatee_status != -1 && hud == HUD_NORMAL)
+ if(!scoreboard_active && !camera_active && intermission != 2 && spectatee_status != -1 && hud == HUD_NORMAL)
{
if (!autocvar_crosshair_enabled) // main toggle for crosshair rendering
return;
-
+
string wcross_style;
float wcross_alpha, wcross_resolution;
wcross_style = autocvar_crosshair;
if(autocvar_crosshair_pickup)
{
float stat_pickup_time = getstatf(STAT_LAST_PICKUP);
-
+
if(pickup_crosshair_time < stat_pickup_time)
{
if(time - stat_pickup_time < MAX_TIME_DIFF) // don't trigger the animation if it's too old
pickup_crosshair_size = 1;
-
+
pickup_crosshair_time = stat_pickup_time;
}
if(autocvar_crosshair_hitindication)
{
vector hitindication_color = ((autocvar_crosshair_color_special == 1) ? stov(autocvar_crosshair_hitindication_per_weapon_color) : stov(autocvar_crosshair_hitindication_color));
-
+
if(hitindication_crosshair_time < hit_time)
{
if(time - hit_time < MAX_TIME_DIFF) // don't trigger the animation if it's too old
hitindication_crosshair_size = 1;
-
+
hitindication_crosshair_time = hit_time;
}
// handle the values
if (autocvar_crosshair_ring && activeweapon == WEP_NEX && nex_charge && autocvar_crosshair_ring_nex) // ring around crosshair representing velocity-dependent damage for the nex
{
- if (nex_chargepool || use_nex_chargepool) {
- use_nex_chargepool = 1;
+ if (nex_chargepool || use_nex_chargepool) {
+ use_nex_chargepool = 1;
ring_inner_value = nex_chargepool;
- } else {
+ } else {
nex_charge_movingavg = (1 - autocvar_crosshair_ring_nex_currentcharge_movingavg_rate) * nex_charge_movingavg + autocvar_crosshair_ring_nex_currentcharge_movingavg_rate * nex_charge;
- ring_inner_value = bound(0, autocvar_crosshair_ring_nex_currentcharge_scale * (nex_charge - nex_charge_movingavg), 1);
+ ring_inner_value = bound(0, autocvar_crosshair_ring_nex_currentcharge_scale * (nex_charge - nex_charge_movingavg), 1);
}
ring_inner_alpha = autocvar_crosshair_ring_nex_inner_alpha;
ring_rgb = wcross_color;
ring_image = "gfx/crosshair_ring_nexgun.tga";
}
- else if (autocvar_crosshair_ring && activeweapon == WEP_MINE_LAYER && minelayer_maxmines && autocvar_crosshair_ring_minelayer)
+ else if (autocvar_crosshair_ring && activeweapon == WEP_MINE_LAYER && minelayer_maxmines && autocvar_crosshair_ring_minelayer)
{
ring_value = bound(0, getstati(STAT_LAYED_MINES) / minelayer_maxmines, 1); // if you later need to use the count of bullets in another place, then add a float for it. For now, no need to.
ring_alpha = autocvar_crosshair_ring_minelayer_alpha;
ring_image = "gfx/crosshair_ring.tga";
}
- if(autocvar_crosshair_ring_reload && weapon_clipsize) // forces there to be only an ammo ring
+ if(autocvar_crosshair_ring_reload && weapon_clipsize) // forces there to be only an ammo ring
{
ring_value = bound(0, weapon_clipload / weapon_clipsize, 1);
ring_scale = autocvar_crosshair_ring_reload_size;
{
wcross_ring_prev = ((ring_image) ? TRUE : FALSE);
}
-
+
if(wcross_ring_prev)
{
if(f < 1)
{
vector wcross_color_old;
wcross_color_old = wcross_color;
-
+
if((autocvar_crosshair_dot_color_custom) && (autocvar_crosshair_dot_color != "0"))
wcross_color = stov(autocvar_crosshair_dot_color);
-
+
CROSSHAIR_DRAW(wcross_resolution * autocvar_crosshair_dot_size, "gfx/crosshairdot.tga", f * autocvar_crosshair_dot_alpha);
// FIXME why don't we use wcross_alpha here?cl_notice_run();
wcross_color = wcross_color_old;
if(autocvar__hud_configure)
HUD_Panel_Mouse();
-
+
if(hud && !intermission)
- {
+ {
if(hud == HUD_SPIDERBOT)
CSQC_SPIDER_HUD();
else if(hud == HUD_WAKIZASHI)
else if(hud == HUD_BUMBLEBEE_GUN)
CSQC_BUMBLE_GUN_HUD();
}
-
+
cl_notice_run();
-
+
// let's reset the view back to normal for the end
setproperty(VF_MIN, '0 0 0');
setproperty(VF_SIZE, '1 0 0' * w + '0 1 0' * h);
case PROJECTILE_NADE_PINK:
case PROJECTILE_NADE_BURN:
case PROJECTILE_NADE:
- rot = self.avelocity;
+ rot = self.avelocity;
break;
case PROJECTILE_HOOKBOMB:
rot = '1000 0 0'; // forward
switch(self.cnt)
{
- case PROJECTILE_BULLET_GLOWING:
- case PROJECTILE_BULLET_GLOWING_TRACER:
- adddynamiclight(self.origin, 50 * a, '1 1 0');
- break;
+ // Possibly add dlights here.
default:
break;
}
{
case PROJECTILE_ELECTRO: setmodel(self, "models/ebomb.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break;
case PROJECTILE_ROCKET: setmodel(self, "models/rocket.md3");self.traileffect = particleeffectnum("TR_ROCKET"); self.scale = 2; break;
- case PROJECTILE_BULLET: setmodel(self, "models/tracer.mdl");self.traileffect = particleeffectnum("tr_bullet"); break;
- case PROJECTILE_BULLET_GLOWING: setmodel(self, "models/tracer.mdl");self.traileffect = particleeffectnum("tr_rifle_weak"); break;
- case PROJECTILE_BULLET_GLOWING_TRACER: setmodel(self, "models/tracer.mdl");self.traileffect = particleeffectnum("tr_rifle"); break;
case PROJECTILE_CRYLINK: setmodel(self, "models/plasmatrail.mdl");self.traileffect = particleeffectnum("TR_CRYLINKPLASMA"); break;
case PROJECTILE_CRYLINK_BOUNCING: setmodel(self, "models/plasmatrail.mdl");self.traileffect = particleeffectnum("TR_CRYLINKPLASMA"); break;
case PROJECTILE_ELECTRO_BEAM: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break;
case PROJECTILE_TAG: setmodel(self, "models/laser.mdl"); self.traileffect = particleeffectnum("TR_ROCKET"); break;
case PROJECTILE_FLAC: setmodel(self, "models/hagarmissile.mdl"); self.scale = 0.4; self.traileffect = particleeffectnum("TR_SEEKER"); break;
case PROJECTILE_SEEKER: setmodel(self, "models/tagrocket.md3"); self.traileffect = particleeffectnum("TR_SEEKER"); break;
+
+ case PROJECTILE_MAGE_SPIKE: setmodel(self, "models/ebomb.mdl"); self.traileffect = particleeffectnum("TR_VORESPIKE"); break;
+ case PROJECTILE_SHAMBLER_LIGHTNING: setmodel(self, "models/ebomb.mdl"); self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break;
case PROJECTILE_RAPTORBOMB: setmodel(self, "models/vehicles/clusterbomb.md3"); self.gravity = 1; self.avelocity = '0 0 180'; self.traileffect = particleeffectnum(""); break;
case PROJECTILE_RAPTORBOMBLET: setmodel(self, "models/vehicles/bomblet.md3"); self.gravity = 1; self.avelocity = '0 0 180'; self.traileffect = particleeffectnum(""); break;
case PROJECTILE_BUMBLE_GUN: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break;
case PROJECTILE_BUMBLE_BEAM: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break;
-
+
case PROJECTILE_NADE_RED: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_red"); break;
case PROJECTILE_NADE_RED_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_red_burn"); break;
case PROJECTILE_NADE_BLUE: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_blue"); break;
self.scale = 1.5;
self.avelocity = randomvec() * 720;
break;
+ case PROJECTILE_SHAMBLER_LIGHTNING:
+ self.mins = '-8 -8 -8';
+ self.maxs = '8 8 8';
+ self.scale = 2.5;
+ self.avelocity = randomvec() * 720;
+ break;
case PROJECTILE_MINE:
self.mins = '-4 -4 -4';
self.maxs = '4 4 4';
break;
case PROJECTILE_WAKIROCKET:
loopsound(self, CH_SHOTS_SINGLE, "weapons/tag_rocket_fly.wav", VOL_BASE, ATTEN_NORM);
- break;
+ break;
/*
case PROJECTILE_WAKICANNON:
break;
precache_model("models/rocket.md3");
precache_model("models/tagrocket.md3");
precache_model("models/tracer.mdl");
-
+
precache_model("models/weapons/v_ok_grenade.md3");
precache_sound("weapons/electro_fly.wav");
} else
str = sprintf("%.1f", num/denom);
return str;
-
+
case SP_SUM:
f = pl.(scores[SP_KILLS]);
f -= pl.(scores[SP_DEATHS]);
drawstring(pos, value, hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
pos_x = px;
pos_y+= hud_fontsize_y;
-
+
return pos;
}
vector HUD_DrawMapStats(vector pos, vector rgb, vector bg_size) {
float stat_secrets_found, stat_secrets_total;
- float rows;
+ float stat_monsters_killed, stat_monsters_total;
+ float rows = 0;
string val;
+
+ // get monster stats
+ stat_monsters_killed = getstatf(STAT_MONSTERS_KILLED);
+ stat_monsters_total = getstatf(STAT_MONSTERS_TOTAL);
// get secrets stats
stat_secrets_found = getstatf(STAT_SECRETS_FOUND);
stat_secrets_total = getstatf(STAT_SECRETS_TOTAL);
// get number of rows
- rows = (stat_secrets_total ? 1 : 0);
+ if(stat_secrets_total)
+ rows += 1;
+ if(stat_monsters_total)
+ rows += 1;
// if no rows, return
if (!rows)
// draw table header
drawstring(pos, _("Map stats:"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
pos_y += 1.25 * hud_fontsize_y + autocvar_scoreboard_border_thickness;
-
- // draw table
+
+ // draw table
vector tmp = '0 0 0';
tmp_x = sbwidth;
tmp_y = hud_fontsize_y * rows;
else
drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg * 0.75, DRAWFLAG_NORMAL);
-
+
+ // draw monsters
+ if(stat_monsters_total)
+ {
+ val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
+ pos = HUD_DrawKeyValue(pos, _("Monsters killed:"), val);
+ }
+
// draw secrets
- val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
- pos = HUD_DrawKeyValue(pos, _("Secrets found:"), val);
+ if(stat_secrets_total)
+ {
+ val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
+ pos = HUD_DrawKeyValue(pos, _("Secrets found:"), val);
+ }
-
+
// update position
pos_y += 1.25 * hud_fontsize_y;
return pos;
pos = HUD_DrawScoreboardAccuracyStats(pos, rgb, bg_size);
}
-
+
if(teamplay)
pos = HUD_DrawMapStats(pos, Team_ColorRGB(myteam), bg_size);
else
str = strcat(str, _(" or"));
if(teamplay)
{
- str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
+ str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
(teamscores_label[ts_primary] == "score") ? CTX(_("SCO^points")) :
(teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
TranslateScoresLabel(teamscores_label[ts_primary])));
}
else
{
- str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
+ str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
(scores_label[ps_primary] == "score") ? CTX(_("SCO^points")) :
(scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
TranslateScoresLabel(scores_label[ps_primary])));
str = strcat(str, _(" or"));
if(teamplay)
{
- str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
+ str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
(teamscores_label[ts_primary] == "score") ? CTX(_("SCO^points")) :
(teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
TranslateScoresLabel(teamscores_label[ts_primary])));
}
else
{
- str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
+ str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
(scores_label[ps_primary] == "score") ? CTX(_("SCO^points")) :
(scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
TranslateScoresLabel(scores_label[ps_primary])));
// a negative number means we are awaiting respawn, time value is still the same
respawn_time *= -1; // remove mark now that we checked it
respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
-
+
str = sprintf(_("^1Respawning in ^3%s^1..."),
(autocvar_scoreboard_respawntime_decimals ?
count_seconds_decs(respawn_time - time, autocvar_scoreboard_respawntime_decimals)
o = hotspot;
ri = '1 0 0';
up = '0 1 0';
-
+
rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
o = rotate(o, rot) + org;
ri = rotate(ri, rot);
case "item-shield": return _("Shield");
case "item-fuelregen": return _("Fuel regen");
case "item-jetpack": return _("Jet Pack");
- case "freezetag_frozen": return _("Frozen!");
+ case "frozen": return _("Frozen!");
case "tagged-target": return _("Tagged");
case "vehicle": return _("Vehicle");
default: return s;
return;
++waypointsprite_newcount;
-
+
float dist;
dist = vlen(self.origin - view_origin);
-
+
float a;
a = self.alpha * autocvar_hud_panel_fg_alpha;
if(rgb == '0 0 0')
{
self.teamradar_color = '1 0 1';
- print(sprintf("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage));
+ print(sprintf("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage));
}
if(time - floor(time) > 0.5)
float ang;
o = project_3d_to_2d(self.origin);
- if(o_z < 0
- || o_x < (vid_conwidth * waypointsprite_edgeoffset_left)
- || o_y < (vid_conheight * waypointsprite_edgeoffset_top)
- || o_x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
+ if(o_z < 0
+ || o_x < (vid_conwidth * waypointsprite_edgeoffset_left)
+ || o_y < (vid_conheight * waypointsprite_edgeoffset_top)
+ || o_x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
|| o_y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
{
// scale it to be just in view
o_z = 0;
float edgedistance_min, crosshairdistance;
- edgedistance_min = min((o_y - (vid_conheight * waypointsprite_edgeoffset_top)),
+ edgedistance_min = min((o_y - (vid_conheight * waypointsprite_edgeoffset_top)),
(o_x - (vid_conwidth * waypointsprite_edgeoffset_left)),
- (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o_x,
+ (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o_x,
(vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o_y);
float vidscale;
}
o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
-
+
string txt;
if(autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
txt = _("Spam");
const float STAT_WEAPONS2 = 74;
const float STAT_WEAPONS3 = 75;
+const float STAT_MONSTERS_TOTAL = 76;
+const float STAT_MONSTERS_KILLED = 77;
+
// mod stats (1xx)
const float STAT_REDALIVE = 100;
const float STAT_BLUEALIVE = 101;
const float PROJECTILE_ELECTRO = 1;
const float PROJECTILE_ROCKET = 2;
const float PROJECTILE_TAG = 3;
- const float PROJECTILE_BULLET = 4;
const float PROJECTILE_CRYLINK = 5;
const float PROJECTILE_ELECTRO_BEAM = 6;
const float PROJECTILE_GRENADE = 7;
const float PROJECTILE_HOOKBOMB = 16;
const float PROJECTILE_HAGAR = 17;
const float PROJECTILE_HAGAR_BOUNCING = 18;
- const float PROJECTILE_BULLET_GLOWING = 19;
const float PROJECTILE_CRYLINK_BOUNCING = 20;
const float PROJECTILE_FIREBALL = 21;
const float PROJECTILE_FIREMINE = 22;
- const float PROJECTILE_BULLET_GLOWING_TRACER = 23;
const float PROJECTILE_RAPTORCANNON = 24;
const float PROJECTILE_RAPTORBOMB = 25;
const float PROJECTILE_BUMBLE_GUN = 30;
const float PROJECTILE_BUMBLE_BEAM = 31;
+float PROJECTILE_MAGE_SPIKE = 32;
+float PROJECTILE_SHAMBLER_LIGHTNING = 33;
+
const float PROJECTILE_NADE_RED = 50;
const float PROJECTILE_NADE_RED_BURN = 51;
const float PROJECTILE_NADE_BLUE = 52;
CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, glowmod_x, 255, 0, 255) \
CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, glowmod_y, 255, 0, 255) \
CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, glowmod_z, 255, 0, 255) \
+ CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, colormod_x, 255, 0, 255) \
+ CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, colormod_y, 255, 0, 255) \
+ CSQCMODEL_PROPERTY_SCALED(256, float, ReadByte, WriteByte, colormod_z, 255, 0, 255) \
CSQCMODEL_ENDIF \
CSQCMODEL_IF(isplayer) \
CSQCMODEL_PROPERTY(128, float, ReadByte, WriteByte, anim_state) \
CSQCMODEL_PROPERTY(512, float, ReadApproxPastTime, WriteApproxPastTime, anim_upper_time) \
CSQCMODEL_PROPERTY(1024, float, ReadAngle, WriteAngle, v_angle_x) \
CSQCMODEL_ENDIF \
- CSQCMODEL_PROPERTY_SCALED(4096, float, ReadShort, WriteShort, scale, 256, 0, 16384)
+ CSQCMODEL_IF(!isplayer) \
+ CSQCMODEL_PROPERTY(2048, float, ReadByte, WriteByte, monsterid) \
+ CSQCMODEL_ENDIF \
+ CSQCMODEL_PROPERTY_SCALED(4096, float, ReadByte, WriteByte, scale, 16, 0, 255)
// TODO get rid of colormod/glowmod here, find good solution for nex charge glowmod hack; also get rid of some useless properties on non-players that only exist for CopyBody
// add hook function calls here
REGISTER_GAMETYPE(_("Last Man Standing"),lms,g_lms,LMS,"timelimit=20 lives=9 leadlimit=0");
#define g_lms IS_GAMETYPE(LMS)
- REGISTER_GAMETYPE(_("Arena"),arena,g_arena,ARENA,"timelimit=20 pointlimit=10 leadlimit=0");
- #define g_arena IS_GAMETYPE(ARENA)
-
REGISTER_GAMETYPE(_("Race"),rc,g_race,RACE,"timelimit=20 qualifying_timelimit=5 laplimit=7 teamlaplimit=15 leadlimit=0");
#define g_race IS_GAMETYPE(RACE)
REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,"timelimit=20 pointlimit=30");
#define g_keepaway IS_GAMETYPE(KEEPAWAY)
+REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,"pointlimit=5");
+#define g_invasion IS_GAMETYPE(INVASION)
+
const float MAPINFO_FEATURE_WEAPONS = 1; // not defined for minstagib-only maps
const float MAPINFO_FEATURE_VEHICLES = 2;
const float MAPINFO_FEATURE_TURRETS = 4;
--- /dev/null
- /* MON_##id */ MAGE,
- /* function */ m_mage,
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
- /* model */ "mage.dpm",
- /* netname */ "mage",
- /* fullname */ _("Mage")
++/* MON_##id */ MAGE,
++/* function */ m_mage,
+/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED,
+/* mins,maxs */ '-36 -36 -24', '36 36 50',
- const float mage_anim_walk = 1;
- const float mage_anim_attack = 2;
- const float mage_anim_pain = 3;
- const float mage_anim_death = 4;
++/* model */ "mage.dpm",
++/* netname */ "mage",
++/* fullname */ _("Mage")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_mage_health;
+float autocvar_g_monster_mage_attack_spike_damage;
+float autocvar_g_monster_mage_attack_spike_radius;
+float autocvar_g_monster_mage_attack_spike_delay;
+float autocvar_g_monster_mage_attack_spike_accel;
+float autocvar_g_monster_mage_attack_spike_decel;
+float autocvar_g_monster_mage_attack_spike_turnrate;
+float autocvar_g_monster_mage_attack_spike_speed_max;
+float autocvar_g_monster_mage_attack_spike_smart;
+float autocvar_g_monster_mage_attack_spike_smart_trace_min;
+float autocvar_g_monster_mage_attack_spike_smart_trace_max;
+float autocvar_g_monster_mage_attack_spike_smart_mindist;
+float autocvar_g_monster_mage_attack_push_damage;
+float autocvar_g_monster_mage_attack_push_radius;
+float autocvar_g_monster_mage_attack_push_delay;
+float autocvar_g_monster_mage_attack_push_force;
+float autocvar_g_monster_mage_heal_self;
+float autocvar_g_monster_mage_heal_allies;
+float autocvar_g_monster_mage_heal_minhealth;
+float autocvar_g_monster_mage_heal_range;
+float autocvar_g_monster_mage_heal_delay;
+float autocvar_g_monster_mage_shield_time;
+float autocvar_g_monster_mage_shield_delay;
+float autocvar_g_monster_mage_shield_blockpercent;
+float autocvar_g_monster_mage_speed_stop;
+float autocvar_g_monster_mage_speed_run;
+float autocvar_g_monster_mage_speed_walk;
+
+const float mage_anim_idle = 0;
-
++const float mage_anim_walk = 1;
++const float mage_anim_attack = 2;
++const float mage_anim_pain = 3;
++const float mage_anim_death = 4;
+const float mage_anim_run = 5;
+
+void() mage_heal;
+void() mage_shield;
+
+.entity mage_spike;
+.float shield_ltime;
+
+float friend_needshelp(entity e)
+{
+ if(e == world)
+ return FALSE;
+ if(e.health <= 0)
+ return FALSE;
+ if(DIFF_TEAM(e, self) && e != self.monster_owner)
+ return FALSE;
+ if(e.frozen)
+ return FALSE;
+ if(!IS_PLAYER(e))
+ return (e.flags & FL_MONSTER && e.health < e.max_health);
+ if(e.items & IT_INVINCIBLE)
+ return FALSE;
+
+ switch(self.skin)
+ {
+ case 0: return (e.health < autocvar_g_balance_health_regenstable);
+ case 1: return ((e.ammo_cells && e.ammo_cells < g_pickup_cells_max) || (e.ammo_rockets && e.ammo_rockets < g_pickup_rockets_max) || (e.ammo_nails && e.ammo_nails < g_pickup_nails_max) || (e.ammo_shells && e.ammo_shells < g_pickup_shells_max));
+ case 2: return (e.armorvalue < autocvar_g_balance_armor_regenstable);
+ case 3: return (e.health > 0);
+ }
-
++
+ return FALSE;
+}
+
+void mage_spike_explode()
+{
+ self.event_damage = func_null;
-
++
+ sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
-
++
+ self.realowner.mage_spike = world;
- e = self.enemy;
- eorg = 0.5 * (e.absmin + e.absmax);
- turnrate = (autocvar_g_monster_mage_attack_spike_turnrate); // how fast to turn
- desireddir = normalize(eorg - self.origin);
- olddir = normalize(self.velocity); // get my current direction
- dist = vlen(eorg - self.origin);
++
+ pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
+ RadiusDamage (self, self.realowner, (autocvar_g_monster_mage_attack_spike_damage), (autocvar_g_monster_mage_attack_spike_damage) * 0.5, (autocvar_g_monster_mage_attack_spike_radius), world, 0, DEATH_MONSTER_MAGE, other);
+
+ remove (self);
+}
+
+void mage_spike_touch()
+{
+ PROJECTILE_TOUCH;
+
+ mage_spike_explode();
+}
+
+// copied from W_Seeker_Think
+void mage_spike_think()
+{
+ entity e;
+ vector desireddir, olddir, newdir, eorg;
+ float turnrate;
+ float dist;
+ float spd;
+
+ if (time > self.ltime || self.enemy.health <= 0 || self.owner.health <= 0)
+ {
+ self.projectiledeathtype |= HITTYPE_SPLASH;
+ mage_spike_explode();
+ }
+
+ spd = vlen(self.velocity);
+ spd = bound(
+ spd - (autocvar_g_monster_mage_attack_spike_decel) * frametime,
+ (autocvar_g_monster_mage_attack_spike_speed_max),
+ spd + (autocvar_g_monster_mage_attack_spike_accel) * frametime
+ );
+
+ if (self.enemy != world)
+ if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
+ self.enemy = world;
+
+ if (self.enemy != world)
+ {
- desireddir = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5);
++ e = self.enemy;
++ eorg = 0.5 * (e.absmin + e.absmax);
++ turnrate = (autocvar_g_monster_mage_attack_spike_turnrate); // how fast to turn
++ desireddir = normalize(eorg - self.origin);
++ olddir = normalize(self.velocity); // get my current direction
++ dist = vlen(eorg - self.origin);
+
+ // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P )
+ if ((autocvar_g_monster_mage_attack_spike_smart) && (dist > (autocvar_g_monster_mage_attack_spike_smart_mindist)))
+ {
+ // Is it a better idea (shorter distance) to trace to the target itself?
+ if ( vlen(self.origin + olddir * self.wait) < dist)
+ traceline(self.origin, self.origin + olddir * self.wait, FALSE, self);
+ else
+ traceline(self.origin, eorg, FALSE, self);
+
+ // Setup adaptive tracelength
+ self.wait = bound((autocvar_g_monster_mage_attack_spike_smart_trace_min), vlen(self.origin - trace_endpos), self.wait = (autocvar_g_monster_mage_attack_spike_smart_trace_max));
+
+ // Calc how important it is that we turn and add this to the desierd (enemy) dir.
-
++ desireddir = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5);
+ }
-
++
+ newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy
+ self.velocity = newdir * spd; // make me fly in the new direction at my flight speed
+ }
+ else
+ dist = 0;
- setsize (missile, '0 0 0', '0 0 0');
++
+ ///////////////
+
+ //self.angles = vectoangles(self.velocity); // turn model in the new flight direction
+ self.nextthink = time;// + 0.05; // csqc projectiles
+ UpdateCSQCProjectile(self);
+}
+
+void mage_attack_spike()
+{
+ entity missile;
+ vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+
+ makevectors(self.angles);
+
+ missile = spawn ();
+ missile.owner = missile.realowner = self;
+ missile.think = mage_spike_think;
+ missile.ltime = time + 7;
+ missile.nextthink = time;
+ missile.solid = SOLID_BBOX;
+ missile.movetype = MOVETYPE_FLYMISSILE;
+ missile.flags = FL_PROJECTILE;
+ setorigin(missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14);
-
++ setsize (missile, '0 0 0', '0 0 0');
+ missile.velocity = dir * 400;
+ missile.avelocity = '300 300 300';
+ missile.enemy = self.enemy;
+ missile.touch = mage_spike_touch;
-
++
+ self.mage_spike = missile;
-
++
+ CSQCProjectile(missile, TRUE, PROJECTILE_MAGE_SPIKE, TRUE);
+}
+
+void mage_heal()
+{
+ entity head;
+ float washealed = FALSE;
-
++
+ for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain) if(friend_needshelp(head))
+ {
+ washealed = TRUE;
+ string fx = "";
+ if(IS_PLAYER(head))
+ {
+ switch(self.skin)
+ {
+ case 0:
+ if(head.health < autocvar_g_balance_health_regenstable) head.health = bound(0, head.health + (autocvar_g_monster_mage_heal_allies), autocvar_g_balance_health_regenstable);
+ fx = "healing_fx";
+ break;
+ case 1:
+ if(head.ammo_cells) head.ammo_cells = bound(head.ammo_cells, head.ammo_cells + 1, g_pickup_cells_max);
+ if(head.ammo_rockets) head.ammo_rockets = bound(head.ammo_rockets, head.ammo_rockets + 1, g_pickup_rockets_max);
+ if(head.ammo_shells) head.ammo_shells = bound(head.ammo_shells, head.ammo_shells + 2, g_pickup_shells_max);
+ if(head.ammo_nails) head.ammo_nails = bound(head.ammo_nails, head.ammo_nails + 5, g_pickup_nails_max);
+ fx = "ammoregen_fx";
+ break;
+ case 2:
+ if(head.armorvalue < autocvar_g_balance_armor_regenstable)
+ {
+ head.armorvalue = bound(0, head.armorvalue + (autocvar_g_monster_mage_heal_allies), autocvar_g_balance_armor_regenstable);
+ fx = "armorrepair_fx";
+ }
+ break;
+ case 3:
+ head.health = bound(0, head.health - ((head == self) ? (autocvar_g_monster_mage_heal_self) : (autocvar_g_monster_mage_heal_allies)), autocvar_g_balance_health_regenstable);
+ fx = "rage";
+ break;
+ }
-
++
+ pointparticles(particleeffectnum(fx), head.origin, '0 0 0', 1);
+ }
+ else
+ {
+ pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
+ head.health = bound(0, head.health + (autocvar_g_monster_mage_heal_allies), head.max_health);
+ WaypointSprite_UpdateHealth(head.sprite, head.health);
+ }
+ }
-
++
+ if(washealed)
+ {
+ self.frame = mage_anim_attack;
+ self.attack_finished_single = time + (autocvar_g_monster_mage_heal_delay);
+ }
+}
+
+void mage_push()
+{
+ sound(self, CH_SHOTS, "weapons/tagexp1.wav", 1, ATTEN_NORM);
+ RadiusDamage (self, self, (autocvar_g_monster_mage_attack_push_damage), (autocvar_g_monster_mage_attack_push_damage), (autocvar_g_monster_mage_attack_push_radius), world, (autocvar_g_monster_mage_attack_push_force), DEATH_MONSTER_MAGE, self.enemy);
+ pointparticles(particleeffectnum("TE_EXPLOSION"), self.origin, '0 0 0', 1);
-
++
+ self.frame = mage_anim_attack;
+ self.attack_finished_single = time + (autocvar_g_monster_mage_attack_push_delay);
+}
+
+void mage_teleport()
+{
+ if(vlen(self.enemy.origin - self.origin) >= 500)
+ return;
+
+ makevectors(self.enemy.angles);
+ tracebox(self.enemy.origin + ((v_forward * -1) * 200), self.mins, self.maxs, self.origin, MOVE_NOMONSTERS, self);
-
++
+ if(trace_fraction < 1)
+ return;
-
++
+ pointparticles(particleeffectnum("spawn_event_neutral"), self.origin, '0 0 0', 1);
+ setorigin(self, self.enemy.origin + ((v_forward * -1) * 200));
-
++
+ self.attack_finished_single = time + 0.2;
+}
+
+void mage_shield_remove()
+{
+ self.effects &= ~(EF_ADDITIVE | EF_BLUE);
+ self.armorvalue = 0;
+ self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent;
+}
+
+void mage_shield()
+{
+ self.effects |= (EF_ADDITIVE | EF_BLUE);
+ self.lastshielded = time + (autocvar_g_monster_mage_shield_delay);
+ self.m_armor_blockpercent = (autocvar_g_monster_mage_shield_blockpercent);
+ self.armorvalue = self.health;
+ self.shield_ltime = time + (autocvar_g_monster_mage_shield_time);
+ self.frame = mage_anim_attack;
+ self.attack_finished_single = time + 1;
+}
+
+float mage_attack(float attack_type)
+{
+ switch(attack_type)
+ {
+ case MONSTER_ATTACK_MELEE:
+ {
+ if(random() <= 0.7)
+ {
+ mage_push();
+ return TRUE;
+ }
-
++
+ return FALSE;
+ }
+ case MONSTER_ATTACK_RANGED:
+ {
+ if(!self.mage_spike)
+ {
+ if(random() <= 0.4)
+ {
+ mage_teleport();
+ return TRUE;
+ }
+ else
+ {
+ self.frame = mage_anim_attack;
+ self.attack_finished_single = time + (autocvar_g_monster_mage_attack_spike_delay);
+ defer(0.2, mage_attack_spike);
+ return TRUE;
+ }
+ }
-
++
+ if(self.mage_spike)
+ return TRUE;
+ else
+ return FALSE;
+ }
+ }
-
++
+ return FALSE;
+}
+
+void spawnfunc_monster_mage()
+{
+ self.classname = "monster_mage";
-
++
+ self.monster_spawnfunc = spawnfunc_monster_mage;
-
++
+ if(Monster_CheckAppearFlags(self))
+ return;
-
++
+ if(!monster_initialize(MON_MAGE, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); }
+
+float m_mage(float req)
+{
+ switch(req)
+ {
+ case MR_THINK:
+ {
+ entity head;
+ float need_help = FALSE;
-
++
+ for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain)
+ if(head != self)
+ if(friend_needshelp(head))
+ {
+ need_help = TRUE;
+ break;
+ }
-
++
+ if(self.health < (autocvar_g_monster_mage_heal_minhealth) || need_help)
+ if(time >= self.attack_finished_single)
+ if(random() < 0.5)
+ mage_heal();
-
++
+ if(time >= self.shield_ltime && self.armorvalue)
+ mage_shield_remove();
-
++
+ if(self.enemy)
+ if(self.health < self.max_health)
+ if(time >= self.lastshielded)
+ if(random() < 0.5)
+ mage_shield();
-
++
+ monster_move((autocvar_g_monster_mage_speed_run), (autocvar_g_monster_mage_speed_walk), (autocvar_g_monster_mage_speed_stop), mage_anim_walk, mage_anim_run, mage_anim_idle);
+ return TRUE;
+ }
+ case MR_DEATH:
+ {
+ self.frame = mage_anim_death;
+ return TRUE;
+ }
+ case MR_SETUP:
+ {
+ if(!self.health) self.health = (autocvar_g_monster_mage_health);
-
++
+ self.monster_loot = spawnfunc_item_health_large;
+ self.monster_attackfunc = mage_attack;
+ self.frame = mage_anim_walk;
-
++
+ return TRUE;
+ }
+ case MR_PRECACHE:
+ {
+ precache_model ("models/monsters/mage.dpm");
+ precache_sound ("weapons/grenade_impact.wav");
+ precache_sound ("weapons/tagexp1.wav");
+ return TRUE;
+ }
+ }
-
++
+ return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_mage(float req)
+{
+ switch(req)
+ {
+ case MR_PRECACHE:
+ {
+ precache_model ("models/monsters/mage.dpm");
+ return TRUE;
+ }
+ }
++
+ return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
--- /dev/null
- /* MON_##id */ SHAMBLER,
- /* function */ m_shambler,
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
- /* model */ "shambler.mdl",
- /* netname */ "shambler",
- /* fullname */ _("Shambler")
++/* MON_##id */ SHAMBLER,
++/* function */ m_shambler,
+/* spawnflags */ MONSTER_SIZE_BROKEN | MON_FLAG_SUPERMONSTER | MON_FLAG_MELEE | MON_FLAG_RANGED,
+/* mins,maxs */ '-41 -41 -31', '41 41 65',
- const float shambler_anim_stand = 0;
- const float shambler_anim_walk = 1;
- const float shambler_anim_run = 2;
- const float shambler_anim_smash = 3;
- const float shambler_anim_swingr = 4;
- const float shambler_anim_swingl = 5;
- const float shambler_anim_magic = 6;
- const float shambler_anim_pain = 7;
- const float shambler_anim_death = 8;
++/* model */ "shambler.mdl",
++/* netname */ "shambler",
++/* fullname */ _("Shambler")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_shambler_health;
+float autocvar_g_monster_shambler_attack_smash_damage;
+float autocvar_g_monster_shambler_attack_claw_damage;
+float autocvar_g_monster_shambler_attack_lightning_damage;
+float autocvar_g_monster_shambler_attack_lightning_force;
+float autocvar_g_monster_shambler_attack_lightning_radius;
+float autocvar_g_monster_shambler_attack_lightning_radius_zap;
+float autocvar_g_monster_shambler_attack_lightning_speed;
+float autocvar_g_monster_shambler_attack_lightning_speed_up;
+float autocvar_g_monster_shambler_speed_stop;
+float autocvar_g_monster_shambler_speed_run;
+float autocvar_g_monster_shambler_speed_walk;
+
-
++const float shambler_anim_stand = 0;
++const float shambler_anim_walk = 1;
++const float shambler_anim_run = 2;
++const float shambler_anim_smash = 3;
++const float shambler_anim_swingr = 4;
++const float shambler_anim_swingl = 5;
++const float shambler_anim_magic = 6;
++const float shambler_anim_pain = 7;
++const float shambler_anim_death = 8;
+
+.float shambler_lastattack; // delay attacks separately
+
+void shambler_smash()
+{
+ makevectors(self.angles);
+ pointparticles(particleeffectnum("explosion_medium"), (self.origin + (v_forward * 150)) - ('0 0 1' * self.maxs_z), '0 0 0', 1);
+ sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
-
++
+ tracebox(self.origin + v_forward * 50, self.mins * 0.5, self.maxs * 0.5, self.origin + v_forward * 500, MOVE_NORMAL, self);
-
++
+ if(trace_ent.takedamage)
+ Damage(trace_ent, self, self, (autocvar_g_monster_shambler_attack_smash_damage) * Monster_SkillModifier(), DEATH_MONSTER_SHAMBLER_SMASH, trace_ent.origin, normalize(trace_ent.origin - self.origin));
+}
+
+void shambler_swing()
+{
+ float r = (random() < 0.5);
+ monster_melee(self.enemy, (autocvar_g_monster_shambler_attack_claw_damage), ((r) ? shambler_anim_swingr : shambler_anim_swingl), self.attack_range, 0.8, DEATH_MONSTER_SHAMBLER_CLAW, TRUE);
+ if(r)
+ {
+ defer(0.5, shambler_swing);
+ self.attack_finished_single += 0.5;
+ }
+}
+
+void shambler_lightning_explode()
+{
+ entity head;
-
++
+ sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTEN_NORM);
+ pointparticles(particleeffectnum("electro_impact"), '0 0 0', '0 0 0', 1);
+
+ self.event_damage = func_null;
+ self.takedamage = DAMAGE_NO;
+ self.movetype = MOVETYPE_NONE;
+ self.velocity = '0 0 0';
+
+ if(self.movetype == MOVETYPE_NONE)
+ self.velocity = self.oldvelocity;
+
+ RadiusDamage (self, self.realowner, (autocvar_g_monster_shambler_attack_lightning_damage), (autocvar_g_monster_shambler_attack_lightning_damage), (autocvar_g_monster_shambler_attack_lightning_radius), world, (autocvar_g_monster_shambler_attack_lightning_force), self.projectiledeathtype, other);
-
++
+ for(head = findradius(self.origin, (autocvar_g_monster_shambler_attack_lightning_radius_zap)); head; head = head.chain) if(head != self.realowner) if(head.takedamage)
+ {
+ te_csqc_lightningarc(self.origin, head.origin);
+ Damage(head, self, self.realowner, (autocvar_g_monster_shambler_attack_lightning_damage) * Monster_SkillModifier(), DEATH_MONSTER_SHAMBLER_ZAP, head.origin, '0 0 0');
+ }
+
+ self.think = SUB_Remove;
+ self.nextthink = time + 0.2;
+}
+
+void shambler_lightning_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ if (self.health <= 0)
+ return;
-
++
+ if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+ return; // g_projectiles_damage says to halt
-
++
+ self.health = self.health - damage;
-
++
+ if (self.health <= 0)
+ W_PrepareExplosionByDamage(attacker, self.use);
+}
+
+void shambler_lightning_touch()
+{
+ PROJECTILE_TOUCH;
-
++
+ self.use ();
+}
+
+void shambler_lightning_think()
+{
+ self.nextthink = time;
+ if (time > self.cnt)
+ {
+ other = world;
+ shambler_lightning_explode();
+ return;
+ }
+}
+
+void shambler_lightning()
+{
+ entity gren;
-
++
+ monster_makevectors(self.enemy);
+
+ gren = spawn ();
+ gren.owner = gren.realowner = self;
+ gren.classname = "grenade";
+ gren.bot_dodge = TRUE;
+ gren.bot_dodgerating = (autocvar_g_monster_shambler_attack_lightning_damage);
+ gren.movetype = MOVETYPE_BOUNCE;
+ PROJECTILE_MAKETRIGGER(gren);
+ gren.projectiledeathtype = DEATH_MONSTER_SHAMBLER_ZAP;
+ setorigin(gren, CENTER_OR_VIEWOFS(self));
+ setsize(gren, '-8 -8 -8', '8 8 8');
+ gren.scale = 2.5;
+
+ gren.cnt = time + 5;
+ gren.nextthink = time;
+ gren.think = shambler_lightning_think;
+ gren.use = shambler_lightning_explode;
+ gren.touch = shambler_lightning_touch;
+
+ gren.takedamage = DAMAGE_YES;
+ gren.health = 50;
+ gren.damageforcescale = 0;
+ gren.event_damage = shambler_lightning_damage;
+ gren.damagedbycontents = TRUE;
+ gren.missile_flags = MIF_SPLASH | MIF_ARC;
+ W_SetupProjectileVelocityEx(gren, v_forward, v_up, (autocvar_g_monster_shambler_attack_lightning_speed), (autocvar_g_monster_shambler_attack_lightning_speed_up), 0, 0, FALSE);
+
+ gren.angles = vectoangles (gren.velocity);
+ gren.flags = FL_PROJECTILE;
+
+ CSQCProjectile(gren, TRUE, PROJECTILE_SHAMBLER_LIGHTNING, TRUE);
+}
+
+float shambler_attack(float attack_type)
+{
+ switch(attack_type)
+ {
+ case MONSTER_ATTACK_MELEE:
+ {
+ shambler_swing();
+ return TRUE;
+ }
+ case MONSTER_ATTACK_RANGED:
+ {
+ if(time >= self.shambler_lastattack) // shambler doesn't attack much
+ if(random() <= 0.5 && vlen(self.enemy.origin - self.origin) <= 500)
+ {
+ self.frame = shambler_anim_smash;
+ defer(0.7, shambler_smash);
+ self.attack_finished_single = time + 1.1;
+ self.shambler_lastattack = time + 3;
+ return TRUE;
+ }
+ else if(random() <= 0.1) // small chance, don't want this spammed
+ {
+ self.frame = shambler_anim_magic;
+ self.attack_finished_single = time + 1.1;
+ self.shambler_lastattack = time + 3;
+ defer(0.6, shambler_lightning);
+ return TRUE;
+ }
-
++
+ return FALSE;
+ }
+ }
-
++
+ return FALSE;
+}
+
+void spawnfunc_monster_shambler()
+{
+ self.classname = "monster_shambler";
-
++
+ self.monster_spawnfunc = spawnfunc_monster_shambler;
-
++
+ if(Monster_CheckAppearFlags(self))
+ return;
-
++
+ if(!monster_initialize(MON_SHAMBLER, FALSE)) { remove(self); return; }
+}
+
+float m_shambler(float req)
+{
+ switch(req)
+ {
+ case MR_THINK:
+ {
+ monster_move((autocvar_g_monster_shambler_speed_run), (autocvar_g_monster_shambler_speed_walk), (autocvar_g_monster_shambler_speed_stop), shambler_anim_run, shambler_anim_walk, shambler_anim_stand);
+ return TRUE;
+ }
+ case MR_DEATH:
+ {
+ self.frame = shambler_anim_death;
+ return TRUE;
+ }
+ case MR_SETUP:
+ {
+ if(!self.health) self.health = (autocvar_g_monster_shambler_health);
+ if(!self.attack_range) self.attack_range = 150;
-
++
+ self.monster_loot = spawnfunc_item_health_mega;
+ self.monster_attackfunc = shambler_attack;
+ self.frame = shambler_anim_stand;
+ self.weapon = WEP_NEX;
-
++
+ return TRUE;
+ }
+ case MR_PRECACHE:
+ {
+ precache_model ("models/monsters/shambler.mdl");
+ return TRUE;
+ }
+ }
-
++
+ return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_shambler(float req)
+{
+ switch(req)
+ {
+ case MR_PRECACHE:
+ {
+ precache_model ("models/monsters/shambler.mdl");
+ return TRUE;
+ }
+ }
++
+ return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
--- /dev/null
- /* MON_##id */ SPIDER,
- /* function */ m_spider,
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
- /* model */ "spider.dpm",
- /* netname */ "spider",
- /* fullname */ _("Spider")
++/* MON_##id */ SPIDER,
++/* function */ m_spider,
+/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED,
+/* mins,maxs */ '-18 -18 -25', '18 18 30',
-
++/* model */ "spider.dpm",
++/* netname */ "spider",
++/* fullname */ _("Spider")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_spider_health;
+float autocvar_g_monster_spider_attack_bite_damage;
+float autocvar_g_monster_spider_attack_bite_delay;
+float autocvar_g_monster_spider_attack_web_damagetime;
+float autocvar_g_monster_spider_attack_web_speed;
+float autocvar_g_monster_spider_attack_web_speed_up;
+float autocvar_g_monster_spider_attack_web_delay;
+float autocvar_g_monster_spider_speed_stop;
+float autocvar_g_monster_spider_speed_run;
+float autocvar_g_monster_spider_speed_walk;
+
+const float spider_anim_idle = 0;
+const float spider_anim_walk = 1;
+const float spider_anim_attack = 2;
+const float spider_anim_attack2 = 3;
+
+.float spider_web_delay;
+
+void spider_web_explode()
+{
+ entity e;
+ if(self)
+ {
+ pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1);
+ RadiusDamage(self, self.realowner, 0, 0, 25, world, 25, self.projectiledeathtype, world);
-
++
+ for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0)
+ e.spider_slowness = time + (autocvar_g_monster_spider_attack_web_damagetime);
-
++
+ remove(self);
+ }
+}
+
+void spider_web_touch()
+{
+ PROJECTILE_TOUCH;
-
++
+ spider_web_explode();
+}
+
+void spider_shootweb()
+{
+ monster_makevectors(self.enemy);
- {
++
+ sound(self, CH_SHOTS, "weapons/electro_fire2.wav", VOL_BASE, ATTEN_NORM);
+
+ entity proj = spawn ();
+ proj.classname = "plasma";
+ proj.owner = proj.realowner = self;
+ proj.use = spider_web_touch;
+ proj.think = adaptor_think2use_hittype_splash;
+ proj.bot_dodge = TRUE;
+ proj.bot_dodgerating = 0;
+ proj.nextthink = time + 5;
+ PROJECTILE_MAKETRIGGER(proj);
+ proj.projectiledeathtype = DEATH_MONSTER_SPIDER;
+ setorigin(proj, CENTER_OR_VIEWOFS(self));
+
+ //proj.glow_size = 50;
+ //proj.glow_color = 45;
+ proj.movetype = MOVETYPE_BOUNCE;
+ W_SetupProjectileVelocityEx(proj, v_forward, v_up, (autocvar_g_monster_spider_attack_web_speed), (autocvar_g_monster_spider_attack_web_speed_up), 0, 0, FALSE);
+ proj.touch = spider_web_touch;
+ setsize(proj, '-4 -4 -4', '4 4 4');
+ proj.takedamage = DAMAGE_NO;
+ proj.damageforcescale = 0;
+ proj.health = 500;
+ proj.event_damage = func_null;
+ proj.flags = FL_PROJECTILE;
+ proj.damagedbycontents = TRUE;
+
+ proj.bouncefactor = 0.3;
+ proj.bouncestop = 0.05;
+ proj.missile_flags = MIF_SPLASH | MIF_ARC;
+
+ CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO, TRUE);
+}
+
+float spider_attack(float attack_type)
+{
+ switch(attack_type)
+ {
+ case MONSTER_ATTACK_MELEE:
-
++ {
+ return monster_melee(self.enemy, (autocvar_g_monster_spider_attack_bite_damage), ((random() > 0.5) ? spider_anim_attack : spider_anim_attack2), self.attack_range, (autocvar_g_monster_spider_attack_bite_delay), DEATH_MONSTER_SPIDER, TRUE);
+ }
+ case MONSTER_ATTACK_RANGED:
+ {
+ if(time >= self.spider_web_delay)
+ {
+ self.frame = spider_anim_attack2;
+ self.attack_finished_single = time + (autocvar_g_monster_spider_attack_web_delay);
+ spider_shootweb();
+ self.spider_web_delay = time + 3;
+ return TRUE;
+ }
-
++
+ return FALSE;
+ }
+ }
- void spawnfunc_monster_spider()
++
+ return FALSE;
+}
+
-
++void spawnfunc_monster_spider()
+{
+ self.classname = "monster_spider";
-
++
+ self.monster_spawnfunc = spawnfunc_monster_spider;
-
++
+ if(Monster_CheckAppearFlags(self))
+ return;
-
++
+ if(!monster_initialize(MON_SPIDER, FALSE)) { remove(self); return; }
+}
+
+float m_spider(float req)
+{
+ switch(req)
+ {
+ case MR_THINK:
+ {
+ monster_move((autocvar_g_monster_spider_speed_run), (autocvar_g_monster_spider_speed_walk), (autocvar_g_monster_spider_speed_stop), spider_anim_walk, spider_anim_walk, spider_anim_idle);
+ return TRUE;
+ }
+ case MR_DEATH:
+ {
+ self.frame = spider_anim_attack;
+ self.angles_x = 180;
+ return TRUE;
+ }
+ case MR_SETUP:
+ {
+ if(!self.health) self.health = (autocvar_g_monster_spider_health);
-
++
+ self.monster_loot = spawnfunc_item_health_medium;
+ self.monster_attackfunc = spider_attack;
+ self.frame = spider_anim_idle;
-
++
+ return TRUE;
+ }
+ case MR_PRECACHE:
+ {
+ precache_model ("models/monsters/spider.dpm");
+ precache_sound ("weapons/electro_fire2.wav");
+ return TRUE;
+ }
+ }
-
++
+ return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_spider(float req)
+{
+ switch(req)
+ {
+ case MR_PRECACHE:
+ {
+ precache_model ("models/monsters/spider.dpm");
+ return TRUE;
+ }
+ }
++
+ return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
--- /dev/null
- /* MON_##id */ WYVERN,
- /* function */ m_wyvern,
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
- /* model */ "wizard.mdl",
- /* netname */ "wyvern",
- /* fullname */ _("Wyvern")
++/* MON_##id */ WYVERN,
++/* function */ m_wyvern,
+/* spawnflags */ MONSTER_TYPE_FLY | MONSTER_SIZE_BROKEN | MON_FLAG_RANGED,
+/* mins,maxs */ '-20 -20 -58', '20 20 20',
- const float wyvern_anim_hover = 0;
- const float wyvern_anim_fly = 1;
- const float wyvern_anim_magic = 2;
- const float wyvern_anim_pain = 3;
- const float wyvern_anim_death = 4;
++/* model */ "wizard.mdl",
++/* netname */ "wyvern",
++/* fullname */ _("Wyvern")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_wyvern_health;
+float autocvar_g_monster_wyvern_attack_fireball_damage;
+float autocvar_g_monster_wyvern_attack_fireball_edgedamage;
+float autocvar_g_monster_wyvern_attack_fireball_damagetime;
+float autocvar_g_monster_wyvern_attack_fireball_force;
+float autocvar_g_monster_wyvern_attack_fireball_radius;
+float autocvar_g_monster_wyvern_attack_fireball_speed;
+float autocvar_g_monster_wyvern_speed_stop;
+float autocvar_g_monster_wyvern_speed_run;
+float autocvar_g_monster_wyvern_speed_walk;
+
-
++const float wyvern_anim_hover = 0;
++const float wyvern_anim_fly = 1;
++const float wyvern_anim_magic = 2;
++const float wyvern_anim_pain = 3;
++const float wyvern_anim_death = 4;
+
+void wyvern_fireball_explode()
+{
+ entity e;
+ if(self)
+ {
+ pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
-
++
+ RadiusDamage(self, self.realowner, (autocvar_g_monster_wyvern_attack_fireball_damage), (autocvar_g_monster_wyvern_attack_fireball_edgedamage), (autocvar_g_monster_wyvern_attack_fireball_force), world, (autocvar_g_monster_wyvern_attack_fireball_radius), self.projectiledeathtype, world);
-
++
+ for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= (autocvar_g_monster_wyvern_attack_fireball_radius))
+ Fire_AddDamage(e, self, 5 * Monster_SkillModifier(), (autocvar_g_monster_wyvern_attack_fireball_damagetime), self.projectiledeathtype);
-
++
+ remove(self);
+ }
+}
+
+void wyvern_fireball_touch()
+{
+ PROJECTILE_TOUCH;
-
++
+ wyvern_fireball_explode();
+}
+
+void wyvern_fireball()
+{
+ entity missile = spawn();
+ vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
- setsize(missile, '-6 -6 -6', '6 6 6');
++
+ monster_makevectors(self.enemy);
+
+ missile.owner = missile.realowner = self;
+ missile.solid = SOLID_TRIGGER;
+ missile.movetype = MOVETYPE_FLYMISSILE;
+ missile.projectiledeathtype = DEATH_MONSTER_WYVERN;
-
++ setsize(missile, '-6 -6 -6', '6 6 6');
+ setorigin(missile, self.origin + self.view_ofs + v_forward * 14);
+ missile.flags = FL_PROJECTILE;
+ missile.velocity = dir * (autocvar_g_monster_wyvern_attack_fireball_speed);
+ missile.avelocity = '300 300 300';
+ missile.nextthink = time + 5;
+ missile.think = wyvern_fireball_explode;
+ missile.enemy = self.enemy;
+ missile.touch = wyvern_fireball_touch;
+ CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE);
+}
+
+float wyvern_attack(float attack_type)
+{
+ switch(attack_type)
+ {
+ case MONSTER_ATTACK_MELEE:
+ case MONSTER_ATTACK_RANGED:
+ {
+ self.attack_finished_single = time + 1.2;
-
++
+ wyvern_fireball();
-
++
+ return TRUE;
+ }
+ }
-
++
+ return FALSE;
+}
+
+void spawnfunc_monster_wyvern()
+{
+ self.classname = "monster_wyvern";
-
++
+ self.monster_spawnfunc = spawnfunc_monster_wyvern;
-
++
+ if(Monster_CheckAppearFlags(self))
+ return;
-
++
+ if(!monster_initialize(MON_WYVERN, TRUE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_wizard() { spawnfunc_monster_wyvern(); }
+
+float m_wyvern(float req)
+{
+ switch(req)
+ {
+ case MR_THINK:
+ {
+ monster_move((autocvar_g_monster_wyvern_speed_run), (autocvar_g_monster_wyvern_speed_walk), (autocvar_g_monster_wyvern_speed_stop), wyvern_anim_fly, wyvern_anim_hover, wyvern_anim_hover);
+ return TRUE;
+ }
+ case MR_DEATH:
+ {
+ self.frame = wyvern_anim_death;
+ self.velocity_x = -200 + 400 * random();
+ self.velocity_y = -200 + 400 * random();
+ self.velocity_z = 100 + 100 * random();
+ return TRUE;
+ }
+ case MR_SETUP:
+ {
+ if(!self.health) self.health = (autocvar_g_monster_wyvern_health);
-
++
+ self.monster_loot = spawnfunc_item_cells;
+ self.monster_attackfunc = wyvern_attack;
+ self.frame = wyvern_anim_hover;
-
++
+ return TRUE;
+ }
+ case MR_PRECACHE:
+ {
+ precache_model ("models/monsters/wizard.mdl");
+ return TRUE;
+ }
+ }
-
++
+ return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_wyvern(float req)
+{
+ switch(req)
+ {
+ case MR_PRECACHE:
+ {
+ precache_model ("models/monsters/wizard.mdl");
+ return TRUE;
+ }
+ }
++
+ return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
--- /dev/null
- /* MON_##id */ ZOMBIE,
- /* function */ m_zombie,
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
- /* model */ "zombie.dpm",
- /* netname */ "zombie",
- /* fullname */ _("Zombie")
++/* MON_##id */ ZOMBIE,
++/* function */ m_zombie,
+/* spawnflags */ MON_FLAG_MELEE,
+/* mins,maxs */ '-18 -18 -25', '18 18 47',
- const float zombie_anim_blockend = 7;
++/* model */ "zombie.dpm",
++/* netname */ "zombie",
++/* fullname */ _("Zombie")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_zombie_health;
+float autocvar_g_monster_zombie_attack_melee_damage;
+float autocvar_g_monster_zombie_attack_melee_delay;
+float autocvar_g_monster_zombie_attack_leap_damage;
+float autocvar_g_monster_zombie_attack_leap_force;
+float autocvar_g_monster_zombie_attack_leap_speed;
+float autocvar_g_monster_zombie_attack_leap_delay;
+float autocvar_g_monster_zombie_speed_stop;
+float autocvar_g_monster_zombie_speed_run;
+float autocvar_g_monster_zombie_speed_walk;
+
+const float zombie_anim_attackleap = 0;
+const float zombie_anim_attackrun1 = 1;
+const float zombie_anim_attackrun2 = 2;
+const float zombie_anim_attackrun3 = 3;
+const float zombie_anim_attackstanding1 = 4;
+const float zombie_anim_attackstanding2 = 5;
+const float zombie_anim_attackstanding3 = 6;
- const float zombie_anim_idle = 19;
- const float zombie_anim_painback1 = 20;
- const float zombie_anim_painback2 = 21;
++const float zombie_anim_blockend = 7;
+const float zombie_anim_blockstart = 8;
+const float zombie_anim_deathback1 = 9;
+const float zombie_anim_deathback2 = 10;
+const float zombie_anim_deathback3 = 11;
+const float zombie_anim_deathfront1 = 12;
+const float zombie_anim_deathfront2 = 13;
+const float zombie_anim_deathfront3 = 14;
+const float zombie_anim_deathleft1 = 15;
+const float zombie_anim_deathleft2 = 16;
+const float zombie_anim_deathright1 = 17;
+const float zombie_anim_deathright2 = 18;
- const float zombie_anim_runbackwards = 24;
- const float zombie_anim_runbackwardsleft = 25;
- const float zombie_anim_runbackwardsright = 26;
++const float zombie_anim_idle = 19;
++const float zombie_anim_painback1 = 20;
++const float zombie_anim_painback2 = 21;
+const float zombie_anim_painfront1 = 22;
+const float zombie_anim_painfront2 = 23;
- const float zombie_anim_runforwardleft = 28;
++const float zombie_anim_runbackwards = 24;
++const float zombie_anim_runbackwardsleft = 25;
++const float zombie_anim_runbackwardsright = 26;
+const float zombie_anim_runforward = 27;
- const float zombie_anim_spawn = 30;
++const float zombie_anim_runforwardleft = 28;
+const float zombie_anim_runforwardright = 29;
-
++const float zombie_anim_spawn = 30;
+
+void zombie_attack_leap_touch()
+{
+ if (self.health <= 0)
+ return;
-
++
+ vector angles_face;
+
+ if(other.takedamage)
+ {
+ angles_face = vectoangles(self.moveto - self.origin);
+ angles_face = normalize(angles_face) * (autocvar_g_monster_zombie_attack_leap_force);
+ Damage(other, self, self, (autocvar_g_monster_zombie_attack_leap_damage) * Monster_SkillModifier(), DEATH_MONSTER_ZOMBIE_JUMP, other.origin, angles_face);
+ self.touch = MonsterTouch; // instantly turn it off to stop damage spam
+ }
+
+ if (trace_dphitcontents)
+ self.touch = MonsterTouch;
+}
+
+void zombie_blockend()
+{
+ if(self.health <= 0)
+ return;
+
+ self.frame = zombie_anim_blockend;
+ self.armorvalue = 0;
+ self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent;
+}
+
+float zombie_block()
+{
+ self.frame = zombie_anim_blockstart;
+ self.armorvalue = 100;
+ self.m_armor_blockpercent = 0.9;
+ self.state = MONSTER_STATE_ATTACK_MELEE; // freeze monster
+ self.attack_finished_single = time + 2.1;
-
++
+ defer(2, zombie_blockend);
-
++
+ return TRUE;
+}
+
+float zombie_attack(float attack_type)
+{
+ switch(attack_type)
+ {
+ case MONSTER_ATTACK_MELEE:
+ {
+ float rand = random(), chosen_anim;
-
++
+ if(rand < 0.33)
+ chosen_anim = zombie_anim_attackstanding1;
+ else if(rand < 0.66)
+ chosen_anim = zombie_anim_attackstanding2;
+ else
+ chosen_anim = zombie_anim_attackstanding3;
-
++
+ if(random() < 0.3 && self.health < 75 && self.enemy.health > 10)
+ return zombie_block();
-
++
+ return monster_melee(self.enemy, (autocvar_g_monster_zombie_attack_melee_damage), chosen_anim, self.attack_range, (autocvar_g_monster_zombie_attack_melee_delay), DEATH_MONSTER_ZOMBIE_MELEE, TRUE);
+ }
+ case MONSTER_ATTACK_RANGED:
+ {
+ makevectors(self.angles);
+ return monster_leap(zombie_anim_attackleap, zombie_attack_leap_touch, v_forward * (autocvar_g_monster_zombie_attack_leap_speed) + '0 0 200', (autocvar_g_monster_zombie_attack_leap_delay));
+ }
+ }
- void spawnfunc_monster_zombie()
++
+ return FALSE;
+}
+
-
++void spawnfunc_monster_zombie()
+{
+ self.classname = "monster_zombie";
-
++
+ self.monster_spawnfunc = spawnfunc_monster_zombie;
-
++
+ self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT;
-
++
+ if(Monster_CheckAppearFlags(self))
+ return;
-
++
+ if(!monster_initialize(MON_ZOMBIE, FALSE)) { remove(self); return; }
+}
+
+float m_zombie(float req)
+{
+ switch(req)
+ {
+ case MR_THINK:
+ {
+ monster_move((autocvar_g_monster_zombie_speed_run), (autocvar_g_monster_zombie_speed_walk), (autocvar_g_monster_zombie_speed_stop), zombie_anim_runforward, zombie_anim_runforward, zombie_anim_idle);
+ return TRUE;
+ }
+ case MR_DEATH:
+ {
+ self.armorvalue = 0;
+ self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent;
+ self.frame = ((random() > 0.5) ? zombie_anim_deathback1 : zombie_anim_deathfront1);
+ return TRUE;
+ }
+ case MR_SETUP:
+ {
+ if(!self.health) self.health = (autocvar_g_monster_zombie_health);
-
++
+ if(self.spawnflags & MONSTERFLAG_NORESPAWN)
+ self.spawnflags &= ~MONSTERFLAG_NORESPAWN; // zombies always respawn
-
++
+ self.monster_loot = spawnfunc_item_health_medium;
+ self.monster_attackfunc = zombie_attack;
+ self.frame = zombie_anim_spawn;
+ self.spawn_time = time + 2.1;
+ self.spawnshieldtime = self.spawn_time;
+ self.respawntime = 0.2;
-
++
+ return TRUE;
+ }
+ case MR_PRECACHE:
+ {
+ precache_model ("models/monsters/zombie.dpm");
+ return TRUE;
+ }
+ }
-
++
+ return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_zombie(float req)
+{
+ switch(req)
+ {
+ case MR_PRECACHE:
+ {
+ precache_model ("models/monsters/zombie.dpm");
+ return TRUE;
+ }
+ }
++
+ return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
--- /dev/null
-
+#include "all.qh"
+
+// MONSTER PLUGIN SYSTEM
+entity monster_info[MON_MAXCOUNT];
+entity dummy_monster_info;
+
+void register_monster(float id, float(float) func, float monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname)
+{
+ entity e;
+ monster_info[id - 1] = e = spawn();
+ e.classname = "monster_info";
+ e.monsterid = id;
+ e.netname = shortname;
+ e.monster_name = mname;
+ e.monster_func = func;
+ e.mdl = modelname;
+ e.spawnflags = monsterflags;
+ e.mins = min_s;
+ e.maxs = max_s;
+ e.model = strzone(strcat("models/monsters/", modelname));
++
+ #ifndef MENUQC
+ func(MR_PRECACHE);
+ #endif
+}
+float m_null(float dummy) { return 0; }
+void register_monsters_done()
+{
+ dummy_monster_info = spawn();
+ dummy_monster_info.classname = "monster_info";
+ dummy_monster_info.monsterid = 0; // you can recognize dummies by this
+ dummy_monster_info.netname = "";
+ dummy_monster_info.monster_name = "Monster";
+ dummy_monster_info.monster_func = m_null;
+ dummy_monster_info.mdl = "";
+ dummy_monster_info.mins = '-0 -0 -0';
+ dummy_monster_info.maxs = '0 0 0';
+ dummy_monster_info.model = "";
+}
+entity get_monsterinfo(float id)
+{
+ entity m;
+ if(id < MON_FIRST || id > MON_LAST)
+ return dummy_monster_info;
+ m = monster_info[id - 1];
+ if(m)
+ return m;
+ return dummy_monster_info;
+}
--- /dev/null
- #define MR_SETUP 1 // (SERVER) setup monster data
+// monster requests
- #define MR_DEATH 3 // (SERVER) called when monster dies
- #define MR_PRECACHE 4 // (BOTH) precaches models/sounds used by this monster
++#define MR_SETUP 1 // (SERVER) setup monster data
+#define MR_THINK 2 // (SERVER) logic to run every frame
- // Monster Registration
++#define MR_DEATH 3 // (SERVER) called when monster dies
++#define MR_PRECACHE 4 // (BOTH) precaches models/sounds used by this monster
+
+// functions:
+entity get_monsterinfo(float id);
+
+// special spawn flags
+const float MONSTER_RESPAWN_DEATHPOINT = 16; // re-spawn where we died
+const float MONSTER_TYPE_FLY = 32;
+const float MONSTER_TYPE_SWIM = 64;
+const float MONSTER_SIZE_BROKEN = 128; // TODO: remove when bad models are replaced
+const float MON_FLAG_SUPERMONSTER = 256; // incredibly powerful monster
+const float MON_FLAG_RANGED = 512; // monster shoots projectiles
+const float MON_FLAG_MELEE = 1024;
+
+// entity properties of monsterinfo:
+.float monsterid; // MON_...
+.string netname; // short name
+.string monster_name; // human readable name
+.float(float) monster_func; // m_...
+.string mdl; // currently a copy of the model
+.string model; // full name of model
+.float spawnflags;
+.vector mins, maxs; // monster hitbox size
+
+// other useful macros
+#define MON_ACTION(monstertype,mrequest) (get_monsterinfo(monstertype)).monster_func(mrequest)
+#define M_NAME(monstertype) (get_monsterinfo(monstertype)).monster_name
+
+// =====================
++// Monster Registration
+// =====================
+
+float m_null(float dummy);
+void register_monster(float id, float(float) func, float monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname);
+void register_monsters_done();
+
+const float MON_MAXCOUNT = 24;
+#define MON_FIRST 1
+float MON_COUNT;
+float MON_LAST;
+
+#define REGISTER_MONSTER_2(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \
+ float id; \
+ float func(float); \
+ void RegisterMonsters_##id() \
+ { \
+ MON_LAST = (id = MON_FIRST + MON_COUNT); \
+ ++MON_COUNT; \
+ register_monster(id,func,monsterflags,min_s,max_s,modelname,shortname,mname); \
+ } \
+ ACCUMULATE_FUNCTION(RegisterMonsters, RegisterMonsters_##id)
+#ifdef MENUQC
+#define REGISTER_MONSTER(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \
+ REGISTER_MONSTER_2(MON_##id,m_null,monsterflags,min_s,max_s,modelname,shortname,mname)
+#else
+#define REGISTER_MONSTER(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \
+ REGISTER_MONSTER_2(MON_##id,func,monsterflags,min_s,max_s,modelname,shortname,mname)
+#endif
+
+#include "all.qh"
+
+#undef REGISTER_MONSTER
+ACCUMULATE_FUNCTION(RegisterMonsters, register_monsters_done);
--- /dev/null
-
+entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float moveflag)
+{
+ // ensure spawnfunc database is initialized
+ initialize_field_db();
-
++
+ entity e = spawn();
-
++
+ e.spawnflags = MONSTERFLAG_SPAWNED;
-
++
+ if(!respwn)
+ e.spawnflags |= MONSTERFLAG_NORESPAWN;
-
++
+ setorigin(e, orig);
-
++
+ if(monster != "")
+ {
+ float i, found = 0;
+ entity mon;
+ for(i = MON_FIRST; i <= MON_LAST; ++i)
+ {
+ mon = get_monsterinfo(i);
+ if(mon.netname == monster)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+ if(!found)
+ monster = (get_monsterinfo(MON_FIRST)).netname;
+ }
-
++
+ if(monster == "")
+ if(monster_id)
+ monster = (get_monsterinfo(monster_id)).netname;
-
++
+ e.realowner = spawnedby;
-
++
+ if(moveflag)
+ e.monster_moveflags = moveflag;
-
++
+ if(IS_PLAYER(spawnedby))
+ {
+ if(teamplay && autocvar_g_monsters_teams)
+ e.team = spawnedby.team; // colors handled in spawn code
-
++
+ if(autocvar_g_monsters_owners)
+ e.monster_owner = own; // using .owner makes the monster non-solid for its master
-
++
+ e.angles = spawnedby.angles;
+ }
-
++
+ monster = strcat("$ spawnfunc_monster_", monster);
-
++
+ target_spawn_edit_entity(e, monster, world, world, world, world, world);
++
+ return e;
+}
--- /dev/null
- // SVQC Monster Properties
+// =========================
-
++// SVQC Monster Properties
+// =========================
+
+
+void monster_item_spawn()
+{
+ if(self.monster_loot)
+ self.monster_loot();
+
+ self.gravity = 1;
+ self.reset = SUB_Remove;
+ self.noalign = TRUE;
+ self.velocity = randomvec() * 175 + '0 0 325';
+ self.classname = "droppedweapon"; // hax
+ self.item_spawnshieldtime = time + 0.7;
+
+ SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1);
+}
+
+void monster_dropitem()
+{
+ if(!self.candrop || !self.monster_loot)
+ return;
+
+ vector org = self.origin + ((self.mins + self.maxs) * 0.5);
+ entity e = spawn();
+
+ setorigin(e, org);
+
+ e.monster_loot = self.monster_loot;
+
+ other = e;
+ MUTATOR_CALLHOOK(MonsterDropItem);
+ e = other;
+
+ if(e)
+ {
+ e.think = monster_item_spawn;
+ e.nextthink = time + 0.3;
+ }
+}
+
+float Monster_SkillModifier()
+{
+ float t = 0.5+self.monster_skill*((1.2-0.3)/10);
-
++
+ return t;
+}
+
+float monster_isvalidtarget (entity targ, entity ent)
+{
+ if(!targ || !ent)
+ return FALSE; // someone doesn't exist
+
+ if(targ == ent)
+ return FALSE; // don't attack ourselves
+
+ traceline(ent.origin, targ.origin, MOVE_NORMAL, ent);
+
+ if(trace_ent != targ)
+ return FALSE;
+
+ if(targ.vehicle_flags & VHF_ISVEHICLE)
+ if(!((get_monsterinfo(ent.monsterid)).spawnflags & MON_FLAG_RANGED))
+ return FALSE; // melee attacks are useless against vehicles
+
+ if(time < game_starttime)
+ return FALSE; // monsters do nothing before the match has started
+
+ if(vlen(targ.origin - ent.origin) >= ent.target_range)
+ return FALSE; // enemy is too far away
+
+ if(targ.takedamage == DAMAGE_NO)
+ return FALSE; // enemy can't be damaged
+
+ if(targ.items & IT_INVISIBILITY)
+ return FALSE; // enemy is invisible
+
+ if(substring(targ.classname, 0, 10) == "onslaught_")
+ return FALSE; // don't attack onslaught targets
+
+ if(IS_SPEC(targ) || IS_OBSERVER(targ))
+ return FALSE; // enemy is a spectator
+
+ if(!(targ.vehicle_flags & VHF_ISVEHICLE))
+ if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0)
+ return FALSE; // enemy/self is dead
+
+ if(ent.monster_owner == targ)
+ return FALSE; // don't attack our master
+
+ if(targ.monster_owner == ent)
+ return FALSE; // don't attack our pet
+
+ if(!(targ.vehicle_flags & VHF_ISVEHICLE))
+ if(targ.flags & FL_NOTARGET)
+ return FALSE; // enemy can't be targeted
+
+ if(!autocvar_g_monsters_typefrag)
+ if(targ.BUTTON_CHAT)
+ return FALSE; // no typefragging!
+
+ if(SAME_TEAM(targ, ent))
+ return FALSE; // enemy is on our team
-
++
+ if (targ.frozen)
+ return FALSE; // ignore frozen
+
+ if(autocvar_g_monsters_target_infront || ent.spawnflags & MONSTERFLAG_INFRONT)
+ if(ent.enemy != targ)
+ {
+ float dot;
+
+ makevectors (ent.angles);
+ dot = normalize (targ.origin - ent.origin) * v_forward;
+
+ if(dot <= 0.3)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+entity FindTarget (entity ent)
+{
+ if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator
+
+ entity head, closest_target = world;
+ head = findradius(ent.origin, ent.target_range);
+
+ while(head) // find the closest acceptable target to pass to
+ {
+ if(head.monster_attack)
+ if(monster_isvalidtarget(head, ent))
+ {
+ // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
+ vector head_center = CENTER_OR_VIEWOFS(head);
+ vector ent_center = CENTER_OR_VIEWOFS(ent);
+
+ //if(ctf_CheckPassDirection(head_center, ent_center, ent.v_angle, head.WarpZone_findradius_nearest))
+ if(closest_target)
+ {
+ vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
+ if(vlen(ent_center - head_center) < vlen(ent_center - closest_target_center))
+ { closest_target = head; }
+ }
+ else { closest_target = head; }
+ }
+
+ head = head.chain;
+ }
+
+ return closest_target;
+}
+
+void MonsterTouch ()
+{
+ if(other == world)
+ return;
+
+ if(self.enemy != other)
+ if(!(other.flags & FL_MONSTER))
+ if(monster_isvalidtarget(other, self))
+ self.enemy = other;
+}
+
+string get_monster_model_datafilename(string m, float sk, string fil)
+{
+ if(m)
+ m = strcat(m, "_");
+ else
+ m = "models/monsters/*_";
+ if(sk >= 0)
+ m = strcat(m, ftos(sk));
+ else
+ m = strcat(m, "*");
+ return strcat(m, ".", fil);
+}
+
+void PrecacheMonsterSounds(string f)
+{
+ float fh;
+ string s;
+ fh = fopen(f, FILE_READ);
+ if(fh < 0)
+ return;
+ while((s = fgets(fh)))
+ {
+ if(tokenize_console(s) != 3)
+ {
+ dprint("Invalid sound info line: ", s, "\n");
+ continue;
+ }
+ PrecacheGlobalSound(strcat(argv(1), " ", argv(2)));
+ }
+ fclose(fh);
+}
+
+void precache_monstersounds()
+{
+ string m = (get_monsterinfo(self.monsterid)).model;
+ float globhandle, n, i;
+ string f;
+
+ globhandle = search_begin(strcat(m, "_*.sounds"), TRUE, FALSE);
+ if (globhandle < 0)
+ return;
+ n = search_getsize(globhandle);
+ for (i = 0; i < n; ++i)
+ {
+ //print(search_getfilename(globhandle, i), "\n");
+ f = search_getfilename(globhandle, i);
+ PrecacheMonsterSounds(f);
+ }
+ search_end(globhandle);
+}
+
+void ClearMonsterSounds()
+{
+#define _MSOUND(m) if(self.monstersound_##m) { strunzone(self.monstersound_##m); self.monstersound_##m = string_null; }
+ ALLMONSTERSOUNDS
+#undef _MSOUND
+}
+
+.string GetMonsterSoundSampleField(string type)
+{
+ GetMonsterSoundSampleField_notFound = 0;
+ switch(type)
+ {
+#define _MSOUND(m) case #m: return monstersound_##m;
+ ALLMONSTERSOUNDS
+#undef _MSOUND
+ }
+ GetMonsterSoundSampleField_notFound = 1;
+ return string_null;
+}
+
+float LoadMonsterSounds(string f, float first)
+{
+ float fh;
+ string s;
+ var .string field;
+ fh = fopen(f, FILE_READ);
+ if(fh < 0)
+ {
+ dprint("Monster sound file not found: ", f, "\n");
+ return 0;
+ }
+ while((s = fgets(fh)))
+ {
+ if(tokenize_console(s) != 3)
+ continue;
+ field = GetMonsterSoundSampleField(argv(0));
+ if(GetMonsterSoundSampleField_notFound)
+ continue;
+ if(self.field)
+ strunzone(self.field);
+ self.field = strzone(strcat(argv(1), " ", argv(2)));
+ }
+ fclose(fh);
+ return 1;
+}
+
+.float skin_for_monstersound;
+void UpdateMonsterSounds()
+{
+ entity mon = get_monsterinfo(self.monsterid);
+
+ if(self.skin == self.skin_for_monstersound)
+ return;
+ self.skin_for_monstersound = self.skin;
+ ClearMonsterSounds();
+ //LoadMonsterSounds("sound/monsters/default.sounds", 1);
+ if(!autocvar_g_debug_defaultsounds)
+ if(!LoadMonsterSounds(get_monster_model_datafilename(mon.model, self.skin, "sounds"), 0))
+ LoadMonsterSounds(get_monster_model_datafilename(mon.model, 0, "sounds"), 0);
+}
+
+void MonsterSound(.string samplefield, float sound_delay, float delaytoo, float chan)
+{
+ if(delaytoo && time < self.msound_delay)
+ return; // too early
+ GlobalSound(self.samplefield, chan, VOICETYPE_PLAYERSOUND);
+
+ self.msound_delay = time + sound_delay;
+}
+
+void monster_makevectors(entity e)
+{
+ vector v;
+
+ v = e.origin + (e.mins + e.maxs) * 0.5;
+ self.v_angle = vectoangles(v - (self.origin + self.view_ofs));
+ self.v_angle_x = -self.v_angle_x;
+
+ makevectors(self.v_angle);
+}
+
+float monster_melee(entity targ, float damg, float anim, float er, float anim_finished, float deathtype, float dostop)
+{
+ if (self.health <= 0)
+ return FALSE; // attacking while dead?!
+
+ if(dostop)
+ {
+ self.velocity_x = 0;
+ self.velocity_y = 0;
+ self.state = MONSTER_STATE_ATTACK_MELEE;
+ }
+
+ self.frame = anim;
+
+ if(anim_finished != 0)
+ self.attack_finished_single = time + anim_finished;
+
+ monster_makevectors(targ);
+
+ traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self);
+
+ if(trace_ent.takedamage)
+ Damage(trace_ent, self, self, damg * Monster_SkillModifier(), deathtype, trace_ent.origin, normalize(trace_ent.origin - self.origin));
+
+ return TRUE;
+}
+
+void Monster_CheckMinibossFlag ()
+{
+ if(MUTATOR_CALLHOOK(MonsterCheckBossFlag))
+ return;
+
+ float chance = random() * 100;
+
+ // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss
+ if ((self.spawnflags & MONSTERFLAG_MINIBOSS) || (chance < autocvar_g_monsters_miniboss_chance))
+ {
+ self.health += autocvar_g_monsters_miniboss_healthboost;
+ self.effects |= EF_RED;
+ if(!self.weapon)
+ self.weapon = WEP_NEX;
+ }
+}
+
+float Monster_CanRespawn(entity ent)
+{
+ other = ent;
+ if(ent.deadflag == DEAD_DEAD) // don't call when monster isn't dead
+ if(MUTATOR_CALLHOOK(MonsterRespawn))
+ return TRUE; // enabled by a mutator
+
+ if(ent.spawnflags & MONSTERFLAG_NORESPAWN)
+ return FALSE;
+
+ if(!autocvar_g_monsters_respawn)
+ return FALSE;
+
+ return TRUE;
+}
+
+void Monster_Fade ()
+{
+ if(Monster_CanRespawn(self))
+ {
+ self.monster_respawned = TRUE;
+ self.think = self.monster_spawnfunc;
+ self.nextthink = time + self.respawntime;
+ self.ltime = 0;
+ self.deadflag = DEAD_RESPAWNING;
+ if(self.spawnflags & MONSTER_RESPAWN_DEATHPOINT)
+ {
+ self.pos1 = self.origin;
+ self.pos2 = self.angles;
+ }
+ self.event_damage = func_null;
+ self.takedamage = DAMAGE_NO;
+ setorigin(self, self.pos1);
+ self.angles = self.pos2;
+ self.health = self.max_health;
+ setmodel(self, "null");
+ }
+ else
+ SUB_SetFade(self, time + 3, 1);
+}
+
+float Monster_CanJump (vector vel)
+{
+ if(self.state)
+ return FALSE; // already attacking
+ if(!(self.flags & FL_ONGROUND))
+ return FALSE; // not on the ground
+ if(self.health <= 0)
+ return FALSE; // called when dead?
+ if(time < self.attack_finished_single)
+ return FALSE; // still attacking
+
+ vector old = self.velocity;
+
+ self.velocity = vel;
+ tracetoss(self, self);
+ self.velocity = old;
+ if (trace_ent != self.enemy)
+ return FALSE;
+
+ return TRUE;
+}
+
+float monster_leap (float anm, void() touchfunc, vector vel, float anim_finished)
+{
+ if(!Monster_CanJump(vel))
+ return FALSE;
+
+ self.frame = anm;
+ self.state = MONSTER_STATE_ATTACK_LEAP;
+ self.touch = touchfunc;
+ self.origin_z += 1;
+ self.velocity = vel;
+ self.flags &= ~FL_ONGROUND;
+
+ self.attack_finished_single = time + anim_finished;
+
+ return TRUE;
+}
+
+void monster_checkattack(entity e, entity targ)
+{
+ if(e == world)
+ return;
+ if(targ == world)
+ return;
+
+ if(!e.monster_attackfunc)
+ return;
+
+ if(time < e.attack_finished_single)
+ return;
+
+ if(vlen(targ.origin - e.origin) <= e.attack_range)
+ if(e.monster_attackfunc(MONSTER_ATTACK_MELEE))
+ {
+ MonsterSound(monstersound_melee, 0, FALSE, CH_VOICE);
+ return;
+ }
+
+ if(vlen(targ.origin - e.origin) > e.attack_range)
+ if(e.monster_attackfunc(MONSTER_ATTACK_RANGED))
+ {
+ MonsterSound(monstersound_ranged, 0, FALSE, CH_VOICE);
+ return;
+ }
+}
+
+void monster_use ()
+{
+ if(!self.enemy)
+ if(self.health > 0)
+ if(monster_isvalidtarget(activator, self))
+ self.enemy = activator;
+}
+
+.float last_trace;
+.float last_enemycheck; // for checking enemy
+vector monster_pickmovetarget(entity targ)
+{
+ // enemy is always preferred target
+ if(self.enemy)
+ {
+ makevectors(self.angles);
+ self.monster_movestate = MONSTER_MOVE_ENEMY;
+ self.last_trace = time + 1.2;
+ return self.enemy.origin;
+ }
+
+ switch(self.monster_moveflags)
+ {
+ case MONSTER_MOVE_OWNER:
+ {
+ self.monster_movestate = MONSTER_MOVE_OWNER;
+ self.last_trace = time + 0.3;
+ return (self.monster_owner) ? self.monster_owner.origin : self.origin;
+ }
+ case MONSTER_MOVE_SPAWNLOC:
+ {
+ self.monster_movestate = MONSTER_MOVE_SPAWNLOC;
+ self.last_trace = time + 2;
+ return self.pos1;
+ }
+ case MONSTER_MOVE_NOMOVE:
+ {
+ self.monster_movestate = MONSTER_MOVE_NOMOVE;
+ self.last_trace = time + 2;
+ return self.origin;
+ }
+ default:
+ case MONSTER_MOVE_WANDER:
+ {
+ vector pos;
+ self.monster_movestate = MONSTER_MOVE_WANDER;
+ self.last_trace = time + 2;
+
+ self.angles_y = rint(random() * 500);
+ makevectors(self.angles);
+ pos = self.origin + v_forward * 600;
+
+ if(self.flags & FL_FLY || self.flags & FL_SWIM)
+ if(self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
+ {
+ pos_z = random() * 200;
+ if(random() >= 0.5)
+ pos_z *= -1;
+ }
+
+ if(targ)
+ {
+ self.last_trace = time + 0.5;
+ pos = targ.origin;
+ }
+
+ return pos;
+ }
+ }
+}
+
+void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle)
+{
+ fixedmakevectors(self.angles);
+
+ if(self.target2)
+ self.goalentity = find(world, targetname, self.target2);
+
+ entity targ;
+
+ if(self.frozen)
+ {
+ self.revive_progress = bound(0, self.revive_progress + self.ticrate * self.revive_speed, 1);
+ self.health = max(1, self.max_health * self.revive_progress);
-
++
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
-
++
+ movelib_beak_simple(stopspeed);
-
++
+ self.frame = manim_idle;
-
++
+ self.enemy = world;
+ self.nextthink = time + self.ticrate;
+
+ if(self.revive_progress >= 1)
+ Unfreeze(self); // wait for next think before attacking
+
+ return; // no moving while frozen
+ }
+
+ if(self.flags & FL_SWIM)
+ {
+ if(self.waterlevel < WATERLEVEL_WETFEET)
+ {
+ if(time >= self.last_trace)
+ {
+ self.fish_wasdrowning = TRUE;
+ self.last_trace = time + 0.4;
+
+ Damage (self, world, world, 2, DEATH_DROWN, self.origin, '0 0 0');
+ self.angles = '90 90 0';
+ if(random() < 0.5)
+ {
+ self.velocity_y += random() * 50;
+ self.velocity_x -= random() * 50;
+ }
+ else
+ {
+ self.velocity_y -= random() * 50;
+ self.velocity_x += random() * 50;
+ }
+ self.velocity_z += random() * 150;
+ }
+
+
+ self.movetype = MOVETYPE_BOUNCE;
+ //self.velocity_z = -200;
+
+ return;
+ }
+ else if(self.fish_wasdrowning)
+ {
+ self.fish_wasdrowning = FALSE;
+ self.angles_x = 0;
+ self.movetype = MOVETYPE_WALK;
+ }
+ }
+
+ targ = self.goalentity;
+
+ monster_target = targ;
+ monster_speed_run = runspeed;
+ monster_speed_walk = walkspeed;
+
+ if(MUTATOR_CALLHOOK(MonsterMove) || gameover || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time)
+ {
+ runspeed = walkspeed = 0;
+ if(time >= self.spawn_time)
+ self.frame = manim_idle;
+ movelib_beak_simple(stopspeed);
+ return;
+ }
+
+ targ = monster_target;
+ runspeed = bound(0, monster_speed_run * Monster_SkillModifier(), runspeed * 2); // limit maxspeed to prevent craziness
+ walkspeed = bound(0, monster_speed_walk * Monster_SkillModifier(), walkspeed * 2); // limit maxspeed to prevent craziness
-
++
+ if(time < self.spider_slowness)
+ {
+ runspeed *= 0.5;
+ walkspeed *= 0.5;
+ }
+
+ if(teamplay)
+ if(autocvar_g_monsters_teams)
+ if(DIFF_TEAM(self.monster_owner, self))
+ self.monster_owner = world;
+
+ if(self.enemy && self.enemy.health < 1)
+ self.enemy = world; // enough!
+
+ if(time >= self.last_enemycheck)
+ {
+ if(!monster_isvalidtarget(self.enemy, self))
+ self.enemy = world;
+
+ if(!self.enemy)
+ {
+ self.enemy = FindTarget(self);
+ if(self.enemy)
+ MonsterSound(monstersound_sight, 0, FALSE, CH_VOICE);
+ }
+
+ self.last_enemycheck = time + 0.5;
+ }
+
+ if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single)
+ self.state = 0;
+
+ if(self.state != MONSTER_STATE_ATTACK_MELEE) // don't move if set
+ if(time >= self.last_trace || self.enemy) // update enemy instantly
+ self.moveto = monster_pickmovetarget(targ);
+
+ if(!self.enemy)
+ MonsterSound(monstersound_idle, 7, TRUE, CH_VOICE);
+
+ if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
+ self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
+
+ if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND))
+ {
+ self.state = 0;
+ self.touch = MonsterTouch;
+ }
+
+ //self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
+
+ float turny = 0;
+ vector real_angle = vectoangles(self.steerto) - self.angles;
+
+ if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
+ turny = 20;
+
+ if(self.flags & FL_SWIM)
+ turny = vlen(self.angles - self.moveto);
+
+ if(turny)
+ {
+ turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny);
+ self.angles_y += turny;
+ }
+
+ if(self.state == MONSTER_STATE_ATTACK_MELEE)
+ self.moveto = self.origin;
+
+ if(self.enemy && self.enemy.vehicle)
+ runspeed = 0;
+
+ if(((self.flags & FL_FLY) || (self.flags & FL_SWIM)) && self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
+ v_forward = normalize(self.moveto - self.origin);
+ else
+ self.moveto_z = self.origin_z;
+
+ if(vlen(self.origin - self.moveto) > 64)
+ {
+ if(self.flags & FL_FLY || self.flags & FL_SWIM)
+ movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
+ else
+ movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
+
+ if(time > self.pain_finished)
+ if(time > self.attack_finished_single)
+ if(vlen(self.velocity) > 10)
+ self.frame = ((self.enemy) ? manim_run : manim_walk);
+ else
+ self.frame = manim_idle;
+ }
+ else
+ {
+ entity e = find(world, targetname, self.target2);
+ if(e.target2)
+ self.target2 = e.target2;
+ else if(e.target)
+ self.target2 = e.target;
+
+ movelib_beak_simple(stopspeed);
+ if(time > self.attack_finished_single)
+ if(time > self.pain_finished)
+ if (vlen(self.velocity) <= 30)
+ self.frame = manim_idle;
+ }
+
+ monster_checkattack(self, self.enemy);
+}
+
+void monster_remove(entity mon)
+{
+ if(!mon)
+ return; // nothing to remove
-
++
+ pointparticles(particleeffectnum("item_pickup"), mon.origin, '0 0 0', 1);
-
++
+ if(mon.weaponentity)
+ remove(mon.weaponentity);
-
++
+ if(mon.iceblock)
+ remove(mon.iceblock);
-
++
+ WaypointSprite_Kill(mon.sprite);
-
++
+ remove(mon);
+}
+
+void monster_dead_think()
+{
+ self.nextthink = time + self.ticrate;
-
++
+ CSQCMODEL_AUTOUPDATE();
+
+ if(self.ltime != 0)
+ if(time >= self.ltime)
+ {
+ Monster_Fade();
+ return;
+ }
+}
+
+void monsters_setstatus()
+{
+ self.stat_monsters_total = monsters_total;
+ self.stat_monsters_killed = monsters_killed;
+}
+
+void Monster_Appear()
+{
+ self.enemy = activator;
+ self.spawnflags &= ~MONSTERFLAG_APPEAR;
+ self.monster_spawnfunc();
+}
+
+float Monster_CheckAppearFlags(entity ent)
+{
+ if(!(ent.spawnflags & MONSTERFLAG_APPEAR))
+ return FALSE;
+
+ ent.think = func_null;
+ ent.nextthink = 0;
+ ent.use = Monster_Appear;
+ ent.flags = FL_MONSTER; // set so this monster can get butchered
+
+ return TRUE;
+}
+
+void monsters_reset()
+{
+ setorigin(self, self.pos1);
+ self.angles = self.pos2;
-
++
+ Unfreeze(self); // remove any icy remains
+
+ self.health = self.max_health;
+ self.velocity = '0 0 0';
+ self.enemy = world;
+ self.goalentity = world;
+ self.attack_finished_single = 0;
+ self.moveto = self.origin;
+}
+
+void monsters_corpse_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ self.health -= damage;
+
+ Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
+
+ if(self.health <= -100) // 100 health until gone?
+ {
+ Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
-
++
+ if(IS_CLIENT(self.realowner))
+ if(!self.monster_respawned)
+ self.realowner.monstercount -= 1;
+
+ self.think = SUB_Remove;
+ self.nextthink = time + 0.1;
+ }
+}
+
+void monster_die(entity attacker, float gibbed)
+{
+ self.think = monster_dead_think;
+ self.nextthink = time;
+ self.ltime = time + 5;
-
++
+ if ( self.frozen )
+ {
+ Unfreeze(self); // remove any icy remains
+ self.health = 0; // reset by Unfreeze
+ }
-
++
+ monster_dropitem();
+
+ MonsterSound(monstersound_death, 0, FALSE, CH_VOICE);
+
+ if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !self.monster_respawned)
+ monsters_killed += 1;
- if( autocvar_g_monsters_score_spawned ||
++
+ if(IS_PLAYER(attacker))
-
++ if( autocvar_g_monsters_score_spawned ||
+ ( !(self.spawnflags & MONSTERFLAG_SPAWNED) && !self.monster_respawned) )
+ PlayerScore_Add(attacker, SP_SCORE, +autocvar_g_monsters_score_kill);
+
+ if(!Monster_CanRespawn(self) || gibbed)
+ {
+ // number of monsters spawned with mobspawn command
+ totalspawned -= 1;
-
++
+ if(IS_CLIENT(self.realowner))
+ if(!self.monster_respawned)
+ self.realowner.monstercount -= 1;
+ }
+
+ if(self.candrop && self.weapon)
+ W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325');
+
+ self.event_damage = monsters_corpse_damage;
+ self.solid = SOLID_CORPSE;
+ self.takedamage = DAMAGE_AIM;
+ self.deadflag = DEAD_DEAD;
+ self.enemy = world;
+ self.movetype = MOVETYPE_TOSS;
+ self.moveto = self.origin;
+ self.touch = MonsterTouch; // reset incase monster was pouncing
+ self.reset = func_null;
+ self.state = 0;
+ self.attack_finished_single = 0;
+
+ if(!(self.flags & FL_FLY))
+ self.velocity = '0 0 0';
+
+ MON_ACTION(self.monsterid, MR_DEATH);
+}
+
+void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
-
++
+ if(self.frozen && deathtype != DEATH_KILL)
+ return;
+
+ if(time < self.pain_finished && deathtype != DEATH_KILL)
+ return;
+
+ if(time < self.spawnshieldtime && deathtype != DEATH_KILL)
+ return;
+
+ vector v;
+ float take, save;
+
+ v = healtharmor_applydamage(self.armorvalue, self.m_armor_blockpercent, deathtype, damage);
+ take = v_x;
+ save = v_y;
+
+ self.health -= take;
-
++
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+
+ self.dmg_time = time;
+
+ if(sound_allowed(MSG_BROADCAST, attacker) && deathtype != DEATH_DROWN)
+ spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER
+
+ self.velocity += force * self.damageforcescale;
+
+ if(deathtype != DEATH_DROWN)
+ {
+ Violence_GibSplash_At(hitloc, force, 2, bound(0, take, 200) / 16, self, attacker);
+ if (take > 50)
+ Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
+ if (take > 100)
+ Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
+ }
+
+ if(self.health <= 0)
+ {
+ if(deathtype == DEATH_KILL)
+ self.candrop = FALSE; // killed by mobkill command
+
+ // TODO: fix this?
+ activator = attacker;
+ other = self.enemy;
+ SUB_UseTargets();
+ self.target2 = self.oldtarget2; // reset to original target on death, incase we respawn
+
+ monster_die(attacker, (self.health <= -100 || deathtype == DEATH_KILL));
-
++
+ WaypointSprite_Kill(self.sprite);
-
++
+ frag_attacker = attacker;
+ frag_target = self;
+ MUTATOR_CALLHOOK(MonsterDies);
+
+ if(self.health <= -100 || deathtype == DEATH_KILL) // check if we're already gibbed
+ {
+ Violence_GibSplash(self, 1, 0.5, attacker);
+
+ self.think = SUB_Remove;
+ self.nextthink = time + 0.1;
+ }
+ }
+}
+
+void monster_setupcolors()
+{
+ if(IS_PLAYER(self.monster_owner))
+ self.colormap = self.monster_owner.colormap;
+ else if(teamplay && self.team)
+ self.colormap = 1024 + (self.team - 1) * 17;
+ else
+ {
+ if(self.monster_skill <= MONSTER_SKILL_EASY)
+ self.colormap = 1029;
+ else if(self.monster_skill <= MONSTER_SKILL_MEDIUM)
+ self.colormap = 1027;
+ else if(self.monster_skill <= MONSTER_SKILL_HARD)
+ self.colormap = 1038;
+ else if(self.monster_skill <= MONSTER_SKILL_INSANE)
+ self.colormap = 1028;
+ else if(self.monster_skill <= MONSTER_SKILL_NIGHTMARE)
+ self.colormap = 1032;
+ else
+ self.colormap = 1024;
+ }
+}
+
+void monster_think()
+{
+ self.think = monster_think;
+ self.nextthink = self.ticrate;
-
++
+ if(self.ltime)
+ if(time >= self.ltime)
+ {
+ Damage(self, self, self, self.health + self.max_health, DEATH_KILL, self.origin, self.origin);
+ return;
+ }
+
+ MON_ACTION(self.monsterid, MR_THINK);
-
++
+ CSQCMODEL_AUTOUPDATE();
+}
+
+float monster_spawn()
+{
+ MON_ACTION(self.monsterid, MR_SETUP);
+
+ if(!self.monster_respawned)
+ {
+ Monster_CheckMinibossFlag();
+ self.health *= Monster_SkillModifier();
+ }
+
+ self.max_health = self.health;
+ self.pain_finished = self.nextthink;
-
++
+ if(IS_PLAYER(self.monster_owner))
+ self.effects |= EF_DIMLIGHT;
+
+ if(!self.monster_respawned)
+ if(!self.skin)
+ self.skin = rint(random() * 4);
+
+ if(!self.attack_range)
+ self.attack_range = autocvar_g_monsters_attack_range;
-
++
+ precache_monstersounds();
+ UpdateMonsterSounds();
+
+ if(teamplay)
+ self.monster_attack = TRUE; // we can have monster enemies in team games
-
++
+ MonsterSound(monstersound_spawn, 0, FALSE, CH_VOICE);
-
++
+ WaypointSprite_Spawn(M_NAME(self.monsterid), 0, 1024, self, '0 0 1' * (self.maxs_z + 15), world, self.team, self, sprite, TRUE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0'));
+ WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+
+ self.think = monster_think;
+ self.nextthink = time + self.ticrate;
-
++
+ if(MUTATOR_CALLHOOK(MonsterSpawn))
+ return FALSE;
-
++
+ return TRUE;
+}
+
+float monster_initialize(float mon_id, float nodrop)
+{
+ if(!autocvar_g_monsters)
+ return FALSE;
+
+ entity mon = get_monsterinfo(mon_id);
-
++
+ if(!self.monster_skill)
+ self.monster_skill = cvar("g_monsters_skill");
+
+ // support for quake style removing monsters based on skill
+ if(self.monster_skill == MONSTER_SKILL_EASY) if(self.spawnflags & MONSTERSKILL_NOTEASY) { return FALSE; }
+ if(self.monster_skill == MONSTER_SKILL_MEDIUM) if(self.spawnflags & MONSTERSKILL_NOTMEDIUM) { return FALSE; }
+ if(self.monster_skill == MONSTER_SKILL_HARD) if(self.spawnflags & MONSTERSKILL_NOTHARD) { return FALSE; }
+
+ if(self.team && !teamplay)
+ self.team = 0;
+
+ if(!(self.spawnflags & MONSTERFLAG_SPAWNED)) // naturally spawned monster
+ if(!self.monster_respawned)
+ monsters_total += 1;
+
+ setmodel(self, mon.model);
+ setsize(self, mon.mins, mon.maxs);
+ self.flags = FL_MONSTER;
+ self.takedamage = DAMAGE_AIM;
+ self.bot_attack = TRUE;
+ self.iscreature = TRUE;
+ self.teleportable = TRUE;
+ self.damagedbycontents = TRUE;
+ self.monsterid = mon_id;
+ self.damageforcescale = 0;
+ self.event_damage = monsters_damage;
+ self.touch = MonsterTouch;
+ self.use = monster_use;
+ self.solid = SOLID_BBOX;
+ self.movetype = MOVETYPE_WALK;
+ self.spawnshieldtime = time + autocvar_g_monsters_spawnshieldtime;
+ self.enemy = world;
+ self.velocity = '0 0 0';
+ self.moveto = self.origin;
+ self.pos1 = self.origin;
+ self.pos2 = self.angles;
+ self.reset = monsters_reset;
+ self.netname = mon.netname;
+ self.monster_name = M_NAME(mon_id);
+ self.candrop = TRUE;
+ self.view_ofs = '0 0 1' * (self.maxs_z * 0.5);
+ self.oldtarget2 = self.target2;
+ self.deadflag = DEAD_NO;
+ self.scale = 1;
+ self.noalign = nodrop;
+ self.spawn_time = time;
+ self.spider_slowness = 0;
+ self.gravity = 1;
+ self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
-
++
+ if(autocvar_g_fullbrightplayers)
+ self.effects |= EF_FULLBRIGHT;
-
++
+ if(autocvar_g_nodepthtestplayers)
+ self.effects |= EF_NODEPTHTEST;
+
+ if(mon.spawnflags & MONSTER_TYPE_SWIM)
+ self.flags |= FL_SWIM;
+
+ if(mon.spawnflags & MONSTER_TYPE_FLY)
+ {
+ self.flags |= FL_FLY;
+ self.movetype = MOVETYPE_FLY;
+ }
+
+ if(mon.spawnflags & MONSTER_SIZE_BROKEN)
+ self.scale = 1.3;
+
+ if(!self.ticrate)
+ self.ticrate = autocvar_g_monsters_think_delay;
+
+ self.ticrate = bound(sys_frametime, self.ticrate, 60);
+
+ if(!self.m_armor_blockpercent)
+ self.m_armor_blockpercent = 0.5;
+
+ if(!self.target_range)
+ self.target_range = autocvar_g_monsters_target_range;
+
+ if(!self.respawntime)
+ self.respawntime = autocvar_g_monsters_respawn_delay;
+
+ if(!self.monster_moveflags)
+ self.monster_moveflags = MONSTER_MOVE_WANDER;
-
++
+ if(!self.noalign)
+ {
+ setorigin(self, self.origin + '0 0 20');
+ tracebox(self.origin + '0 0 64', self.mins, self.maxs, self.origin - '0 0 10000', MOVE_WORLDONLY, self);
+ setorigin(self, trace_endpos);
+ }
-
++
+ if(!monster_spawn())
+ return FALSE;
-
++
+ if(!self.monster_respawned)
+ monster_setupcolors();
++
+ CSQCMODEL_AUTOINIT();
+
+ return TRUE;
+}
--- /dev/null
- _MSOUND(idle)
+.string spawnmob;
+.float monster_attack;
+
+.entity monster_owner; // new monster owner entity, fixes non-solid monsters
+.float monstercount; // per player monster count
+
+.float stat_monsters_killed; // stats
+.float stat_monsters_total;
+float monsters_total;
+float monsters_killed;
+void monsters_setstatus(); // monsters.qc
+.float monster_moveflags; // checks where to move when not attacking
+
+.float spider_slowness; // special spider timer
+
+void monster_remove(entity mon); // removes a monster
+
+.float(float attack_type) monster_attackfunc;
+const float MONSTER_ATTACK_MELEE = 1;
+const float MONSTER_ATTACK_RANGED = 2;
+
+.float monster_skill;
+const float MONSTER_SKILL_EASY = 1;
+const float MONSTER_SKILL_MEDIUM = 3;
+const float MONSTER_SKILL_HARD = 5;
+const float MONSTER_SKILL_INSANE = 7;
+const float MONSTER_SKILL_NIGHTMARE = 10;
+
+.float fish_wasdrowning; // used to reset a drowning fish's angles if it reaches water again
+
+.float candrop;
+
+.float attack_range;
+
+.float spawn_time; // stop monster from moving around right after spawning
+
+.string oldtarget2;
+.float lastshielded;
+
+.vector oldangles;
+
+.float m_armor_blockpercent;
+
+// monster sounds
+// copied from player sounds
+.float msound_delay; // temporary antilag system
+#define ALLMONSTERSOUNDS \
+ _MSOUND(death) \
+ _MSOUND(sight) \
+ _MSOUND(ranged) \
+ _MSOUND(melee) \
+ _MSOUND(pain) \
+ _MSOUND(spawn) \
- const float MONSTERFLAG_MINIBOSS = 64; // monster spawns as mini-boss (also has a chance of naturally becoming one)
++ _MSOUND(idle)
+
+#define _MSOUND(m) .string monstersound_##m;
+ALLMONSTERSOUNDS
+#undef _MSOUND
+
+float GetMonsterSoundSampleField_notFound;
+
+.float monster_respawned; // used to make sure we're not recounting respawned monster stats
+
+const float MONSTERSKILL_NOTEASY = 256; // monster will not spawn on skill <= 1
+const float MONSTERSKILL_NOTMEDIUM = 512; // monster will not spawn on skill 2
+const float MONSTERSKILL_NOTHARD = 1024; // monster will not spawn on skill >= 3
+
+// new flags
+const float MONSTERFLAG_APPEAR = 2; // delay spawn until triggered
+const float MONSTERFLAG_NORESPAWN = 4;
+const float MONSTERFLAG_FLY_VERTICAL = 8; // fly/swim vertically
+const float MONSTERFLAG_INFRONT = 32; // only check for enemies infront of us
-
++const float MONSTERFLAG_MINIBOSS = 64; // monster spawns as mini-boss (also has a chance of naturally becoming one)
+const float MONSTERFLAG_SPAWNED = 16384; // flag for spawned monsters
+
+.void() monster_spawnfunc;
+
+.float monster_movestate; // used to tell what the monster is currently doing
+const float MONSTER_MOVE_OWNER = 1; // monster will move to owner if in range, or stand still
+const float MONSTER_MOVE_WANDER = 2; // monster will ignore owner & wander around
+const float MONSTER_MOVE_SPAWNLOC = 3; // monster will move to its spawn location when not attacking
+const float MONSTER_MOVE_NOMOVE = 4; // monster simply stands still
+const float MONSTER_MOVE_ENEMY = 5; // used only as a movestate
+
+const float MONSTER_STATE_ATTACK_LEAP = 1;
+const float MONSTER_STATE_ATTACK_MELEE = 2;
// main types/groups of notifications
#define MSG_ANNCE 1 // "Global" AND "personal" announcer messages
- #define MSG_INFO 2 // "Global" information messages
+ #define MSG_INFO 2 // "Global" information messages
#define MSG_CENTER 3 // "Personal" centerprint messages
#define MSG_CENTER_CPID 4 // Kill centerprint message
#define MSG_MULTI 5 // Subcall MSG_INFO and/or MSG_CENTER notifications
float strnum,
float flnum,
/* MSG_ANNCE */
- float channel,
+ float channel,
string snd,
float vol,
float position,
Check out the definitions in util.qc/util.qh/teams.qh for string CCR(...) and
string TCR(...) to better understand how these code replacements work.
-
+
Additionally, you can find all the definitions and explanations for
the argument values and what they return down below in this file.
MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_FALL, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_fall", _("^BG%s%s^K1 was grounded by ^BG%s^K1%s%s"), "") \
MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_FIRE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was burnt up into a crisp by ^BG%s^K1%s%s"), _("^BG%s%s^K1 felt a little hot from ^BG%s^K1's fire^K1%s%s")) \
MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_LAVA, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_lava", _("^BG%s%s^K1 was cooked by ^BG%s^K1%s%s"), "") \
+ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_MONSTER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "", "", _("^BG%s%s^K1 was pushed infront of a monster by ^BG%s^K1%s%s"), "") \
MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_NADE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was blown up by ^BG%s^K1's Nade%s%s"), "") \
MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_SHOOTING_STAR, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_shootingstar", _("^BG%s%s^K1 was shot into space by ^BG%s^K1%s%s"), "") \
MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_SLIME, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_slime", _("^BG%s%s^K1 was slimed by ^BG%s^K1%s%s"), "") \
MSG_INFO_NOTIF(1, INFO_DEATH_SELF_GENERIC, 2, 1, "s1 s2loc spree_lost", "s1", "notify_selfkill", _("^BG%s^K1 died%s%s"), "") \
MSG_INFO_NOTIF(1, INFO_DEATH_SELF_LAVA, 2, 1, "s1 s2loc spree_lost", "s1", "notify_lava", _("^BG%s^K1 turned into hot slag%s%s"), _("^BG%s^K1 found a hot place%s%s")) \
MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NADE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 mastered the art of self-nading%s%s"), "") \
+ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_MAGE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was exploded by a Mage%s%s"), "") \
+ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SHAMBLER_CLAW, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1's innards became outwards by a Shambler%s%s"), "") \
+ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SHAMBLER_SMASH, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was smashed by a Shambler%s%s"), "") \
+ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SHAMBLER_ZAP, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was zapped to death by a Shambler%s%s"), "") \
+ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SPIDER, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was bitten by a Spider%s%s"), "") \
+ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_WYVERN, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was fireballed by a Wyvern%s%s"), "") \
+ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_ZOMBIE_JUMP, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 joins the Zombies%s%s"), "") \
+ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_ZOMBIE_MELEE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was given kung fu lessons by a Zombie%s%s"), "") \
MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NOAMMO, 2, 1, "s1 s2loc spree_lost", "s1", "notify_outofammo", _("^BG%s^K1 died%s%s. What's the point of living without ammo?"), _("^BG%s^K1 ran out of ammo%s%s")) \
MSG_INFO_NOTIF(1, INFO_DEATH_SELF_ROT, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 rotted away%s%s"), "") \
MSG_INFO_NOTIF(1, INFO_DEATH_SELF_SHOOTING_STAR, 2, 1, "s1 s2loc spree_lost", "s1", "notify_shootingstar", _("^BG%s^K1 became a shooting star%s%s"), "") \
MULTITEAM_INFO(1, INFO_KEYHUNT_PICKUP_, 4, 1, 0, "s1", "", "", _("^BG%s^BG picked up the ^TC^TT Key"), "") \
MSG_INFO_NOTIF(1, INFO_LMS_FORFEIT, 1, 0, "s1", "", "", _("^BG%s^F3 forfeited"), "") \
MSG_INFO_NOTIF(1, INFO_LMS_NOLIVES, 1, 0, "s1", "", "", _("^BG%s^F3 has no more lives left"), "") \
+ MSG_INFO_NOTIF(1, INFO_MONSTERS_DISABLED, 0, 0, "", "", "", _("^BGMonsters are currently disabled"), "") \
MSG_INFO_NOTIF(1, INFO_POWERUP_INVISIBILITY, 1, 0, "s1", "s1", "strength", _("^BG%s^K1 picked up Invisibility"), "") \
MSG_INFO_NOTIF(1, INFO_POWERUP_SHIELD, 1, 0, "s1", "s1", "shield", _("^BG%s^K1 picked up Shield"), "") \
MSG_INFO_NOTIF(1, INFO_POWERUP_SPEED, 1, 0, "s1", "s1", "shield", _("^BG%s^K1 picked up Speed"), "") \
MSG_INFO_NOTIF(1, INFO_WEAPON_TUBA_MURDER, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weapontuba", _("^BG%s%s^K1 died of ^BG%s^K1's great playing on the @!#%%'n Tuba%s%s"), "") \
MSG_INFO_NOTIF(1, INFO_WEAPON_TUBA_SUICIDE, 2, 1, "s1 s2loc spree_lost", "s1", "weapontuba", _("^BG%s^K1 hurt their own ears with the @!#%%'n Tuba%s%s"), "") \
MSG_INFO_NOTIF(1, INFO_WEAPON_UZI_MURDER_SNIPE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponuzi", _("^BG%s%s^K1 was sniped by ^BG%s^K1's Machine Gun%s%s"), "") \
- MSG_INFO_NOTIF(1, INFO_WEAPON_UZI_MURDER_SPRAY, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponuzi", _("^BG%s%s^K1 was riddled full of holes by ^BG%s^K1's Machine Gun%s%s"), "")
+ MSG_INFO_NOTIF(1, INFO_WEAPON_UZI_MURDER_SPRAY, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "weaponuzi", _("^BG%s%s^K1 was riddled full of holes by ^BG%s^K1's Machine Gun%s%s"), "")
#define MULTITEAM_CENTER2(default,prefix,strnum,flnum,args,cpid,durcnt,normal,gentle) \
MSG_CENTER_NOTIF(default, prefix##RED, strnum, flnum, args, cpid, durcnt, TCR(normal, COL_TEAM_1, strtoupper(NAME_TEAM_1)), TCR(gentle, COL_TEAM_1, strtoupper(NAME_TEAM_1))) \
MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_FIRE, 0, 0, "", NO_CPID, "0 0", _("^K1You got a little bit too crispy!"), _("^K1You felt a little too hot!")) \
MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_GENERIC, 0, 0, "", NO_CPID, "0 0", _("^K1You killed your own dumb self!"), _("^K1You need to be more careful!")) \
MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_LAVA, 0, 0, "", NO_CPID, "0 0", _("^K1You couldn't stand the heat!"), "") \
+ MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_MONSTER, 0, 0, "", NO_CPID, "0 0", _("^K1You were killed by a monster!"), _("^K1You need to watch out for monsters!")) \
MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_NADE, 0, 0, "", NO_CPID, "0 0", _("^K1You forgot to put the pin back in!"), _("^K1Tastes like chicken!")) \
MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_NOAMMO, 0, 0, "", NO_CPID, "0 0", _("^K1You were killed for running out of ammo..."), _("^K1You are respawning for running out of ammo...")) \
MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_ROT, 0, 0, "", NO_CPID, "0 0", _("^K1You grew too old without taking your medicine"), _("^K1You need to preserve your health")) \
MSG_CENTER_NOTIF(1, CENTER_ROUND_PLAYER_WIN, 1, 0, "s1", CPID_ROUND, "0 0", _("^BG%s^BG wins the round"), "") \
MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_SELF, 0, 0, "", NO_CPID, "0 0", _("^K1You froze yourself"), "") \
MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_SPAWN_LATE, 0, 0, "", NO_CPID, "0 0", _("^K1Round already started, you spawn as frozen"), "") \
+ MSG_CENTER_NOTIF(1, CENTER_INVASION_SUPERMONSTER, 1, 0, "s1", NO_CPID, "0 0", _("^K1A %s has arrived!"), "") \
MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_DONTHAVE, 0, 1, "item_wepname", CPID_ITEM, "item_centime 0", _("^BGYou do not have the ^F1%s"), "") \
MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_DROP, 1, 1, "item_wepname item_wepammo", CPID_ITEM, "item_centime 0", _("^BGYou dropped the ^F1%s^BG%s"), "") \
MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_GOT, 0, 1, "item_wepname", CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1%s"), "") \
MSG_MULTI_NOTIF(1, DEATH_MURDER_FALL, NO_MSG, INFO_DEATH_MURDER_FALL, NO_MSG) \
MSG_MULTI_NOTIF(1, DEATH_MURDER_FIRE, NO_MSG, INFO_DEATH_MURDER_FIRE, NO_MSG) \
MSG_MULTI_NOTIF(1, DEATH_MURDER_LAVA, NO_MSG, INFO_DEATH_MURDER_LAVA, NO_MSG) \
+ MSG_MULTI_NOTIF(1, DEATH_MURDER_MONSTER, NO_MSG, INFO_DEATH_MURDER_MONSTER, CENTER_DEATH_SELF_MONSTER) \
MSG_MULTI_NOTIF(1, DEATH_MURDER_NADE, NO_MSG, INFO_DEATH_MURDER_NADE, NO_MSG) \
MSG_MULTI_NOTIF(1, DEATH_MURDER_SHOOTING_STAR, NO_MSG, INFO_DEATH_MURDER_SHOOTING_STAR, NO_MSG) \
MSG_MULTI_NOTIF(1, DEATH_MURDER_SLIME, NO_MSG, INFO_DEATH_MURDER_SLIME, NO_MSG) \
MSG_MULTI_NOTIF(1, DEATH_SELF_FIRE, NO_MSG, INFO_DEATH_SELF_FIRE, CENTER_DEATH_SELF_FIRE) \
MSG_MULTI_NOTIF(1, DEATH_SELF_GENERIC, NO_MSG, INFO_DEATH_SELF_GENERIC, CENTER_DEATH_SELF_GENERIC) \
MSG_MULTI_NOTIF(1, DEATH_SELF_LAVA, NO_MSG, INFO_DEATH_SELF_LAVA, CENTER_DEATH_SELF_LAVA) \
+ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_MAGE, NO_MSG, INFO_DEATH_SELF_MON_MAGE, CENTER_DEATH_SELF_MONSTER) \
+ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SHAMBLER_CLAW, NO_MSG, INFO_DEATH_SELF_MON_SHAMBLER_CLAW, CENTER_DEATH_SELF_MONSTER) \
+ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SHAMBLER_SMASH, NO_MSG, INFO_DEATH_SELF_MON_SHAMBLER_SMASH, CENTER_DEATH_SELF_MONSTER) \
+ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SHAMBLER_ZAP, NO_MSG, INFO_DEATH_SELF_MON_SHAMBLER_ZAP, CENTER_DEATH_SELF_MONSTER) \
+ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_SPIDER, NO_MSG, INFO_DEATH_SELF_MON_SPIDER, CENTER_DEATH_SELF_MONSTER) \
+ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_WYVERN, NO_MSG, INFO_DEATH_SELF_MON_WYVERN, CENTER_DEATH_SELF_MONSTER) \
+ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ZOMBIE_JUMP, NO_MSG, INFO_DEATH_SELF_MON_ZOMBIE_JUMP, CENTER_DEATH_SELF_MONSTER) \
+ MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ZOMBIE_MELEE, NO_MSG, INFO_DEATH_SELF_MON_ZOMBIE_MELEE, CENTER_DEATH_SELF_MONSTER) \
MSG_MULTI_NOTIF(1, DEATH_SELF_NADE, NO_MSG, INFO_DEATH_SELF_NADE, CENTER_DEATH_SELF_NADE) \
MSG_MULTI_NOTIF(1, DEATH_SELF_NOAMMO, NO_MSG, INFO_DEATH_SELF_NOAMMO, CENTER_DEATH_SELF_NOAMMO) \
MSG_MULTI_NOTIF(1, DEATH_SELF_ROT, NO_MSG, INFO_DEATH_SELF_ROT, CENTER_DEATH_SELF_ROT) \
// 0 = no, 1 = yes, 2 = forced on for all MSG_INFO notifs
// DISABLED IN CODE, BUT ENABLED IN CONFIG FOR COMPATIBILITY WITH OLD CLIENTS
- var float autocvar_notification_allow_chatboxprint = 0;
+ var float autocvar_notification_allow_chatboxprint = 0;
var float autocvar_notification_show_sprees_center = TRUE;
var float autocvar_notification_show_sprees_center_specialonly = TRUE;
allows for more dynamic data to be inferred by the local
notification parser, so that the server does not have to network
anything too crazy on a per-client/per-situation basis.
-
+
Pay attention to the CSQC/SVQC relations, some of these are redefined
in slightly different ways for different programs, this is because the
server does a more conservative approach to the notifs than the client.
-
+
All arguments are swapped into strings, so be sure that your
sprintf usage matches with proper %s placement.
-
+
Argument descriptions:
s1-s4: string arguments to be literally swapped into sprintf
s2loc: s2 string of locations of deaths or other events
string notif_arg_spree_cen(float spree)
{
// 0 = off, 1 = target (but only for first victim) and attacker
- if(autocvar_notification_show_sprees_center)
+ if(autocvar_notification_show_sprees_center)
{
if(spree > 1)
{
#define SPREE_ITEM(counta,countb,center,normal,gentle) \
case counta: { return normal_or_gentle(center, sprintf(_("%d score spree! "), spree)); }
-
+
switch(spree)
{
KILL_SPREE_LIST
),
spree);
}
- else { return ""; } // don't show spree information if it isn't an achievement
+ else { return ""; } // don't show spree information if it isn't an achievement
}
}
{
// 0 = off, 1 = target only, 2 = attacker only, 3 = target and attacker
// this conditional (& 2) is true for 2 and 3
- if(autocvar_notification_show_sprees_info & 2)
+ if(autocvar_notification_show_sprees_info & 2)
{
#ifdef CSQC
string spree_newline =
string spree_newline =
(autocvar_notification_show_sprees_info_newline ? "\n" : "");
#endif
-
+
if(spree > 1)
{
#define SPREE_ITEM(counta,countb,center,normal,gentle) \
case counta: { return sprintf(CCR(normal_or_gentle(normal, gentle)), player, spree_newline); }
-
+
switch(spree)
{
KILL_SPREE_LIST
spree_newline
);
}
- else { return ""; } // don't show spree information if it isn't an achievement
+ else { return ""; } // don't show spree information if it isn't an achievement
}
}
#else
#define dedi ""
#endif
-
+
print(sprintf("Beginning notification initialization on %s%s program...\n", dedi, PROGNAME));
-
+
// maybe do another implementation of this with checksums? for now, we don't need versioning
/*if(autocvar_notification_version != NOTIF_VERSION)
{
// used for simplifying ACCUMULATE_FUNCTIONs
#define SET_FIRST_OR_LAST(input,first,count) if(!input) { input = (first + count); }
#define SET_FIELD_COUNT(field,first,count) if(!field) { field = (first + count); ++count; }
- #define CHECK_MAX_COUNT(name,max,count,type) if(count == max) { error(strcat("Maximum ", type, " hit: ", #name, ": ", ftos(count), ".\n")); }
+ #define CHECK_MAX_COUNT(name,max,count,type) if(count > max) { error(strcat("Maximum ", type, " hit: ", #name, ": ", ftos(count), ".\n")); }
// this returns a tempstring containing a copy of s with additional \n newlines added, it also replaces \n in the text with a real newline
// NOTE: s IS allowed to be a tempstring
// modulo function
#ifndef MENUQC
- float mod(float a, float b) { return a - (floor(a / b) * b); }
+ float mod(float a, float b) { return a - (floor(a / b) * b); }
#endif
#define TIME_TO_NTHS(t,n) floor((t) * (n) + 0.4)
vector decompressShotOrigin(float f);
#ifdef SVQC
-string rankings_reply, ladder_reply, lsmaps_reply, maplist_reply; // cached replies
+string rankings_reply, ladder_reply, lsmaps_reply, maplist_reply, monsterlist_reply; // cached replies
string records_reply[10];
#endif
me.firstRunDialog = i = spawnXonoticFirstRunDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
-
+
+
// hud_configure dialogs
i = spawnXonoticHUDExitDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
i = spawnXonoticHUDNotificationDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
i = spawnXonoticHUDAmmoDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
i = spawnXonoticHUDHealthArmorDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
i = spawnXonoticHUDChatDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
i = spawnXonoticHUDModIconsDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
i = spawnXonoticHUDPowerupsDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
i = spawnXonoticHUDPressedKeysDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
i = spawnXonoticHUDRaceTimerDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
i = spawnXonoticHUDRadarDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
i = spawnXonoticHUDScoreDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
i = spawnXonoticHUDTimerDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
i = spawnXonoticHUDVoteDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
i = spawnXonoticHUDWeaponsDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
i = spawnXonoticHUDCenterprintDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
-
+
+
// dialogs used by settings
me.userbindEditDialog = i = spawnXonoticUserbindEditDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
me.cvarsDialog = i = spawnXonoticCvarsDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
-
+
+
// dialog used by singleplayer
me.winnerDialog = i = spawnXonoticWinnerDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
-
+
+
// dialog used by multiplayer/join
me.serverInfoDialog = i = spawnXonoticServerInfoDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
-
+
+
// dialogs used by multiplayer/create
me.mapInfoDialog = i = spawnXonoticMapInfoDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
me.advancedDialog = i = spawnXonoticAdvancedDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
me.mutatorsDialog = i = spawnXonoticMutatorsDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
// dialogs used by multiplayer/player setup
me.crosshairDialog = i = spawnXonoticCrosshairDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
me.hudDialog = i = spawnXonoticHUDDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
me.hudconfirmDialog = i = spawnXonoticHUDConfirmDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
me.modelDialog = i = spawnXonoticModelDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
me.viewDialog = i = spawnXonoticViewDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
me.weaponsDialog = i = spawnXonoticWeaponsDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
// mutator dialogs
i = spawnXonoticSandboxToolsDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z * SKINALPHA_DIALOG_SANDBOXTOOLS);
-
-
+
+
// miscellaneous dialogs
i = spawnXonoticTeamSelectDialog();
i.configureDialog(i);
me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
-
+
+ i = spawnXonoticMonsterToolsDialog();
+ i.configureDialog(i);
+ me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z * SKINALPHA_DIALOG_SANDBOXTOOLS);
-
-
++
+
// main dialogs/windows
me.mainNexposee = n = spawnXonoticNexposee();
/*
i.configureDialog(i);
n.addItemCentered(n, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
n.setNexposee(n, i, SKINPOSITION_DIALOG_SINGLEPLAYER, SKINALPHAS_MAINMENU_x, SKINALPHAS_MAINMENU_y);
-
+
i = spawnXonoticMultiplayerDialog();
i.configureDialog(i);
n.addItemCentered(n, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
n.addItemCentered(n, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z);
n.setNexposee(n, i, SKINPOSITION_DIALOG_QUIT, SKINALPHAS_MAINMENU_x, SKINALPHAS_MAINMENU_y);
n.pullNexposee(n, i, eY * (SKINHEIGHT_TITLE * SKINFONTSIZE_TITLE / conheight));
-
+
me.addItem(me, n, '0 0 0', '1 1 0', SKINALPHAS_MAINMENU_z);
me.moveItemAfter(me, n, NULL);
#define autocvar_fraglimit_override cvar("fraglimit_override")
float autocvar_g_allow_oldnexbeam;
float autocvar_g_antilag;
- float autocvar_g_antilag_bullets;
float autocvar_g_antilag_nudge;
- float autocvar_g_arena_maxspawned;
- float autocvar_g_arena_point_leadlimit;
- float autocvar_g_arena_point_limit;
- float autocvar_g_arena_roundbased;
- float autocvar_g_arena_round_timelimit;
- float autocvar_g_arena_warmup;
float autocvar_g_balance_armor_blockpercent;
float autocvar_g_balance_armor_limit;
float autocvar_g_balance_armor_regen;
float autocvar_g_chat_tellprivacy;
float autocvar_g_ctf_allow_vehicle_carry;
float autocvar_g_ctf_allow_vehicle_touch;
+float autocvar_g_ctf_allow_monster_touch;
float autocvar_g_ctf_throw;
float autocvar_g_ctf_throw_angle_max;
float autocvar_g_ctf_throw_angle_min;
float autocvar_g_domination_point_rate;
float autocvar_g_domination_teams_override;
float autocvar_g_forced_respawn;
+ float autocvar_g_respawn_delay_max;
string autocvar_g_forced_team_blue;
string autocvar_g_forced_team_otherwise;
string autocvar_g_forced_team_pink;
float autocvar_g_physical_items;
float autocvar_g_physical_items_damageforcescale;
float autocvar_g_physical_items_reset;
+float autocvar_g_monsters;
+float autocvar_g_monsters_think_delay;
+float autocvar_g_monsters_max;
+float autocvar_g_monsters_max_perplayer;
+float autocvar_g_monsters_target_range;
+float autocvar_g_monsters_target_infront;
+float autocvar_g_monsters_attack_range;
+float autocvar_g_monsters_score_kill;
+float autocvar_g_monsters_score_spawned;
+float autocvar_g_monsters_typefrag;
+float autocvar_g_monsters_owners;
+float autocvar_g_monsters_miniboss_chance;
+float autocvar_g_monsters_miniboss_healthboost;
+float autocvar_g_monsters_drop_time;
+float autocvar_g_monsters_spawnshieldtime;
+float autocvar_g_monsters_teams;
+float autocvar_g_monsters_respawn_delay;
+float autocvar_g_monsters_respawn;
+float autocvar_g_monsters_armor_blockpercent;
float autocvar_g_touchexplode_radius;
float autocvar_g_touchexplode_damage;
float autocvar_g_touchexplode_edgedamage;
float autocvar_g_touchexplode_force;
+float autocvar_g_invasion_round_timelimit;
+#define autocvar_g_invasion_round_limit cvar("g_invasion_round_limit")
+float autocvar_g_invasion_warmup;
+float autocvar_g_invasion_monster_count;
+float autocvar_g_invasion_zombies_only;
#define autocvar_g_bloodloss cvar("g_bloodloss")
float autocvar_g_random_gravity_negative_chance;
float autocvar_g_random_gravity_min;
return FALSE;
}
- if(e.freezetag_frozen)
+ if(e.frozen)
return FALSE;
// If neither player has ball then don't attack unless the ball is on the
return FALSE;
if(e.flags & FL_NOTARGET)
return FALSE;
-
+
checkentity = e;
if(MUTATOR_CALLHOOK(BotShouldAttack))
return FALSE;
-
+
return TRUE;
}
MUTATOR_CALLHOOK(MakePlayerObserver);
Portal_ClearAll(self);
-
+
+ Unfreeze(self);
-
++
if(self.alivetime)
{
if(!warmup_stage)
}
if(self.vehicle)
- vehicles_exit(VHEF_RELESE);
+ vehicles_exit(VHEF_RELESE);
WaypointSprite_PlayerDead();
accuracy_resend(self);
self.spectatortime = time;
-
+
self.classname = "observer";
self.iscreature = FALSE;
self.teleportable = TELEPORT_SIMPLE;
self.event_damage = PlayerDamage;
self.bot_attack = TRUE;
+ self.monster_attack = TRUE;
+
+ self.spider_slowness = 0;
self.statdraintime = time + 5;
self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = 0;
//stuffcmd(self, "chase_active 0");
//stuffcmd(self, "set viewsize $tmpviewsize \n");
-
+
target_voicescript_clear(self);
// reset fields the weapons may use
self.target = s;
activator = world;
self = oldself;
+
+ Unfreeze(self);
spawn_spot = spot;
MUTATOR_CALLHOOK(PlayerSpawn);
if(!self.killindicator_teamchange)
{
self.vehicle_health = -1;
- Damage(self, self, self, 1 , DEATH_KILL, self.origin, '0 0 0');
+ Damage(self, self, self, 1 , DEATH_KILL, self.origin, '0 0 0');
}
}
{
if(gameover) return;
if(self.player_blocked) return;
- if(self.freezetag_frozen) return;
+ if(self.frozen) return;
-
+
ClientKill_TeamChange(0);
}
if(autocvar_sv_eventlog)
GameLogEcho(strcat(":part:", ftos(self.playerid)));
-
+
Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_QUIT_DISCONNECT, self.netname);
MUTATOR_CALLHOOK(ClientDisconnect);
Portal_ClearAll(self);
+
+ Unfreeze(self);
RemoveGrapplingHook(self);
self.superweapons_finished = 0;
}
}
-
+
if(autocvar_g_nodepthtestplayers)
self.effects = self.effects | EF_NODEPTHTEST;
limita = limita * limit_mod;
//limitf = limitf * limit_mod;
- if(g_ca)
+ if(g_ca || g_invasion)
rot_mod = 0;
- if (!g_minstagib && !g_ca && (!g_lms || autocvar_g_lms_regenerate))
+ if (!g_minstagib && !g_ca && !g_invasion && (!g_lms || autocvar_g_lms_regenerate))
{
self.armorvalue = CalcRotRegen(self.armorvalue, mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regen_mod * frametime * (time > self.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rot_mod * frametime * (time > self.pauserotarmor_finished), limita);
self.health = CalcRotRegen(self.health, minh, autocvar_g_balance_health_regen, autocvar_g_balance_health_regenlinear, regen_mod * frametime * (time > self.pauseregen_finished), maxh, autocvar_g_balance_health_rot, autocvar_g_balance_health_rotlinear, rot_mod * frametime * (time > self.pauserothealth_finished), limith);
self.dmg_inflictor = spectatee.dmg_inflictor;
self.v_angle = spectatee.v_angle;
self.angles = spectatee.v_angle;
+ self.frozen = spectatee.frozen;
+ self.revive_progress = spectatee.revive_progress;
if(!self.BUTTON_USE)
self.fixangle = TRUE;
setorigin(self, spectatee.origin);
setsize(self, spectatee.mins, spectatee.maxs);
SetZoomState(spectatee.zoomstate);
-
+
anticheat_spectatecopy(spectatee);
self.hud = spectatee.hud;
if(spectatee.vehicle)
self.vehicle_reload2 = spectatee.vehicle_reload2;
msg_entity = self;
-
+
WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
WriteAngle(MSG_ONE, spectatee.v_angle_x);
WriteAngle(MSG_ONE, spectatee.v_angle_y);
WriteAngle(MSG_ONE, spectatee.v_angle_z);
//WriteByte (MSG_ONE, SVC_SETVIEW);
- // WriteEntity(MSG_ONE, self);
+ // WriteEntity(MSG_ONE, self);
//makevectors(spectatee.v_angle);
- //setorigin(self, spectatee.origin - v_forward * 400 + v_up * 300);*/
+ //setorigin(self, spectatee.origin - v_forward * 400 + v_up * 300);*/
}
}
float SpectateUpdate() {
if(!self.enemy)
- return 0;
+ return 0;
if (self == self.enemy)
return 0;
if (start.team == self.team) {
return start;
}
-
+
other = start;
// continue from current player
while(other && other.team != self.team) {
other = find(other, classname, "player");
}
-
+
if (!other) {
// restart from begining
other = find(other, classname, "player");
other = find(other, classname, "player");
}
}
-
+
return other;
}
vehicles_exit(VHEF_NORMAL);
return;
}
-
+
// a use key was pressed; call handlers
MUTATOR_CALLHOOK(PlayerUseKey);
}
return;
#endif
+ if(self.frozen == 2)
+ {
+ self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1);
+ self.health = max(1, self.revive_progress * autocvar_g_balance_health_start);
+ self.iceblock.alpha = 1 - self.revive_progress;
+
+ if(self.revive_progress >= 1)
+ Unfreeze(self);
+ }
+
MUTATOR_CALLHOOK(PlayerPreThink);
if(!self.cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button
if(frametime)
player_anim();
button_pressed = (self.BUTTON_ATCK || self.BUTTON_JUMP || self.BUTTON_ATCK2 || self.BUTTON_HOOK || self.BUTTON_USE);
-
+
if (self.deadflag == DEAD_DYING)
{
- if(self.respawn_flags & RESPAWN_FORCE)
+ if((self.respawn_flags & RESPAWN_FORCE) && !autocvar_g_respawn_delay_max)
self.deadflag = DEAD_RESPAWNING;
else if(!button_pressed)
self.deadflag = DEAD_DEAD;
{
if(button_pressed)
self.deadflag = DEAD_RESPAWNABLE;
+ else if(time >= self.respawn_time_max && (self.respawn_flags & RESPAWN_FORCE))
+ self.deadflag = DEAD_RESPAWNING;
}
else if (self.deadflag == DEAD_RESPAWNABLE)
{
if(time > self.respawn_time)
{
self.respawn_time = time + 1; // only retry once a second
+ self.respawn_time_max = self.respawn_time;
respawn();
}
}
if(self.respawn_flags & RESPAWN_SILENT)
self.stat_respawn_time = 0;
+ else if((self.respawn_flags & RESPAWN_FORCE) && autocvar_g_respawn_delay_max)
+ self.stat_respawn_time = self.respawn_time_max;
else
self.stat_respawn_time = self.respawn_time;
}
do_crouch = 0;
if(self.vehicle)
do_crouch = 0;
- if(self.freezetag_frozen)
+ if(self.frozen)
do_crouch = 0;
if(self.weapon == WEP_SHOTGUN && self.weaponentity.wframe == WFRAME_FIRE2 && time < self.weapon_nextthink)
do_crouch = 0;
if(frametime)
player_anim();
-
+
// secret status
secrets_setstatus();
-
+
+ // monsters status
+ monsters_setstatus();
-
++
self.dmg_team = max(0, self.dmg_team - autocvar_g_teamdamage_resetspeed * frametime);
//self.angles_y=self.v_angle_y + 90; // temp
return; // intermission or finale
GetPressedKeys();
}
-
+
#ifdef TETRIS
}
#endif
*/
void PlayerJump (void)
{
+ if(self.frozen)
+ return; // no jumping in freezetag when frozen
+
float doublejump = FALSE;
player_multijump = doublejump;
if(MUTATOR_CALLHOOK(PlayerJump))
return;
-
+
doublejump = player_multijump;
float mjumpheight;
-
+
if (autocvar_sv_doublejump)
{
tracebox(self.origin + '0 0 0.01', self.mins, self.maxs, self.origin - '0 0 0.01', MOVE_NORMAL, self);
self.flags &= ~FL_JUMPRELEASED;
animdecide_setaction(self, ANIMACTION_JUMP, TRUE);
-
+
if(autocvar_g_jump_grunt)
PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND);
}
else
vel_perpend = vel_perpend * max(0, 1 - frametime * wishspeed * sidefric);
-
+
vel_xy = vel_straight * wishdir + vel_perpend;
-
+
if(speedclamp >= 0)
{
float vel_xy_preclamp;
string c;
WarpZone_PlayerPhysics_FixVAngle();
-
+
maxspd_mod = 1;
if(self.ballcarried)
if(g_nexball)
return;
bot_think();
}
-
+
self.items &= ~IT_USING_JETPACK;
if(IS_PLAYER(self))
self.disableclientprediction = 0;
if(time < self.ladder_time)
self.disableclientprediction = 1;
+
+ if(time < self.spider_slowness)
+ {
+ self.stat_sv_maxspeed *= 0.5; // half speed while slow from spider
+ self.stat_sv_airspeedlimit_nonqw *= 0.5;
+ }
+
+ if(self.frozen)
+ {
+ if(autocvar_sv_dodging_frozen && IS_REAL_CLIENT(self))
+ {
+ self.movement_x = bound(-5, self.movement_x, 5);
+ self.movement_y = bound(-5, self.movement_y, 5);
+ self.movement_z = bound(-5, self.movement_z, 5);
+ }
+ else
+ self.movement = '0 0 0';
+ self.disableclientprediction = 1;
+ }
MUTATOR_CALLHOOK(PlayerPhysics);
PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
}
}
- else if ((self.items & IT_JETPACK) && self.BUTTON_HOOK && (!autocvar_g_jetpack_fuel || self.ammo_fuel >= 0.01 || self.items & IT_UNLIMITED_WEAPON_AMMO) && !self.freezetag_frozen)
+ else if ((self.items & IT_JETPACK) && self.BUTTON_HOOK && (!autocvar_g_jetpack_fuel || self.ammo_fuel >= 0.01 || self.items & IT_UNLIMITED_WEAPON_AMMO) && !self.frozen)
{
//makevectors(self.v_angle_y * '0 1 0');
makevectors(self.v_angle);
else
deadbits = ANIMSTATE_DEAD2;
float animbits = deadbits;
- if(self.freezetag_frozen)
+ if(self.frozen)
animbits |= ANIMSTATE_FROZEN;
if(self.crouch)
animbits |= ANIMSTATE_DUCK;
self.respawn_time = ceil((time + sdelay) / waves) * waves;
else
self.respawn_time = time + sdelay;
+ if(autocvar_g_respawn_delay_max > sdelay)
+ self.respawn_time_max = time + autocvar_g_respawn_delay_max;
+ else
+ self.respawn_time_max = self.respawn_time;
if((sdelay + waves >= 5.0) && (self.respawn_time - time > 1.75))
self.respawn_countdown = 10; // first number to count down from is 10
else
return 0;
if(w == 0)
return 0;
-
+
wa = W_AmmoItemCode(w);
if(start_weapons & WepSet_FromWeapon(w))
{
w = self.weapon;
if (w == 0)
return; // just in case
+ if(self.frozen)
+ return;
if(MUTATOR_CALLHOOK(ForbidThrowCurrentWeapon))
return;
if(!autocvar_g_weapon_throwable)
W_SwitchWeapon_Force(self, w_getbestweapon(self));
a = W_ThrowNewWeapon(self, w, doreduce, self.origin + delta, velo);
-
+
if (!a) return;
Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_WEAPON_DROP, a, w);
}
return 1;
if(self.player_blocked)
return 1;
- if(self.freezetag_frozen)
+ if(self.frozen)
return 1;
return 0;
}
self.switchingweapon = self.switchweapon;
entity oldwep = get_weaponinfo(self.weapon);
-
+
#ifndef INDEPENDENT_ATTACK_FINISHED
if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5)
{
// track max damage
if(accuracy_canbegooddamage(ent))
accuracy_add(ent, ent.weapon, maxdamage, 0);
-
+
W_HitPlotAnalysis(ent, v_forward, v_right, v_up);
if(ent.weaponentity.movedir_x > 0)
}
self.angles = '0 0 0';
-
+
float f = (self.owner.weapon_nextthink - time);
if (self.state == WS_RAISE && !intermission_running)
{
complain = 0;
if(complain)
self.hasweapon_complain_spam = time + 0.2;
-
+
if(wpn == WEP_HOOK && !g_grappling_hook && autocvar_g_nades && !((cl.weapons | weaponsInMap) & WepSet_FromWeapon(wpn)))
complain = 0;
{
W_SwitchToOtherWeapon(self);
}
-
+
return FALSE;
}
return TRUE;
if(spread <= 0)
return forward;
sstyle = autocvar_g_projectiles_spread_style;
-
+
if(sstyle == 0)
{
// this is the baseline for the spread value!
return;
}
}
-
+
default:
sprint(self, "Incorrect parameters for ^2autoswitch^7\n");
case CMD_REQUEST_USAGE:
{
sprint(self, "\nUsage:^3 cmd autoswitch selection\n");
- sprint(self, " Where 'selection' controls if autoswitch is on or off.\n");
+ sprint(self, " Where 'selection' controls if autoswitch is on or off.\n");
return;
}
}
self.checkfail = 1;
return; // never fall through to usage
}
-
+
default:
sprint(self, "Incorrect parameters for ^2checkfail^7\n");
case CMD_REQUEST_USAGE:
if(IS_CLIENT(self))
{
self.version = ((argv(1) == "$gameversion") ? 1 : stof(argv(1)));
-
+
if(self.version < autocvar_gameversion_min || self.version > autocvar_gameversion_max)
{
self.version_mismatch = 1;
ClientKill_TeamChange(-2); // observe
- }
- else if(autocvar_g_campaign || autocvar_g_balance_teams)
+ }
+ else if(autocvar_g_campaign || autocvar_g_balance_teams)
{
//JoinBestTeam(self, FALSE, TRUE);
- }
- else if(teamplay && !autocvar_sv_spectate && !(self.team_forced > 0))
+ }
+ else if(teamplay && !autocvar_sv_spectate && !(self.team_forced > 0))
{
self.classname = "observer"; // really?
stuffcmd(self, "menu_showteamselect\n");
}
}
-
+
return;
}
}
-
+
default:
sprint(self, "Incorrect parameters for ^2clientversion^7\n");
case CMD_REQUEST_USAGE:
{
if(argv(1) != "")
{
- if(intermission_running)
+ if(intermission_running)
MapVote_SendPicture(stof(argv(1)));
return;
}
}
-
+
default:
sprint(self, "Incorrect parameters for ^2mv_getpicture^7\n");
case CMD_REQUEST_USAGE:
}
}
- void ClientCommand_join(float request)
+ void ClientCommand_join(float request)
{
switch(request)
{
{
if(IS_CLIENT(self))
{
- if(!IS_PLAYER(self) && !lockteams && !g_arena)
+ if(!IS_PLAYER(self) && !lockteams)
{
- if(nJoinAllowed(self))
+ if(nJoinAllowed(self))
{
if(autocvar_g_campaign) { campaign_bots_may_start = 1; }
Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_JOIN_PLAY, self.netname);
PutClientInServer();
}
- else
+ else
{
//player may not join because of g_maxplayers is set
Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_JOIN_PREVENT);
}
return; // never fall through to usage
}
-
+
default:
case CMD_REQUEST_USAGE:
{
}
}
+void ClientCommand_mobedit(float request, float argc)
+{
+ switch(request)
+ {
+ case CMD_REQUEST_COMMAND:
+ {
+ makevectors(self.v_angle);
+ WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
+
+ if(!(trace_ent.flags & FL_MONSTER)) { sprint(self, "You need to aim at your monster to edit its properties.\n"); return; }
+ if(trace_ent.realowner != self) { sprint(self, "That monster does not belong to you.\n"); return; }
+
+ switch(argv(1))
+ {
+ case "skin": if(trace_ent.monsterid != MON_MAGE) { trace_ent.skin = stof(argv(2)); } return;
+ case "movetarget": trace_ent.monster_moveflags = stof(argv(2)); return;
+ }
+ }
+ default:
+ sprint(self, "Incorrect parameters for ^2mobedit^7\n");
+ case CMD_REQUEST_USAGE:
+ {
+ sprint(self, "\nUsage:^3 cmd mobedit [argument]\n");
+ sprint(self, " Where 'argument' can be skin or movetarget.\n");
+ return;
+ }
+ }
+}
+
+void ClientCommand_mobkill(float request)
+{
+ switch(request)
+ {
+ case CMD_REQUEST_COMMAND:
+ {
+ makevectors(self.v_angle);
+ WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
+
+ if(trace_ent.flags & FL_MONSTER)
+ {
+ if(trace_ent.realowner != self)
+ {
+ sprint(self, "That monster does not belong to you.\n");
+ return;
+ }
+ sprint(self, strcat("Your pet '", trace_ent.monster_name, "' has been brutally mutilated.\n"));
+ Damage (trace_ent, world, world, trace_ent.health + trace_ent.max_health + 200, DEATH_KILL, trace_ent.origin, '0 0 0');
+ return;
+ }
+ else
+ sprint(self, "You need to aim at your monster to kill it.\n");
+
+ return;
+ }
+
+ default:
+ sprint(self, "Incorrect parameters for ^2mobkill^7\n");
+ case CMD_REQUEST_USAGE:
+ {
+ sprint(self, "\nUsage:^3 cmd mobkill\n");
+ sprint(self, " Aim at your monster to kill it.\n");
+ return;
+ }
+ }
+}
+
+void ClientCommand_mobspawn(float request, float argc)
+{
+ switch(request)
+ {
+ case CMD_REQUEST_COMMAND:
+ {
+ entity e;
+ string tospawn;
+ float moveflag, i;
+
+ moveflag = (argv(2) ? stof(argv(2)) : 1); // follow owner if not defined
+ tospawn = strtolower(argv(1));
+
+ if(tospawn == "list")
+ {
+ sprint(self, monsterlist_reply);
+ return;
+ }
+
+ if(tospawn == "random")
+ {
+ RandomSelection_Init();
+ for(i = MON_FIRST; i <= MON_LAST; ++i)
+ RandomSelection_Add(world, 0, (get_monsterinfo(i)).netname, 1, 1);
+
+ tospawn = RandomSelection_chosen_string;
+ }
+
+ if(autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { sprint(self, "Monster spawning is disabled.\n"); }
+ else if(!IS_PLAYER(self)) { sprint(self, "You can't spawn monsters while spectating.\n"); }
+ else if(g_invasion) { sprint(self, "You can't spawn monsters during an invasion!\n"); }
+ else if(!autocvar_g_monsters) { Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_MONSTERS_DISABLED); }
+ else if(self.vehicle) { sprint(self, "You can't spawn monsters while driving a vehicle.\n"); }
+ else if(autocvar_g_campaign) { sprint(self, "You can't spawn monsters in campaign mode.\n"); }
+ else if(self.deadflag != DEAD_NO) { sprint(self, "You can't spawn monsters while dead.\n"); }
+ else if(self.monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); }
+ else if(totalspawned >= autocvar_g_monsters_max) { sprint(self, "The global maximum monster count has been reached, kill some before trying to spawn any more.\n"); }
+ else // all worked out, so continue
+ {
+ self.monstercount += 1;
+ totalspawned += 1;
+
+ makevectors(self.v_angle);
+ WarpZone_TraceBox (CENTER_OR_VIEWOFS(self), PL_MIN, PL_MAX, CENTER_OR_VIEWOFS(self) + v_forward * 150, TRUE, self);
+ //WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 150, MOVE_NORMAL, self);
+
+ e = spawnmonster(tospawn, 0, self, self, trace_endpos, FALSE, moveflag);
+
+ sprint(self, strcat("Spawned ", e.monster_name, "\n"));
+ }
+
+ return;
+ }
+
+ default:
+ sprint(self, "Incorrect parameters for ^2mobspawn^7\n");
+ case CMD_REQUEST_USAGE:
+ {
+ sprint(self, "\nUsage:^3 cmd mobspawn monster\n");
+ sprint(self, " See 'cmd mobspawn list' for available arguments.\n");
+ sprint(self, " Argument 'random' spawns a randomly selected monster.\n");
+ return;
+ }
+ }
+}
+
void ClientCommand_ready(float request) // todo: anti-spam for toggling readyness
{
switch(request)
}
return; // never fall through to usage
}
-
+
default:
case CMD_REQUEST_USAGE:
{
if(argc >= 2) { Say(self, FALSE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1); }
return; // never fall through to usage
}
-
+
default:
case CMD_REQUEST_USAGE:
{
if(argc >= 2) { Say(self, TRUE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1); }
return; // never fall through to usage
}
-
+
default:
case CMD_REQUEST_USAGE:
{
{
if(teamplay)
if(self.team_forced <= 0)
- if (!lockteams)
+ if (!lockteams)
{
float selection;
-
+
switch(argv(1))
{
case "red": selection = NUM_TEAM_1; break;
case "yellow": selection = NUM_TEAM_3; break;
case "pink": selection = NUM_TEAM_4; break;
case "auto": selection = (-1); break;
-
+
default: selection = 0; break;
}
-
+
if(selection)
{
if(self.team == selection && self.deadflag == DEAD_NO)
else
sprint(self, "^7selectteam can only be used in teamgames\n");
}
- return;
+ return;
}
}
return;
}
}
-
+
default:
sprint(self, "Incorrect parameters for ^2selectteam^7\n");
case CMD_REQUEST_USAGE:
{
//float tokens;
string s;
-
+
if(argc == 2) // undefined cvar: use the default value on the server then
{
s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
tokenize_console(s);
}
-
+
GetCvars(1);
-
+
return;
}
}
-
+
default:
sprint(self, "Incorrect parameters for ^2sentcvar^7\n");
case CMD_REQUEST_USAGE:
}
}
- void ClientCommand_spectate(float request)
+ void ClientCommand_spectate(float request)
{
switch(request)
{
{
if(IS_CLIENT(self))
{
- if(g_arena) { return; }
if(g_lms)
{
if(self.lms_spectate_warning)
return;
}
}
-
- if(IS_PLAYER(self) && autocvar_sv_spectate == 1)
+
+ if(IS_PLAYER(self) && autocvar_sv_spectate == 1)
ClientKill_TeamChange(-2); // observe
// in CA, allow a dead player to move to spectators (without that, caplayer!=0 will be moved back to the player list)
}
return; // never fall through to usage
}
-
+
default:
case CMD_REQUEST_USAGE:
{
return;
}
}
-
+
default:
sprint(self, "Incorrect parameters for ^2suggestmap^7\n");
case CMD_REQUEST_USAGE:
{
entity tell_to = GetIndexedEntity(argc, 1);
float tell_accepted = VerifyClientEntity(tell_to, TRUE, FALSE);
-
+
if(tell_accepted > 0) // the target is a real client
{
if(tell_to != self) // and we're allowed to send to them :D
}
else { print_to(self, "You can't ^2tell^7 a message to yourself."); return; }
}
- else if(argv(1) == "#0")
- {
+ else if(argv(1) == "#0")
+ {
trigger_magicear_processmessage_forallears(self, -1, world, substring(command, argv_start_index(next_token), argv_end_index(-1) - argv_start_index(next_token)));
return;
}
else { print_to(self, strcat("tell: ", GetClientErrorString(tell_accepted, argv(1)), ".")); return; }
}
}
-
+
default:
sprint(self, "Incorrect parameters for ^2tell^7\n");
case CMD_REQUEST_USAGE:
}
}
- void ClientCommand_voice(float request, float argc, string command)
+ void ClientCommand_voice(float request, float argc, string command)
{
switch(request)
{
VoiceMessage(argv(1), substring(command, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
else
VoiceMessage(argv(1), "");
-
+
return;
}
}
-
+
default:
sprint(self, "Incorrect parameters for ^2voice^7\n");
case CMD_REQUEST_USAGE:
{
case CMD_REQUEST_COMMAND:
{
-
+
return; // never fall through to usage
}
-
+
default:
case CMD_REQUEST_USAGE:
{
CLIENT_COMMAND("clientversion", ClientCommand_clientversion(request, arguments), "Release version of the game") \
CLIENT_COMMAND("mv_getpicture", ClientCommand_mv_getpicture(request, arguments), "Retrieve mapshot picture from the server") \
CLIENT_COMMAND("join", ClientCommand_join(request), "Become a player in the game") \
+ CLIENT_COMMAND("mobedit", ClientCommand_mobedit(request, arguments), "Edit your monster's properties") \
+ CLIENT_COMMAND("mobkill", ClientCommand_mobkill(request), "Kills your monster") \
+ CLIENT_COMMAND("mobspawn", ClientCommand_mobspawn(request, arguments), "Spawn monsters infront of yourself") \
CLIENT_COMMAND("ready", ClientCommand_ready(request), "Qualify as ready to end warmup stage (or restart server if allowed)") \
CLIENT_COMMAND("say", ClientCommand_say(request, arguments, command), "Print a message to chat to all players") \
CLIENT_COMMAND("say_team", ClientCommand_say_team(request, arguments, command), "Print a message to chat to all team mates") \
CLIENT_COMMAND("tell", ClientCommand_tell(request, arguments, command), "Send a message directly to a player") \
CLIENT_COMMAND("voice", ClientCommand_voice(request, arguments, command), "Send voice message via sound") \
/* nothing */
-
+
void ClientCommand_macro_help()
{
#define CLIENT_COMMAND(name,function,description) \
{ sprint(self, " ^2", name, "^7: ", description, "\n"); }
-
+
CLIENT_COMMANDS(0, 0, "")
#undef CLIENT_COMMAND
-
+
return;
}
{
#define CLIENT_COMMAND(name,function,description) \
{ if(name == strtolower(argv(0))) { function; return TRUE; } }
-
+
CLIENT_COMMANDS(CMD_REQUEST_COMMAND, argc, command)
#undef CLIENT_COMMAND
-
+
return FALSE;
}
{
#define CLIENT_COMMAND(name,function,description) \
{ if(name == strtolower(argv(1))) { function; return TRUE; } }
-
+
CLIENT_COMMANDS(CMD_REQUEST_USAGE, argc, "")
#undef CLIENT_COMMAND
-
+
return FALSE;
}
void ClientCommand_macro_write_aliases(float fh)
{
#define CLIENT_COMMAND(name,function,description) \
- { CMD_Write_Alias("qc_cmd_cmd", name, description); }
-
+ { CMD_Write_Alias("qc_cmd_cmd", name, description); }
+
CLIENT_COMMANDS(0, 0, "")
#undef CLIENT_COMMAND
-
+
return;
}
return;
float argc = tokenize_console(command);
-
+
// for the mutator hook system
cmd_name = strtolower(argv(0));
cmd_argc = argc;
cmd_string = command;
-
+
// Guide for working with argc arguments by example:
// argc: 1 - 2 - 3 - 4
- // argv: 0 - 1 - 2 - 3
+ // argv: 0 - 1 - 2 - 3
// cmd vote - master - login - password
-
+
// for floodcheck
switch(strtolower(argv(0)))
{
case "prespawn": break; // handled by engine in host_cmd.c
case "sentcvar": break; // handled by server in this file
case "spawn": break; // handled by engine in host_cmd.c
-
- default:
+
+ default:
if(SV_ParseClientCommand_floodcheck())
break; // "TRUE": continue, as we're not flooding yet
else
return; // "FALSE": not allowed to continue, halt // print("^1ERROR: ^7ANTISPAM CAUGHT: ", command, ".\n");
}
-
+
/* NOTE: should this be disabled? It can be spammy perhaps, but hopefully it's okay for now */
- if(argv(0) == "help")
+ if(argv(0) == "help")
{
- if(argc == 1)
+ if(argc == 1)
{
sprint(self, "\nClient networked commands:\n");
ClientCommand_macro_help();
-
+
sprint(self, "\nCommon networked commands:\n");
CommonCommand_macro_help(self);
-
+
sprint(self, "\nUsage:^3 cmd COMMAND...^7, where possible commands are listed above.\n");
sprint(self, "For help about a specific command, type cmd help COMMAND\n");
return;
- }
+ }
else if(CommonCommand_macro_usage(argc, self)) // Instead of trying to call a command, we're going to see detailed information about it
{
return;
{
return;
}
- }
+ }
else if(MUTATOR_CALLHOOK(SV_ParseClientCommand))
{
return; // handled by a mutator
}
- else if(CheatCommand(argc))
+ else if(CheatCommand(argc))
{
return; // handled by server/cheats.qc
}
// without using any extra processing time.
// See common.qc for their proper commands
-
+
string getrecords(float page) // 50 records per page
- {
+ {
float rec = 0, r, i;
string h, s;
-
+
s = "";
if (g_ctf)
if (MapInfo_Get_ByID(i))
{
r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
-
+
if (!r)
continue;
-
+
// TODO: uid2name
h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
s = strcat(s, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
if (MapInfo_Get_ByID(i))
{
r = race_readTime(MapInfo_Map_bspname, 1);
-
+
if (!r)
continue;
-
+
h = race_readName(MapInfo_Map_bspname, 1);
s = strcat(s, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
++rec;
if (MapInfo_Get_ByID(i))
{
r = race_readTime(MapInfo_Map_bspname, 1);
-
+
if (!r)
continue;
-
+
h = race_readName(MapInfo_Map_bspname, 1);
s = strcat(s, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
++rec;
for (i = 1; i <= RANKINGS_CNT; ++i)
{
t = race_readTime(map, i);
-
+
if (t == 0)
continue;
-
+
n = race_readName(map, i);
p = count_ordinal(i);
s = strcat(s, strpad(8, p), " ", strpad(-8, TIME_ENCODED_TOSTRING(t)), " ", n, "\n");
{
float i, j, k, uidcnt = 0, thiscnt;
string s, temp_s, rr, myuid, thisuid;
-
+
if(g_cts)
rr = CTS_RECORD;
else
// LADDER_CNT+1 = total points
temp_s = db_get(TemporaryDB, strcat("ladder", myuid));
-
+
if(temp_s == "")
{
db_put(TemporaryDB, strcat("uid", ftos(uidcnt)), myuid);
++uidcnt;
-
+
for(j = 0; j <= LADDER_CNT + 1; ++j)
{
if(j != LADDER_CNT + 1)
top_uids[k] = top_uids[k-1];
top_scores[k] = top_scores[k-1];
}
-
+
top_uids[j] = thisuid;
top_scores[j] = thiscnt;
break;
}
}
}
-
+
s = "^3-----------------------\n\n";
-
+
s = strcat(s, "Pos ^3|");
s = strcat(s, " ^7Total ^3|");
-
+
for(i = 1; i <= LADDER_CNT; ++i)
{ s = strcat(s, " ^7", count_ordinal(i), " ^3|"); }
-
+
s = strcat(s, " ^7Speed awards ^3| ^7Name");
s = strcat(s, "\n^3----+--------");
-
+
for(i = 1; i <= min(9, LADDER_CNT); ++i)
{ s = strcat(s, "+-----"); }
-
+
#if LADDER_CNT > 9
for(i = 1; i <= LADDER_CNT - 9; ++i)
{ s = strcat(s, "+------"); }
{
temp_s = db_get(TemporaryDB, strcat("ladder", top_uids[i]));
tokenize_console(temp_s);
-
+
if(argv(LADDER_CNT+1) == "") // total is 0, skip
continue;
-
+
s = strcat(s, strpad(4, count_ordinal(i+1)), "^3| ^7"); // pos
s = strcat(s, strpad(7, argv(LADDER_CNT+1)), "^3| ^7"); // total
-
+
for(j = 1; j <= min(9, LADDER_CNT); ++j)
{ s = strcat(s, strpad(4, argv(j)), "^3| ^7"); } // 1st, 2nd, 3rd etc cnt
-
+
#if LADDER_CNT > 9
for(j = 10; j <= LADDER_CNT; ++j)
{ s = strcat(s, strpad(4, argv(j)), " ^3| ^7"); } // 1st, 2nd, 3rd etc cnt
{
string maplist = "", col;
float i, argc;
-
+
argc = tokenize_console(autocvar_g_maplist);
for(i = 0; i < argc; ++i)
{
return sprintf("^7Maps in list: %s\n", maplist);
}
-
+
string getlsmaps()
{
string lsmaps = "", col;
float i, newmaps = 0;
-
+
for(i = 0; i < MapInfo_count; ++i)
{
if((MapInfo_Get_ByID(i)) && !(MapInfo_Map_flags & MapInfo_ForbiddenFlags()))
MapInfo_ClearTemps();
return sprintf("^7Maps available%s: %s\n", (newmaps ? " (New maps have asterisks marked in blue)" : ""), lsmaps);
}
+
+string getmonsterlist()
+{
+ string monsterlist = "", col;
+ float i;
+
+ for(i = MON_FIRST; i <= MON_LAST; ++i)
+ {
+ if(mod(i, 2)) { col = "^2"; }
+ else { col = "^3"; }
+ monsterlist = sprintf("%s%s%s ", monsterlist, col, (get_monsterinfo(i)).netname);
+ }
+
+ return sprintf("^7Monsters available: %s\n", monsterlist);
+}
{
entity client;
float accepted;
-
+
string targets = strreplace(",", " ", argv(1));
string original_targets = strreplace(" ", ", ", targets);
string admin_message = argv(2);
float infobartime = stof(argv(3));
-
+
string successful, t;
successful = string_null;
-
+
if((targets) && (admin_message))
{
for(;targets;)
{
t = car(targets); targets = cdr(targets);
-
+
// Check to see if the player is a valid target
client = GetFilteredEntity(t);
accepted = VerifyClientEntity(client, TRUE, FALSE);
-
- if(accepted <= 0)
+
+ if(accepted <= 0)
{
- print("adminmsg: ", GetClientErrorString(accepted, t), (targets ? ", skipping to next player.\n" : ".\n"));
+ print("adminmsg: ", GetClientErrorString(accepted, t), (targets ? ", skipping to next player.\n" : ".\n"));
continue;
}
-
+
// send the centerprint/console print or infomessage
if(infobartime)
{
centerprint(client, strcat("^3", admin_name(), ":\n^7", admin_message));
sprint(client, strcat("\{1}\{13}^3", admin_name(), "^7: ", admin_message, "\n"));
}
-
+
successful = strcat(successful, (successful ? ", " : ""), client.netname);
dprint("Message sent to ", client.netname, "\n");
continue;
}
-
+
if(successful)
bprint("Successfully sent message '", admin_message, "' to ", successful, ".\n");
else
print("No players given (", original_targets, ") could receive the message.\n");
-
+
return;
}
}
-
+
default:
print("Incorrect parameters for ^2adminmsg^7\n");
case CMD_REQUEST_USAGE:
}
}
+void GameCommand_butcher(float request)
+{
+ switch(request)
+ {
+ case CMD_REQUEST_COMMAND:
+ {
+ if(autocvar_g_campaign) { print("This command doesn't work in campaign mode.\n"); return; }
+ if(g_invasion) { print("This command doesn't work during an invasion.\n"); return; }
+
+ float removed_count = 0;
+ entity head;
+
+ FOR_EACH_MONSTER(head)
+ {
+ monster_remove(head);
+ ++removed_count;
+ }
+
+ FOR_EACH_PLAYER(head)
+ head.monstercount = 0;
+
+ monsters_total = 0; // reset stats?
+ monsters_killed = 0;
+
+ totalspawned = 0;
+
+ if(removed_count <= 0)
+ print("No monsters to kill\n");
+ else
+ print(sprintf("Killed %d monster%s\n", removed_count, ((removed_count == 1) ? "" : "s")));
+
+ return; // never fall through to usage
+ }
+
+ default:
+ case CMD_REQUEST_USAGE:
+ {
+ print("\nUsage:^3 sv_cmd butcher\n");
+ print(" No arguments required.\n");
+ return;
+ }
+ }
+}
+
void GameCommand_allready(float request)
{
switch(request)
ReadyRestart();
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
}
void GameCommand_allspec(float request, float argc)
- {
+ {
switch(request)
{
case CMD_REQUEST_COMMAND:
entity client;
string reason = argv(1);
float i = 0;
-
+
FOR_EACH_REALPLAYER(client)
{
self = client;
else { print("No players found to spectate.\n"); }
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
}
}
- void GameCommand_anticheat(float request, float argc)
+ void GameCommand_anticheat(float request, float argc)
{
switch(request)
{
{
entity client = GetIndexedEntity(argc, 1);
float accepted = VerifyClientEntity(client, FALSE, FALSE);
-
- if(accepted > 0)
+
+ if(accepted > 0)
{
self = client;
anticheat_report();
}
else
{
- print("anticheat: ", GetClientErrorString(accepted, argv(1)), ".\n");
+ print("anticheat: ", GetClientErrorString(accepted, argv(1)), ".\n");
}
}
-
+
default:
print("Incorrect parameters for ^2anticheat^7\n");
case CMD_REQUEST_USAGE:
}
}
- void GameCommand_bbox(float request)
+ void GameCommand_bbox(float request)
{
switch(request)
{
print(" ", ftos(world.absmax_z));
else
print(" ", ftos(trace_endpos_z));
-
+
print("\n");
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
case CMD_REQUEST_COMMAND:
{
entity bot;
-
+
if(argv(1) == "reset")
{
bot_resetqueues();
print(strcat("Error: Can't find bot with the name or id '", argv(1),"' - Did you mistype the command?\n")); // don't return so that usage is shown
}
}
-
+
default:
print("Incorrect parameters for ^2bot_cmd^7\n");
case CMD_REQUEST_USAGE:
string result1 = (argv(2) ? strcat("^7", argv(1), "^3!\n") : "^1HEADS^3!\n");
string result2 = (argv(2) ? strcat("^7", argv(2), "^3!\n") : "^4TAILS^3!\n");
string choice = ((random() > 0.5) ? result1 : result2);
-
+
FOR_EACH_CLIENT(client)
centerprint(client, strcat("^3Throwing coin... Result: ", choice));
bprint(strcat("^3Throwing coin... Result: ", choice));
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
}
}
- void GameCommand_database(float request, float argc)
+ void GameCommand_database(float request, float argc)
{
switch(request)
{
}
}
}
-
+
default:
print("Incorrect parameters for ^2database^7\n");
case CMD_REQUEST_USAGE:
}
void GameCommand_defer_clear(float request, float argc)
- {
+ {
switch(request)
{
case CMD_REQUEST_COMMAND:
{
entity client;
float accepted;
-
+
if(argc >= 2)
{
client = GetIndexedEntity(argc, 1);
accepted = VerifyClientEntity(client, TRUE, FALSE);
-
+
if(accepted > 0)
{
stuffcmd(client, "defer clear\n");
print("defer clear stuffed to ", client.netname, "\n");
}
else { print("defer_clear: ", GetClientErrorString(accepted, argv(1)), ".\n"); }
-
+
return;
}
}
-
+
default:
print("Incorrect parameters for ^2defer_clear^7\n");
case CMD_REQUEST_USAGE:
}
void GameCommand_defer_clear_all(float request)
- {
+ {
switch(request)
{
case CMD_REQUEST_COMMAND:
entity client;
float i = 0;
float argc;
-
+
FOR_EACH_CLIENT(client)
{
argc = tokenize_console(strcat("defer_clear ", ftos(num_for_edict(client))));
- GameCommand_defer_clear(CMD_REQUEST_COMMAND, argc);
+ GameCommand_defer_clear(CMD_REQUEST_COMMAND, argc);
++i;
}
- if(i) { print(strcat("Successfully stuffed defer clear to all clients (", ftos(i), ")\n")); } // should a message be added if no players were found?
+ if(i) { print(strcat("Successfully stuffed defer clear to all clients (", ftos(i), ")\n")); } // should a message be added if no players were found?
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
race_deleteTime(GetMapname(), stof(argv(1)));
return;
}
- }
-
+ }
+
default:
print("Incorrect parameters for ^2delrec^7\n");
case CMD_REQUEST_USAGE:
}
}
- void GameCommand_effectindexdump(float request)
+ void GameCommand_effectindexdump(float request)
{
switch(request)
{
{
float fh, d;
string s;
-
+
d = db_create();
print("begin of effects list\n");
db_put(d, "TE_GUNSHOT", "1"); print("effect TE_GUNSHOT is ", ftos(particleeffectnum("TE_GUNSHOT")), "\n");
db_close(d);
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
}
}
- void GameCommand_extendmatchtime(float request)
+ void GameCommand_extendmatchtime(float request)
{
switch(request)
{
changematchtime(autocvar_timelimit_increment * 60, autocvar_timelimit_min * 60, autocvar_timelimit_max * 60);
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
}
void GameCommand_find(float request, float argc) // is this even needed? We have prvm_edicts command and such ANYWAY
- {
+ {
switch(request)
{
case CMD_REQUEST_COMMAND:
{
entity client;
-
+
for(client = world; (client = find(client, classname, argv(1))); )
print(etos(client), "\n");
-
+
return;
}
-
+
default:
print("Incorrect parameters for ^2find^7\n");
case CMD_REQUEST_USAGE:
}
}
- void GameCommand_gametype(float request, float argc)
- {
+ void GameCommand_gametype(float request, float argc)
+ {
switch(request)
{
case CMD_REQUEST_COMMAND:
{
string s = argv(1);
float t = MapInfo_Type_FromString(s), tsave = MapInfo_CurrentGametype();
-
+
if(t)
{
MapInfo_SwitchGameType(t);
}
else
bprint("Game type switch to ", s, " failed: this type does not exist!\n");
-
+
return;
}
}
-
+
default:
print("Incorrect parameters for ^2gametype^7\n");
case CMD_REQUEST_USAGE:
}
}
- void GameCommand_gettaginfo(float request, float argc)
- {
+ void GameCommand_gettaginfo(float request, float argc)
+ {
switch(request)
{
case CMD_REQUEST_COMMAND:
entity tmp_entity;
float i;
vector v;
-
+
if(argc >= 4)
{
tmp_entity = spawn();
}
else
print("bone not found\n");
-
+
remove(tmp_entity);
return;
}
}
-
+
default:
print("Incorrect parameters for ^2gettaginfo^7\n");
case CMD_REQUEST_USAGE:
}
}
- void GameCommand_animbench(float request, float argc)
+ void GameCommand_animbench(float request, float argc)
{
switch(request)
{
return;
}
}
-
+
default:
print("Incorrect parameters for ^2gotomap^7\n");
case CMD_REQUEST_USAGE:
}
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
}
}
- void GameCommand_make_mapinfo(float request)
+ void GameCommand_make_mapinfo(float request)
{
switch(request)
{
case CMD_REQUEST_COMMAND:
- {
+ {
entity tmp_entity;
-
+
tmp_entity = spawn();
tmp_entity.classname = "make_mapinfo";
tmp_entity.think = make_mapinfo_Think;
MapInfo_Enumerate();
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
{
float accepted;
entity client;
-
+
string targets = strreplace(",", " ", argv(1));
string original_targets = strreplace(" ", ", ", targets);
string destination = argv(2);
-
+
string successful, t;
successful = string_null;
-
+
// lets see if the target(s) even actually exist.
if((targets) && (destination))
- {
+ {
for(;targets;)
{
t = car(targets); targets = cdr(targets);
// Check to see if the player is a valid target
client = GetFilteredEntity(t);
accepted = VerifyClientEntity(client, FALSE, FALSE);
-
- if(accepted <= 0)
+
+ if(accepted <= 0)
{
- print("moveplayer: ", GetClientErrorString(accepted, t), (targets ? ", skipping to next player.\n" : ".\n"));
+ print("moveplayer: ", GetClientErrorString(accepted, t), (targets ? ", skipping to next player.\n" : ".\n"));
continue;
}
-
+
// Where are we putting this player?
- if(destination == "spec" || destination == "spectator")
+ if(destination == "spec" || destination == "spectator")
{
if(!IS_SPEC(client) && !IS_OBSERVER(client))
{
self = client;
PutObserverInServer();
-
+
successful = strcat(successful, (successful ? ", " : ""), client.netname);
}
else
// keep the forcing undone
print("Player ", ftos(GetFilteredNumber(t)), " (", client.netname, ") is already on the ", Team_ColoredFullName(client.team), (targets ? "^7, skipping to next player.\n" : "^7.\n"));
continue;
- }
+ }
else if(team_id == 0) // auto team
{
team_id = Team_NumberToTeam(FindSmallestTeam(client, FALSE));
CheckAllowedTeams(client);
}
client.team_forced = save;
-
+
// Check to see if the destination team is even available
- switch(team_id)
+ switch(team_id)
{
case NUM_TEAM_1: if(c1 == -1) { print("Sorry, can't move player to red team if it doesn't exist.\n"); return; } break;
case NUM_TEAM_2: if(c2 == -1) { print("Sorry, can't move player to blue team if it doesn't exist.\n"); return; } break;
case NUM_TEAM_3: if(c3 == -1) { print("Sorry, can't move player to yellow team if it doesn't exist.\n"); return; } break;
case NUM_TEAM_4: if(c4 == -1) { print("Sorry, can't move player to pink team if it doesn't exist.\n"); return; } break;
-
+
default: print("Sorry, can't move player here if team ", destination, " doesn't exist.\n"); return;
}
-
+
// If so, lets continue and finally move the player
client.team_forced = 0;
MoveToTeam(client, team_id, 6);
}
else
{
- print("Can't change teams if the player isn't in the game.\n"); // well technically we could, but should we allow that? :P
+ print("Can't change teams if the player isn't in the game.\n"); // well technically we could, but should we allow that? :P
return;
}
}
}
-
+
if(successful)
bprint("Successfully moved players ", successful, " to destination ", destination, ".\n");
else
print("No players given (", original_targets, ") are able to move.\n");
-
+
return; // still correct parameters so return to avoid usage print
}
}
-
+
default:
print("Incorrect parameters for ^2moveplayer^7\n");
case CMD_REQUEST_USAGE:
}
}
- void GameCommand_nospectators(float request)
+ void GameCommand_nospectators(float request)
{
switch(request)
{
bprint(strcat("^7All spectators will be automatically kicked when not joining the game after ", ftos(autocvar_g_maxplayers_spectator_blocktime), " seconds!\n"));
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
}
void GameCommand_playerdemo(float request, float argc)
- {
+ {
switch(request)
{
case CMD_REQUEST_COMMAND:
{
entity client;
float i, n, accepted;
-
+
switch(argv(1))
{
case "read":
{
client = GetIndexedEntity(argc, 2);
accepted = VerifyClientEntity(client, FALSE, TRUE);
-
- if(accepted <= 0)
+
+ if(accepted <= 0)
{
- print("playerdemo: read: ", GetClientErrorString(accepted, argv(2)), ".\n");
+ print("playerdemo: read: ", GetClientErrorString(accepted, argv(2)), ".\n");
return;
}
-
+
self = client;
playerdemo_open_read(argv(next_token));
return;
}
-
+
case "write":
{
client = GetIndexedEntity(argc, 2);
accepted = VerifyClientEntity(client, FALSE, FALSE);
-
- if(accepted <= 0)
+
+ if(accepted <= 0)
{
- print("playerdemo: write: ", GetClientErrorString(accepted, argv(2)), ".\n");
+ print("playerdemo: write: ", GetClientErrorString(accepted, argv(2)), ".\n");
return;
}
-
+
self = client;
playerdemo_open_write(argv(next_token));
return;
}
-
+
case "auto_read_and_write":
{
n = GetFilteredNumber(argv(3));
cvar_set("bot_number", ftos(n));
-
+
localcmd("wait; wait; wait\n");
for(i = 0; i < n; ++i) { localcmd("sv_cmd playerdemo read ", ftos(i+2), " ", argv(2), ftos(i+1), "\n"); }
-
+
localcmd("sv_cmd playerdemo write 1 ", ftos(n+1), "\n");
return;
}
-
+
case "auto_read":
{
n = GetFilteredNumber(argv(3));
cvar_set("bot_number", ftos(n));
-
+
localcmd("wait; wait; wait\n");
for(i = 0; i < n; ++i) { localcmd("sv_cmd playerdemo read ", ftos(i+2), " ", argv(2), ftos(i+1), "\n"); }
return;
}
}
}
-
+
default:
print("Incorrect parameters for ^2playerdemo^7\n");
case CMD_REQUEST_USAGE:
}
}
- void GameCommand_printstats(float request)
+ void GameCommand_printstats(float request)
{
switch(request)
{
print("stats dumped.\n");
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
if(RadarMap_Make(argc))
return;
}
-
+
default:
print("Incorrect parameters for ^2radarmap^7\n");
case CMD_REQUEST_USAGE:
}
}
- void GameCommand_reducematchtime(float request)
+ void GameCommand_reducematchtime(float request)
{
switch(request)
{
changematchtime(autocvar_timelimit_decrement *-60, autocvar_timelimit_min * 60, autocvar_timelimit_max * 60);
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
return;
}
}
-
+
default:
print("Incorrect parameters for ^2setbots^7\n");
case CMD_REQUEST_USAGE:
FOR_EACH_PLAYER(tmp_player)
{
CheckAllowedTeams(tmp_player);
-
+
if(c1 >= 0) t_teams = max(1, t_teams);
if(c2 >= 0) t_teams = max(2, t_teams);
if(c3 >= 0) t_teams = max(3, t_teams);
if(c4 >= 0) t_teams = max(4, t_teams);
-
+
++t_players;
}
-
+
// build a list of the players in a random order
FOR_EACH_PLAYER(tmp_player)
{
for(;;)
{
i = bound(1, floor(random() * maxclients) + 1, maxclients);
-
+
if(shuffleteams_players[i])
{
continue; // a player is already assigned to this slot
}
}
- // finally, from the list made earlier, re-join the players in different order.
+ // finally, from the list made earlier, re-join the players in different order.
for(i = 1; i <= t_teams; ++i)
{
// find out how many players to assign to this team
x = (t_players / t_teams);
x = ((i == 1) ? ceil(x) : floor(x));
-
+
team_color = Team_NumberToTeam(i);
-
- // sort through the random list of players made earlier
+
+ // sort through the random list of players made earlier
for(z = 1; z <= maxclients; ++z)
- {
+ {
if (!(shuffleteams_teams[i] >= x))
{
if (!(shuffleteams_players[z]))
continue; // not a player, move on to next random slot
-
+
if(VerifyClientNumber(shuffleteams_players[z]))
self = edict_num(shuffleteams_players[z]);
- if(self.team != team_color)
+ if(self.team != team_color)
MoveToTeam(self, team_color, 6);
shuffleteams_players[z] = 0;
}
}
}
-
+
bprint("Successfully shuffled the players around randomly.\n");
-
+
// clear the buffers now
for (i=0; i<SHUFFLETEAMS_MAX_PLAYERS; ++i)
shuffleteams_players[i] = 0;
-
+
for (i=0; i<SHUFFLETEAMS_MAX_TEAMS; ++i)
shuffleteams_teams[i] = 0;
}
{
print("Can't shuffle teams when currently not playing a team game.\n");
}
-
+
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
{
entity client = GetIndexedEntity(argc, 1);
float accepted = VerifyClientEntity(client, TRUE, FALSE);
-
+
if(accepted > 0)
{
stuffcmd(client, strcat("\n", argv(next_token), "\n"));
print(strcat("Command: \"", argv(next_token), "\" sent to ", GetCallerName(client), " (", argv(1) ,").\n"));
}
else
- print("stuffto: ", GetClientErrorString(accepted, argv(1)), ".\n");
-
+ print("stuffto: ", GetClientErrorString(accepted, argv(1)), ".\n");
+
return;
}
}
-
+
default:
print("Incorrect parameters for ^2stuffto^7\n");
case CMD_REQUEST_USAGE:
entity e;
vector org, delta, start, end, p, q, q0, pos, vv, dv;
float i, f, safe, unsafe, dq, dqf;
-
+
switch(argv(1))
{
case "debug":
}
return;
}
-
+
case "debug2":
{
e = nextent(world);
print("highest possible dist: ", ftos(f), "\n");
return;
}
-
+
case "walk":
{
if(argc == 4)
return;
}
}
-
+
case "showline":
{
if(argc == 4)
return;
}
}
-
+
// no default case, just go straight to invalid
}
}
-
+
default:
print("Incorrect parameters for ^2trace^7\n");
case CMD_REQUEST_USAGE:
}
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
{
CampaignLevelWarp(stof(argv(1)));
print("Successfully warped to campaign level ", stof(argv(1)), ".\n");
- }
+ }
else
{
CampaignLevelWarp(-1);
print("Not in campaign, can't level warp\n");
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
{
case CMD_REQUEST_COMMAND:
{
-
+
return;
}
-
+
default:
case CMD_REQUEST_USAGE:
{
// Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
#define SERVER_COMMANDS(request,arguments,command) \
SERVER_COMMAND("adminmsg", GameCommand_adminmsg(request, arguments), "Send an admin message to a client directly") \
+ SERVER_COMMAND("butcher", GameCommand_butcher(request), "Instantly removes all monsters on the map") \
SERVER_COMMAND("allready", GameCommand_allready(request), "Restart the server and reset the players") \
SERVER_COMMAND("allspec", GameCommand_allspec(request, arguments), "Force all players to spectate") \
SERVER_COMMAND("anticheat", GameCommand_anticheat(request, arguments), "Create an anticheat report for a client") \
{
#define SERVER_COMMAND(name,function,description) \
{ print(" ^2", name, "^7: ", description, "\n"); }
-
+
SERVER_COMMANDS(0, 0, "")
#undef SERVER_COMMAND
-
+
return;
}
{
#define SERVER_COMMAND(name,function,description) \
{ if(name == strtolower(argv(0))) { function; return TRUE; } }
-
+
SERVER_COMMANDS(CMD_REQUEST_COMMAND, argc, command)
#undef SERVER_COMMAND
-
+
return FALSE;
}
{
#define SERVER_COMMAND(name,function,description) \
{ if(name == strtolower(argv(1))) { function; return TRUE; } }
-
+
SERVER_COMMANDS(CMD_REQUEST_USAGE, argc, "")
#undef SERVER_COMMAND
-
+
return FALSE;
}
{
#define SERVER_COMMAND(name,function,description) \
{ CMD_Write_Alias("qc_cmd_sv", name, description); }
-
+
SERVER_COMMANDS(0, 0, "")
#undef SERVER_COMMAND
-
+
return;
}
-
+
// =========================================
// Main Function Called By Engine (sv_cmd)
void GameCommand(string command)
{
float argc = tokenize_console(command);
-
+
// Guide for working with argc arguments by example:
// argc: 1 - 2 - 3 - 4
- // argv: 0 - 1 - 2 - 3
+ // argv: 0 - 1 - 2 - 3
// cmd vote - master - login - password
- if(strtolower(argv(0)) == "help")
+ if(strtolower(argv(0)) == "help")
{
- if(argc == 1)
+ if(argc == 1)
{
print("\nServer console commands:\n");
GameCommand_macro_help();
-
+
print("\nBanning commands:\n");
BanCommand_macro_help();
-
+
print("\nCommon networked commands:\n");
CommonCommand_macro_help(world);
-
+
print("\nGeneric commands shared by all programs:\n");
GenericCommand_macro_help();
-
+
print("\nUsage:^3 sv_cmd COMMAND...^7, where possible commands are listed above.\n");
print("For help about a specific command, type sv_cmd help COMMAND\n");
-
+
return;
- }
+ }
else if(BanCommand_macro_usage(argc)) // Instead of trying to call a command, we're going to see detailed information about it
{
return;
{
return;
}
- }
- else if(BanCommand(command))
+ }
+ else if(BanCommand(command))
{
return; // handled by server/command/ipban.qc
}
{
return; // handled by server/command/common.qc
}
- else if(GenericCommand(command))
+ else if(GenericCommand(command))
{
return; // handled by common/command/generic.qc
}
{
return; // handled by one of the above GameCommand_* functions
}
-
+
// nothing above caught the command, must be invalid
print(((command != "") ? strcat("Unknown server command \"", command, "\"") : "No command provided"), ". For a list of supported commands, try sv_cmd help.\n");
-
+
return;
}
float maxclients;
// flag set on worldspawn so that the code knows if it is dedicated or not
- float server_is_dedicated;
+ float server_is_dedicated;
// Fields
.float play_time;
.float respawn_flags;
.float respawn_time;
+ .float respawn_time_max;
.float death_time;
.float fade_time;
.float fade_rate;
float nJoinAllowed(entity ignore);
.float spawnshieldtime;
+.float item_spawnshieldtime;
.entity flagcarried;
.float target_random;
.float trigger_reverse;
- // Nexball
+ // Nexball
.entity ballcarried; // Also used for keepaway
.float metertime;
float g_nexball_meter_period;
.float last_pickup;
- .float hit_time;
- .float typehit_time;
+ .float hit_time;
+ .float typehit_time;
.float stat_leadlimit;
.float player_blocked;
-.float freezetag_frozen;
+.float frozen; // for freeze attacks
+.float revive_progress;
+.float revive_speed; // NOTE: multiplier (anything above 1 is instaheal)
+.entity iceblock;
.entity muzzle_flash;
.float misc_bulletcounter; // replaces uzi & hlac bullet counter.
)
);
#endif
-
+
// =======
// SUICIDE
// =======
Obituary_SpecialDeath(targ, FALSE, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
break;
}
-
+
default:
{
Obituary_SpecialDeath(targ, FALSE, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
GiveFrags(attacker, targ, -1, deathtype);
attacker.killcount = 0;
-
+
Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker.netname);
Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(targ.team, INFO_DEATH_TEAMKILL_), targ.netname, attacker.netname, deathlocation, targ.killcount);
0);
break;
}
-
+
default:
{
Obituary_SpecialDeath(targ, FALSE, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
if(targ.killcount) { targ.killcount = 0; }
}
+void Ice_Think()
+{
+ setorigin(self, self.owner.origin - '0 0 16');
+ self.nextthink = time;
+}
+
+void Freeze (entity targ, float freeze_time, float frozen_type, float show_waypoint)
+{
+ if(!IS_PLAYER(targ) && !(targ.flags & FL_MONSTER)) // only specified entities can be freezed
+ return;
+
+ if(targ.frozen)
+ return;
+
+ targ.frozen = frozen_type;
+ targ.revive_progress = 0;
+ targ.health = 1;
+ targ.revive_speed = freeze_time;
+
+ entity ice, head;
+ ice = spawn();
+ ice.owner = targ;
+ ice.classname = "ice";
+ ice.scale = targ.scale;
+ ice.think = Ice_Think;
+ ice.nextthink = time;
+ ice.frame = floor(random() * 21); // ice model has 20 different looking frames
+ setmodel(ice, "models/ice/ice.md3");
+ ice.alpha = 1;
+ ice.colormod = Team_ColorRGB(targ.team);
+ ice.glowmod = ice.colormod;
+ targ.iceblock = ice;
+
+ entity oldself;
+ oldself = self;
+ self = ice;
+ Ice_Think();
+ self = oldself;
+
+ RemoveGrapplingHook(targ);
+
+ FOR_EACH_PLAYER(head)
+ if(head.hook.aiment == targ)
+ RemoveGrapplingHook(head);
+
+ // add waypoint
+ if(show_waypoint)
+ WaypointSprite_Spawn("frozen", 0, 0, targ, '0 0 64', world, targ.team, targ, waypointsprite_attached, TRUE, RADARICON_WAYPOINT, '0.25 0.90 1');
+}
+
+void Unfreeze (entity targ)
+{
+ if(targ.frozen) // only reset health if target was frozen
+ targ.health = ((IS_PLAYER(targ)) ? autocvar_g_balance_health_start : targ.max_health);
+
+ entity head;
+ targ.frozen = 0;
+ targ.revive_progress = 0;
+
+ WaypointSprite_Kill(targ.waypointsprite_attached);
+
+ FOR_EACH_PLAYER(head)
+ if(head.hook.aiment == targ)
+ RemoveGrapplingHook(head);
+
+ // remove the ice block
+ if(targ.iceblock)
+ remove(targ.iceblock);
+ targ.iceblock = world;
+}
+
// these are updated by each Damage call for use in button triggering and such
entity damage_targ;
entity damage_inflictor;
{
float mirrordamage;
float mirrorforce;
- float complainteamdamage = 0;
+ float complainteamdamage = 0;
entity attacker_save;
mirrordamage = 0;
mirrorforce = 0;
// exit the vehicle before killing (fixes a crash)
if(IS_PLAYER(targ) && targ.vehicle)
vehicles_exit(VHEF_RELESE);
-
+
// These are ALWAYS lethal
// No damage modification here
// Instead, prepare the victim for his death...
force = force * g_weaponforcefactor;
mirrorforce *= g_weaponforcefactor;
}
-
+
+ if(targ.frozen && deathtype != DEATH_HURTTRIGGER)
+ {
+ damage = 0;
+ force *= 0.2;
+ }
-
++
// should this be changed at all? If so, in what way?
frag_attacker = attacker;
frag_target = targ;
frag_damage = damage;
frag_force = force;
- frag_deathtype = deathtype;
+ frag_deathtype = deathtype;
frag_mirrordamage = mirrordamage;
MUTATOR_CALLHOOK(PlayerDamage_Calculate);
damage = frag_damage;
mirrordamage = frag_mirrordamage;
force = frag_force;
-
+
if (!g_minstagib)
{
// apply strength multiplier
else
victim = targ;
- if(IS_PLAYER(victim) || victim.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+ if(IS_PLAYER(victim) || victim.turrcaps_flags & TFL_TURRCAPS_ISTURRET || victim.flags & FL_MONSTER)
{
if(DIFF_TEAM(victim, attacker))
{
if(maxtime > mintime || maxdps > mindps)
{
// Constraints:
-
+
// damage we have right now
mindamage = mindps * mintime;
e.fire_endtime = 0;
// ice stops fire
- if(e.freezetag_frozen)
+ if(e.frozen)
e.fire_endtime = 0;
t = min(frametime, e.fire_endtime - time);
// mapinfo
BADCVAR("fraglimit");
- BADCVAR("g_arena");
BADCVAR("g_assault");
BADCVAR("g_ca");
BADCVAR("g_ca_teams");
// does nothing visible
BADCVAR("captureleadlimit_override");
- BADCVAR("g_arena_point_leadlimit");
BADCVAR("g_balance_kill_delay");
BADCVAR("g_ca_point_leadlimit");
BADCVAR("g_ctf_captimerecord_always");
BADCVAR("g_nix");
BADCVAR("g_grappling_hook");
BADCVAR("g_jetpack");
-
+
#undef BADPREFIX
#undef BADCVAR
// needs to be done so early because of the constants they create
CALL_ACCUMULATED_FUNCTION(RegisterWeapons);
+ CALL_ACCUMULATED_FUNCTION(RegisterMonsters);
CALL_ACCUMULATED_FUNCTION(RegisterGametypes);
CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
// needs to be done so early because of the constants they create
CALL_ACCUMULATED_FUNCTION(RegisterWeapons);
+ CALL_ACCUMULATED_FUNCTION(RegisterMonsters);
CALL_ACCUMULATED_FUNCTION(RegisterGametypes);
CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
addstat(STAT_NEX_CHARGEPOOL, AS_FLOAT, nex_chargepool_ammo);
addstat(STAT_HAGAR_LOAD, AS_INT, hagar_load);
+
+ // freeze attacks
+ addstat(STAT_FROZEN, AS_INT, frozen);
+ addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, revive_progress);
// g_movementspeed hack
addstat(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW, AS_FLOAT, stat_sv_airspeedlimit_nonqw);
// secrets
addstat(STAT_SECRETS_TOTAL, AS_FLOAT, stat_secrets_total);
addstat(STAT_SECRETS_FOUND, AS_FLOAT, stat_secrets_found);
+
+ // monsters
+ addstat(STAT_MONSTERS_TOTAL, AS_FLOAT, stat_monsters_total);
+ addstat(STAT_MONSTERS_KILLED, AS_FLOAT, stat_monsters_killed);
// misc
addstat(STAT_RESPAWN_TIME, AS_FLOAT, stat_respawn_time);
// set up information replies for clients and server to use
maplist_reply = strzone(getmaplist());
lsmaps_reply = strzone(getlsmaps());
+ monsterlist_reply = strzone(getmonsterlist());
for(i = 0; i < 10; ++i)
{
s = getrecords(i);
{
s = strcat(":player:see-labels:", GetPlayerScoreString(other, 0), ":");
s = strcat(s, ftos(rint(time - other.jointime)), ":");
- if(IS_PLAYER(other) || g_arena || other.caplayer == 1 || g_lms)
+ if(IS_PLAYER(other) || other.caplayer == 1 || g_lms)
s = strcat(s, ftos(other.team), ":");
else
s = strcat(s, "spectator:");
PlayerStats_AddGlobalInfo(e);
PlayerStats_Shutdown();
WeaponStats_Shutdown();
-
+
Kill_Notification(NOTIF_ALL, world, MSG_CENTER, 0); // kill all centerprints now
if(autocvar_sv_eventlog)
if(WinningConditionHelper_zeroisworst)
leadlimit = 0; // not supported in this mode
- if(g_dm || g_tdm || g_arena || g_ca || (g_race && !g_race_qualifying) || g_nexball)
+ if(g_dm || g_tdm || g_ca || (g_race && !g_race_qualifying) || g_nexball)
// these modes always score in increments of 1, thus this makes sense
{
if(leaderfrags != WinningConditionHelper_topscore)
#define FOR_EACH_SPEC(v) FOR_EACH_CLIENT(v) if (!IS_PLAYER(v)) // Samual: shouldn't this be IS_SPEC(v)? and rather create a separate macro to include observers too
#define FOR_EACH_REALPLAYER(v) FOR_EACH_REALCLIENT(v) if(IS_PLAYER(v))
+#define FOR_EACH_MONSTER(v) for(v = world; (v = findflags(v, flags, FL_MONSTER)) != world; )
+
#define CENTER_OR_VIEWOFS(ent) (ent.origin + (IS_PLAYER(ent) ? ent.view_ofs : ((ent.mins + ent.maxs) * 0.5)))
// copies a string to a tempstring (so one can strunzone it)
d = 0; // weapon is set a few lines later
else
d = (i == WEP_LASER || i == WEP_SHOTGUN);
-
+
if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook
d |= (i == WEP_HOOK);
if(weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED) // never default mutator blocked guns
d = 0;
var float t = cvar(strcat(cvarprefix, weaponinfo.netname));
-
+
//print(strcat("want_weapon: ", weaponinfo.netname, " - d: ", ftos(d), ", t: ", ftos(t), ". \n"));
-
+
// bit order in t:
// 1: want or not
// 2: is default?
if(!cvar("g_use_ammunition"))
start_items |= IT_UNLIMITED_AMMO;
-
+
if(start_items & IT_UNLIMITED_WEAPON_AMMO)
{
start_ammo_rockets = 999;
// load mutators
#define CHECK_MUTATOR_ADD(mut_cvar,mut_name,dependence) \
{ if(cvar(mut_cvar) && dependence) { MUTATOR_ADD(mut_name); } }
-
+
CHECK_MUTATOR_ADD("g_dodging", mutator_dodging, 1);
CHECK_MUTATOR_ADD("g_spawn_near_teammate", mutator_spawn_near_teammate, 1);
CHECK_MUTATOR_ADD("g_physical_items", mutator_physical_items, 1);
CHECK_MUTATOR_ADD("g_nades", mutator_nades, 1);
CHECK_MUTATOR_ADD("g_sandbox", sandbox, 1);
CHECK_MUTATOR_ADD("g_campcheck", mutator_campcheck, 1);
-
+
#undef CHECK_MUTATOR_ADD
-
+
if(cvar("sv_allow_fullbright"))
serverflags |= SERVERFLAG_ALLOW_FULLBRIGHT;
g_bugrigs_speed_ref = cvar("g_bugrigs_speed_ref");
g_bugrigs_speed_pow = cvar("g_bugrigs_speed_pow");
g_bugrigs_steer = cvar("g_bugrigs_steer");
-
+
g_minstagib = cvar("g_minstagib");
sv_clones = cvar("sv_clones");
g_warmup_allguns = cvar("g_warmup_allguns");
g_warmup_allow_timeout = cvar("g_warmup_allow_timeout");
- if ((g_race && g_race_qualifying == 2) || g_arena || g_assault || cvar("g_campaign"))
+ if ((g_race && g_race_qualifying == 2) || g_assault || cvar("g_campaign"))
warmup_stage = 0; // these modes cannot work together, sorry
g_pickup_respawntime_weapon = cvar("g_pickup_respawntime_weapon");
db_put(ServerProgsDB, strcat("uid2name", myuid), "");
}
}
-
+
if(s == "")
s = "^1Unregistered Player";
return s;
- #ifdef SVQC
+ #ifdef SVQC
.vector moveto;
/**
*/
#define movelib_move_simple(newdir,velo,blendrate) \
self.velocity = self.velocity * (1 - blendrate) + (newdir * blendrate) * velo
+
+#define movelib_move_simple_gravity(newdir,velo,blendrate) \
+ if(self.flags & FL_ONGROUND) movelib_move_simple(newdir,velo,blendrate)
void movelib_beak_simple(float force)
{
entity frag_attacker;
entity frag_target; // same as self
float frag_deathtype;
-
+
MUTATOR_HOOKABLE(PlayerJump);
// called when a player presses the jump key
// INPUT, OUTPUT:
// appends ", Mutator name" to ret_string for display
// INPUT, OUTPUT:
string ret_string;
-
+
MUTATOR_HOOKABLE(CustomizeWaypoint);
// called every frame
// customizes the waypoint for spectators
MUTATOR_HOOKABLE(TurretSpawn);
// return error to request removal
// INPUT: self - turret
-
+
MUTATOR_HOOKABLE(OnEntityPreSpawn);
// return error to prevent entity spawn, or modify the entity
// INPUT:
entity self;
entity other;
+
+MUTATOR_HOOKABLE(MonsterSpawn);
+ // called when a monster spawns
+
+MUTATOR_HOOKABLE(MonsterDies);
+ // called when a monster dies
+ // INPUT:
+ entity frag_attacker;
+
+MUTATOR_HOOKABLE(MonsterRespawn);
+ // called when a monster wants to respawn
+ // INPUT:
+ entity other;
+
+MUTATOR_HOOKABLE(MonsterDropItem);
+ // called when a monster is dropping loot
+ // INPUT, OUTPUT:
+ .void() monster_loot;
+ entity other;
+
+MUTATOR_HOOKABLE(MonsterMove);
+ // called when a monster moves
+ // returning TRUE makes the monster stop
+ // INPUT:
+ float monster_speed_run;
+ float monster_speed_walk;
+ entity monster_target;
+
+MUTATOR_HOOKABLE(MonsterFindTarget);
+ // called when a monster looks for another target
+
+MUTATOR_HOOKABLE(MonsterCheckBossFlag);
+ // called to change a random monster to a miniboss
MUTATOR_HOOKABLE(PlayerDamage_SplitHealthArmor);
// called when a player gets damaged to e.g. remove stuff he was carrying.
// INPUT, OUTPUT:
float damage_take;
float damage_save;
-
+
MUTATOR_HOOKABLE(PlayerDamage_Calculate);
// called to adjust damage and force values which are applied to the player, used for e.g. strength damage/force multiplier
// i'm not sure if I should change this around slightly (Naming of the entities, and also how they're done in g_damage).
// called at the end of player_powerups() in cl_client.qc, used for manipulating the values which are set by powerup items.
// INPUT
entity self;
- float olditems; // also technically output, but since it is at the end of the function it's useless for that :P
+ float olditems; // also technically output, but since it is at the end of the function it's useless for that :P
MUTATOR_HOOKABLE(PlayerUseKey);
// called when the use key is pressed
MUTATOR_HOOKABLE(SetModname);
// OUT
string modname; // name of the mutator/mod if it warrants showing as such in the server browser
-
+
MUTATOR_HOOKABLE(Item_Spawn);
// called for each item being spawned on a map, including dropped weapons
// return 1 to remove an item
entity other; // weapon info
// IN+OUT
string ret_string;
-
+
MUTATOR_HOOKABLE(Item_RespawnCountdown);
// called when an item is about to respawn
// INPUT+OUTPUT:
string item_name;
vector item_color;
-
+
MUTATOR_HOOKABLE(BotShouldAttack);
// called when a bot checks a target to attack
// INPUT
// allows you to strip a player of an item if they go through the teleporter to help prevent cheating
// INPUT
entity self;
-
+
MUTATOR_HOOKABLE(HelpMePing);
// called whenever a player uses impulse 33 (help me) in cl_impulse.qc
// normally help me ping uses self.waypointsprite_attachedforcarrier,
// in a special manner using this hook
// INPUT
entity self; // the player who pressed impulse 33
-
+
MUTATOR_HOOKABLE(VehicleSpawn);
// called when a vehicle initializes
// return TRUE to remove the vehicle
-
+
MUTATOR_HOOKABLE(VehicleEnter);
// called when a player enters a vehicle
// allows mutators to set special settings in this event
// INPUT
entity vh_player; // player
entity vh_vehicle; // vehicle
-
+
MUTATOR_HOOKABLE(VehicleTouch);
// called when a player touches a vehicle
// return TRUE to stop player from entering the vehicle
// INPUT
entity self; // vehicle
entity other; // player
-
+
MUTATOR_HOOKABLE(VehicleExit);
// called when a player exits a vehicle
// allows mutators to set special settings in this event
// INPUT
entity vh_player; // player
entity vh_vehicle; // vehicle
-
+
MUTATOR_HOOKABLE(AbortSpeedrun);
// called when a speedrun is aborted and the player is teleported back to start position
// INPUT
float cap_record = ctf_captimerecord;
float cap_time = (time - flag.ctf_pickuptime);
string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
-
+
// notify about shit
if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
-
+
// write that shit in the database
if((!ctf_captimerecord) || (cap_time < cap_record))
{
ctf_captimerecord = cap_time;
db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
- write_recordmarker(player, (time - cap_time), cap_time);
- }
+ write_recordmarker(player, (time - cap_time), cap_time);
+ }
}
void ctf_FlagcarrierWaypoints(entity player)
float ang; // angle between shotdir and h
float h; // hypotenuse, which is the distance between attacker to head
float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
-
+
h = vlen(head_center - passer_center);
ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
a = h * cos(ang);
// =======================
- // CaptureShield Functions
+ // CaptureShield Functions
// =======================
- float ctf_CaptureShield_CheckStatus(entity p)
+ float ctf_CaptureShield_CheckStatus(entity p)
{
float s, se;
entity e;
// player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
// use this rule here
-
+
if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
return FALSE;
{
if(!other.ctf_captureshielded) { return FALSE; }
if(SAME_TEAM(self, other)) { return FALSE; }
-
+
return TRUE;
}
{
if(!other.ctf_captureshielded) { return; }
if(SAME_TEAM(self, other)) { return; }
-
+
vector mymid = (self.absmin + self.absmax) * 0.5;
vector othermid = (other.absmin + other.absmax) * 0.5;
void ctf_CaptureShield_Spawn(entity flag)
{
entity shield = spawn();
-
+
shield.enemy = self;
shield.team = self.team;
shield.touch = ctf_CaptureShield_Touch;
shield.solid = SOLID_TRIGGER;
shield.avelocity = '7 0 11';
shield.scale = 0.5;
-
+
setorigin(shield, self.origin);
setmodel(shield, "models/ctf/shield.md3");
setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
flag.ctf_droptime = time;
flag.ctf_dropper = player;
flag.ctf_status = FLAG_DROPPED;
-
+
// messages and sounds
Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
ctf_EventLog("dropped", player.team, player);
// scoring
- PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
+ PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
PlayerScore_Add(player, SP_CTF_DROPS, 1);
-
+
// waypoints
if(autocvar_g_ctf_flag_dropped_waypoint)
WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
}
-
+
player.throw_antispam = time + autocvar_g_ctf_pass_wait;
-
+
if(droptype == DROP_PASS)
{
flag.pass_distance = 0;
{
entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
entity sender = flag.pass_sender;
-
+
// transfer flag to player
flag.owner = player;
flag.owner.flagcarried = flag;
-
+
// reset flag
setattachment(flag, player, "");
setorigin(flag, FLAG_CARRY_OFFSET);
// messages and sounds
sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
ctf_EventLog("receive", flag.team, player);
-
+
FOR_EACH_REALPLAYER(tmp_player)
{
if(tmp_player == sender)
else if(SAME_TEAM(tmp_player, sender))
Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
}
-
+
// create new waypoint
ctf_FlagcarrierWaypoints(player);
-
+
sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
player.throw_antispam = sender.throw_antispam;
{
entity flag = player.flagcarried;
vector targ_origin, flag_velocity;
-
+
if(!flag) { return; }
if((droptype == DROP_PASS) && !receiver) { return; }
-
+
if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
-
+
// reset the flag
setattachment(flag, world, "");
setorigin(flag, player.origin + FLAG_DROP_OFFSET);
flag.solid = SOLID_TRIGGER;
flag.ctf_dropper = player;
flag.ctf_droptime = time;
-
+
flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
-
+
switch(droptype)
{
case DROP_PASS:
flag.pass_sender = player;
flag.pass_target = receiver;
flag.ctf_status = FLAG_PASSING;
-
+
// other
sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
ctf_EventLog("pass", flag.team, player);
break;
}
-
+
case DROP_THROW:
{
makevectors((player.v_angle_y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle_x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
-
+
flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
ctf_Handle_Drop(flag, player, droptype);
break;
}
-
+
case DROP_RESET:
{
flag.velocity = '0 0 0'; // do nothing
break;
}
-
+
default:
case DROP_NORMAL:
{
// kill old waypointsprite
WaypointSprite_Ping(player.wps_flagcarrier);
WaypointSprite_Kill(player.wps_flagcarrier);
-
+
if(player.wps_enemyflagcarrier)
WaypointSprite_Kill(player.wps_enemyflagcarrier);
-
+
// captureshield
ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
}
{
entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
- float old_time, new_time;
-
+ float old_time, new_time;
+
if (!player) { return; } // without someone to give the reward to, we can't possibly cap
-
+
// messages and sounds
Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
ctf_CaptureRecord(enemy_flag, player);
sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTEN_NONE);
-
+
switch(capturetype)
{
case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
default: break;
}
-
+
// scoring
PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
{
WaypointSprite_Kill(player.wps_flagcarrier);
if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
-
+
if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
{ PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
}
-
+
// reset the flag
player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
ctf_RespawnFlag(enemy_flag);
void ctf_Handle_Return(entity flag, entity player)
{
// messages and sounds
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
- Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
+ if(IS_PLAYER(player))
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
+
+ Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), (player.flags & FL_MONSTER) ? player.monster_name : player.netname);
sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
ctf_EventLog("return", flag.team, player);
// scoring
- PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
- PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
+ if(IS_PLAYER(player))
+ {
+ PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
+ PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
+ }
TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
-
- if(flag.ctf_dropper)
+
+ if(flag.ctf_dropper)
{
PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
- ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
+ ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
}
-
+
// reset the flag
ctf_RespawnFlag(flag);
}
{
// declarations
float pickup_dropped_score; // used to calculate dropped pickup score
-
+
// attach the flag to the player
flag.owner = player;
player.flagcarried = flag;
setattachment(flag, player, "");
setorigin(flag, FLAG_CARRY_OFFSET);
-
+
// flag setup
flag.movetype = MOVETYPE_NONE;
flag.takedamage = DAMAGE_NO;
flag.solid = SOLID_NOT;
flag.angles = '0 0 0';
flag.ctf_status = FLAG_CARRY;
-
+
switch(pickuptype)
{
case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
}
// messages and sounds
- Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
+ Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
-
+
Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
-
+
sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
-
+
// scoring
PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
switch(pickuptype)
- {
+ {
case PICKUP_BASE:
{
PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
ctf_EventLog("steal", flag.team, player);
break;
}
-
+
case PICKUP_DROPPED:
{
pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
ctf_EventLog("pickup", flag.team, player);
break;
}
-
+
default: break;
}
-
+
// speedrunning
if(pickuptype == PICKUP_BASE)
{
if((player.speedrunning) && (ctf_captimerecord))
ctf_FakeTimeLimit(player, time + ctf_captimerecord);
}
-
+
// effects
pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
-
- // waypoints
+
+ // waypoints
if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
ctf_FlagcarrierWaypoints(player);
WaypointSprite_Ping(player.wps_flagcarrier);
if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
{
if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
-
+
if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
{
switch(returntype)
case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
-
+
default:
case RETURN_TIMEOUT:
{ Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
{
tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
ctf_staleflaglist = tmp_entity;
-
+
switch(tmp_entity.team)
{
case NUM_TEAM_1: ++stale_red_flags; break;
{ ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
{ ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
-
+
// if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
if(ctf_stalemate)
{
if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, tmp_entity.team, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
}
-
+
if (!wpforenemy_announced)
{
FOR_EACH_REALPLAYER(tmp_entity)
Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
-
+
wpforenemy_announced = TRUE;
}
}
ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
return;
}
- if(autocvar_g_ctf_flag_return_damage)
+ if(autocvar_g_ctf_flag_return_damage)
{
// reduce health and check if it should be returned
self.health = self.health - damage;
tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
if(!trace_startsolid) // can we resize it without getting stuck?
setsize(self, FLAG_MIN, FLAG_MAX); }
-
+
switch(self.ctf_status) // reset flag angles in case warpzones adjust it
{
case FLAG_DROPPED:
self.angles = '0 0 0';
break;
}
-
+
default: break;
}
// main think method
switch(self.ctf_status)
- {
+ {
case FLAG_BASE:
{
if(autocvar_g_ctf_dropped_capture_radius)
}
return;
}
-
+
case FLAG_DROPPED:
{
if(autocvar_g_ctf_flag_dropped_floatinwater)
if(pointcontents(midpoint) == CONTENT_WATER)
{
self.velocity = self.velocity * 0.5;
-
+
if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
{ self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
else
self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
return;
- }
+ }
return;
}
-
+
case FLAG_CARRY:
{
- if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
+ if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
{
self.health = 0;
ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
}
return;
}
-
+
case FLAG_PASSING:
{
vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
-
+
if((self.pass_target == world)
|| (self.pass_target.deadflag != DEAD_NO)
|| (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
void ctf_FlagTouch()
{
if(gameover) { return; }
-
+
entity toucher = other;
-
+
// automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
if(ITEM_TOUCH_NEEDKILL())
{
ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
return;
}
-
+
// special touch behaviors
- if(toucher.vehicle_flags & VHF_ISVEHICLE)
+ if(toucher.frozen) { return; }
+ else if(toucher.vehicle_flags & VHF_ISVEHICLE)
{
if(autocvar_g_ctf_allow_vehicle_touch)
toucher = toucher.owner; // the player is actually the vehicle owner, not other
else
return; // do nothing
}
+ else if(toucher.flags & FL_MONSTER)
+ {
+ if(!autocvar_g_ctf_allow_monster_touch)
+ return; // do nothing
+ }
else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
{
if(time > self.wait) // if we haven't in a while, play a sound/effect
}
else if(toucher.deadflag != DEAD_NO) { return; }
- switch(self.ctf_status)
- {
+ switch(self.ctf_status)
+ {
case FLAG_BASE:
{
- if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self))
+ if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && !(toucher.flags & FL_MONSTER))
ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
- else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
+ else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && !(toucher.flags & FL_MONSTER))
ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
break;
}
-
+
case FLAG_DROPPED:
{
if(SAME_TEAM(toucher, self))
ctf_Handle_Return(self, toucher); // toucher just returned his own flag
- else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
+ else if(!(toucher.flags & FL_MONSTER) && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
break;
}
-
+
case FLAG_CARRY:
{
dprint("Someone touched a flag even though it was being carried?\n");
break;
}
-
+
case FLAG_PASSING:
{
if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
{ backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
flag.last_respawn = time;
-
+
// reset the player (if there is one)
if((flag.owner) && (flag.owner.flagcarried == flag))
{
if(flag.owner.wps_enemyflagcarrier)
WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
-
+
WaypointSprite_Kill(flag.wps_flagcarrier);
-
+
flag.owner.flagcarried = world;
if(flag.speedrunning)
// reset the flag
setattachment(flag, world, "");
setorigin(flag, flag.ctf_spawnorigin);
-
+
flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
flag.takedamage = DAMAGE_NO;
flag.health = flag.max_flag_health;
flag.velocity = '0 0 0';
flag.angles = flag.mangle;
flag.flags = FL_ITEM | FL_NOTARGET;
-
+
flag.ctf_status = FLAG_BASE;
flag.owner = world;
flag.pass_distance = 0;
if(self.owner)
if(IS_PLAYER(self.owner))
ctf_Handle_Throw(self.owner, world, DROP_RESET);
-
+
ctf_RespawnFlag(self);
}
ctf_CaptureShield_Spawn(self);
}
- void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
- {
+ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
+ {
// declarations
- teamnumber = fabs(teamnumber - bound(0, autocvar_g_ctf_reverse, 1)); // if we were originally 1, this will become 0. If we were originally 0, this will become 1.
+ teamnumber = fabs(teamnumber - bound(0, autocvar_g_ctf_reverse, 1)); // if we were originally 1, this will become 0. If we were originally 0, this will become 1.
self = flag; // for later usage with droptofloor()
-
+
// main setup
flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
ctf_worldflaglist = flag;
flag.flags = FL_ITEM | FL_NOTARGET;
flag.solid = SOLID_TRIGGER;
flag.takedamage = DAMAGE_NO;
- flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
+ flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
flag.health = flag.max_flag_health;
flag.event_damage = ctf_FlagDamage;
if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
if(flag.passeffect == "") { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
if(flag.capeffect == "") { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
-
- // sound
+
+ // sound
if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
if(flag.snd_flag_capture == "") { flag.snd_flag_capture = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
-
+
// precache
precache_sound(flag.snd_flag_taken);
precache_sound(flag.snd_flag_returned);
setmodel(flag, flag.model); // precision set below
setsize(flag, FLAG_MIN, FLAG_MAX);
setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
-
+
if(autocvar_g_ctf_flag_glowtrails)
{
flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
flag.glow_size = 25;
flag.glow_trail = 1;
}
-
+
flag.effects |= EF_LOWPRECISION;
if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
-
+
// flag placement
if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
- {
- flag.dropped_origin = flag.origin;
+ {
+ flag.dropped_origin = flag.origin;
flag.noalign = TRUE;
flag.movetype = MOVETYPE_NONE;
}
else // drop to floor, automatically find a platform and set that as spawn origin
- {
+ {
flag.noalign = FALSE;
self = flag;
droptofloor();
- flag.movetype = MOVETYPE_TOSS;
- }
-
+ flag.movetype = MOVETYPE_TOSS;
+ }
+
InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
}
MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
{
entity flag;
-
+
// initially clear items so they can be set as necessary later.
self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
| IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
- // scan through all the flags and notify the client about them
+ // scan through all the flags and notify the client about them
for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
{
switch(flag.ctf_status)
{
if((flag.owner == self) || (flag.pass_sender == self))
self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
- else
+ else
self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
break;
}
}
}
}
-
+
// item for stopping players from capturing the flag too often
if(self.ctf_captureshielded)
self.items |= IT_CTF_SHIELDED;
-
+
// update the health of the flag carrier waypointsprite
- if(self.wps_flagcarrier)
+ if(self.wps_flagcarrier)
WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
-
+
return FALSE;
}
PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
}
-
+
if(frag_target.flagcarried)
{ ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
-
+
return FALSE;
}
MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
{
entity flag; // temporary entity for the search method
-
+
if(self.flagcarried)
{ ctf_Handle_Throw(self, world, DROP_NORMAL); }
-
+
for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
{
if(flag.pass_sender == self) { flag.pass_sender = world; }
if(flag.pass_target == self) { flag.pass_target = world; }
if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
}
-
+
return FALSE;
}
MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
{
- if(self.flagcarried)
+ if(self.flagcarried)
if(!autocvar_g_ctf_portalteleport)
{ ctf_Handle_Throw(self, world, DROP_NORMAL); }
MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
{
if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
-
+
entity player = self;
if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
{
entity head, closest_target = world;
head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
-
+
while(head) // find the closest acceptable target to pass to
{
if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
if(head != player && SAME_TEAM(head, player))
if(!head.speedrunning && !head.vehicle)
{
- // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
+ // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
vector passer_center = CENTER_OR_VIEWOFS(player);
-
+
if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
{
- if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
- {
+ if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
+ {
if(IS_BOT_CLIENT(head))
{
Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
}
- player.throw_antispam = time + autocvar_g_ctf_pass_wait;
- return TRUE;
+ player.throw_antispam = time + autocvar_g_ctf_pass_wait;
+ return TRUE;
}
else if(player.flagcarried)
{
}
head = head.chain;
}
-
+
if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
}
-
+
// throw the flag in front of you
if(autocvar_g_ctf_throw && player.flagcarried)
{
if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
else { player.throw_count += 1; }
if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
-
+
player.throw_prevtime = time;
ctf_Handle_Throw(player, world, DROP_THROW);
return TRUE;
}
}
}
-
+
return FALSE;
}
{
self.wps_helpme_time = time;
WaypointSprite_HelpMePing(self.wps_flagcarrier);
- }
+ }
else // create a normal help me waypointsprite
{
WaypointSprite_Spawn("helpme", waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, FALSE, RADARICON_HELPME, '1 0.5 0');
ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
}
else
- {
- setattachment(vh_player.flagcarried, vh_vehicle, "");
+ {
+ setattachment(vh_player.flagcarried, vh_vehicle, "");
setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
- //vh_player.flagcarried.angles = '0 0 0';
+ //vh_player.flagcarried.angles = '0 0 0';
}
return TRUE;
}
-
+
return FALSE;
}
{
if(vh_player.flagcarried)
{
- setattachment(vh_player.flagcarried, vh_player, "");
+ setattachment(vh_player.flagcarried, vh_player, "");
setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
vh_player.flagcarried.scale = FLAG_SCALE;
vh_player.flagcarried.angles = '0 0 0';
ctf_RespawnFlag(self.flagcarried);
return TRUE;
}
-
+
return FALSE;
}
MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
{
entity flag; // temporary entity for the search method
-
+
for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
{
switch(flag.ctf_status)
flag.takedamage = DAMAGE_NO;
flag.solid = SOLID_NOT;
flag.nextthink = FALSE; // stop thinking
-
+
//dprint("stopping the ", flag.netname, " from moving.\n");
break;
}
-
+
default:
case FLAG_BASE:
case FLAG_CARRY:
}
}
}
-
+
return FALSE;
}
void spawnfunc_info_player_team1()
{
if(g_assault) { remove(self); return; }
-
+
self.team = NUM_TEAM_1; // red
spawnfunc_info_player_deathmatch();
}
void spawnfunc_info_player_team2()
{
if(g_assault) { remove(self); return; }
-
+
self.team = NUM_TEAM_2; // blue
spawnfunc_info_player_deathmatch();
}
void spawnfunc_info_player_team3()
{
if(g_assault) { remove(self); return; }
-
+
self.team = NUM_TEAM_3; // yellow
spawnfunc_info_player_deathmatch();
}
void spawnfunc_info_player_team4()
{
if(g_assault) { remove(self); return; }
-
+
self.team = NUM_TEAM_4; // purple
spawnfunc_info_player_deathmatch();
}
/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
CTF flag for team one (Red).
- Keys:
- "angle" Angle the flag will point (minus 90 degrees)...
+ Keys:
+ "angle" Angle the flag will point (minus 90 degrees)...
"model" model to use, note this needs red and blue as skins 0 and 1...
"noise" sound played when flag is picked up...
"noise1" sound played when flag is returned by a teammate...
"noise2" sound played when flag is captured...
- "noise3" sound played when flag is lost in the field and respawns itself...
+ "noise3" sound played when flag is lost in the field and respawns itself...
"noise4" sound played when flag is dropped by a player...
"noise5" sound played when flag touches the ground... */
void spawnfunc_item_flag_team1()
/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
CTF flag for team two (Blue).
- Keys:
- "angle" Angle the flag will point (minus 90 degrees)...
+ Keys:
+ "angle" Angle the flag will point (minus 90 degrees)...
"model" model to use, note this needs red and blue as skins 0 and 1...
"noise" sound played when flag is picked up...
"noise1" sound played when flag is returned by a teammate...
"noise2" sound played when flag is captured...
- "noise3" sound played when flag is lost in the field and respawns itself...
+ "noise3" sound played when flag is lost in the field and respawns itself...
"noise4" sound played when flag is dropped by a player...
"noise5" sound played when flag touches the ground... */
void spawnfunc_item_flag_team2()
void spawnfunc_ctf_team()
{
if(!g_ctf) { remove(self); return; }
-
+
self.classname = "ctf_team";
self.team = self.cnt + 1;
}
ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
}
-
+
ctf_ScoreRules();
}
ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
ctf_captureshield_force = autocvar_g_ctf_shield_force;
-
+
InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
}
MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
-
+
MUTATOR_ONADD
{
if(time > 1) // game loads at time 1
.float freezetag_frozen_time;
.float freezetag_frozen_timeout;
.float freezetag_revive_progress;
-.entity freezetag_ice;
#define ICE_MAX_ALPHA 1
#define ICE_MIN_ALPHA 0.1
float freezetag_teams;
if(e.team == NUM_TEAM_1 && e.health >= 1)
{
++total_players;
- if (!e.freezetag_frozen) ++redalive;
+ if (e.frozen != 1) ++redalive;
}
else if(e.team == NUM_TEAM_2 && e.health >= 1)
{
++total_players;
- if (!e.freezetag_frozen) ++bluealive;
+ if (e.frozen != 1) ++bluealive;
}
else if(e.team == NUM_TEAM_3 && e.health >= 1)
{
++total_players;
- if (!e.freezetag_frozen) ++yellowalive;
+ if (e.frozen != 1) ++yellowalive;
}
else if(e.team == NUM_TEAM_4 && e.health >= 1)
{
++total_players;
- if (!e.freezetag_frozen) ++pinkalive;
+ if (e.frozen != 1) ++pinkalive;
}
}
FOR_EACH_REALCLIENT(e) {
return 1;
}
-// this is needed to allow the player to turn his view around (fixangle can't
-// be used to freeze his view, as that also changes the angles), while not
-// turning that ice object with the player
-void freezetag_Ice_Think()
-{
- setorigin(self, self.owner.origin - '0 0 16');
- self.nextthink = time;
-}
-
void freezetag_Add_Score(entity attacker)
{
if(attacker == self)
void freezetag_Freeze(entity attacker)
{
- if(self.freezetag_frozen)
+ if(self.frozen)
return;
- self.freezetag_frozen = 1;
- self.freezetag_frozen_time = time;
- self.freezetag_revive_progress = 0;
- self.health = 1;
- if(autocvar_g_freezetag_frozen_maxtime > 0)
- self.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
-
+
+ Freeze(self, 0, 1, TRUE);
+
freezetag_count_alive_players();
- entity ice;
- ice = spawn();
- ice.owner = self;
- ice.classname = "freezetag_ice";
- ice.think = freezetag_Ice_Think;
- ice.nextthink = time;
- ice.frame = floor(random() * 21); // ice model has 20 different looking frames
- ice.alpha = ICE_MAX_ALPHA;
- ice.colormod = Team_ColorRGB(self.team);
- ice.glowmod = ice.colormod;
- setmodel(ice, "models/ice/ice.md3");
-
- self.freezetag_ice = ice;
-
- RemoveGrapplingHook(self);
-
- // add waypoint
- WaypointSprite_Spawn("freezetag_frozen", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attached, TRUE, RADARICON_WAYPOINT, '0.25 0.90 1');
-
freezetag_Add_Score(attacker);
}
void freezetag_Unfreeze(entity attacker)
{
- self.freezetag_frozen = 0;
self.freezetag_frozen_time = 0;
self.freezetag_frozen_timeout = 0;
- self.freezetag_revive_progress = 0;
-
- remove(self.freezetag_ice);
- self.freezetag_ice = world;
-
- if(self.waypointsprite_attached)
- WaypointSprite_Kill(self.waypointsprite_attached);
+
+ Unfreeze(self);
}
-
// ================
// Bot player logic
// ================
{
if ((head != self) && (head.team == self.team))
{
- if (head.freezetag_frozen)
+ if (head.frozen == 1)
{
distance = vlen(head.origin - org);
if (distance > sradius)
unfrozen = 0;
FOR_EACH_PLAYER(head)
{
- if ((head.team == self.team) && (!head.freezetag_frozen))
+ if ((head.team == self.team) && (head.frozen != 1))
unfrozen++;
}
// If only one left on team or if role has timed out then start trying to free players.
- if (((unfrozen == 0) && (!self.freezetag_frozen)) || (time > self.havocbot_role_timeout))
+ if (((unfrozen == 0) && (!self.frozen)) || (time > self.havocbot_role_timeout))
{
dprint("changing role to freeing\n");
self.havocbot_role = havocbot_role_ft_freeing;
if(round_handler_IsActive())
if(round_handler_CountdownRunning())
{
- if(self.freezetag_frozen)
+ if(self.frozen)
freezetag_Unfreeze(world);
freezetag_count_alive_players();
return 1; // let the player die so that he can respawn whenever he wants
|| frag_deathtype == DEATH_TEAMCHANGE || frag_deathtype == DEATH_AUTOTEAMCHANGE)
{
// let the player die, he will be automatically frozen when he respawns
- if(!self.freezetag_frozen)
+ if(self.frozen != 1)
{
freezetag_Add_Score(frag_attacker);
freezetag_count_alive_players();
return 1;
}
- if(self.freezetag_frozen)
+ if(self.frozen)
return 1;
freezetag_Freeze(frag_attacker);
Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
}
- frag_target.health = 1; // "respawn" the player :P
-
return 1;
}
{
FOR_EACH_PLAYER(self)
{
- if (self.freezetag_frozen)
+ if (self.frozen)
freezetag_Unfreeze(world);
self.freezetag_frozen_timeout = -1;
PutClientInServer();
if(gameover)
return 1;
- if(self.freezetag_frozen)
+ if(self.frozen == 1)
{
// keep health = 1
self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
entity o;
o = world;
if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout)
- self.freezetag_ice.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (self.freezetag_frozen_timeout - time) / (self.freezetag_frozen_timeout - self.freezetag_frozen_time);
+ self.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (self.freezetag_frozen_timeout - time) / (self.freezetag_frozen_timeout - self.freezetag_frozen_time);
if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout)
n = -1;
n = 0;
FOR_EACH_PLAYER(other) if(self != other)
{
- if(other.freezetag_frozen == 0)
+ if(other.frozen == 0)
{
- if(other.team == self.team)
+ if(SAME_TEAM(other, self))
{
if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
{
if(!o)
o = other;
- if(self.freezetag_frozen)
+ if(self.frozen == 1)
other.reviving = TRUE;
++n;
}
}
}
- if(n && self.freezetag_frozen) // OK, there is at least one teammate reviving us
+ if(n && self.frozen == 1) // OK, there is at least one teammate reviving us
{
- self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
- self.health = max(1, self.freezetag_revive_progress * autocvar_g_balance_health_start);
+ self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
+ self.health = max(1, self.revive_progress * autocvar_g_balance_health_start);
- if(self.freezetag_revive_progress >= 1)
+ if(self.revive_progress >= 1)
{
freezetag_Unfreeze(self);
freezetag_count_alive_players();
{
if(other.reviving)
{
- other.freezetag_revive_progress = self.freezetag_revive_progress;
+ other.revive_progress = self.revive_progress;
other.reviving = FALSE;
}
}
}
- else if(!n && self.freezetag_frozen) // only if no teammate is nearby will we reset
+ else if(!n && self.frozen == 1) // only if no teammate is nearby will we reset
{
- self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
- self.health = max(1, self.freezetag_revive_progress * autocvar_g_balance_health_start);
+ self.revive_progress = bound(0, self.revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
+ self.health = max(1, self.revive_progress * autocvar_g_balance_health_start);
}
- else if(!n)
+ else if(!n && !self.frozen)
{
- self.freezetag_revive_progress = 0; // thawing nobody
+ self.revive_progress = 0; // thawing nobody
}
return 1;
}
-MUTATOR_HOOKFUNCTION(freezetag_PlayerPhysics)
-{
- if(self.freezetag_frozen)
- {
- if(autocvar_sv_dodging_frozen && IS_REAL_CLIENT(self))
- {
- self.movement_x = bound(-5, self.movement_x, 5);
- self.movement_y = bound(-5, self.movement_y, 5);
- self.movement_z = bound(-5, self.movement_z, 5);
- }
- else
- self.movement = '0 0 0';
-
- self.disableclientprediction = 1;
- }
- return 1;
-}
-
MUTATOR_HOOKFUNCTION(freezetag_PlayerDamage_Calculate)
{
- if(frag_target.freezetag_frozen && frag_deathtype != DEATH_HURTTRIGGER)
+ if(frag_target.frozen == 1 && frag_deathtype != DEATH_HURTTRIGGER)
{
if(autocvar_g_freezetag_revive_falldamage > 0)
if(frag_deathtype == DEATH_FALL)
Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, frag_target.netname);
Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_FALL);
}
-
+
frag_damage = 0;
frag_force = frag_force * autocvar_g_freezetag_frozen_force;
}
return 1;
}
-MUTATOR_HOOKFUNCTION(freezetag_PlayerJump)
-{
- if(self.freezetag_frozen)
- return TRUE; // no jumping in freezetag when frozen
-
- return FALSE;
-}
-
-MUTATOR_HOOKFUNCTION(freezetag_ForbidThrowCurrentWeapon)
-{
- if (self.freezetag_frozen)
- return 1;
- return 0;
-}
-
-MUTATOR_HOOKFUNCTION(freezetag_ItemTouch)
-{
- if (other.freezetag_frozen)
- return MUT_ITEMTOUCH_RETURN;
- return MUT_ITEMTOUCH_CONTINUE;
-}
-
MUTATOR_HOOKFUNCTION(freezetag_BotRoles)
{
if (!self.deadflag)
return TRUE;
}
-MUTATOR_HOOKFUNCTION(freezetag_SpectateCopy)
-{
- self.freezetag_frozen = other.freezetag_frozen;
- self.freezetag_revive_progress = other.freezetag_revive_progress;
- return 0;
-}
-
MUTATOR_HOOKFUNCTION(freezetag_GetTeamCount)
{
freezetag_teams = autocvar_g_freezetag_teams_override;
return 0;
}
-MUTATOR_HOOKFUNCTION(freezetag_VehicleTouch)
-{
- if(other.freezetag_frozen)
- return TRUE;
-
- return FALSE;
-}
-
void freezetag_Initialize()
{
precache_model("models/ice/ice.md3");
addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
-
- addstat(STAT_FROZEN, AS_INT, freezetag_frozen);
- addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, freezetag_revive_progress);
}
MUTATOR_DEFINITION(gamemode_freezetag)
MUTATOR_HOOK(reset_map_players, freezetag_reset_map_players, CBC_ORDER_ANY);
MUTATOR_HOOK(GiveFragsForKill, freezetag_GiveFragsForKill, CBC_ORDER_FIRST);
MUTATOR_HOOK(PlayerPreThink, freezetag_PlayerPreThink, CBC_ORDER_FIRST);
- MUTATOR_HOOK(PlayerPhysics, freezetag_PlayerPhysics, CBC_ORDER_FIRST);
MUTATOR_HOOK(PlayerDamage_Calculate, freezetag_PlayerDamage_Calculate, CBC_ORDER_ANY);
- MUTATOR_HOOK(PlayerJump, freezetag_PlayerJump, CBC_ORDER_ANY);
- MUTATOR_HOOK(ForbidThrowCurrentWeapon, freezetag_ForbidThrowCurrentWeapon, CBC_ORDER_ANY);
- MUTATOR_HOOK(ItemTouch, freezetag_ItemTouch, CBC_ORDER_ANY);
MUTATOR_HOOK(HavocBot_ChooseRule, freezetag_BotRoles, CBC_ORDER_ANY);
- MUTATOR_HOOK(SpectateCopy, freezetag_SpectateCopy, CBC_ORDER_ANY);
MUTATOR_HOOK(GetTeamCount, freezetag_GetTeamCount, CBC_ORDER_EXCLUSIVE);
- MUTATOR_HOOK(VehicleTouch, freezetag_VehicleTouch, CBC_ORDER_ANY);
MUTATOR_ONADD
{
--- /dev/null
-
+void invasion_spawnpoint()
+{
+ if(!g_invasion) { remove(self); return; }
-
++
+ self.classname = "invasion_spawnpoint";
+}
+
+float invasion_PickMonster(float supermonster_count)
+{
+ if(autocvar_g_invasion_zombies_only)
+ return MON_ZOMBIE;
+
+ float i;
+ entity mon;
-
++
+ RandomSelection_Init();
-
++
+ for(i = MON_FIRST; i <= MON_LAST; ++i)
+ {
+ mon = get_monsterinfo(i);
+ if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM) || (mon.spawnflags & MON_FLAG_SUPERMONSTER && supermonster_count >= 1))
+ continue; // flying/swimming monsters not yet supported
-
++
+ RandomSelection_Add(world, i, "", 1, 1);
+ }
-
++
+ return RandomSelection_chosen_float;
+}
+
+entity invasion_PickSpawn()
+{
+ entity e;
-
++
+ RandomSelection_Init();
-
++
+ for(e = world;(e = find(e, classname, "invasion_spawnpoint")); )
+ RandomSelection_Add(e, 0, string_null, 1, 1);
-
++
+ return RandomSelection_chosen_ent;
+}
+
+void invasion_SpawnChosenMonster(float mon)
+{
+ entity spawn_point, monster;
-
++
+ spawn_point = invasion_PickSpawn();
-
++
+ if(spawn_point == world)
+ {
+ dprint("Warning: couldn't find any invasion_spawnpoint spawnpoints, no monsters will spawn!\n");
+ return;
+ }
-
++
+ monster = spawnmonster("", mon, spawn_point, spawn_point, spawn_point.origin, FALSE, 2);
-
++
+ if(roundcnt >= maxrounds)
+ monster.spawnflags |= MONSTERFLAG_MINIBOSS;
+}
+
+void invasion_SpawnMonsters(float supermonster_count)
+{
+ float chosen_monster = invasion_PickMonster(supermonster_count);
-
++
+ invasion_SpawnChosenMonster(chosen_monster);
+}
+
+float Invasion_CheckWinner()
+{
+ entity head;
+ if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+ {
+ FOR_EACH_MONSTER(head)
+ monster_remove(head);
-
++
+ if(roundcnt >= maxrounds)
+ {
+ NextLevel();
+ return 1;
+ }
-
++
+ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
+ round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+ return 1;
+ }
-
++
+ // boss round
+ if(roundcnt >= maxrounds)
+ {
+ if(numspawned < 1)
+ {
+ maxspawned = 1;
+ invasion_SpawnMonsters(0);
+ }
+ }
+ else
+ {
+ float total_alive_monsters = 0, supermonster_count = 0;
-
++
+ FOR_EACH_MONSTER(head) if(head.health > 0)
+ {
+ if((get_monsterinfo(head.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
+ ++supermonster_count;
+ ++total_alive_monsters;
+ }
+
+ if((total_alive_monsters + numkilled) < maxspawned && maxcurrent < 10) // 10 at a time should be plenty
+ {
+ if(time >= last_check)
+ {
+ invasion_SpawnMonsters(supermonster_count);
+ last_check = time + 2;
+ }
-
++
+ return 0;
+ }
+ }
-
++
+ if(numspawned < 1 || numkilled < maxspawned)
+ return 0; // nothing has spawned yet, or there are still alive monsters
-
++
+ if(roundcnt >= maxrounds)
+ {
+ NextLevel();
+ return 1;
+ }
-
++
+ entity winner = world;
+ float winning_score = 0;
-
++
+ FOR_EACH_PLAYER(head)
+ {
+ float cs = PlayerScore_Add(head, SP_KILLS, 0);
+ if(cs > winning_score)
+ {
+ winning_score = cs;
+ winner = head;
+ }
+ }
-
++
+ FOR_EACH_MONSTER(head)
+ monster_remove(head);
+
+ if(winner)
+ {
+ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
+ Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
+ }
-
++
+ round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+
+ return 1;
+}
+
+float Invasion_CheckPlayers()
+{
+ return TRUE;
+}
+
+void Invasion_RoundStart()
+{
+ entity e;
+ float numplayers = 0;
+ FOR_EACH_PLAYER(e)
+ {
+ e.player_blocked = 0;
+ ++numplayers;
+ }
-
++
+ roundcnt += 1;
-
++
+ invasion_monsterskill = roundcnt + (numplayers * 0.3);
-
++
+ maxcurrent = 0;
+ numspawned = 0;
+ numkilled = 0;
-
++
+ if(roundcnt > 1)
+ maxspawned = rint(autocvar_g_invasion_monster_count * (roundcnt * 0.5));
+ else
+ maxspawned = autocvar_g_invasion_monster_count;
+}
+
+MUTATOR_HOOKFUNCTION(invasion_MonsterDies)
+{
+ if(!self.monster_respawned)
+ {
+ numkilled += 1;
+ maxcurrent -= 1;
-
++
+ if(IS_PLAYER(frag_attacker))
+ PlayerScore_Add(frag_attacker, SP_KILLS, +1);
+ }
-
++
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(invasion_MonsterSpawn)
+{
+ if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
+ {
+ monster_remove(self);
+ return FALSE;
+ }
-
++
+ if(roundcnt < maxrounds && self.spawnflags & MONSTERFLAG_MINIBOSS)
+ self.spawnflags &= ~MONSTERFLAG_MINIBOSS;
-
++
+ if(!self.monster_respawned)
+ {
+ numspawned += 1;
+ maxcurrent += 1;
+ }
-
++
+ self.monster_skill = invasion_monsterskill;
-
++
+ if((get_monsterinfo(self.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
+ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, M_NAME(self.monsterid));
-
++
+ self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
-
++
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(invasion_PlayerThink)
+{
+ monsters_total = maxspawned; // TODO: make sure numspawned never exceeds maxspawned
+ monsters_killed = numkilled;
-
++
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(invasion_PlayerSpawn)
+{
+ self.bot_attack = FALSE;
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(invasion_PlayerDamage)
+{
+ if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target)
+ {
+ frag_damage = 0;
+ frag_force = '0 0 0';
+ }
-
++
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(invasion_PlayerCommand)
+{
+ if(MUTATOR_RETURNVALUE) // command was already handled?
+ return FALSE;
-
++
+ if(cmd_name == "debuginvasion")
+ {
+ sprint(self, strcat("maxspawned = ", ftos(maxspawned), "\n"));
+ sprint(self, strcat("numspawned = ", ftos(numspawned), "\n"));
+ sprint(self, strcat("numkilled = ", ftos(numkilled), "\n"));
+ sprint(self, strcat("roundcnt = ", ftos(roundcnt), "\n"));
+ sprint(self, strcat("monsters_total = ", ftos(monsters_total), "\n"));
+ sprint(self, strcat("monsters_killed = ", ftos(monsters_killed), "\n"));
+ sprint(self, strcat("invasion_monsterskill = ", ftos(invasion_monsterskill), "\n"));
-
++
+ return TRUE;
+ }
-
++
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(invasion_SetStartItems)
+{
+ start_health = 200;
+ start_armorvalue = 200;
-
++
+ return FALSE;
+}
+
+void invasion_ScoreRules()
+{
+ ScoreRules_basics(0, 0, 0, FALSE);
+ ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
+ ScoreRules_basics_end();
+}
+
+void invasion_Initialize()
+{
+ independent_players = 1; // to disable extra useless scores
+
+ invasion_ScoreRules();
-
++
+ independent_players = 0;
+
+ round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart);
+ round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
-
++
+ allowed_to_spawn = TRUE;
-
++
+ roundcnt = 0;
+}
+
+MUTATOR_DEFINITION(gamemode_invasion)
+{
+ MUTATOR_HOOK(MonsterDies, invasion_MonsterDies, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MonsterSpawn, invasion_MonsterSpawn, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerPreThink, invasion_PlayerThink, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerSpawn, invasion_PlayerSpawn, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerDamage_Calculate, invasion_PlayerDamage, CBC_ORDER_ANY);
+ MUTATOR_HOOK(SV_ParseClientCommand, invasion_PlayerCommand, CBC_ORDER_ANY);
+ MUTATOR_HOOK(SetStartItems, invasion_SetStartItems, CBC_ORDER_ANY);
-
++
+ MUTATOR_ONADD
+ {
+ if(time > 1) // game loads at time 1
+ error("This is a game type and it cannot be added at runtime.");
+ invasion_Initialize();
++
+ cvar_settemp("g_monsters", "1");
+ }
+
+ MUTATOR_ONROLLBACK_OR_REMOVE
+ {
+ // we actually cannot roll back invasion_Initialize here
+ // BUT: we don't need to! If this gets called, adding always
+ // succeeds.
+ }
+
+ MUTATOR_ONREMOVE
+ {
+ print("This is a game type and it cannot be removed at runtime.");
+ return -1;
+ }
+
+ return 0;
+}
d = d + 1;
e = e.chain;
}
-
+
if(autocvar_g_campaign && autocvar__campaign_testrun)
d = d * self.max_health;
else
d = d * self.max_health / max(30, 60 * autocvar_timelimit_suddendeath);
-
+
Damage(self, self, self, d, DEATH_HURTTRIGGER, self.origin, '0 0 0');
}
else if (overtime_msg_time)
precache_sound("onslaught/electricity_explode.wav");
if (!self.team)
objerror("team must be set");
-
+
if(self.team == NUM_TEAM_1)
ons_red_generator = self;
if(self.team == NUM_TEAM_2)
ons_blue_generator = self;
-
+
self.team_saved = self.team;
self.colormap = 1024 + (self.team - 1) * 17;
self.solid = SOLID_BBOX;
waypoint_spawnforitem(self);
onslaught_updatelinks();
-
+
self.reset = onslaught_generator_reset;
}
float _friendly_count = 0;
float _dist;
entity _player;
-
+
FOR_EACH_PLAYER(_player)
{
if(!_player.deadflag)
a = onslaught_controlpoint_can_be_linked(self.owner, self.owner.team);
if(!a)
return;
-
+
self.health = self.health + self.count;
if (self.health >= self.max_health)
onslaught_updatelinks();
}
+ void onslaught_controlpoint_think()
+ {
+ self.nextthink = time;
+ CSQCMODEL_AUTOUPDATE();
+ }
+
void onslaught_controlpoint_reset()
{
if(self.goalentity && self.goalentity != world)
self.isshielded = TRUE;
self.enemy.solid = SOLID_NOT;
self.enemy.colormap = self.colormap;
- self.think = self.enemy.think = func_null;
- self.nextthink = 0; // don't like func_null :P
+ self.think = onslaught_controlpoint_think;
+ self.enemy.think = func_null;
+ self.nextthink = time; // don't like func_null :P
setmodel(self, "models/onslaught/controlpoint_pad.md3");
//setsize(self, '-32 -32 0', '32 32 8');
activator = self;
SUB_UseTargets(); // to reset the structures, playerspawns etc.
+
+ CSQCMODEL_AUTOUPDATE();
}
/*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128)
self.enemy.colormap = self.colormap;
waypoint_spawnforitem(self);
+
+ self.think = onslaught_controlpoint_think;
+ self.nextthink = time;
WaypointSprite_SpawnFixed(string_null, self.origin + '0 0 128', self, sprite, RADARICON_NONE, '0 0 0');
WaypointSprite_UpdateRule(self.sprite, NUM_TEAM_2, SPRITERULE_TEAMPLAY);
onslaught_updatelinks();
self.reset = onslaught_controlpoint_reset;
+
+ CSQCMODEL_AUTOINIT();
}
float onslaught_link_send(entity to, float sendflags)
MUTATOR_HOOKFUNCTION(ons_Spawn_Score)
{
-
+
/*
float _neer_home = (random() > 0.5 ? TRUE : FALSE);
-
+
RandomSelection_Init();
-
+
if(self.team == NUM_TEAM_1)
RandomSelection_Add(ons_red_generator, 0, string_null, 1, 1);
-
+
if(self.team == NUM_TEAM_2)
RandomSelection_Add(ons_blue_generator, 0, string_null, 1, 1);
-
+
entity _cp = findchain(classname, "onslaught_controlpoint"):
while _cp;
{
- if(_cp.team == self.team)
+ if(_cp.team == self.team)
RandomSelection_Add(_cp, 0, string_null, 1, 1);
-
+
_cp = _cp.chain;
}
}
else if(self.team == spawn_spot.team)
spawn_score_x += SPAWN_PRIO_NEAR_TEAMMATE_SAMETEAM; // prefer same team, if we can't find a spawn near teammate
-
+
*/
-
+
return 0;
}
{
if(!autocvar_g_onslaught_spawn_at_controlpoints)
return 0;
-
+
if(random() < 0.5) // 50/50 chane to use default spawnsystem.
return 0;
-
+
float _close_to_home = ((random() > 0.5) ? TRUE : FALSE);
entity _best = world, _trg_gen = world;
float _score, _best_score = MAX_SHOT_DISTANCE;
-
+
RandomSelection_Init();
-
+
if(self.team == NUM_TEAM_1)
{
if(!_close_to_home)
_trg_gen = ons_blue_generator;
- else
- _trg_gen = ons_red_generator;
+ else
+ _trg_gen = ons_red_generator;
}
-
+
if(self.team == NUM_TEAM_2)
{
if(_close_to_home)
_trg_gen = ons_blue_generator;
- else
- _trg_gen = ons_red_generator;
+ else
+ _trg_gen = ons_red_generator;
}
-
+
entity _cp = findchain(classname, "onslaught_controlpoint");
while(_cp)
{
- if(_cp.team == self.team)
- {
+ if(_cp.team == self.team)
+ {
_score = vlen(_trg_gen.origin - _cp.origin);
if(_score < _best_score)
{
_best = _cp;
- _best_score = _score;
+ _best_score = _score;
}
- }
+ }
_cp = _cp.chain;
}
-
- vector _loc;
- float i;
+
+ vector _loc;
+ float i;
if(_best)
{
for(i = 0; i < 10; ++i)
{
_loc = _best.origin + '0 0 96';
- _loc += ('0 1 0' * random()) * 128;
+ _loc += ('0 1 0' * random()) * 128;
tracebox(_loc, PL_MIN, PL_MAX, _loc, MOVE_NORMAL, self);
if(trace_fraction == 1.0 && !trace_startsolid)
{
{
if(!autocvar_g_onslaught_spawn_at_generator)
return 0;
-
+
_trg_gen = ((self.team == NUM_TEAM_1) ? ons_red_generator : ons_blue_generator);
-
+
for(i = 0; i < 10; ++i)
{
_loc = _trg_gen.origin + '0 0 96';
- _loc += ('0 1 0' * random()) * 128;
+ _loc += ('0 1 0' * random()) * 128;
tracebox(_loc, PL_MIN, PL_MAX, _loc, MOVE_NORMAL, self);
if(trace_fraction == 1.0 && !trace_startsolid)
{
}
}
}
-
+
return 0;
}
+MUTATOR_HOOKFUNCTION(ons_MonsterThink)
+{
+ entity e = find(world, targetname, self.target);
+ if (e != world)
+ self.team = e.team;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(ons_MonsterSpawn)
+{
+ entity e, ee = world;
+
+ if(self.targetname)
+ {
+ e = find(world,target,self.targetname);
+ if(e != world)
+ {
+ self.team = e.team;
+ ee = e;
+ }
+ }
+
+ if(ee)
+ {
+ activator = ee;
+ self.use();
+ }
+
+ return FALSE;
+}
+
MUTATOR_DEFINITION(gamemode_onslaught)
{
MUTATOR_HOOK(BuildMutatorsPrettyString, ons_BuildMutatorsPrettyString, CBC_ORDER_ANY);
MUTATOR_HOOK(BuildMutatorsString, ons_BuildMutatorsString, CBC_ORDER_ANY);
MUTATOR_HOOK(PlayerSpawn, ons_PlayerSpawn, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MonsterMove, ons_MonsterThink, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MonsterSpawn, ons_MonsterSpawn, CBC_ORDER_ANY);
//MUTATOR_HOOK(Spawn_Score, ons_Spawn_Score, CBC_ORDER_ANY);
MUTATOR_ONADD
float velocity_difference;
float clean_up_and_do_nothing;
float horiz_speed = autocvar_sv_dodging_horiz_speed;
-
+
- if(self.freezetag_frozen)
+ if(self.frozen)
horiz_speed = autocvar_sv_dodging_horiz_speed_frozen;
if (self.deadflag != DEAD_NO)
// make sure v_up, v_right and v_forward are sane
makevectors(self.angles);
- // if we have e.g. 0.5 sec ramptime and a frametime of 0.25, then the ramp code
- // will be called ramp_time/frametime times = 2 times. so, we need to
+ // if we have e.g. 0.5 sec ramptime and a frametime of 0.25, then the ramp code
+ // will be called ramp_time/frametime times = 2 times. so, we need to
// add 0.5 * the total speed each frame until the dodge action is done..
common_factor = sys_frametime / autocvar_sv_dodging_ramp_time;
// if ramp time is smaller than frametime we get problems ;D
- if (common_factor > 1)
+ if (common_factor > 1)
common_factor = 1;
new_velocity_gain = self.dodging_velocity_gain - (common_factor * horiz_speed);
//disable jump key during dodge accel phase
if (self.movement_z > 0) self.movement_z = 0;
- self.velocity =
- self.velocity
+ self.velocity =
+ self.velocity
+ ((self.dodging_direction_y * velocity_difference) * v_right)
+ ((self.dodging_direction_x * velocity_difference) * v_forward);
if (self.dodging_single_action == 1) {
self.flags &= ~FL_ONGROUND;
- self.velocity =
- self.velocity
+ self.velocity =
+ self.velocity
+ (autocvar_sv_dodging_up_speed * v_up);
if (autocvar_sv_dodging_sound == 1)
tap_direction_x = 0;
tap_direction_y = 0;
-
+
float frozen_dodging;
- frozen_dodging = (self.freezetag_frozen && autocvar_sv_dodging_frozen);
+ frozen_dodging = (self.frozen && autocvar_sv_dodging_frozen);
float dodge_detected;
if (g_dodging == 0)
if ((time - self.last_dodging_time) < autocvar_sv_dodging_delay)
return 0;
- if (check_close_to_ground(autocvar_sv_dodging_height_threshold) != 1
+ if (check_close_to_ground(autocvar_sv_dodging_height_threshold) != 1
&& check_close_to_wall(autocvar_sv_dodging_wall_distance_threshold) != 1)
return 0;
if (self.movement_x > 0) {
// is this a state change?
if (!(self.pressedkeys & KEY_FORWARD) || frozen_dodging) {
- if ((time - self.last_FORWARD_KEY_time) < self.cvar_cl_dodging_timeout) {
+ if ((time - self.last_FORWARD_KEY_time) < self.cvar_cl_dodging_timeout) {
tap_direction_x = 1.0;
dodge_detected = 1;
}
// is this a state change?
if (!(self.pressedkeys & KEY_BACKWARD) || frozen_dodging) {
tap_direction_x = -1.0;
- if ((time - self.last_BACKWARD_KEY_time) < self.cvar_cl_dodging_timeout) {
+ if ((time - self.last_BACKWARD_KEY_time) < self.cvar_cl_dodging_timeout) {
dodge_detected = 1;
}
self.last_BACKWARD_KEY_time = time;
// is this a state change?
if (!(self.pressedkeys & KEY_RIGHT) || frozen_dodging) {
tap_direction_y = 1.0;
- if ((time - self.last_RIGHT_KEY_time) < self.cvar_cl_dodging_timeout) {
+ if ((time - self.last_RIGHT_KEY_time) < self.cvar_cl_dodging_timeout) {
dodge_detected = 1;
}
self.last_RIGHT_KEY_time = time;
// is this a state change?
if (!(self.pressedkeys & KEY_LEFT) || frozen_dodging) {
tap_direction_y = -1.0;
- if ((time - self.last_LEFT_KEY_time) < self.cvar_cl_dodging_timeout) {
+ if ((time - self.last_LEFT_KEY_time) < self.cvar_cl_dodging_timeout) {
dodge_detected = 1;
}
self.last_LEFT_KEY_time = time;
- void spawnfunc_item_minst_cells (void)
+ void spawnfunc_item_minst_cells (void)
{
if (!g_minstagib) { remove(self); return; }
if (!self.ammo_cells)
self.ammo_cells = autocvar_g_minstagib_ammo_drop;
-
+
StartItem ("models/items/a_cells.md3",
"misc/itempickup.wav", 45, 0,
"MinstaNex Ammo", IT_CELLS, 0, 0, generic_pickupevalfunc, 100);
entity head;
FOR_EACH_PLAYER(head)
minstagib_stop_countdown(head);
-
+
return FALSE;
}
+MUTATOR_HOOKFUNCTION(minstagib_MonsterLoot)
+{
+ other.monster_loot = spawnfunc_item_minst_cells;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(minstagib_MonsterSpawn)
+{
+ // always refill ammo
+ if(self.monsterid == MON_MAGE)
+ self.skin = 1;
+
+ return FALSE;
+}
+
MUTATOR_HOOKFUNCTION(minstagib_BotShouldAttack)
{
if(checkentity.items & IT_STRENGTH)
return TRUE;
-
+
return FALSE;
}
{
if(self.items & IT_INVINCIBLE)
self.stat_sv_maxspeed = self.stat_sv_maxspeed * autocvar_g_minstagib_speed_highspeed;
-
+
return FALSE;
}
{
damage_save = 0;
damage_take = frag_damage;
-
+
return FALSE;
}
{
if(autocvar_g_friendlyfire == 0 && SAME_TEAM(frag_target, frag_attacker) && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker))
frag_damage = 0;
-
+
if(IS_PLAYER(frag_target))
{
if ((frag_deathtype == DEATH_FALL) ||
{
frag_damage = 0;
}
-
+
if(IS_PLAYER(frag_attacker))
if(DEATH_ISWEAPON(frag_deathtype, WEP_MINSTANEX))
if(frag_target.armorvalue)
frag_target.hitsound += 1;
frag_attacker.hitsound += 1; // TODO change this to a future specific hitsound for armor hit
}
-
+
if(IS_PLAYER(frag_attacker))
if (DEATH_ISWEAPON(frag_deathtype, WEP_LASER))
{
}
}
}
-
+
if(IS_PLAYER(frag_attacker))
if(frag_mirrordamage > 0)
{
}
frag_mirrordamage = 0;
}
-
+
if(frag_target.items & IT_STRENGTH)
yoda = 1;
-
+
return FALSE;
}
MUTATOR_HOOKFUNCTION(minstagib_SetStartItems)
{
start_ammo_cells = cvar("g_minstagib_ammo_start");
-
+
start_health = 100;
start_armorvalue = 0;
start_weapons = WEPSET_MINSTANEX;
warmup_start_weapons = WEPSET_MINSTANEX;
start_items |= IT_UNLIMITED_SUPERWEAPONS;
-
+
return FALSE;
}
{
if(self.classname == "item_cells")
return TRUE; // no normal cells?
-
+
if(self.weapon == WEP_MINSTANEX && self.classname == "droppedweapon")
{
self.ammo_cells = autocvar_g_minstagib_ammo_drop;
return FALSE;
}
-
+
if(self.weapon == WEP_ROCKET_LAUNCHER || self.weapon == WEP_NEX)
{
entity e = spawn();
self = oldself;
return TRUE;
}
-
+
if(self.flags & FL_POWERUP)
return FALSE;
-
+
if(self.ammo_cells > autocvar_g_minstagib_ammo_drop && self.classname != "item_minst_cells")
self.ammo_cells = autocvar_g_minstagib_ammo_drop;
-
+
if(self.ammo_cells && !self.weapon)
return FALSE;
-
+
return TRUE;
}
MUTATOR_HOOKFUNCTION(minstagib_CustomizeWaypoint)
{
entity e = WaypointSprite_getviewentity(other);
-
+
// if you have the invisibility powerup, sprites ALWAYS are restricted to your team
// but only apply this to real players, not to spectators
if((self.owner.flags & FL_CLIENT) && (self.owner.items & IT_STRENGTH) && (e == other))
if(DIFF_TEAM(self.owner, e))
return TRUE;
-
+
return FALSE;
}
return MUT_ITEMTOUCH_CONTINUE;
}
-
+
if(self.max_health)
{
other.armorvalue = bound(other.armorvalue, 999, other.armorvalue + autocvar_g_minstagib_extralives);
Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_EXTRALIVES);
return MUT_ITEMTOUCH_PICKUP;
}
-
+
return MUT_ITEMTOUCH_CONTINUE;
}
if (!autocvar_g_powerups) { return FALSE; }
if (!(self.classname == "item_strength" || self.classname == "item_invincible" || self.classname == "item_health_mega"))
return FALSE;
-
+
entity e = spawn();
-
+
if(random() < 0.3)
e.think = spawnfunc_item_strength;
else if(random() < 0.6)
e.think = minstagib_health_mega;
else
e.think = spawnfunc_item_invincible;
-
+
e.nextthink = time + 0.1;
e.spawnflags = self.spawnflags;
e.noalign = self.noalign;
setorigin(e, self.origin);
-
+
return TRUE;
}
MUTATOR_DEFINITION(mutator_minstagib)
{
MUTATOR_HOOK(MatchEnd, minstagib_MatchEnd, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MonsterDropItem, minstagib_MonsterLoot, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MonsterSpawn, minstagib_MonsterSpawn, CBC_ORDER_ANY);
MUTATOR_HOOK(BotShouldAttack, minstagib_BotShouldAttack, CBC_ORDER_ANY);
MUTATOR_HOOK(PlayerPhysics, minstagib_PlayerPhysics, CBC_ORDER_ANY);
MUTATOR_HOOK(PlayerSpawn, minstagib_PlayerSpawn, CBC_ORDER_ANY);
MUTATOR_DECLARATION(gamemode_assault);
- MUTATOR_DECLARATION(gamemode_arena);
MUTATOR_DECLARATION(gamemode_ca);
MUTATOR_DECLARATION(gamemode_keyhunt);
MUTATOR_DECLARATION(gamemode_freezetag);
MUTATOR_DECLARATION(gamemode_onslaught);
MUTATOR_DECLARATION(gamemode_domination);
MUTATOR_DECLARATION(gamemode_lms);
+MUTATOR_DECLARATION(gamemode_invasion);
MUTATOR_DECLARATION(mutator_dodging);
MUTATOR_DECLARATION(mutator_invincibleprojectiles);
../common/net_notice.qh
../common/animdecide.qh
+../common/monsters/monsters.qh
+../common/monsters/sv_monsters.qh
+../common/monsters/spawn.qh
+
+
autocvars.qh
constants.qh
defs.qh // Should rename this, it has fields and globals
mutators/base.qh
mutators/mutators.qh
mutators/gamemode_assault.qh
- mutators/gamemode_arena.qh
mutators/gamemode_ca.qh
mutators/gamemode_ctf.qh
mutators/gamemode_domination.qh
mutators/gamemode_keepaway.qh
mutators/gamemode_nexball.qh
mutators/gamemode_lms.qh
+mutators/gamemode_invasion.qh
mutators/mutator_dodging.qh
mutators/mutator_nades.qh
../common/explosion_equation.qc
+../common/monsters/sv_monsters.qc
+../common/monsters/monsters.qc
+
+../common/monsters/spawn.qc
+
mutators/base.qc
mutators/gamemode_assault.qc
- mutators/gamemode_arena.qc
mutators/gamemode_ca.qc
mutators/gamemode_ctf.qc
mutators/gamemode_domination.qc
mutators/gamemode_nexball.qc
mutators/gamemode_onslaught.qc
mutators/gamemode_lms.qc
+mutators/gamemode_invasion.qc
mutators/mutator_invincibleproj.qc
mutators/mutator_new_toys.qc
mutators/mutator_nix.qc
{
entity oldself;
float dm;
-
+
oldself = self;
for(self = world; (self = findfloat(self, damagedbycontents, TRUE)); )
{
if (self.movetype == MOVETYPE_NOCLIP) { continue; }
-
+
float vehic = (self.vehicle_flags & VHF_ISVEHICLE);
float projectile = (self.flags & FL_PROJECTILE);
-
+ float monster = (self.flags & FL_MONSTER);
+
if (self.watertype <= CONTENT_WATER && self.waterlevel > 0) // workaround a retarded bug made by id software :P (yes, it's that old of a bug)
{
if (!(self.flags & FL_INWATER))
self.dmgtime = 0;
}
- if(!vehic && !projectile) // vehicles and projectiles don't drown
+ if(!vehic && !projectile && !monster) // vehicles, monsters and projectiles don't drown
{
if (self.waterlevel != WATERLEVEL_SUBMERGED)
{
}
}
}
-
+
if (self.dmgtime < time)
{
- self.dmgtime = time + autocvar_g_balance_contents_damagerate;
-
+ self.dmgtime = time + autocvar_g_balance_contents_damagerate;
+
if (projectile)
{
if (self.watertype == CONTENT_LAVA)
}
}
}
-
+
self.oldvelocity = self.velocity;
}
self = oldself;
.float gravity;
.vector colormod;
void ItemDraw()
- {
+ {
if(self.gravity)
- {
+ {
Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy);
- if(self.move_flags & FL_ONGROUND)
+ if(self.move_flags & FL_ONGROUND)
{ // For some reason move_avelocity gets set to '0 0 0' here ...
self.oldorigin = self.origin;
self.gravity = 0;
- if(autocvar_cl_animate_items)
- { // ... so reset it if animations are requested.
+ if(autocvar_cl_animate_items)
+ { // ... so reset it if animations are requested.
if(self.ItemStatus & ITS_ANIMATE1)
self.move_avelocity = '0 180 0';
-
+
if(self.ItemStatus & ITS_ANIMATE2)
self.move_avelocity = '0 -90 0';
}
}
}
else if (autocvar_cl_animate_items)
- {
+ {
if(self.ItemStatus & ITS_ANIMATE1)
{
self.angles += self.move_avelocity * frametime;
- setorigin(self, '0 0 10' + self.oldorigin + '0 0 8' * sin(time * 2));
- }
-
+ setorigin(self, '0 0 10' + self.oldorigin + '0 0 8' * sin(time * 2));
+ }
+
if(self.ItemStatus & ITS_ANIMATE2)
{
self.angles += self.move_avelocity * frametime;
- setorigin(self, '0 0 8' + self.oldorigin + '0 0 4' * sin(time * 3));
+ setorigin(self, '0 0 8' + self.oldorigin + '0 0 4' * sin(time * 3));
}
}
}
void ItemDrawSimple()
{
if(self.gravity)
- {
- Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy);
-
+ {
+ Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy);
+
if(self.move_flags & FL_ONGROUND)
self.gravity = 0;
}
setorigin(self, self.origin);
self.oldorigin = self.origin;
}
-
- if(sf & ISF_ANGLES)
+
+ if(sf & ISF_ANGLES)
{
self.angles_x = ReadCoord();
self.angles_y = ReadCoord();
- self.angles_z = ReadCoord();
+ self.angles_z = ReadCoord();
self.move_angles = self.angles;
}
-
+
if(sf & ISF_STATUS) // need to read/write status frist so model can handle simple, fb etc.
{
- self.ItemStatus = ReadByte();
-
+ self.ItemStatus = ReadByte();
+
if(self.ItemStatus & ITS_AVAILABLE)
{
self.alpha = 1;
}
else
self.alpha = -1;
- }
-
+ }
+
if(autocvar_cl_fullbright_items)
if(self.ItemStatus & ITS_ALLOWFB)
self.effects |= EF_FULLBRIGHT;
-
+
if(self.ItemStatus & ITS_STAYWEP)
{
self.colormod = self.glowmod = autocvar_cl_weapon_stay_color;
self.alpha = autocvar_cl_weapon_stay_alpha;
-
+
}
-
+
if(self.ItemStatus & ITS_POWERUP)
{
if(self.ItemStatus & ITS_AVAILABLE)
self.effects &= ~(EF_ADDITIVE | EF_FULLBRIGHT);
}
}
-
+
if(sf & ISF_MODEL)
{
self.drawmask = MASK_NORMAL;
self.movetype = MOVETYPE_NOCLIP;
self.draw = ItemDraw;
-
+
if(self.mdl)
strunzone(self.mdl);
-
+
self.mdl = "";
string _fn = ReadString();
-
+
if(autocvar_cl_simple_items && (self.ItemStatus & ITS_ALLOWSI))
{
string _fn2 = substring(_fn, 0 , strlen(_fn) -4);
self.draw = ItemDrawSimple;
-
-
-
+
+
+
if(fexists(sprintf("%s%s.md3", _fn2, autocvr_cl_simpleitems_postfix)))
self.mdl = strzone(sprintf("%s%s.md3", _fn2, autocvr_cl_simpleitems_postfix));
else if(fexists(sprintf("%s%s.dpm", _fn2, autocvr_cl_simpleitems_postfix)))
dprint("Simple item requested for ", _fn, " but no model exsist for it\n");
}
}
-
- if(self.draw != ItemDrawSimple)
- self.mdl = strzone(_fn);
-
-
+
+ if(self.draw != ItemDrawSimple)
+ self.mdl = strzone(_fn);
+
+
if(self.mdl == "")
dprint("^1WARNING!^7 self.mdl is unset for item ", self.classname, " tell tZork aboute this!\n");
-
+
precache_model(self.mdl);
setmodel(self, self.mdl);
}
-
+
if(sf & ISF_COLORMAP)
self.colormap = ReadShort();
-
+
if(sf & ISF_DROP)
{
self.gravity = 1;
self.move_velocity_z = ReadCoord();
self.velocity = self.move_velocity;
self.move_origin = self.oldorigin;
-
+
if(!self.move_time)
{
self.move_time = time;
else
self.move_time = max(self.move_time, time);
}
-
+
if(autocvar_cl_animate_items)
- {
+ {
if(self.ItemStatus & ITS_ANIMATE1)
self.move_avelocity = '0 180 0';
-
+
if(self.ItemStatus & ITS_ANIMATE2)
self.move_avelocity = '0 -90 0';
}
sf |= ISF_DROP;
else
sf &= ~ISF_DROP;
-
- WriteByte(MSG_ENTITY, ENT_CLIENT_ITEM);
+
+ WriteByte(MSG_ENTITY, ENT_CLIENT_ITEM);
WriteByte(MSG_ENTITY, sf);
//WriteByte(MSG_ENTITY, self.cnt);
WriteCoord(MSG_ENTITY, self.origin_y);
WriteCoord(MSG_ENTITY, self.origin_z);
}
-
+
if(sf & ISF_ANGLES)
{
WriteCoord(MSG_ENTITY, self.angles_x);
if(sf & ISF_MODEL)
{
-
+
if(self.mdl == "")
dprint("^1WARNING!^7 self.mdl is unset for item ", self.classname, "exspect a crash just aboute now\n");
-
+
WriteString(MSG_ENTITY, self.mdl);
}
-
-
+
+
if(sf & ISF_COLORMAP)
WriteShort(MSG_ENTITY, self.colormap);
WriteCoord(MSG_ENTITY, self.velocity_y);
WriteCoord(MSG_ENTITY, self.velocity_z);
}
-
+
return TRUE;
}
*/
void Item_Show (entity e, float mode)
- {
+ {
e.effects &= ~(EF_ADDITIVE | EF_STARDUST | EF_FULLBRIGHT | EF_NODEPTHTEST);
e.ItemStatus &= ~ITS_STAYWEP;
if (mode > 0)
e.spawnshieldtime = 1;
e.ItemStatus &= ~ITS_AVAILABLE;
}
-
+
if (e.items & IT_STRENGTH || e.items & IT_INVINCIBLE)
- e.ItemStatus |= ITS_POWERUP;
-
+ e.ItemStatus |= ITS_POWERUP;
+
if (autocvar_g_nodepthtestitems)
e.effects |= EF_NODEPTHTEST;
-
-
+
+
if (autocvar_g_fullbrightitems)
e.ItemStatus |= ITS_ALLOWFB;
-
+
if (autocvar_sv_simple_items)
e.ItemStatus |= ITS_ALLOWSI;
}
:skip
-
+
// always eat teamed entities
if(item.team)
pickedup = TRUE;
void Item_Touch (void)
{
entity e, head;
-
+
// remove the item if it's currnetly in a NODROP brush or hits a NOIMPACT surface (such as sky)
if(self.classname == "droppedweapon")
{
if (!IS_PLAYER(other))
return;
+ if (other.frozen)
+ return;
if (other.deadflag)
return;
if (self.solid != SOLID_TRIGGER)
return;
if (self.owner == other)
return;
+ if (time < self.item_spawnshieldtime)
+ return;
switch(MUTATOR_CALLHOOK(ItemTouch))
{
if(self.model == "")
self.model = itemmodel;
-
+
if(self.model == "")
{
error(strcat("^1Tried to spawn ", itemname, " with no model!\n"));
return;
}
-
+
if(self.item_pickupsound == "")
self.item_pickupsound = pickupsound;
-
+
if(!self.respawntime) // both need to be set
{
self.respawntime = defaultrespawntime;
if(weaponid)
self.weapons = WepSet_FromWeapon(weaponid);
-
+
self.flags = FL_ITEM | itemflags;
if(MUTATOR_CALLHOOK(FilterItem)) // error means we do not want the item
remove (self);
return;
}
-
+
if(self.angles != '0 0 0')
self.SendFlags |= ISF_ANGLES;
self.netname = itemname;
self.touch = Item_Touch;
setmodel(self, "null"); // precision set below
- //self.effects |= EF_LOWPRECISION;
-
+ //self.effects |= EF_LOWPRECISION;
+
if((itemflags & FL_POWERUP) || self.health || self.armorvalue)
{
self.pos1 = '-16 -16 0';
self.pos2 = '16 16 32';
}
setsize (self, self.pos1, self.pos2);
-
- if(itemflags & FL_POWERUP)
+
+ if(itemflags & FL_POWERUP)
self.ItemStatus |= ITS_ANIMATE1;
-
+
if(self.armorvalue || self.health)
self.ItemStatus |= ITS_ANIMATE2;
-
+
if(itemflags & FL_WEAPON)
{
if (self.classname != "droppedweapon") // if dropped, colormap is already set up nicely
self.colormap = 1024; // color shirt=0 pants=0 grey
else
self.gravity = 1;
-
+
self.ItemStatus |= ITS_ANIMATE1;
self.ItemStatus |= ISF_COLORMAP;
}
{
if(!self.cnt)
self.cnt = 1; // item probability weight
-
+
self.effects |= EF_NODRAW; // marker for item team search
InitializeEntity(self, Item_FindTeam, INITPRIO_FINDTARGET);
}
else
Item_Reset();
-
+
Net_LinkEntity(self, FALSE, 0, ItemSend);
// call this hook after everything else has been done
e.strength_finished = max(0, e.strength_finished - time);
e.invincible_finished = max(0, e.invincible_finished - time);
e.superweapons_finished = max(0, e.superweapons_finished - time);
-
+
PREGIVE(e, items);
PREGIVE_WEAPONS(e);
PREGIVE(e, strength_finished);
self = e;
activator = act;
- self.target_spawn_spawnfunc();
+ if(self.target_spawn_spawnfunc)
+ self.target_spawn_spawnfunc();
self = oldself;
activator = oldactivator;
++c; // increase count to not include MYSELF
for(e = world; (e = findfloat(e, target_spawn_id, self.target_spawn_id)); --c)
;
-
+
// if c now is 0, we have AT LEAST the given count (maybe more), so don't spawn any more
if(c == 0)
return 0;
MUTATOR_ADD(gamemode_lms);
}
- if(g_arena)
- {
- fraglimit_override = autocvar_g_arena_point_limit;
- leadlimit_override = autocvar_g_arena_point_leadlimit;
- MUTATOR_ADD(gamemode_arena);
- }
-
if(g_ca)
{
ActivateTeamplay();
have_team_spawns = -1; // request team spawns
MUTATOR_ADD(gamemode_nexball);
}
-
+
if(g_keepaway)
{
MUTATOR_ADD(gamemode_keepaway);
}
+
+ if(g_invasion)
+ {
+ timelimit_override = 0; // no timelimit in invasion, round based
+ fraglimit_override = autocvar_g_invasion_round_limit;
+ MUTATOR_ADD(gamemode_invasion);
+ }
if(teamplay)
entcs_init();
else
g_race_qualifying = 0;
}
+
+ if(g_invasion)
+ {
+ maxrounds = cvar("fraglimit");
+ cvar_set("fraglimit", "0");
+ }
if(g_race || g_cts)
{
cb -= cbb * 0.999;
}
}
-
+
// keep teams alive (teams of size 0 always count as smaller, ignoring score)
if(ca < 1)
if(cb >= 1)
GetTeamCounts(world);
RandomSelection_Init();
-
+
t = 1;
if(TeamSmallerEqThanTeam(2, t, pl))
t = 2;
#define cvar_base "g_turrets_unit_"
.float clientframe;
void turrets_setframe(float _frame, float client_only)
- {
+ {
if((client_only ? self.clientframe : self.frame ) != _frame)
{
self.SendFlags |= TNSF_ANIM;
self.anim_start_time = time;
}
-
+
if(client_only)
self.clientframe = _frame;
else
self.frame = _frame;
-
+
}
float turret_send(entity to, float sf)
{
-
- WriteByte(MSG_ENTITY, ENT_CLIENT_TURRET);
+
+ WriteByte(MSG_ENTITY, ENT_CLIENT_TURRET);
WriteByte(MSG_ENTITY, sf);
if(sf & TNSF_SETUP)
{
WriteByte(MSG_ENTITY, self.turret_type);
-
+
WriteCoord(MSG_ENTITY, self.origin_x);
WriteCoord(MSG_ENTITY, self.origin_y);
WriteCoord(MSG_ENTITY, self.origin_z);
-
+
WriteAngle(MSG_ENTITY, self.angles_x);
WriteAngle(MSG_ENTITY, self.angles_y);
}
-
+
if(sf & TNSF_ANG)
{
WriteShort(MSG_ENTITY, rint(self.tur_head.angles_x));
WriteShort(MSG_ENTITY, rint(self.tur_head.angles_y));
}
-
+
if(sf & TNSF_AVEL)
- {
+ {
WriteShort(MSG_ENTITY, rint(self.tur_head.avelocity_x));
WriteShort(MSG_ENTITY, rint(self.tur_head.avelocity_y));
}
-
+
if(sf & TNSF_MOVE)
{
WriteShort(MSG_ENTITY, rint(self.origin_x));
WriteShort(MSG_ENTITY, rint(self.velocity_x));
WriteShort(MSG_ENTITY, rint(self.velocity_y));
- WriteShort(MSG_ENTITY, rint(self.velocity_z));
-
- WriteShort(MSG_ENTITY, rint(self.angles_y));
+ WriteShort(MSG_ENTITY, rint(self.velocity_z));
+
+ WriteShort(MSG_ENTITY, rint(self.angles_y));
}
-
+
if(sf & TNSF_ANIM)
{
WriteCoord(MSG_ENTITY, self.anim_start_time);
WriteByte(MSG_ENTITY, self.frame);
}
-
+
if(sf & TNSF_STATUS)
{
WriteByte(MSG_ENTITY, self.team);
-
+
if(self.health <= 0)
WriteByte(MSG_ENTITY, 0);
else
WriteByte(MSG_ENTITY, ceil((self.health / self.tur_health) * 255));
}
-
+
return TRUE;
}
void turret_projectile_explode()
{
-
+
self.takedamage = DAMAGE_NO;
- self.event_damage = func_null;
+ self.event_damage = func_null;
#ifdef TURRET_DEBUG
float d;
d = RadiusDamage (self, self.owner, self.owner.shot_dmg, 0, self.owner.shot_radius, self, self.owner.shot_force, self.totalfrags, world);
proj.owner = self;
proj.realowner = self;
proj.bot_dodge = TRUE;
- proj.bot_dodgerating = self.shot_dmg;
+ proj.bot_dodgerating = self.shot_dmg;
proj.think = turret_projectile_explode;
proj.touch = turret_projectile_touch;
- proj.nextthink = time + 9;
+ proj.nextthink = time + 9;
proj.movetype = MOVETYPE_FLYMISSILE;
- proj.velocity = normalize(self.tur_shotdir_updated + randomvec() * self.shot_spread) * self.shot_speed;
+ proj.velocity = normalize(self.tur_shotdir_updated + randomvec() * self.shot_spread) * self.shot_speed;
proj.flags = FL_PROJECTILE;
proj.enemy = self.enemy;
proj.totalfrags = _death;
proj.flags |= FL_NOTARGET;
CSQCProjectile(proj, _cli_anim, _proj_type, _cull);
-
+
return proj;
}
}
else*/
tracebox(self.tur_shotorg, '-1 -1 -1','1 1 1', self.tur_shotorg + (self.tur_shotdir_updated * self.tur_dist_aimpos), MOVE_NORMAL,self);
-
- self.tur_dist_impact_to_aimpos = vlen(trace_endpos - self.tur_aimpos) - (vlen(self.enemy.maxs - self.enemy.mins) * 0.5);
+
+ self.tur_dist_impact_to_aimpos = vlen(trace_endpos - self.tur_aimpos) - (vlen(self.enemy.maxs - self.enemy.mins) * 0.5);
self.tur_impactent = trace_ent;
self.tur_impacttime = vlen(self.tur_shotorg - trace_endpos) / self.shot_speed;
vector v1, v2;
v1 = self.tur_head.angles;
v2 = self.tur_head.avelocity;
-
+
if (self.track_flags == TFL_TRACK_NO)
return;
}
else
{
- target_angle = vectoangles(normalize(self.tur_aimpos - self.tur_shotorg));
+ target_angle = vectoangles(normalize(self.tur_aimpos - self.tur_shotorg));
}
-
+
self.tur_head.angles_x = anglemods(self.tur_head.angles_x);
self.tur_head.angles_y = anglemods(self.tur_head.angles_y);
// Find the diffrence between where we currently aim and where we want to aim
//move_angle = target_angle - (self.angles + self.tur_head.angles);
//move_angle = shortangle_vxy(move_angle,(self.angles + self.tur_head.angles));
-
- move_angle = AnglesTransform_ToAngles(AnglesTransform_LeftDivide(AnglesTransform_FromAngles(self.angles), AnglesTransform_FromAngles(target_angle))) - self.tur_head.angles;
+
+ move_angle = AnglesTransform_ToAngles(AnglesTransform_LeftDivide(AnglesTransform_FromAngles(self.angles), AnglesTransform_FromAngles(target_angle))) - self.tur_head.angles;
move_angle = shortangle_vxy(move_angle, self.tur_head.angles);
switch(self.track_type)
if(self.tur_head.angles_y < -self.aim_maxrot)
self.tur_head.angles_y = self.aim_maxrot;
}
-
+
// CSQC
self.SendFlags |= TNSF_ANG;
-
+
return;
case TFL_TRACKTYPE_FLUIDINERTIA:
{
self.tur_head.avelocity_x = 0;
self.tur_head.angles_x = self.aim_maxpitch;
-
+
self.SendFlags |= TNSF_ANG;
}
-
+
if((self.tur_head.angles_x + self.tur_head.avelocity_x * self.ticrate) < -self.aim_maxpitch)
{
self.tur_head.avelocity_x = 0;
self.tur_head.angles_x = -self.aim_maxpitch;
-
+
self.SendFlags |= TNSF_ANG;
}
}
{
self.tur_head.avelocity_y = 0;
self.tur_head.angles_y = self.aim_maxrot;
-
+
self.SendFlags |= TNSF_ANG;
}
{
self.tur_head.avelocity_y = 0;
self.tur_head.angles_y = -self.aim_maxrot;
-
+
self.SendFlags |= TNSF_ANG;
}
}
-
+
self.SendFlags |= TNSF_AVEL;
-
+
// Force a angle update every 10'th frame
self.turret_framecounter += 1;
if(self.turret_framecounter >= 10)
- {
+ {
self.SendFlags |= TNSF_ANG;
self.turret_framecounter = 0;
- }
+ }
}
float turret_stdproc_firecheck()
{
// This one just dont care =)
- if (self.firecheck_flags & TFL_FIRECHECK_NO)
+ if (self.firecheck_flags & TFL_FIRECHECK_NO)
return 1;
if (self.enemy == world)
if (self.shoot_flags & TFL_SHOOT_VOLLYALWAYS)
if (self.volly_counter != self.shot_volly)
if(self.ammo >= self.shot_dmg)
- return 1;
+ return 1;
// Lack of zombies makes shooting dead things unnecessary :P
if (self.firecheck_flags & TFL_FIRECHECK_DEAD)
if (self.firecheck_flags & TFL_FIRECHECK_OTHER_AMMO)
if (self.enemy.ammo >= self.enemy.ammo_max)
return 0;
-
+
// Target of opertunity?
if(turret_validate_target(self, self.tur_impactent, self.target_validate_flags) > 0)
{
self.enemy = self.tur_impactent;
return 1;
- }
+ }
if (self.firecheck_flags & TFL_FIRECHECK_DISTANCES)
{
// To close?
if (self.tur_dist_aimpos < self.target_range_min)
- if(turret_validate_target(self, self.tur_impactent, self.target_validate_flags) > 0)
+ if(turret_validate_target(self, self.tur_impactent, self.target_validate_flags) > 0)
return 1; // Target of opertunity?
- else
- return 0;
+ else
+ return 0;
}
// Try to avoid FF?
float turret_validate_target(entity e_turret, entity e_target, float validate_flags)
{
vector v_tmp;
-
+
//if(!validate_flags & TFL_TARGETSELECT_NOBUILTIN)
// return -0.5;
return -0.5;
if (!checkpvs(e_target.origin, e_turret))
- return -1;
+ return -1;
if (!e_target)
return -2;
return -5;
// Cant touch this
- if(e_target.vehicle_flags & VHF_ISVEHICLE)
+ if(e_target.vehicle_flags & VHF_ISVEHICLE)
{
if (e_target.vehicle_health <= 0)
return -6;
e = findradius(self.origin, self.target_range);
// Nothing to aim at?
- if (!e)
+ if (!e)
return world;
while (e)
entity e;
self.nextthink = time + self.ticrate;
-
+
// ONS uses somewhat backwards linking.
if (teamplay)
{
if (!(self.spawnflags & TSF_NO_AMMO_REGEN))
if (self.ammo < self.ammo_max)
self.ammo = min(self.ammo + self.ammo_recharge, self.ammo_max);
-
+
// Inactive turrets needs to run the think loop,
// So they can handle animation and wake up if need be.
if (!self.active)
// Are turrets allowed?
if (autocvar_g_turrets == 0)
return 0;
-
+
if(_turret_type < 1 || _turret_type > TID_LAST)
{
dprint("Invalid / Unkown turret type\"", ftos(_turret_type), "\", aborting!\n");
return 0;
- }
+ }
self.turret_type = _turret_type;
-
+
e = find(world, classname, "turret_manager");
if (!e)
{
e.think = turrets_manager_think;
e.nextthink = time + 2;
}
-
+
if (!(self.spawnflags & TSF_SUSPENDED))
builtin_droptofloor(); // why can't we use regular droptofloor here?
load_unit_settings(self, self.cvar_basename, 0);
self.effects = EF_NODRAW;
-
+
// Handle turret teams.
if (!teamplay)
self.team = MAX_SHOT_DISTANCE; // Group all turrets into the same team, so they dont kill eachother.
* as possible beforehand.
*/
if (!self.ticrate)
- {
+ {
if (self.turrcaps_flags & TFL_TURRCAPS_SUPPORT)
self.ticrate = 0.2; // Support units generaly dont need to have a high speed ai-loop
else
self.ticrate = 0.1; // 10 fps for normal turrets
}
-
+
self.ticrate = bound(sys_frametime, self.ticrate, 60); // keep it sane
// General stuff
if (!self.health)
self.health = 1000;
self.tur_health = max(1, self.health);
+ self.bot_attack = TRUE;
+ self.monster_attack = TRUE;
if (!self.turrcaps_flags)
self.turrcaps_flags = TFL_TURRCAPS_RADIUSDMG | TFL_TURRCAPS_MEDPROJ | TFL_TURRCAPS_PLAYERKILL;
// Offsets & origins
if (!self.tur_shotorg) self.tur_shotorg = '50 0 50';
-
+
if (!self.health)
self.health = 150;
self.turret_firecheckfunc = turret_stdproc_firecheck;
self.turret_firefunc = turret_stdproc_fire;
self.event_damage = turret_stdproc_damage;
-
+
if (self.turrcaps_flags & TFL_TURRCAPS_SUPPORT)
self.turret_score_target = turret_stdproc_targetscore_support;
else
self.turret_score_target = turret_stdproc_targetscore_generic;
self.use = turret_stdproc_use;
- self.bot_attack = TRUE;
++turret_count;
self.nextthink = time + 1;
activator = ee;
self.use();
}
-
+
turret_link();
- turret_stdproc_respawn();
+ turret_stdproc_respawn();
turret_tag_fire_update();
-
+
return 1;
}
{
if (!IS_REAL_CLIENT(own))
return;
-
+
msg_entity = own;
WriteByte(MSG_ONE, SVC_TEMPENTITY);
WriteByte(MSG_ONE, TE_CSQC_VEHICLESETUP);
if(vehicle_id != 0)
- WriteByte(MSG_ONE, vehicle_id);
+ WriteByte(MSG_ONE, vehicle_id);
else
WriteByte(MSG_ONE, 1 + own.vehicle.vehicle_weapon2mode + HUD_VEHICLE_LAST);
}
setorigin(self, self.pos1 + '0 0 0');
// Show it
pointparticles(particleeffectnum("teleport"), self.origin + '0 0 64', '0 0 0', 1);
-
+
if(self.vehicle_controller)
self.team = self.vehicle_controller.team;
-
+
vehicles_reset_colors();
self.vehicle_spawn(VHSF_NORMAL);
}
{
if(MUTATOR_CALLHOOK(VehicleTouch))
return;
-
+
// Vehicle currently in use
if(self.owner)
{
if(other.deadflag != DEAD_NO)
return;
+
+ if(other.frozen)
+ return;
if(other.vehicle != world)
return;
void vehicles_enter()
{
// Remove this when bots know how to use vehicles
-
- if (IS_BOT_CLIENT(other))
+
+ if (IS_BOT_CLIENT(other))
if (autocvar_g_vehicles_allow_bots)
dprint("Bot enters vehicle\n"); // This is where we need to disconnect (some, all?) normal bot AI and hand over to vehicle's _aiframe()
else
if(self.phase > time)
return;
+
+ if(other.frozen)
+ return;
if(teamplay)
if(self.team)
self.team = self.owner.team;
self.flags -= FL_NOTARGET;
-
+ self.monster_attack = TRUE;
+
if (IS_REAL_CLIENT(other))
{
msg_entity = other;
WriteByte (MSG_ONE, SVC_SETVIEWPORT);
WriteEntity(MSG_ONE, self.vehicle_viewport);
-
+
WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
if(self.tur_head)
{
vehicles_clearrturn();
CSQCVehicleSetup(self.owner, self.hud);
-
+
vh_player = other;
vh_vehicle = self;
MUTATOR_CALLHOOK(VehicleEnter);
entity _vehicle;
entity _player;
entity _oldself = self;
-
+
if(vehicles_exit_running)
{
dprint("^1vehicles_exit allready running! this is not good..\n");
return;
}
-
+
vehicles_exit_running = TRUE;
if(IS_CLIENT(self))
{
_vehicle = self.vehicle;
-
+
if (_vehicle.vehicle_flags & VHF_PLAYERSLOT)
{
_vehicle.vehicle_exit(eject);
self = _oldself;
vehicles_exit_running = FALSE;
- return;
+ return;
}
}
else
_vehicle = self;
-
+
_player = _vehicle.owner;
-
+
self = _vehicle;
if (_player)
WriteAngle(MSG_ONE, _vehicle.angles_y);
WriteAngle(MSG_ONE, 0);
}
-
+
setsize(_player, PL_MIN,PL_MAX);
_player.takedamage = DAMAGE_AIM;
CSQCVehicleSetup(_player, HUD_NORMAL);
}
_vehicle.flags |= FL_NOTARGET;
-
+
if(_vehicle.deadflag == DEAD_NO)
_vehicle.avelocity = '0 0 0';
-
+
_vehicle.tur_head.nodrawtoclient = world;
-
+
if(!teamplay)
_vehicle.team = 0;
_vehicle = vh_vehicle;
_vehicle.team = _vehicle.tur_head.team;
-
+
sound (_vehicle, CH_TRIGGER_SINGLE, "misc/null.wav", 1, ATTEN_NORM);
- _vehicle.vehicle_hudmodel.viewmodelforclient = _vehicle;
+ _vehicle.vehicle_hudmodel.viewmodelforclient = _vehicle;
_vehicle.phase = time + 1;
-
+ _vehicle.monster_attack = FALSE;
+
_vehicle.vehicle_exit(eject);
-
+
vehicles_setreturn();
- vehicles_reset_colors();
+ vehicles_reset_colors();
_vehicle.owner = world;
self = _oldself;
-
+
vehicles_exit_running = FALSE;
}
{
if(_healthscale)
regen = regen * (self.vehicle_health / self.tur_health);
-
+
self.regen_field = min(self.regen_field + regen * delta_time, field_max);
if(self.owner)
void vehicles_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
{
self.dmg_time = time;
-
+
if(DEATH_ISWEAPON(deathtype, WEP_NEX))
damage *= autocvar_g_vehicles_nex_damagerate;
-
+
if(DEATH_ISWEAPON(deathtype, WEP_UZI))
damage *= autocvar_g_vehicles_uzi_damagerate;
-
+
if(DEATH_ISWEAPON(deathtype, WEP_RIFLE))
damage *= autocvar_g_vehicles_rifle_damagerate;
-
+
if(DEATH_ISWEAPON(deathtype, WEP_MINSTANEX))
damage *= autocvar_g_vehicles_minstanex_damagerate;
if(DEATH_ISWEAPON(deathtype, WEP_SEEKER))
damage *= autocvar_g_vehicles_tag_damagerate;
-
+
self.enemy = attacker;
-
+
if((self.vehicle_flags & VHF_HASSHIELD) && (self.vehicle_shield > 0))
{
if (wasfreed(self.vehicle_shieldent) || self.vehicle_shieldent == world)
if(sound_allowed(MSG_BROADCAST, attacker))
spamsound (self, CH_PAIN, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER
}
-
+
if(self.damageforcescale < 1 && self.damageforcescale > 0)
self.velocity += force * self.damageforcescale;
else
self.active = ACTIVE_NOT;
else
self.active = ACTIVE_ACTIVE;
-
+
if(self.active == ACTIVE_ACTIVE && self.deadflag == DEAD_NO)
{
dprint("^3Eat shit yall!\n");
}
else if(self.active == ACTIVE_NOT && self.deadflag != DEAD_NO)
{
-
+
}
}
- float vehicle_addplayerslot( entity _owner,
- entity _slot,
- float _hud,
+ float vehicle_addplayerslot( entity _owner,
+ entity _slot,
+ float _hud,
string _hud_model,
- float() _framefunc,
+ float() _framefunc,
void(float) _exitfunc)
{
if (!(_owner.vehicle_flags & VHF_MULTISLOT))
_slot.vehicle_hudmodel = spawn();
_slot.vehicle_hudmodel.viewmodelforclient = _slot;
_slot.vehicle_viewport.effects = (EF_ADDITIVE | EF_DOUBLESIDED | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NOGUNBOB | EF_NOSHADOW | EF_LOWPRECISION | EF_SELECTABLE | EF_TELEPORT_BIT);
-
+
setmodel(_slot.vehicle_hudmodel, _hud_model);
setmodel(_slot.vehicle_viewport, "null");
-
+
setattachment(_slot.vehicle_hudmodel, _slot, "");
setattachment(_slot.vehicle_viewport, _slot.vehicle_hudmodel, "");
-
+
return TRUE;
}
{
if(!autocvar_g_vehicles)
return FALSE;
-
+
if(self.targetname)
{
self.vehicle_controller = find(world, target, self.targetname);
}
else
{
- self.team = self.vehicle_controller.team;
+ self.team = self.vehicle_controller.team;
self.use = vehicle_use;
-
+
if(teamplay)
{
if(self.vehicle_controller.team == 0)
self.active = ACTIVE_NOT;
else
- self.active = ACTIVE_ACTIVE;
+ self.active = ACTIVE_ACTIVE;
}
}
}
-
+
precache_sound("onslaught/ons_hit2.wav");
precache_sound("onslaught/electricity_explode.wav");
tracebox(self.origin + '0 0 100', min_s, max_s, self.origin - '0 0 10000', MOVE_WORLDONLY, self);
setorigin(self, trace_endpos);
}
-
+
self.pos1 = self.origin;
self.pos2 = self.angles;
self.tur_head.team = self.team;
-
+
if(MUTATOR_CALLHOOK(VehicleSpawn))
return FALSE;
return TRUE;
}
- vector vehicle_aimturret(entity _vehic, vector _target, entity _turrret, string _tagname,
- float _pichlimit_min, float _pichlimit_max,
+ vector vehicle_aimturret(entity _vehic, vector _target, entity _turrret, string _tagname,
+ float _pichlimit_min, float _pichlimit_max,
float _rotlimit_min, float _rotlimit_max, float _aimspeed)
{
vector vtmp, vtag;
ftmp = _aimspeed * frametime;
vtmp_y = bound(-ftmp, vtmp_y, ftmp);
vtmp_x = bound(-ftmp, vtmp_x, ftmp);
- _turrret.angles_y = bound(_rotlimit_min, _turrret.angles_y + vtmp_y, _rotlimit_max);
+ _turrret.angles_y = bound(_rotlimit_min, _turrret.angles_y + vtmp_y, _rotlimit_max);
_turrret.angles_x = bound(_pichlimit_min, _turrret.angles_x + vtmp_x, _pichlimit_max);
return vtag;
}
_gib.movetype = MOVETYPE_TOSS;
_gib.solid = SOLID_CORPSE;
_gib.colormod = '-0.5 -0.5 -0.5';
- _gib.effects = EF_LOWPRECISION;
+ _gib.effects = EF_LOWPRECISION;
_gib.avelocity = _rot;
-
+
if(_burn)
_gib.effects |= EF_FLAME;
-
+
if(_explode)
{
- _gib.think = vehicles_gib_explode;
+ _gib.think = vehicles_gib_explode;
_gib.nextthink = time + random() * _explode;
_gib.touch = vehicles_gib_explode;
}
else
{
_gib.cnt = time + _maxtime;
- _gib.think = vehicles_gib_think;
- _gib.nextthink = time + _maxtime - 1;
+ _gib.think = vehicles_gib_think;
+ _gib.nextthink = time + _maxtime - 1;
_gib.alpha = 1;
}
return _gib;
// note: combos are usually triggered by W_Plasma_TriggerCombo, not damage
float is_combo = (inflictor.classname == "plasma_chain" || inflictor.classname == "plasma_prim");
-
+
if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_combo ? 1 : -1)))
- return; // g_projectiles_damage says to halt
-
+ return; // g_projectiles_damage says to halt
+
self.health = self.health - damage;
if (self.health <= 0)
{
return;
}
- if (owner_player.weaponentity.state != WS_INUSE || !lgbeam_checkammo() || owner_player.deadflag != DEAD_NO || !owner_player.BUTTON_ATCK || owner_player.freezetag_frozen)
+ if (owner_player.weaponentity.state != WS_INUSE || !lgbeam_checkammo() || owner_player.deadflag != DEAD_NO || !owner_player.BUTTON_ATCK || owner_player.frozen)
{
if(self == owner_player.lgbeam)
owner_player.lgbeam = world;
W_DecreaseAmmo(ammo_shells, ammoamount, autocvar_g_balance_shotgun_reload_ammo);
- W_SetupShot (self, autocvar_g_antilag_bullets && bulletspeed >= autocvar_g_antilag_bullets, 5, "weapons/shotgun_fire.wav", CH_WEAPON_A, d * bullets);
+ W_SetupShot (self, TRUE, 5, "weapons/shotgun_fire.wav", CH_WEAPON_A, d * bullets);
for (sc = 0;sc < bullets;sc = sc + 1)
- fireBallisticBullet(w_shotorg, w_shotdir, spread, bulletspeed, 5, d, f, WEP_SHOTGUN, 0, 1, bulletconstant);
+ fireBallisticBullet(w_shotorg, w_shotdir, spread, bulletspeed, 5, d, f, WEP_SHOTGUN, 0, bulletconstant);
endFireBallisticBullet();
pointparticles(particleeffectnum("shotgun_muzzleflash"), w_shotorg, w_shotdir * 1000, autocvar_g_balance_shotgun_primary_ammo);
void shotgun_meleethink (void)
{
// declarations
- float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
+ float i, f, swing, swing_factor, swing_damage, meleetime, is_player, is_monster;
entity target_victim;
vector targpos;
if(!self.cnt) // set start time of melee
{
- self.cnt = time;
+ self.cnt = time;
W_PlayStrengthSound(self.realowner);
}
makevectors(self.realowner.v_angle); // update values for v_* vectors
-
+
// calculate swing percentage based on time
meleetime = autocvar_g_balance_shotgun_secondary_melee_time * W_WeaponRateFactor();
swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10);
f = ((1 - swing) * autocvar_g_balance_shotgun_secondary_melee_traces);
-
+
// check to see if we can still continue, otherwise give up now
if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_shotgun_secondary_melee_no_doubleslap)
{
remove(self);
return;
}
-
- // if okay, perform the traces needed for this frame
+
+ // if okay, perform the traces needed for this frame
for(i=self.swing_prev; i < f; ++i)
{
swing_factor = ((1 - (i / autocvar_g_balance_shotgun_secondary_melee_traces)) * 2 - 1);
-
- targpos = (self.realowner.origin + self.realowner.view_ofs
+
+ targpos = (self.realowner.origin + self.realowner.view_ofs
+ (v_forward * autocvar_g_balance_shotgun_secondary_melee_range)
+ (v_up * swing_factor * autocvar_g_balance_shotgun_secondary_melee_swing_up)
+ (v_right * swing_factor * autocvar_g_balance_shotgun_secondary_melee_swing_side));
WarpZone_traceline_antilag(self, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self, ANTILAG_LATENCY(self.realowner));
-
+
// draw lightning beams for debugging
- //te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5);
+ //te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5);
//te_customflash(targpos, 40, 2, '1 1 1');
-
+
is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body");
+ is_monster = (trace_ent.flags & FL_MONSTER);
if((trace_fraction < 1) // if trace is good, apply the damage and remove self
- && (trace_ent.takedamage == DAMAGE_AIM)
+ && (trace_ent.takedamage == DAMAGE_AIM)
&& (trace_ent != self.swing_alreadyhit)
- && (is_player || autocvar_g_balance_shotgun_secondary_melee_nonplayerdamage))
+ && ((is_player || is_monster) || autocvar_g_balance_shotgun_secondary_melee_nonplayerdamage))
{
target_victim = trace_ent; // so it persists through other calls
-
+
- if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught.
+ if(is_player || is_monster) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught.
swing_damage = (autocvar_g_balance_shotgun_secondary_damage * min(1, swing_factor + 1));
else
swing_damage = (autocvar_g_balance_shotgun_secondary_melee_nonplayerdamage * min(1, swing_factor + 1));
-
+
//print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n"));
-
- Damage(target_victim, self.realowner, self.realowner,
- swing_damage, WEP_SHOTGUN | HITTYPE_SECONDARY,
- self.realowner.origin + self.realowner.view_ofs,
+
+ Damage(target_victim, self.realowner, self.realowner,
+ swing_damage, WEP_SHOTGUN | HITTYPE_SECONDARY,
+ self.realowner.origin + self.realowner.view_ofs,
v_forward * autocvar_g_balance_shotgun_secondary_force);
-
+
if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_SHOTGUN, 0, swing_damage); }
-
+
// draw large red flash for debugging
//te_customflash(targpos, 200, 2, '15 0 0');
-
+
if(autocvar_g_balance_shotgun_secondary_melee_multihit) // allow multiple hits with one swing, but not against the same player twice.
{
self.swing_alreadyhit = target_victim;
}
}
}
-
+
if(time >= self.cnt + meleetime)
{
// melee is finished
}
else
{
- // set up next frame
+ // set up next frame
self.swing_prev = i;
self.nextthink = time;
}
if(vlen(self.origin-self.enemy.origin) <= autocvar_g_balance_shotgun_secondary_melee_range)
self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE);
else
- {
- if(autocvar_g_antilag_bullets)
- self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE);
- else
- self.BUTTON_ATCK = bot_aim(autocvar_g_balance_shotgun_primary_speed, 0, 0.001, FALSE);
- }
+ self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE);
else if (req == WR_THINK)
{