//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))
{
- 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(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);
+ }
}
if(autocvar_r_letterbox == 0)
const float STAT_SECRETS_FOUND = 71;
const float STAT_RESPAWN_TIME = 72;
+ const float STAT_ROUNDSTARTTIME = 73;
+const float STAT_CURRENT_WAVE = 73;
+const float STAT_TOTALWAVES = 74;
+
+const float STAT_MONSTERS_TOTAL = 75;
+const float STAT_MONSTERS_KILLED = 76;
+
// mod stats (1xx)
const float STAT_REDALIVE = 100;
const float STAT_BLUEALIVE = 101;
MSG_MULTI_NOTIF(1, ITEM_WEAPON_NOAMMO, NO_MSG, INFO_ITEM_WEAPON_NOAMMO, CENTER_ITEM_WEAPON_NOAMMO) \
MSG_MULTI_NOTIF(1, ITEM_WEAPON_PRIMORSEC, NO_MSG, INFO_ITEM_WEAPON_PRIMORSEC, CENTER_ITEM_WEAPON_PRIMORSEC) \
MSG_MULTI_NOTIF(1, ITEM_WEAPON_UNAVAILABLE, NO_MSG, INFO_ITEM_WEAPON_UNAVAILABLE, CENTER_ITEM_WEAPON_UNAVAILABLE) \
- MSG_MULTI_NOTIF(1, MULTI_ARENA_BEGIN, ANNCE_BEGIN, NO_MSG, CENTER_ARENA_BEGIN) \
MSG_MULTI_NOTIF(1, MULTI_COUNTDOWN_BEGIN, ANNCE_BEGIN, NO_MSG, CENTER_COUNTDOWN_BEGIN) \
MSG_MULTI_NOTIF(1, MULTI_MINSTA_FINDAMMO, ANNCE_NUM_10, NO_MSG, CENTER_MINSTA_FINDAMMO_FIRST) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_AIM_REMOVE, NO_MSG, INFO_TD_AIM_REMOVE, CENTER_TD_AIM_REMOVE) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_AIM_REPAIR, NO_MSG, INFO_TD_AIM_REPAIR, CENTER_TD_AIM_REPAIR) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_AIM_UPGRADE, NO_MSG, INFO_TD_AIM_UPGRADE, CENTER_TD_AIM_UPGRADE) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_CANTSPAWN, NO_MSG, INFO_TD_CANTSPAWN, CENTER_TD_CANTSPAWN) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_DISABLED, NO_MSG, INFO_TD_DISABLED, CENTER_TD_DISABLED) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_GENDESTROYED, NO_MSG, INFO_TD_GENDESTROYED, CENTER_TD_GENDESTROYED) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_LIST, NO_MSG, INFO_TD_LIST, CENTER_TD_LIST) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_MAXHEALTH, NO_MSG, INFO_TD_MAXHEALTH, CENTER_TD_MAXHEALTH) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_MAXPOWER, NO_MSG, INFO_TD_MAXPOWER, CENTER_TD_MAXPOWER) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_MAXTURRETS, NO_MSG, INFO_TD_MAXTURRETS, CENTER_TD_MAXTURRETS) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_NOFUEL, NO_MSG, INFO_TD_NOFUEL, CENTER_TD_NOFUEL) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_NOFUEL_REPAIR, NO_MSG, INFO_TD_NOFUEL_REPAIR, CENTER_TD_NOFUEL_REPAIR) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_NOFUEL_UPGRADE, NO_MSG, INFO_TD_NOFUEL_UPGRADE, CENTER_TD_NOFUEL_UPGRADE) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_PHASE_BUILD, NO_MSG, INFO_TD_PHASE_BUILD, CENTER_TD_PHASE_BUILD) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_PHASE_COMBAT, NO_MSG, INFO_TD_PHASE_COMBAT, CENTER_TD_PHASE_COMBAT) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_REMOVE, NO_MSG, INFO_TD_REMOVE, CENTER_TD_REMOVE) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_REPAIR, NO_MSG, INFO_TD_REPAIR, CENTER_TD_REPAIR) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_SPAWN, NO_MSG, INFO_TD_SPAWN, CENTER_TD_SPAWN) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_UPGRADE, NO_MSG, INFO_TD_UPGRADE, CENTER_TD_UPGRADE) \
+ MSG_MULTI_NOTIF(1, MULTI_TD_VICTORY, NO_MSG, INFO_TD_VICTORY, CENTER_TD_VICTORY) \
MSG_MULTI_NOTIF(1, WEAPON_ACCORDEON_MURDER, NO_MSG, INFO_WEAPON_ACCORDEON_MURDER, NO_MSG) \
MSG_MULTI_NOTIF(1, WEAPON_ACCORDEON_SUICIDE, NO_MSG, INFO_WEAPON_ACCORDEON_SUICIDE, CENTER_DEATH_SELF_GENERIC) \
MSG_MULTI_NOTIF(1, WEAPON_CRYLINK_MURDER, NO_MSG, INFO_WEAPON_CRYLINK_MURDER, NO_MSG) \
void ClientKill (void)
{
- if (gameover)
- return;
+ if(gameover) return;
+ if(self.player_blocked) return;
- if(self.freezetag_frozen) return;
++ if(self.frozen) return;
- if((g_arena || g_ca) && ((champion && champion.classname == "player" && player_count > 1) || player_count == 1)) // don't allow a kill in this case either
- {
- // do nothing
- }
- else if(self.frozen == 1)
- {
- // do nothing
- }
- else
- ClientKill_TeamChange(0);
+ ClientKill_TeamChange(0);
}
void CTS_ClientKill (entity e) // silent version of ClientKill, used when player finishes a CTS run. Useful to prevent cheating by running back to the start line and starting out with more speed
Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_WEAPON_DROP, a, w);
}
- // Bringed back weapon frame
+ float forbidWeaponUse()
+ {
+ if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown)
+ return 1;
+ if(round_handler_IsActive() && !round_handler_IsRoundStarted())
+ return 1;
+ if(self.player_blocked)
+ return 1;
- if(self.freezetag_frozen)
++ if(self.frozen)
+ return 1;
+ return 0;
+ }
+
void W_WeaponFrame()
{
vector fo, ri, up;
.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.
if(targ.killcount) { targ.killcount = 0; }
}
- entity ice;
- for(ice = world; (ice = find(ice, classname, "ice")); ) if(ice.owner == targ)
- {
- remove(ice);
- break;
- }
+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)
+{
+ float monster = (targ.flags & FL_MONSTER);
+ float player = (targ.flags & FL_CLIENT);
+
+ if(!player && !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;
+ 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(self.team);
++ ice.glowmod = ice.colormod;
++ targ.iceblock = ice;
+
+ entity oldself;
+ oldself = self;
+ self = ice;
+ Ice_Think();
+ self = oldself;
+
+ RemoveGrapplingHook(targ);
+
+ // 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)
+{
+ targ.frozen = 0;
+ targ.revive_progress = 0;
+ targ.health = ((targ.classname == STR_PLAYER) ? autocvar_g_balance_health_start : targ.max_health);
+
+ WaypointSprite_Kill(targ.waypointsprite_attached);
+
+ // remove the ice block
++ 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;
BADCVAR("g_domination");
BADCVAR("g_domination_default_teams");
BADCVAR("g_freezetag");
+ BADCVAR("g_freezetag_teams");
BADCVAR("g_keepaway");
BADCVAR("g_keyhunt");
+ BADCVAR("g_td");
BADCVAR("g_keyhunt_teams");
- BADCVAR("g_keyhunt_teams");
BADCVAR("g_lms");
BADCVAR("g_nexball");
BADCVAR("g_onslaught");
addstat(STAT_NEX_CHARGEPOOL, AS_FLOAT, nex_chargepool_ammo);
addstat(STAT_HAGAR_LOAD, AS_INT, hagar_load);
-
- if(g_ca || g_freezetag)
- {
- addstat(STAT_REDALIVE, AS_INT, redalive_stat);
- addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
- addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
- addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
- }
-
- if(g_td)
- {
- addstat(STAT_CURRENT_WAVE, AS_FLOAT, stat_current_wave);
- addstat(STAT_TOTALWAVES, AS_FLOAT, stat_totalwaves);
- }
+
+ // 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);
--- /dev/null
- if(ent.team == COLOR_TEAM1)
- ent.team = COLOR_TEAM2;
+ // random functions
+ void assault_objective_use()
+ {
+ // activate objective
+ self.health = 100;
+ //print("^2Activated objective ", self.targetname, "=", etos(self), "\n");
+ //print("Activator is ", activator.classname, "\n");
+
+ entity oldself;
+ oldself = self;
+
+ for(self = world; (self = find(self, target, oldself.targetname)); )
+ {
+ if(self.classname == "target_objective_decrease")
+ target_objective_decrease_activate();
+ }
+
+ self = oldself;
+ }
+
+ vector target_objective_spawn_evalfunc(entity player, entity spot, vector current)
+ {
+ if(self.health < 0 || self.health >= ASSAULT_VALUE_INACTIVE)
+ return '-1 0 0';
+ return current;
+ }
+
+ // reset this objective. Used when spawning an objective
+ // and when a new round starts
+ void assault_objective_reset()
+ {
+ self.health = ASSAULT_VALUE_INACTIVE;
+ }
+
+ // decrease the health of targeted objectives
+ void assault_objective_decrease_use()
+ {
+ if(activator.team != assault_attacker_team)
+ {
+ // wrong team triggered decrease
+ return;
+ }
+
+ if(other.assault_sprite)
+ {
+ WaypointSprite_Disown(other.assault_sprite, waypointsprite_deadlifetime);
+ if(other.classname == "func_assault_destructible")
+ other.sprite = world;
+ }
+ else
+ return; // already activated! cannot activate again!
+
+ if(self.enemy.health < ASSAULT_VALUE_INACTIVE)
+ {
+ if(self.enemy.health - self.dmg > 0.5)
+ {
+ PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.dmg);
+ self.enemy.health = self.enemy.health - self.dmg;
+ }
+ else
+ {
+ PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.enemy.health);
+ PlayerTeamScore_Add(activator, SP_ASSAULT_OBJECTIVES, ST_ASSAULT_OBJECTIVES, 1);
+ self.enemy.health = -1;
+
+ entity oldself, oldactivator;
+
+ oldself = self;
+ self = oldself.enemy;
+ if(self.message)
+ {
+ entity player;
+ string s;
+ FOR_EACH_PLAYER(player)
+ {
+ s = strcat(self.message, "\n");
+ centerprint(player, s);
+ }
+ }
+
+ oldactivator = activator;
+ activator = oldself;
+ SUB_UseTargets();
+ activator = oldactivator;
+ self = oldself;
+ }
+ }
+ }
+
+ void assault_setenemytoobjective()
+ {
+ entity objective;
+ for(objective = world; (objective = find(objective, targetname, self.target)); )
+ {
+ if(objective.classname == "target_objective")
+ {
+ if(self.enemy == world)
+ self.enemy = objective;
+ else
+ objerror("more than one objective as target - fix the map!");
+ break;
+ }
+ }
+
+ if(self.enemy == world)
+ objerror("no objective as target - fix the map!");
+ }
+
+ float assault_decreaser_sprite_visible(entity e)
+ {
+ entity decreaser;
+
+ decreaser = self.assault_decreaser;
+
+ if(decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE)
+ return FALSE;
+
+ return TRUE;
+ }
+
+ void target_objective_decrease_activate()
+ {
+ entity ent, spr;
+ self.owner = world;
+ for(ent = world; (ent = find(ent, target, self.targetname)); )
+ {
+ if(ent.assault_sprite != world)
+ {
+ WaypointSprite_Disown(ent.assault_sprite, waypointsprite_deadlifetime);
+ if(ent.classname == "func_assault_destructible")
+ ent.sprite = world;
+ }
+
+ spr = WaypointSprite_SpawnFixed("<placeholder>", 0.5 * (ent.absmin + ent.absmax), ent, assault_sprite, RADARICON_OBJECTIVE, '1 0.5 0');
+ spr.assault_decreaser = self;
+ spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
+ spr.classname = "sprite_waypoint";
+ WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
+ if(ent.classname == "func_assault_destructible")
+ {
+ WaypointSprite_UpdateSprites(spr, "as-defend", "as-destroy", "as-destroy");
+ WaypointSprite_UpdateMaxHealth(spr, ent.max_health);
+ WaypointSprite_UpdateHealth(spr, ent.health);
+ ent.sprite = spr;
+ }
+ else
+ WaypointSprite_UpdateSprites(spr, "as-defend", "as-push", "as-push");
+ }
+ }
+
+ void target_objective_decrease_findtarget()
+ {
+ assault_setenemytoobjective();
+ }
+
+ void target_assault_roundend_reset()
+ {
+ //print("round end reset\n");
+ self.cnt = self.cnt + 1; // up round counter
+ self.winning = 0; // up round
+ }
+
+ void target_assault_roundend_use()
+ {
+ self.winning = 1; // round has been won by attackers
+ }
+
+ void assault_roundstart_use()
+ {
+ activator = self;
+ SUB_UseTargets();
+
+ #ifdef TTURRETS_ENABLED
+ entity ent, oldself;
+
+ //(Re)spawn all turrets
+ oldself = self;
+ ent = find(world, classname, "turret_main");
+ while(ent) {
+ // Swap turret teams
- ent.team = COLOR_TEAM1;
++ if(ent.team == NUM_TEAM_1)
++ ent.team = NUM_TEAM_2;
+ else
- if(assault_attacker_team == COLOR_TEAM1)
- assault_attacker_team = COLOR_TEAM2;
++ ent.team = NUM_TEAM_1;
+
+ self = ent;
+
+ // Dubbles as teamchange
+ turret_stdproc_respawn();
+
+ ent = find(ent, classname, "turret_main");
+ }
+ self = oldself;
+ #endif
+ }
+
+ void assault_wall_think()
+ {
+ if(self.enemy.health < 0)
+ {
+ self.model = "";
+ self.solid = SOLID_NOT;
+ }
+ else
+ {
+ self.model = self.mdl;
+ self.solid = SOLID_BSP;
+ }
+
+ self.nextthink = time + 0.2;
+ }
+
+ // trigger new round
+ // reset objectives, toggle spawnpoints, reset triggers, ...
+ void vehicles_clearrturn();
+ void vehicles_spawn();
+ void assault_new_round()
+ {
+ entity oldself;
+ //bprint("ASSAULT: new round\n");
+
+ oldself = self;
+ // Eject players from vehicles
+ FOR_EACH_PLAYER(self)
+ {
+ if(self.vehicle)
+ vehicles_exit(VHEF_RELESE);
+ }
+
+ self = findchainflags(vehicle_flags, VHF_ISVEHICLE);
+ while(self)
+ {
+ vehicles_clearrturn();
+ vehicles_spawn();
+ self = self.chain;
+ }
+
+ self = oldself;
+
+ // up round counter
+ self.winning = self.winning + 1;
+
+ // swap attacker/defender roles
- assault_attacker_team = COLOR_TEAM1;
++ if(assault_attacker_team == NUM_TEAM_1)
++ assault_attacker_team = NUM_TEAM_2;
+ else
- if(ent.team_saved == COLOR_TEAM1)
- ent.team_saved = COLOR_TEAM2;
- else if(ent.team_saved == COLOR_TEAM2)
- ent.team_saved = COLOR_TEAM1;
++ assault_attacker_team = NUM_TEAM_1;
+
+ entity ent;
+ for(ent = world; (ent = nextent(ent)); )
+ {
+ if(clienttype(ent) == CLIENTTYPE_NOTACLIENT)
+ {
- self.team = COLOR_TEAM1; // red, gets swapped every round
++ if(ent.team_saved == NUM_TEAM_1)
++ ent.team_saved = NUM_TEAM_2;
++ else if(ent.team_saved == NUM_TEAM_2)
++ ent.team_saved = NUM_TEAM_1;
+ }
+ }
+
+ // reset the level with a countdown
+ cvar_set("timelimit", ftos(ceil(time - game_starttime) / 60));
+ ReadyRestart_force(); // sets game_starttime
+ }
+
+ // spawnfuncs
+ void spawnfunc_info_player_attacker()
+ {
+ if not(g_assault) { remove(self); return; }
+
- self.team = COLOR_TEAM2; // blue, gets swapped every round
++ self.team = NUM_TEAM_1; // red, gets swapped every round
+ spawnfunc_info_player_deathmatch();
+ }
+
+ void spawnfunc_info_player_defender()
+ {
+ if not(g_assault) { remove(self); return; }
+
- if(assault_attacker_team == COLOR_TEAM1)
- self.team = COLOR_TEAM2;
++ self.team = NUM_TEAM_2; // blue, gets swapped every round
+ spawnfunc_info_player_deathmatch();
+ }
+
+ void spawnfunc_target_objective()
+ {
+ if not(g_assault) { remove(self); return; }
+
+ self.classname = "target_objective";
+ self.use = assault_objective_use;
+ assault_objective_reset();
+ self.reset = assault_objective_reset;
+ self.spawn_evalfunc = target_objective_spawn_evalfunc;
+ }
+
+ void spawnfunc_target_objective_decrease()
+ {
+ if not(g_assault) { remove(self); return; }
+
+ self.classname = "target_objective_decrease";
+
+ if(!self.dmg)
+ self.dmg = 101;
+
+ self.use = assault_objective_decrease_use;
+ self.health = ASSAULT_VALUE_INACTIVE;
+ self.max_health = ASSAULT_VALUE_INACTIVE;
+ self.enemy = world;
+
+ InitializeEntity(self, target_objective_decrease_findtarget, INITPRIO_FINDTARGET);
+ }
+
+ // destructible walls that can be used to trigger target_objective_decrease
+ void spawnfunc_func_assault_destructible()
+ {
+ if not(g_assault) { remove(self); return; }
+
+ self.spawnflags = 3;
+ self.classname = "func_assault_destructible";
+
- self.team = COLOR_TEAM1;
++ if(assault_attacker_team == NUM_TEAM_1)
++ self.team = NUM_TEAM_2;
+ else
- assault_attacker_team = COLOR_TEAM1;
++ self.team = NUM_TEAM_1;
+
+ spawnfunc_func_breakable();
+ }
+
+ void spawnfunc_func_assault_wall()
+ {
+ if not(g_assault) { remove(self); return; }
+
+ self.classname = "func_assault_wall";
+ self.mdl = self.model;
+ setmodel(self, self.mdl);
+ self.solid = SOLID_BSP;
+ self.think = assault_wall_think;
+ self.nextthink = time;
+ InitializeEntity(self, assault_setenemytoobjective, INITPRIO_FINDTARGET);
+ }
+
+ void spawnfunc_target_assault_roundend()
+ {
+ if not(g_assault) { remove(self); return; }
+
+ self.winning = 0; // round not yet won by attackers
+ self.classname = "target_assault_roundend";
+ self.use = target_assault_roundend_use;
+ self.cnt = 0; // first round
+ self.reset = target_assault_roundend_reset;
+ }
+
+ void spawnfunc_target_assault_roundstart()
+ {
+ if not(g_assault) { remove(self); return; }
+
++ assault_attacker_team = NUM_TEAM_1;
+ self.classname = "target_assault_roundstart";
+ self.use = assault_roundstart_use;
+ self.reset2 = assault_roundstart_use;
+ InitializeEntity(self, assault_roundstart_use, INITPRIO_FINDTARGET);
+ }
+
+ // legacy bot code
+ void havocbot_goalrating_ast_targets(float ratingscale)
+ {
+ entity ad, best, wp, tod;
+ float radius, found, bestvalue;
+ vector p;
+
+ ad = findchain(classname, "func_assault_destructible");
+
+ for (; ad; ad = ad.chain)
+ {
+ if (ad.target == "")
+ continue;
+
+ if not(ad.bot_attack)
+ continue;
+
+ found = FALSE;
+ for(tod = world; (tod = find(tod, targetname, ad.target)); )
+ {
+ if(tod.classname == "target_objective_decrease")
+ {
+ if(tod.enemy.health > 0 && tod.enemy.health < ASSAULT_VALUE_INACTIVE)
+ {
+ // dprint(etos(ad),"\n");
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ if(!found)
+ {
+ /// dprint("target not found\n");
+ continue;
+ }
+ /// dprint("target #", etos(ad), " found\n");
+
+
+ p = 0.5 * (ad.absmin + ad.absmax);
+ // dprint(vtos(ad.origin), " ", vtos(ad.absmin), " ", vtos(ad.absmax),"\n");
+ // te_knightspike(p);
+ // te_lightning2(world, '0 0 0', p);
+
+ // Find and rate waypoints around it
+ found = FALSE;
+ best = world;
+ bestvalue = 99999999999;
+ for(radius=0; radius<1500 && !found; radius+=500)
+ {
+ for(wp=findradius(p, radius); wp; wp=wp.chain)
+ {
+ if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
+ if(wp.classname=="waypoint")
+ if(checkpvs(wp.origin, ad))
+ {
+ found = TRUE;
+ if(wp.cnt<bestvalue)
+ {
+ best = wp;
+ bestvalue = wp.cnt;
+ }
+ }
+ }
+ }
+
+ if(best)
+ {
+ /// dprint("waypoints around target were found\n");
+ // te_lightning2(world, '0 0 0', best.origin);
+ // te_knightspike(best.origin);
+
+ navigation_routerating(best, ratingscale, 4000);
+ best.cnt += 1;
+
+ self.havocbot_attack_time = 0;
+
+ if(checkpvs(self.view_ofs,ad))
+ if(checkpvs(self.view_ofs,best))
+ {
+ // dprint("increasing attack time for this target\n");
+ self.havocbot_attack_time = time + 2;
+ }
+ }
+ }
+ }
+
+ void havocbot_role_ast_offense()
+ {
+ if(self.deadflag != DEAD_NO)
+ {
+ self.havocbot_attack_time = 0;
+ havocbot_ast_reset_role(self);
+ return;
+ }
+
+ // Set the role timeout if necessary
+ if (!self.havocbot_role_timeout)
+ self.havocbot_role_timeout = time + 120;
+
+ if (time > self.havocbot_role_timeout)
+ {
+ havocbot_ast_reset_role(self);
+ return;
+ }
+
+ if(self.havocbot_attack_time>time)
+ return;
+
+ if (self.bot_strategytime < time)
+ {
+ navigation_goalrating_start();
+ havocbot_goalrating_enemyplayers(20000, self.origin, 650);
+ havocbot_goalrating_ast_targets(20000);
+ havocbot_goalrating_items(15000, self.origin, 10000);
+ navigation_goalrating_end();
+
+ self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+ }
+ }
+
+ void havocbot_role_ast_defense()
+ {
+ if(self.deadflag != DEAD_NO)
+ {
+ self.havocbot_attack_time = 0;
+ havocbot_ast_reset_role(self);
+ return;
+ }
+
+ // Set the role timeout if necessary
+ if (!self.havocbot_role_timeout)
+ self.havocbot_role_timeout = time + 120;
+
+ if (time > self.havocbot_role_timeout)
+ {
+ havocbot_ast_reset_role(self);
+ return;
+ }
+
+ if(self.havocbot_attack_time>time)
+ return;
+
+ if (self.bot_strategytime < time)
+ {
+ navigation_goalrating_start();
+ havocbot_goalrating_enemyplayers(20000, self.origin, 3000);
+ havocbot_goalrating_ast_targets(20000);
+ havocbot_goalrating_items(15000, self.origin, 10000);
+ navigation_goalrating_end();
+
+ self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+ }
+ }
+
+ void havocbot_role_ast_setrole(entity bot, float role)
+ {
+ switch(role)
+ {
+ case HAVOCBOT_AST_ROLE_DEFENSE:
+ bot.havocbot_role = havocbot_role_ast_defense;
+ bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE;
+ bot.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_AST_ROLE_OFFENSE:
+ bot.havocbot_role = havocbot_role_ast_offense;
+ bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE;
+ bot.havocbot_role_timeout = 0;
+ break;
+ }
+ }
+
+ void havocbot_ast_reset_role(entity bot)
+ {
+ if(self.deadflag != DEAD_NO)
+ return;
+
+ if(bot.team == assault_attacker_team)
+ havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_OFFENSE);
+ else
+ havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_DEFENSE);
+ }
+
+ // mutator hooks
+ MUTATOR_HOOKFUNCTION(assault_PlayerSpawn)
+ {
+ if(self.team == assault_attacker_team)
+ centerprint(self, "You are attacking!");
+ else
+ centerprint(self, "You are defending!");
+
+ return FALSE;
+ }
+
+ MUTATOR_HOOKFUNCTION(assault_TurretSpawn)
+ {
+ if not (self.team)
+ self.team = 14;
+
+ return FALSE;
+ }
+
+ MUTATOR_HOOKFUNCTION(assault_VehicleSpawn)
+ {
+ self.nextthink = time + 0.5;
+
+ return FALSE;
+ }
+
+ MUTATOR_HOOKFUNCTION(assault_BotRoles)
+ {
+ havocbot_ast_reset_role(self);
+ return TRUE;
+ }
+
+ // scoreboard setup
+ void assault_ScoreRules()
+ {
+ ScoreRules_basics(2, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, TRUE);
+ ScoreInfo_SetLabel_TeamScore( ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
+ ScoreInfo_SetLabel_PlayerScore(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
+ ScoreRules_basics_end();
+ }
+
+ MUTATOR_DEFINITION(gamemode_assault)
+ {
+ MUTATOR_HOOK(PlayerSpawn, assault_PlayerSpawn, CBC_ORDER_ANY);
+ MUTATOR_HOOK(TurretSpawn, assault_TurretSpawn, CBC_ORDER_ANY);
+ MUTATOR_HOOK(VehicleSpawn, assault_VehicleSpawn, CBC_ORDER_ANY);
+ MUTATOR_HOOK(HavocBot_ChooseRule, assault_BotRoles, 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.");
+ assault_ScoreRules();
+ }
+
+ MUTATOR_ONROLLBACK_OR_REMOVE
+ {
+ // we actually cannot roll back assault_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;
+ }
- void freezetag_Initialize()
+ .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;
+
+ void freezetag_count_alive_players()
{
- precache_model("models/ice/ice.md3");
- warmup = time + autocvar_g_start_delay + autocvar_g_freezetag_warmup;
- ScoreRules_freezetag();
+ entity e;
+ total_players = redalive = bluealive = yellowalive = pinkalive = 0;
+ FOR_EACH_PLAYER(e) {
+ 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) {
+ e.redalive_stat = redalive;
+ e.bluealive_stat = bluealive;
+ e.yellowalive_stat = yellowalive;
+ e.pinkalive_stat = pinkalive;
+ }
}
+ #define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
+ #define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == freezetag_teams)
- void freezetag_CheckWinner()
+ float prev_total_players;
+ float freezetag_CheckTeams()
{
- if(time <= game_starttime) // game didn't even start yet! nobody can win in that case.
- return;
+ if(FREEZETAG_ALIVE_TEAMS_OK())
+ {
+ if(prev_total_players > 0)
+ Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
+ prev_total_players = -1;
+ return 1;
+ }
+ if(prev_total_players != total_players)
+ {
+ float p1 = 0, p2 = 0, p3 = 0, p4 = 0;
+ if(!redalive) p1 = NUM_TEAM_1;
+ if(!bluealive) p2 = NUM_TEAM_2;
+ if(freezetag_teams >= 3)
+ if(!yellowalive) p3 = NUM_TEAM_3;
+ if(freezetag_teams >= 4)
+ if(!pinkalive) p4 = NUM_TEAM_4;
+ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, p1, p2, p3, p4);
+ prev_total_players = total_players;
+ }
+ return 0;
+ }
- if(next_round || (time > warmup - autocvar_g_freezetag_warmup && time < warmup))
- return; // already waiting for next round to start
+ float freezetag_getWinnerTeam()
+ {
+ float winner_team = 0;
+ if(redalive >= 1)
+ winner_team = NUM_TEAM_1;
+ if(bluealive >= 1)
+ {
+ if(winner_team) return 0;
+ winner_team = NUM_TEAM_2;
+ }
+ if(yellowalive >= 1)
+ {
+ if(winner_team) return 0;
+ winner_team = NUM_TEAM_3;
+ }
+ if(pinkalive >= 1)
+ {
+ if(winner_team) return 0;
+ winner_team = NUM_TEAM_4;
+ }
+ if(winner_team)
+ return winner_team;
+ return -1; // no player left
+ }
- if((redalive >= 1 && bluealive >= 1)
- || (redalive >= 1 && yellowalive >= 1)
- || (redalive >= 1 && pinkalive >= 1)
- || (bluealive >= 1 && yellowalive >= 1)
- || (bluealive >= 1 && pinkalive >= 1)
- || (yellowalive >= 1 && pinkalive >= 1))
- return; // we still have active players on two or more teams, nobody won yet
+ float freezetag_CheckWinner()
+ {
+ entity e;
+ if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+ {
+ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
+ FOR_EACH_PLAYER(e)
+ e.freezetag_frozen_timeout = 0;
+ round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+ return 1;
+ }
- entity e, winner;
- winner = world;
+ if(FREEZETAG_ALIVE_TEAMS() > 1)
+ return 0;
- FOR_EACH_PLAYER(e)
+ float winner_team;
+ winner_team = freezetag_getWinnerTeam();
+ if(winner_team > 0)
{
- if(e.frozen != 1 && e.health >= 1) // here's one player from the winning team... good
- {
- winner = e;
- break; // break, we found the winner
- }
+ Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
+ Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
+ TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
}
-
- if(winner != world) // just in case a winner wasn't found
+ else if(winner_team == -1)
{
- Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner.team, CENTER_FREEZETAG_ROUND_WIN_));
- Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner.team, INFO_FREEZETAG_ROUND_WIN_));
- TeamScore_AddToTeam(winner.team, ST_SCORE, +1);
+ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
+ Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
}
- next_round = time + 5;
- }
-
- // 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;
+ FOR_EACH_PLAYER(e)
+ e.freezetag_frozen_timeout = 0;
+ round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+ return 1;
}
- void freezetag_Freeze(entity attacker)
-// 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(self.frozen)
- return;
-
- Freeze(self, 0, 1, TRUE);
-
if(attacker == self)
{
// you froze your own dumb self
PlayerScore_Add(self, SP_SCORE, -1);
PlayerScore_Add(attacker, SP_SCORE, +1);
}
- else
- {
- // nothing - got frozen by the game type rules themselves
- }
+ // else nothing - got frozen by the game type rules themselves
}
- if(self.freezetag_frozen)
+ void freezetag_Freeze(entity attacker)
+ {
- 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;
-
++ if(self.frozen)
+ return;
- 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');
-
++
++ Freeze(self, 0, 1, TRUE);
++
+ freezetag_count_alive_players();
+
- self.freezetag_frozen = 0;
+ freezetag_Add_Score(attacker);
+ }
+
+ void freezetag_Unfreeze(entity attacker)
+ {
- self.freezetag_revive_progress = 0;
-
- remove(self.freezetag_ice);
- self.freezetag_ice = world;
-
- if(self.waypointsprite_attached)
- WaypointSprite_Kill(self.waypointsprite_attached);
+ self.freezetag_frozen_time = 0;
+ self.freezetag_frozen_timeout = 0;
++
++ Unfreeze(self);
+ }
-
// ================
// Bot player logic
// ================
MUTATOR_HOOKFUNCTION(freezetag_PlayerDies)
{
- if(self.frozen != 1)
+ if(round_handler_IsActive())
+ if(round_handler_CountdownRunning())
{
- if(self.team == NUM_TEAM_1)
- --redalive;
- else if(self.team == NUM_TEAM_2)
- --bluealive;
- else if(self.team == NUM_TEAM_3)
- --yellowalive;
- else if(self.team == NUM_TEAM_4)
- --pinkalive;
- --totalalive;
- if(self.freezetag_frozen)
++ if(self.frozen == 1)
+ freezetag_Unfreeze(world);
+ freezetag_count_alive_players();
+ return 1; // let the player die so that he can respawn whenever he wants
+ }
- freezetag_Freeze(frag_attacker);
+ // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
+ // you succeed changing team through the menu: you both really die (gibbing) and get frozen
+ if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
+ || 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();
+ }
+ else
+ freezetag_Unfreeze(world); // remove ice
+ self.freezetag_frozen_timeout = -2; // freeze on respawn
+ return 1;
}
- if(self.freezetag_frozen)
++ if(self.frozen)
+ return 1;
+
+ freezetag_Freeze(frag_attacker);
+
if(frag_attacker == frag_target || frag_attacker == world)
{
if(frag_target.classname == STR_PLAYER)
Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
}
-- frag_target.health = 1; // "respawn" the player :P
-
- freezetag_CheckWinner();
--
return 1;
}
return 1;
}
- if (self.freezetag_frozen)
+ MUTATOR_HOOKFUNCTION(freezetag_reset_map_players)
+ {
+ FOR_EACH_PLAYER(self)
+ {
++ if (self.frozen)
+ freezetag_Unfreeze(world);
+ self.freezetag_frozen_timeout = -1;
+ PutClientInServer();
+ self.freezetag_frozen_timeout = 0;
+ }
+ freezetag_count_alive_players();
+ return 1;
+ }
+
MUTATOR_HOOKFUNCTION(freezetag_GiveFragsForKill)
{
frag_score = 0; // no frags counted in Freeze Tag
MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink)
{
float n;
- vector revive_extra_size;
- revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
-
+ 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;
+ }
+
+ if(round_handler_IsActive())
+ if(!round_handler_IsRoundStarted())
+ return 1;
+
entity o;
o = world;
- n = 0;
- FOR_EACH_PLAYER(other) if(self != other)
+ 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;
+ else
{
- if(other.frozen != 1)
+ vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
+ n = 0;
+ FOR_EACH_PLAYER(other) if(self != other)
{
- if(other.team == self.team)
- if(other.freezetag_frozen == 0)
++ if(!other.frozen)
{
- if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
+ if(other.team == self.team)
{
- if(!o)
- o = other;
- ++n;
+ 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.revive_progress = bound(0, self.revive_progress + frametime * autocvar_g_freezetag_revive_speed, 1);
- 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)
{
- Unfreeze(self);
+ freezetag_Unfreeze(self);
+ freezetag_count_alive_players();
+
+ if(n == -1)
+ {
+ Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
+ Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, self.netname, autocvar_g_freezetag_frozen_maxtime);
+ return 1;
+ }
// EVERY team mate nearby gets a point (even if multiple!)
- FOR_EACH_PLAYER(other) if(self != other)
+ FOR_EACH_PLAYER(other)
{
- if(!other.frozen)
+ if(other.reviving)
{
- if(other.team == self.team)
- {
- if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
- {
- PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1);
- PlayerScore_Add(other, SP_SCORE, +1);
- }
- }
+ PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1);
+ PlayerScore_Add(other, SP_SCORE, +1);
}
}
Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname);
- Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVE, self.netname, o.netname);
+ Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED, self.netname, o.netname);
}
- // now find EVERY teammate within reviving radius, set their revive_progress values correct
- FOR_EACH_PLAYER(other) if(self != other)
+ FOR_EACH_PLAYER(other)
{
- if(!other.frozen)
+ if(other.reviving)
{
- if(other.team == self.team)
- {
- if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
- other.revive_progress = self.revive_progress;
- }
- other.freezetag_revive_progress = self.freezetag_revive_progress;
++ other.revive_progress = self.revive_progress;
+ other.reviving = FALSE;
}
}
}
return 1;
}
-MUTATOR_HOOKFUNCTION(freezetag_PlayerPhysics)
-{
- if(self.freezetag_frozen)
- {
- self.movement = '0 0 0';
- self.disableclientprediction = 1;
- }
- return 1;
-}
-
MUTATOR_HOOKFUNCTION(freezetag_PlayerDamage_Calculate)
{
- if(g_freezetag)
- {
- if(frag_target.frozen == 1 && frag_deathtype != DEATH_HURTTRIGGER)
- {
- frag_damage = 0;
- frag_force = frag_force * autocvar_g_freezetag_frozen_force;
- }
- }
- return 1;
- if(frag_target.freezetag_frozen && frag_deathtype != DEATH_HURTTRIGGER)
++ if(frag_target.frozen == 1 && frag_deathtype != DEATH_HURTTRIGGER)
+ {
+ frag_damage = 0;
+ frag_force = frag_force * autocvar_g_freezetag_frozen_force;
+ }
+ return 1;
}
-MUTATOR_HOOKFUNCTION(freezetag_ForbidThrowCurrentWeapon)
-{
- if (self.freezetag_frozen)
- return 1;
- return 0;
-}
-
-MUTATOR_HOOKFUNCTION(freezetag_ItemTouch)
-{
- if (other.freezetag_frozen)
- return 1;
- return 0;
-}
-
MUTATOR_HOOKFUNCTION(freezetag_BotRoles)
{
if not(self.deadflag)
return TRUE;
}
- self.freezetag_frozen = other.freezetag_frozen;
- self.freezetag_revive_progress = other.freezetag_revive_progress;
+ MUTATOR_HOOKFUNCTION(freezetag_SpectateCopy)
+ {
-
- addstat(STAT_FROZEN, AS_INT, freezetag_frozen);
- addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, freezetag_revive_progress);
++ self.frozen = other.frozen;
++ self.revive_progress = other.revive_progress;
+ return 0;
+ }
+
+ MUTATOR_HOOKFUNCTION(freezetag_GetTeamCount)
+ {
+ freezetag_teams = autocvar_g_freezetag_teams_override;
+ if(freezetag_teams < 2)
+ freezetag_teams = autocvar_g_freezetag_teams;
+ freezetag_teams = bound(2, freezetag_teams, 4);
+ ret_float = freezetag_teams;
+ return 0;
+ }
+
+ void freezetag_Initialize()
+ {
+ precache_model("models/ice/ice.md3");
+ ScoreRules_freezetag();
+
+ round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
+ round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+
+ addstat(STAT_REDALIVE, AS_INT, redalive_stat);
+ addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
+ addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
+ addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
+ }
+
MUTATOR_DEFINITION(gamemode_freezetag)
{
MUTATOR_HOOK(MakePlayerObserver, freezetag_RemovePlayer, CBC_ORDER_ANY);
MUTATOR_HOOK(ClientDisconnect, freezetag_RemovePlayer, CBC_ORDER_ANY);
MUTATOR_HOOK(PlayerDies, freezetag_PlayerDies, CBC_ORDER_ANY);
MUTATOR_HOOK(PlayerSpawn, freezetag_PlayerSpawn, CBC_ORDER_ANY);
+ 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(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_ONADD
{
--- /dev/null
+// Tower Defense
+// Gamemode by Mario
+
+void spawnfunc_td_controller()
+{
+ if not(g_td) { remove(self); return; }
+
+ if(autocvar_g_td_force_settings)
+ {
+ // TODO: find a better way to do this?
+ self.dontend = FALSE;
+ self.maxwaves = 0;
+ self.monstercount = 0;
+ self.startwave = 0;
+ self.maxturrets = 0;
+ self.buildtime = 0;
+ self.mspeed_walk = 0;
+ self.mspeed_run = 0;
+ self.spawndelay = 0;
+ self.maxcurrent = 0;
+ self.ignoreturrets = 0;
+ }
+
+ self.netname = "Tower Defense controller entity";
+ self.classname = "td_controller";
+
+ gensurvived = FALSE;
+
+ td_dont_end = ((self.dontend == 0) ? autocvar_g_td_generator_dontend : self.dontend);
+ max_waves = ((self.maxwaves == 0) ? autocvar_g_td_max_waves : self.maxwaves);
+ totalmonsters = ((self.monstercount == 0) ? autocvar_g_td_monster_count : self.monstercount);
+ wave_count = ((self.startwave == 0) ? autocvar_g_td_start_wave : self.startwave);
+ max_turrets = ((self.maxturrets == 0) ? autocvar_g_td_turret_max : self.maxturrets);
+ build_time = ((self.buildtime == 0) ? autocvar_g_td_buildphase_time : self.buildtime);
+ m_speed_walk = ((self.mspeed_walk == 0) ? autocvar_g_td_monsters_speed_walk : self.mspeed_walk);
+ m_speed_run = ((self.mspeed_run == 0) ? autocvar_g_td_monsters_speed_run : self.mspeed_run);
+ spawn_delay = ((self.spawndelay == 0) ? autocvar_g_td_monsters_spawn_delay : self.spawndelay);
+ max_current = ((self.maxcurrent == 0) ? autocvar_g_td_current_monsters : self.maxcurrent);
+ ignore_turrets = ((self.ignoreturrets == 0) ? autocvar_g_td_monsters_ignore_turrets : self.ignoreturrets);
+
+ if(autocvar_g_td_monsters_skill_start)
+ monster_skill = autocvar_g_td_monsters_skill_start;
+
+ wave_end(TRUE);
+}
+
+void td_generator_die()
+{
+ if(autocvar_sv_eventlog)
+ GameLogEcho(":gendestroyed");
+
+ gendestroyed = TRUE;
+
+ pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
+ sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
+
+ Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_GENDESTROYED);
+
+ self.solid = SOLID_NOT;
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+ self.enemy = world;
+ td_gencount -= 1;
+
+ WaypointSprite_Kill(self.sprite);
+}
+
+void td_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ if(IS_PLAYER(attacker) || attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET || attacker.vehicle_flags & VHF_ISVEHICLE || self.takedamage == DAMAGE_NO)
+ return;
+
+ if (time > self.pain_finished)
+ {
+ self.pain_finished = time + 10;
+ play2all("onslaught/generator_underattack.wav");
+ }
+
+ if (random() < 0.5)
+ spamsound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE, ATTN_NORM);
+ else
+ spamsound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE, ATTN_NORM);
+
+ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TD_GENDAMAGED);
+
+ self.health -= damage;
+
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+
+ if(self.health <= 0)
+ td_generator_die();
+
+ self.SendFlags |= GSF_STATUS;
+}
+
+void td_generator_setup()
+{
+ self.think = func_null;
+ self.nextthink = -1;
+ self.solid = SOLID_BBOX;
+ self.takedamage = DAMAGE_AIM;
+ self.event_damage = td_generator_damage;
+ self.enemy = world;
+ self.movetype = MOVETYPE_NONE;
+ self.monster_attack = TRUE;
+ self.netname = "Generator";
+ self.SendFlags = GSF_SETUP;
+
+ WaypointSprite_SpawnFixed(self.netname, self.origin + '0 0 90', self, sprite, RADARICON_OBJECTIVE, '1 0.5 0');
+ WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+}
+
+void spawnfunc_td_generator()
+{
+ if not(g_td) { remove(self); return; }
+
+ precache_sound("onslaught/generator_underattack.wav");
+ precache_sound("onslaught/ons_hit1.wav");
+ precache_sound("onslaught/ons_hit2.wav");
+ precache_sound("weapons/rocket_impact.wav");
+
+ gendestroyed = FALSE;
+
+ if not(self.health)
+ self.health = autocvar_g_td_generator_health;
+
+ self.max_health = self.health;
+
+ self.classname = "td_generator";
+ self.flags = FL_GENERATOR;
+ td_gencount += 1;
+
+ setsize(self, GENERATOR_MIN, GENERATOR_MAX);
+
+ setorigin(self, self.origin + '0 0 20');
+ droptofloor();
+
+ generator_link(td_generator_setup);
+}
+
+entity PickGenerator()
+{
+ entity generator, head;
+ if(td_gencount == 1)
+ generator = findflags(world, flags, FL_GENERATOR);
+ else
+ {
+ RandomSelection_Init();
+ for(head = world;(head = findflags(head, flags, FL_GENERATOR)); )
+ {
+ RandomSelection_Add(head, 0, string_null, 1, 1);
+ }
+ generator = RandomSelection_chosen_ent;
+ }
+ return generator;
+}
+
+void spawn_td_fuel(float fuel_size)
+{
+ if not(g_td) {remove(self); return; }
+
+ self.ammo_fuel = fuel_size * monster_skill;
+ StartItem("models/items/g_fuel.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "Turret Fuel", IT_FUEL, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW);
+
+ self.velocity = randomvec() * 175 + '0 0 325';
+}
+
+void spawnfunc_td_waypoint()
+{
+ if not(g_td) { remove(self); return; }
+
+ self.classname = "td_waypoint";
+}
+
+void spawnfunc_monster_swarm()
+{
+ if not(g_td) { remove(self); return; }
+
+ self.flags = SWARM_NORMAL; // marked as a spawnpoint
+ self.classname = "monster_swarm";
+
+ if(self.spawntype == SWARM_SWIM) waterspawns_count += 1;
+ if(self.spawntype == SWARM_FLY) flyspawns_count += 1;
+
+ WaypointSprite_SpawnFixed("Monsters", self.origin + '0 0 60', self, sprite, RADARICON_HERE, '0 0 1');
+
+ if(self.target == "")
+ dprint("Warning: monster_swarm entity without a set target\n");
+}
+
+void barricade_touch()
+{
+ if not(other.flags & FL_MONSTER)
+ return;
+
+ if(time < self.dmg_time)
+ return;
+
+ Damage(other, self, self, autocvar_g_td_barricade_damage, DEATH_HURTTRIGGER, self.origin, '0 0 0');
+
+ self.dmg_time = time + 1;
+}
+
+void barricade_die()
+{
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+
+ WaypointSprite_Kill(self.sprite);
+
+ pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
+ sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
+
+ if(self.realowner)
+ self.realowner.turret_cnt -= 1;
+
+ self.think = SUB_Remove;
+ self.nextthink = time;
+}
+
+void barricade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ if not(attacker.flags & FL_MONSTER) return;
+
+ self.health -= damage;
+
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+
+ if(self.health < 1)
+ barricade_die();
+}
+
+void spawn_barricade()
+{
+ self.health = 2000;
+ self.max_health = self.health;
+ self.dmg_time = time;
+ self.touch = barricade_touch;
+ self.think = func_null;
+ self.nextthink = -1;
+ self.takedamage = DAMAGE_AIM;
+ self.turrcaps_flags = TFL_TURRCAPS_ISTURRET; // for turretremove commands etc.
+ self.solid = SOLID_CORPSE; // hax
+ self.event_damage = barricade_damage;
+ self.netname = "Barricade";
+
+ WaypointSprite_Spawn(self.netname, 0, 1200, self, '0 0 110', world, 0, self, sprite, FALSE, RADARICON_DOMPOINT, '1 1 0');
+ WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+
+ precache_model("models/td/barricade.md3");
+ setmodel(self, "models/td/barricade.md3");
+
+ droptofloor();
+
+ self.movetype = MOVETYPE_NONE;
+}
+
+float td_checkfuel(entity ent, string tur)
+{
+ float turcost = cvar(strcat("g_td_turret_", tur, "_cost"));
+
+ if(ent.ammo_fuel < turcost)
+ {
+ Send_Notification(NOTIF_ONE, ent, MSG_MULTI, MULTI_TD_NOFUEL);
+ return FALSE;
+ }
+
+ ent.ammo_fuel -= turcost;
+
+ return TRUE;
+}
+
+void spawnturret(entity spawnedby, entity own, string turet, vector orig)
+{
+ if not(IS_PLAYER(spawnedby)) { dprint("Warning: A non-player entity tried to spawn a turret\n"); return; }
+ if not(td_checkfuel(spawnedby, turet)) { return; }
+
+ entity oldself;
+
+ oldself = self;
+ self = spawn();
+
+ setorigin(self, orig);
+ self.spawnflags = TSL_NO_RESPAWN;
+ self.monster_attack = TRUE;
+ self.realowner = own;
+ self.playerid = own.playerid;
+ self.angles_y = spawnedby.v_angle_y;
+ spawnedby.turret_cnt += 1;
+ self.colormap = spawnedby.colormap;
+ self.colormod = '1 1 1';
+
+ switch(turet)
+ {
+ case "plasma": spawnfunc_turret_plasma(); break;
+ case "mlrs": spawnfunc_turret_mlrs(); break;
+ case "walker": spawnfunc_turret_walker(); break;
+ case "flac": spawnfunc_turret_flac(); break;
+ case "towerbuff": spawnfunc_turret_fusionreactor(); break;
+ case "barricade": spawn_barricade(); break;
+ default: Send_Notification(NOTIF_ONE, spawnedby, MSG_INFO, INFO_TD_INVALID); remove(self); self = oldself; return;
+ }
+
+ Send_Notification(NOTIF_ONE, spawnedby, MSG_MULTI, MULTI_TD_SPAWN);
+
+ self = oldself;
+}
+
+void buffturret (entity tur, float buff)
+{
+ tur.turret_buff += 1;
+ tur.max_health *= buff;
+ tur.tur_health = tur.max_health;
+ tur.health = tur.max_health;
+ tur.ammo_max *= buff;
+ tur.ammo_recharge *= buff;
+ tur.shot_dmg *= buff;
+ tur.shot_refire -= buff * 0.2;
+ tur.shot_radius *= buff;
+ tur.shot_speed *= buff;
+ tur.shot_spread *= buff;
+ tur.shot_force *= buff;
+}
+
+void AnnounceSpawn(string anounce)
+{
+ entity e;
+ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TD_ANNOUNCE_SPAWN, anounce);
+
+ FOR_EACH_REALCLIENT(e) soundto(MSG_ONE, e, CHAN_AUTO, "kh/alarm.wav", VOL_BASE, ATTN_NONE);
+}
+
+entity PickSpawn (float strngth, float type)
+{
+ entity e;
+ RandomSelection_Init();
+ for(e = world;(e = find(e, classname, "monster_swarm")); )
+ {
+ if(flyspawns_count > 0 && type == SWARM_FLY && e.spawntype != SWARM_FLY) continue;
+ if(waterspawns_count > 0 && type == SWARM_SWIM && e.spawntype != SWARM_SWIM) continue;
+
+ RandomSelection_Add(e, 0, string_null, 1, 1);
+ }
+
+ return RandomSelection_chosen_ent;
+}
+
+void TD_SpawnMonster(string mnster, float strngth, float type)
+{
+ entity e, mon;
+
+ e = PickSpawn(strngth, type);
+
+ if(e == world) // couldn't find anything for our class, so check for normal spawns
+ e = PickSpawn(SWARM_NORMAL, SWARM_NORMAL);
+
+ if(e == world)
+ {
+ dprint("Warning: couldn't find any monster_swarm spawnpoints, no monsters will spawn!\n");
+ return;
+ }
+
+ mon = spawnmonster(mnster, e, e, e.origin, FALSE, 2);
+ if(e.target2)
+ {
+ if(random() <= 0.5 && e.target)
+ mon.target2 = e.target;
+ else
+ mon.target2 = e.target2;
+ }
+ else
+ mon.target2 = e.target;
+}
+
+float Monster_GetStrength(float mnster)
+{
+ switch(mnster)
+ {
+ default:
+ case MONSTER_BRUISER:
+ case MONSTER_ZOMBIE:
+ case MONSTER_SPIDER:
+ case MONSTER_SLIME:
+ case MONSTER_CERBERUS:
+ case MONSTER_WYVERN:
+ case MONSTER_STINGRAY:
+ return SWARM_WEAK;
+ case MONSTER_KNIGHT:
+ case MONSTER_BRUTE:
+ case MONSTER_SHAMBLER:
+ case MONSTER_MAGE:
+ case MONSTER_ANIMUS:
+ return SWARM_STRONG;
+ default: return SWARM_NORMAL;
+ }
+}
+
+string monster_type2string(float mnster)
+{
+ switch(mnster)
+ {
+ case MONSTER_ZOMBIE: return "zombie";
+ case MONSTER_BRUTE: return "brute";
+ case MONSTER_ANIMUS: return "animus";
+ case MONSTER_SHAMBLER: return "shambler";
+ case MONSTER_BRUISER: return "bruiser";
+ case MONSTER_WYVERN: return "wyvern";
+ case MONSTER_CERBERUS: return "cerberus";
+ case MONSTER_SLIME: return "slime";
+ case MONSTER_KNIGHT: return "knight";
+ case MONSTER_STINGRAY: return "stingray";
+ case MONSTER_MAGE: return "mage";
+ case MONSTER_SPIDER: return "spider";
+ default: return "";
+ }
+}
+
+float Monster_GetType(float mnster)
+{
+ switch(mnster)
+ {
+ default:
+ case MONSTER_BRUISER:
+ case MONSTER_ZOMBIE:
+ case MONSTER_SPIDER:
+ case MONSTER_SLIME:
+ case MONSTER_CERBERUS:
+ case MONSTER_BRUTE:
+ case MONSTER_SHAMBLER:
+ case MONSTER_MAGE:
+ case MONSTER_KNIGHT:
+ case MONSTER_ANIMUS:
+ return SWARM_NORMAL;
+ case MONSTER_WYVERN:
+ return SWARM_FLY;
+ case MONSTER_STINGRAY:
+ return SWARM_SWIM;
+ }
+}
+
+float RandomMonster()
+{
+ RandomSelection_Init();
+
+ if(n_animuses) RandomSelection_Add(world, MONSTER_ANIMUS, "", 1, 1);
+ if(n_mages) RandomSelection_Add(world, MONSTER_MAGE, "", 1, 1);
+ if(n_knights) RandomSelection_Add(world, MONSTER_KNIGHT, "", 1, 1);
+ if(n_zombies) RandomSelection_Add(world, MONSTER_ZOMBIE, "", 1, 1);
+ if(n_spiders) RandomSelection_Add(world, MONSTER_SPIDER, "", 1, 1);
+ if(n_brutes) RandomSelection_Add(world, MONSTER_BRUTE, "", 1, 1);
+ if(n_cerberuses) RandomSelection_Add(world, MONSTER_CERBERUS, "", 1, 1);
+ if(n_bruisers) RandomSelection_Add(world, MONSTER_BRUISER, "", 1, 1);
+ if(n_shamblers) RandomSelection_Add(world, MONSTER_SHAMBLER, "", 0.2, 0.2);
+ if(n_slimes) RandomSelection_Add(world, MONSTER_SLIME, "", 0.2, 0.2);
+ if(n_wyverns && flyspawns_count) RandomSelection_Add(world, MONSTER_WYVERN, "", 1, 1);
+ if(n_stingrays && waterspawns_count) RandomSelection_Add(world, MONSTER_STINGRAY, "", 0.2, 0.2);
+
+ return RandomSelection_chosen_float;
+}
+
+void combat_phase()
+{
+ float mstrength, montype, whichmon;
+
+ current_phase = PHASE_COMBAT;
+
+ if(monster_count <= 0)
+ {
+ wave_end(FALSE);
+ return;
+ }
+
+ self.think = combat_phase;
+
+ whichmon = RandomMonster();
+
+ mstrength = Monster_GetStrength(whichmon);
+ montype = Monster_GetType(whichmon);
+
+ if(current_monsters <= max_current && whichmon)
+ {
+ TD_SpawnMonster(monster_type2string(whichmon), mstrength, montype);
+ self.nextthink = time + spawn_delay;
+ }
+ else
+ self.nextthink = time + 6;
+}
+
+void queue_monsters(float maxmonsters)
+{
+ float mc = 9; // note: shambler + slime = 1
+
+ if(waterspawns_count > 0)
+ mc += 1;
+ if(flyspawns_count > 0)
+ mc += 1;
+
+ DistributeEvenly_Init(maxmonsters, mc);
+ n_animuses = DistributeEvenly_Get(1);
+ n_brutes = DistributeEvenly_Get(1);
+ n_cerberuses = DistributeEvenly_Get(1);
+ n_bruisers = DistributeEvenly_Get(1);
+ n_mages = DistributeEvenly_Get(1);
+ n_knights = DistributeEvenly_Get(1);
+ n_zombies = DistributeEvenly_Get(1);
+ n_spiders = DistributeEvenly_Get(1);
+ n_slimes = DistributeEvenly_Get(0.7);
+ n_shamblers = DistributeEvenly_Get(0.3);
+ if(flyspawns_count > 0)
+ n_wyverns = DistributeEvenly_Get(1);
+ if(waterspawns_count > 0)
+ n_stingrays = DistributeEvenly_Get(1);
+}
+
+void combat_phase_begin()
+{
+ monster_count = totalmonsters;
+ entity gen;
+
+ Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_PHASE_COMBAT);
+
+ if(autocvar_sv_eventlog)
+ GameLogEcho(":combatphase");
+
+ self.think = combat_phase;
+ self.nextthink = time + 1;
+
+ for(gen = world;(gen = findflags(gen, flags, FL_GENERATOR)); )
+ gen.takedamage = DAMAGE_AIM;
+}
+
+float cphase_updates;
+void combat_phase_announce() // TODO: clean up these fail nextthinks...
+{
+ cphase_updates += 1;
+
+ if(cphase_updates == 0)
+ Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_PREPARE);
+ else if(cphase_updates == 3)
+ Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_NUM_3);
+ else if(cphase_updates == 4)
+ Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_NUM_2);
+ else if(cphase_updates == 5)
+ Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_NUM_1);
+ else if(cphase_updates == 6)
+ {
+ Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_BEGIN);
+ combat_phase_begin();
+ }
+
+ if(cphase_updates >= 6)
+ return;
+
+ self.think = combat_phase_announce;
+ self.nextthink = time + 1;
+}
+
+void build_phase()
+{
+ entity head;
+ float n_players = 0, gen_washealed = FALSE, mcount, mskill;
+
+ current_phase = PHASE_BUILD;
+
+ for(head = world;(head = findflags(head, flags, FL_GENERATOR)); )
+ {
+ if(head.health < head.max_health)
+ {
+ gen_washealed = TRUE;
+ pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
+ head.health = head.max_health;
+ WaypointSprite_UpdateHealth(head.sprite, head.health);
+ head.SendFlags |= GSF_STATUS;
+ }
+ head.takedamage = DAMAGE_NO;
+ }
+
+ FOR_EACH_PLAYER(head)
+ {
+ if(head.health < 100) head.health = 100;
+ if(gen_washealed) PlayerScore_Add(head, SP_TD_SCORE, -autocvar_g_td_generator_damaged_points);
+
+ n_players += 1;
+ }
+
+ mcount = autocvar_g_td_monster_count_increment * wave_count;
+ mskill = n_players * 0.02;
+
+ totalmonsters += mcount;
+ monster_skill += autocvar_g_td_monsters_skill_increment;
+ monster_skill += mskill;
+
+ if(monster_skill < 1) monster_skill = 1;
+ if(totalmonsters < 1) totalmonsters = ((autocvar_g_td_monster_count > 0) ? autocvar_g_td_monster_count : 10);
+ if(wave_count < 1) wave_count = 1;
+
+ Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_PHASE_BUILD, wave_count, totalmonsters, build_time);
+
+ FOR_EACH_MONSTER(head)
+ {
+ if(head.health <= 0)
+ continue;
+
+ dprint(strcat("Warning: Monster still alive during build phase! Monster name: ", head.netname, "\n"));
+
+ WaypointSprite_Kill(head.sprite);
+ remove(head);
+ }
+
+ monsters_total = totalmonsters;
+ monsters_killed = 0;
+
+ queue_monsters(totalmonsters);
+
+ cphase_updates = -1;
+
+ if(autocvar_sv_eventlog)
+ GameLogEcho(strcat(":buildphase:", ftos(wave_count), ":", ftos(totalmonsters)));
+
+ self.think = combat_phase_announce;
+ self.nextthink = time + build_time - 6;
+}
+
+void wave_end(float starting)
+{
+ if not(starting)
+ {
+ Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_TD_VICTORY, ((wave_count >= max_waves) ? "Level" : "Wave"));
+
+ if(autocvar_sv_eventlog)
+ GameLogEcho(strcat(":wave:", ftos(wave_count), ":victory"));
+ }
+
+ if(wave_count >= max_waves)
+ {
+ gensurvived = TRUE;
+ return;
+ }
+
+ if not(starting)
+ wave_count += 1;
+
+ self.think = build_phase;
+ self.nextthink = time + 3;
+}
+
+void td_ScoreRules()
+{
+ ScoreInfo_SetLabel_PlayerScore(SP_TD_SCORE, "score", SFL_SORT_PRIO_PRIMARY);
+ ScoreInfo_SetLabel_PlayerScore(SP_TD_KILLS, "kills", SFL_LOWER_IS_BETTER);
+ ScoreInfo_SetLabel_PlayerScore(SP_TD_TURKILLS, "frags", SFL_LOWER_IS_BETTER);
+ ScoreInfo_SetLabel_PlayerScore(SP_TD_DEATHS, "deaths", SFL_LOWER_IS_BETTER);
+ ScoreInfo_SetLabel_PlayerScore(SP_TD_SUICIDES, "suicides", SFL_LOWER_IS_BETTER | SFL_ALLOW_HIDE);
+ ScoreRules_basics_end();
+}
+
+void td_SpawnController()
+{
+ entity oldself = self;
+ self = spawn();
+ self.classname = "td_controller";
+ spawnfunc_td_controller();
+ self = oldself;
+}
+
+void td_DelayedInit()
+{
+ if(find(world, classname, "td_controller") == world)
+ {
+ print("No ""td_controller"" entity found on this map, creating it anyway.\n");
+ td_SpawnController();
+ }
+
+ td_ScoreRules();
+}
+
+void td_Initialize()
+{
+ InitializeEntity(world, td_DelayedInit, INITPRIO_GAMETYPE);
++
++ addstat(STAT_CURRENT_WAVE, AS_FLOAT, stat_current_wave);
++ addstat(STAT_TOTALWAVES, AS_FLOAT, stat_totalwaves);
+}
+
+MUTATOR_HOOKFUNCTION(td_TurretValidateTarget)
+{
+ if(time < game_starttime || current_phase != PHASE_COMBAT || gameover)
+ {
+ turret_target = world;
+ return FALSE; // battle hasn't started
+ }
+
+ if(turret_flags & TFL_TARGETSELECT_MISSILESONLY)
+ if(turret_target.flags & FL_PROJECTILE)
+ if(turret_target.owner.flags & FL_MONSTER)
+ return TRUE; // flac support
+
+ if(turret.turrcaps_flags & TFL_TURRCAPS_SUPPORT && turret_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+ return TRUE;
+ if not(turret_target.flags & FL_MONSTER)
+ turret_target = world;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerThink)
+{
+ self.stat_current_wave = wave_count;
+ self.stat_totalwaves = max_waves;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerSpawn)
+{
+ self.bot_attack = FALSE;
+
+ Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_TD_PROTECT);
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerDies)
+{
+ if(frag_attacker.flags & FL_MONSTER)
+ PlayerScore_Add(frag_target, SP_TD_DEATHS, 1);
+
+ if(frag_target == frag_attacker)
+ PlayerScore_Add(frag_attacker, SP_TD_SUICIDES, 1);
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_GiveFragsForKill)
+{
+ frag_score = 0;
+
+ return TRUE; // no frags counted in td
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerDamage)
+{
+ if(frag_attacker.realowner == frag_target)
+ frag_damage = 0;
+
+ if(frag_target.flags & FL_MONSTER && time < frag_target.spawnshieldtime)
+ frag_damage = 0;
+
+ if(frag_target.vehicle_flags & VHF_ISVEHICLE && !(frag_attacker.flags & FL_MONSTER))
+ frag_damage = 0;
+
+ if(frag_attacker.vehicle_flags & VHF_ISVEHICLE && !(frag_target.flags & FL_MONSTER))
+ frag_damage = 0;
+
+ if(!autocvar_g_td_pvp && frag_attacker != frag_target && IS_PLAYER(frag_target) && IS_PLAYER(frag_attacker))
+ {
+ frag_attacker.typehitsound += 1;
+ frag_damage = 0;
+ }
+
+ if(frag_attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET && IS_PLAYER(frag_target))
+ frag_damage = 0;
+
+ if((frag_target.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && !(frag_attacker.flags & FL_MONSTER || frag_attacker.turrcaps_flags & TFL_TURRCAPS_SUPPORT))
+ frag_damage = 0;
+
+ return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_TurretDies)
+{
+ if(self.realowner)
+ self.realowner.turret_cnt -= 1;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterCheckBossFlag)
+{
+ // No minibosses in tower defense
+ return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterMove)
+{
+ entity head;
+ float n_players = 0;
+
+ FOR_EACH_PLAYER(head) { ++n_players; }
+ if(n_players < 1) return TRUE;
+
+ if not(self.enemy) // don't change targets while attacking
+ if((vlen(monster_target.origin - self.origin) <= 100 && monster_target.classname == "td_waypoint") || (vlen(monster_target.origin - self.origin) <= 200 && (self.flags & FL_FLY) && monster_target.classname == "td_waypoint"))
+ {
+ if(monster_target.target2)
+ {
+ if(random() > 0.5)
+ self.target2 = monster_target.target2;
+ else
+ self.target2 = monster_target.target;
+ }
+ else
+ self.target2 = monster_target.target;
+
+ monster_target = find(world, targetname, self.target2);
+
+ if(monster_target == world)
+ monster_target = PickGenerator();
+ }
+
+ monster_speed_run = (m_speed_run + random() * 4) * monster_skill;
+ monster_speed_walk = (m_speed_walk + random() * 4) * monster_skill;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterSpawn)
+{
+ if(self.realowner == world) // nothing spawned it, so kill it
+ {
+ WaypointSprite_Kill(self.sprite);
+ remove(self.weaponentity);
+ remove(self);
+ return TRUE;
+ }
+
+ current_monsters += 1;
+
+ self.spawnshieldtime = time + autocvar_g_td_monsters_spawnshield_time;
+
+ self.drop_size = bound(5, self.health * 0.05, autocvar_g_pickup_fuel_max);
+
+ self.target_range = 600;
+
+ self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_CORPSE | DPCONTENTS_MONSTERCLIP;
+
+ switch(self.monsterid)
+ {
+ case MONSTER_ZOMBIE: n_zombies -= 1; break;
+ case MONSTER_BRUTE: n_brutes -= 1; break;
+ case MONSTER_ANIMUS: n_animuses -= 1; break;
+ case MONSTER_SHAMBLER: n_shamblers -= 1; break;
+ case MONSTER_BRUISER: n_bruisers -= 1; break;
+ case MONSTER_WYVERN: n_wyverns -= 1; break;
+ case MONSTER_CERBERUS: n_cerberuses -= 1; break;
+ case MONSTER_SLIME: n_slimes -= 1; break;
+ case MONSTER_KNIGHT: n_knights -= 1; break;
+ case MONSTER_STINGRAY: n_stingrays -= 1; break;
+ case MONSTER_MAGE: n_mages -= 1; break;
+ case MONSTER_SPIDER: n_spiders -= 1; break;
+ }
+
+ return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterDies)
+{
+ entity oldself;
+ vector backuporigin;
+
+ monster_count -= 1;
+ current_monsters -= 1;
+ monsters_killed += 1;
+
+ if(IS_PLAYER(frag_attacker))
+ {
+ PlayerScore_Add(frag_attacker, SP_TD_SCORE, autocvar_g_td_kill_points);
+ PlayerScore_Add(frag_attacker, SP_TD_KILLS, 1);
+ }
+ else if(IS_PLAYER(frag_attacker.realowner))
+ if(frag_attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+ {
+ PlayerScore_Add(frag_attacker.realowner, SP_TD_SCORE, autocvar_g_td_turretkill_points);
+ PlayerScore_Add(frag_attacker.realowner, SP_TD_TURKILLS, 1);
+ }
+
+ backuporigin = self.origin;
+ oldself = self;
+ self = spawn();
+
+ self.gravity = 1;
+ setorigin(self, backuporigin + '0 0 5');
+ spawn_td_fuel(oldself.drop_size);
+ self.touch = M_Item_Touch;
+ if(self == world)
+ {
+ self = oldself;
+ return FALSE;
+ }
+ SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1);
+
+ self = oldself;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_MonsterFindTarget)
+{
+ float n_players = 0;
+ entity player;
+ local entity e;
+
+ FOR_EACH_PLAYER(player) { ++n_players; }
+
+ if(n_players < 1) // no players online, so do nothing
+ {
+ self.enemy = world;
+ return TRUE;
+ }
+
+ for(e = world;(e = findflags(e, monster_attack, TRUE)); )
+ {
+ if(ignore_turrets)
+ if(e.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+ continue;
+
+ if(monster_isvalidtarget(e, self))
+ if((vlen(trace_endpos - self.origin) < 200 && e.turrcaps_flags & TFL_TURRCAPS_ISTURRET) || (vlen(trace_endpos - self.origin) < 200 && !(e.flags & FL_GENERATOR)) || (vlen(trace_endpos - self.origin) < 500 && e.flags & FL_GENERATOR))
+ {
+ self.enemy = e;
+ }
+ }
+
+ return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_SetStartItems)
+{
+ start_ammo_fuel = 150; // to be nice...
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_TurretSpawn)
+{
+ if(self.realowner == world)
+ return TRUE; // wasn't spawned by a player
+
+ self.bot_attack = FALSE;
+ buffturret(self, 0.5);
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_DisableVehicles)
+{
+ // you shall not spawn!
+ return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(td_PlayerCommand)
+{
+ if(MUTATOR_RETURNVALUE) { return FALSE; } // command was already handled?
+
+ makevectors(self.v_angle);
+ WarpZone_TraceLine(self.origin, self.origin + v_forward * 100, MOVE_HITMODEL, self);
+ entity targ = trace_ent;
+ if(targ.owner.realowner == self)
+ targ = targ.owner;
+
+ if(cmd_name == "turretspawn")
+ {
+ if(argv(1) == "list")
+ {
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_LIST, "mlrs walker plasma towerbuff flac barricade");
+ return TRUE;
+ }
+ if(!IS_PLAYER(self) || self.health <= 0)
+ {
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_CANTSPAWN);
+ return TRUE;
+ }
+ if(max_turrets <= 0)
+ {
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_DISABLED);
+ return TRUE;
+ }
+ if(self.turret_cnt >= max_turrets)
+ {
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_MAXTURRETS, max_turrets);
+ return TRUE;
+ }
+
+ spawnturret(self, self, argv(1), trace_endpos);
+
+ return TRUE;
+ }
+ if(cmd_name == "repairturret")
+ {
+ if((targ.playerid != self.playerid || targ.realowner != self) || !(targ.turrcaps_flags & TFL_TURRCAPS_ISTURRET))
+ {
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_AIM_REPAIR);
+ return TRUE;
+ }
+ if(self.ammo_fuel < autocvar_g_td_turret_repair_cost)
+ {
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_NOFUEL_REPAIR, autocvar_g_td_turret_repair_cost);
+ return TRUE;
+ }
+ if(targ.health >= targ.max_health)
+ {
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_MAXHEALTH);
+ return TRUE;
+ }
+
+ self.ammo_fuel -= autocvar_g_td_turret_repair_cost;
+ targ.SendFlags |= TNSF_STATUS;
+ targ.health = bound(1, targ.health + 100, targ.max_health);
+ WaypointSprite_UpdateHealth(targ.sprite, targ.health);
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_REPAIR);
+
+ return TRUE;
+ }
+ if(cmd_name == "buffturret")
+ {
+ if((targ.playerid != self.playerid || targ.realowner != self) || !(targ.turrcaps_flags & TFL_TURRCAPS_ISTURRET))
+ {
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_AIM_UPGRADE);
+ return TRUE;
+ }
+ if(self.ammo_fuel < autocvar_g_td_turret_upgrade_cost)
+ {
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_NOFUEL_UPGRADE, autocvar_g_td_turret_upgrade_cost);
+ return TRUE;
+ }
+ if(targ.turret_buff >= 5)
+ {
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_MAXPOWER);
+ return TRUE;
+ }
+
+ self.ammo_fuel -= autocvar_g_td_turret_upgrade_cost;
+ targ.SendFlags |= TNSF_STATUS;
+ buffturret(targ, 1.2);
+ WaypointSprite_UpdateHealth(targ.sprite, targ.health);
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_UPGRADE);
+
+ return TRUE;
+ }
+ if(cmd_name == "turretremove")
+ {
+ if((targ.turrcaps_flags & TFL_TURRCAPS_ISTURRET) && (targ.playerid == self.playerid || targ.realowner == self))
+ {
+ self.turret_cnt -= 1;
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_REMOVE);
+ WaypointSprite_Kill(targ.sprite);
+ remove(targ.tur_head);
+ remove(targ);
+ return TRUE;
+ }
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, MULTI_TD_AIM_REMOVE);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(td_ClientConnect)
+{
+ entity t;
+
+ self.turret_cnt = 0;
+
+ for(t = world; (t = findflags(t, turrcaps_flags, TFL_TURRCAPS_ISTURRET)); )
+ if(t.playerid == self.playerid)
+ {
+ t.realowner = self;
+ self.turret_cnt += 1;
+ }
+
+ return FALSE;
+}
+
+MUTATOR_DEFINITION(gamemode_td)
+{
+ MUTATOR_HOOK(MonsterSpawn, td_MonsterSpawn, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MonsterDies, td_MonsterDies, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MonsterMove, td_MonsterMove, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MonsterFindTarget, td_MonsterFindTarget, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MonsterCheckBossFlag, td_MonsterCheckBossFlag, CBC_ORDER_ANY);
+ MUTATOR_HOOK(SetStartItems, td_SetStartItems, CBC_ORDER_ANY);
+ MUTATOR_HOOK(TurretValidateTarget, td_TurretValidateTarget, CBC_ORDER_ANY);
+ MUTATOR_HOOK(TurretSpawn, td_TurretSpawn, CBC_ORDER_ANY);
+ MUTATOR_HOOK(TurretDies, td_TurretDies, CBC_ORDER_ANY);
+ MUTATOR_HOOK(GiveFragsForKill, td_GiveFragsForKill, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerPreThink, td_PlayerThink, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerDies, td_PlayerDies, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerDamage_Calculate, td_PlayerDamage, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerSpawn, td_PlayerSpawn, CBC_ORDER_ANY);
+ MUTATOR_HOOK(VehicleSpawn, td_DisableVehicles, CBC_ORDER_ANY);
+ MUTATOR_HOOK(SV_ParseClientCommand, td_PlayerCommand, CBC_ORDER_ANY);
+ MUTATOR_HOOK(ClientConnect, td_ClientConnect, 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.");
+ cvar_settemp("g_monsters", "1");
+ cvar_settemp("g_turrets", "1");
+ td_Initialize();
+ }
+
+ MUTATOR_ONROLLBACK_OR_REMOVE
+ {
+ // we actually cannot roll back td_Initialize here
+ // BUT: we don't need to! If this gets called, adding always
+ // succeeds.
+ }
+
+ MUTATOR_ONREMOVE
+ {
+ error("This is a game type and it cannot be removed at runtime.");
+ return -1;
+ }
+
+ return FALSE;
+}
MUTATOR_DECLARATION(gamemode_ctf);
MUTATOR_DECLARATION(gamemode_nexball);
MUTATOR_DECLARATION(gamemode_onslaught);
+MUTATOR_DECLARATION(gamemode_td);
MUTATOR_DECLARATION(gamemode_domination);
+ MUTATOR_DECLARATION(gamemode_lms);
MUTATOR_DECLARATION(mutator_dodging);
MUTATOR_DECLARATION(mutator_invincibleprojectiles);
mutators/gamemode_keyhunt.qh // TODO fix this
mutators/gamemode_keepaway.qh
mutators/gamemode_nexball.qh
+ mutators/gamemode_lms.qh
mutators/mutator_dodging.qh
+mutators/gamemode_towerdefense.qh
//// tZork Turrets ////
tturrets/include/turrets_early.qh
cheats.qc
playerstats.qc
+ round_handler.qc
+
../common/explosion_equation.qc
+monsters/monsters.qh
+
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_freezetag.qc
mutators/gamemode_keepaway.qc
mutators/gamemode_nexball.qc
mutators/gamemode_onslaught.qc
+mutators/gamemode_towerdefense.qc
+ mutators/gamemode_lms.qc
mutators/mutator_invincibleproj.qc
mutators/mutator_new_toys.qc
mutators/mutator_nix.qc
if (other.classname != "player")
return;
++ if (other.frozen)
++ return;
if (other.deadflag)
return;
if (self.solid != SOLID_TRIGGER)