]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/monsters
authorMario <mario.mario@y7mail.com>
Tue, 7 May 2013 08:44:25 +0000 (18:44 +1000)
committerMario <mario.mario@y7mail.com>
Tue, 7 May 2013 08:44:25 +0000 (18:44 +1000)
25 files changed:
1  2 
gamemodes.cfg
qcsrc/client/View.qc
qcsrc/common/constants.qh
qcsrc/common/mapinfo.qc
qcsrc/common/notifications.qh
qcsrc/server/autocvars.qh
qcsrc/server/cl_client.qc
qcsrc/server/cl_player.qc
qcsrc/server/cl_weapons.qc
qcsrc/server/command/cmd.qc
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/g_world.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/mutators/base.qh
qcsrc/server/mutators/gamemode_assault.qc
qcsrc/server/mutators/gamemode_freezetag.qc
qcsrc/server/mutators/gamemode_towerdefense.qc
qcsrc/server/mutators/mutators.qh
qcsrc/server/progs.src
qcsrc/server/sv_main.qc
qcsrc/server/t_items.qc
qcsrc/server/teamplay.qc
qcsrc/server/tturrets/system/system_main.qc
qcsrc/server/vehicles/vehicles.qc

diff --cc gamemodes.cfg
Simple merge
index 6e71bb18176e57e88b607df9b6df11d6514c5c81,d27fd4d98e8927cddc2fa255496a39124c2c27af..d75635037fa96190317cfdb3e3d80d1fc0265eba
@@@ -1073,12 -1073,15 +1073,17 @@@ void CSQC_UpdateView(float w, float h
  
        //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)
index 510a498ccee53605dfcede619b188591e3f503c6,cbda9dcdd5232bfbb6d50753844cddcaf996c18a..2450b25aadafab65f7072ea2448b49167b9b5cb3
@@@ -179,13 -177,8 +179,14 @@@ const float STAT_SECRETS_TOTAL = 70
  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;
Simple merge
index 231a04d300619fdfd291d2a39c82f7cbdce44840,d1aeba001e5e2f6a4c36c5d03a8c1f2f8117febd..93907ecb4e284fbf69e802f87c44ed29ad21822a
@@@ -704,29 -623,8 +715,28 @@@ void Send_Notification_WOVA
        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) \
Simple merge
index 85e2344fbe704707a95230889543f252374c0c6e,075280c264e2094c8ecd40785ad946c00736dec2..24e7048ae4e5475420241fd576c88a7a60253e0a
@@@ -1235,19 -1188,11 +1193,11 @@@ void ClientKill_TeamChange (float targe
  
  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
Simple merge
index ca1f83bfee910922d2f4d47c766bc23bc79dea35,0014e9182d4f4c864559af9f5982902dbc37efcb..0f65bb914c302d677c5cb1dc39fd2aee57a687a8
@@@ -356,7 -350,19 +352,19 @@@ void W_ThrowWeapon(vector velo, vector 
        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;
Simple merge
index 1ff324467aa19bcb37b0d7e3c17f820411e9f94c,fb93b7c7876b3d133d4f5b30b2daa872b56e4aa8..e2707277cde0941f767bf970a6f99b7f3c9107b6
@@@ -587,9 -585,7 +585,10 @@@ float serverflags
  
  .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.
index 468e83a9290250335ef6eb66c73fc86fe98e6208,6687f18ebc24064cf023faaa1c9a6b766cdb9396..8473062c8bf03604d2ba31031ce020bb13b0af1a
@@@ -566,68 -544,6 +544,68 @@@ void Obituary(entity attacker, entity i
        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;
index 3faf84e4a0448915869669a9615ed0faabfdb8e9,1af52b13209eea54f3d915f1a1992a1c0b12cc6f..653bb1f41a5445a3fc9138733f896e2fe296a092
@@@ -262,11 -263,10 +263,11 @@@ void cvar_changes_init(
                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");
@@@ -805,24 -806,6 +807,10 @@@ void spawnfunc_worldspawn (void
        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);
Simple merge
Simple merge
index 0000000000000000000000000000000000000000,bf2594785cff6bbb9b70e426bbd79f23902fcf8d..94e104728f3ab27271f920c49edfe2d474e2c13e
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,614 +1,614 @@@
 -              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;
+ }
index 51d9ca531323da20b8bc00177a347c70fb1f4b58,980a9b20d9aff6c37ac55d344404a4bf9c821b5e..c5afea7de150eee768266329066724de06b6f59d
- 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
  // ================
@@@ -212,21 -323,37 +283,37 @@@ MUTATOR_HOOKFUNCTION(freezetag_RemovePl
  
  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;
  }
  
@@@ -268,6 -397,20 +355,20 @@@ MUTATOR_HOOKFUNCTION(freezetag_PlayerSp
        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
        {
index d74ccc75201d3ac2a13f4eaf2f4e45f39ee56a03,0000000000000000000000000000000000000000..7e02ac1716a1ddaea3d704abeff38d305b200586
mode 100644,000000..100644
--- /dev/null
@@@ -1,1116 -1,0 +1,1119 @@@
 +// 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;
 +}
index 55c453f0691269bd5662aa01f2fbd30e76523966,dab42bd65d8c39287701a4c3cffb3daf808f9609..20b64c324a9d3818a6847951469ab6c82243fa45
@@@ -4,8 -7,8 +7,9 @@@ MUTATOR_DECLARATION(gamemode_keepaway)
  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);
index ab8ea3c79b584347b13b9723124e31bfc3dcebdb,b3cc22ddaa7a785c9a44472025a74e1b0cde3cb3..e8f388e6cfc2dfb6e1361d02f20b2d174d315666
@@@ -40,8 -43,8 +43,9 @@@ mutators/gamemode_domination.q
  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
@@@ -218,11 -216,14 +223,16 @@@ anticheat.q
  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
@@@ -230,7 -231,7 +240,8 @@@ mutators/gamemode_keyhunt.q
  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
Simple merge
index 392c05ce14f3e9b9ac0671b662e510adf34393fd,40abd18b135fe920265d197357927a458308f990..e8b183b8f3422c0c4b693817d9242d2495e8eecd
@@@ -768,6 -764,6 +764,8 @@@ void Item_Touch (void
  
        if (other.classname != "player")
                return;
++      if (other.frozen)
++              return;
        if (other.deadflag)
                return;
        if (self.solid != SOLID_TRIGGER)
Simple merge
Simple merge