]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/monsters
authorMario <mario.mario@y7mail.com>
Fri, 29 Apr 2022 12:43:46 +0000 (22:43 +1000)
committerMario <mario.mario@y7mail.com>
Fri, 29 Apr 2022 12:43:46 +0000 (22:43 +1000)
17 files changed:
1  2 
qcsrc/client/csqcmodel_hooks.qc
qcsrc/client/weapons/projectile.qc
qcsrc/common/gamemodes/gamemode/invasion/sv_invasion.qc
qcsrc/common/gamemodes/gamemode/invasion/sv_invasion.qh
qcsrc/common/mapobjects/func/breakable.qc
qcsrc/common/monsters/monster/golem.qc
qcsrc/common/monsters/monster/mage.qc
qcsrc/common/monsters/monster/spider.qc
qcsrc/common/monsters/monster/spider.qh
qcsrc/common/monsters/monster/zombie.qc
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/notifications/all.inc
qcsrc/common/turrets/sv_turrets.qc
qcsrc/menu/xonotic/util.qc
qcsrc/server/command/common.qc
qcsrc/server/world.qc
qcsrc/server/world.qh

index 2686a8501aec2d56098c1fbf115f10122e2bbeac,7c9234fae33511d22a321fe1188f550c14c8cd3e..06cf44bdbd1bf9b30422a59983a6de93919e386d
  .int lodmodelindex0;
  .int lodmodelindex1;
  .int lodmodelindex2;
 -void CSQCPlayer_LOD_Apply(entity this)
 +void CSQCPlayer_LOD_Apply(entity this, bool isplayer)
  {
 +      int detailreduction = ((isplayer) ? autocvar_cl_playerdetailreduction : autocvar_cl_modeldetailreduction);
 +
        // LOD model loading
        if(this.lodmodelindex0 != this.modelindex)
        {
        }
  
        // apply LOD
 -      if(autocvar_cl_playerdetailreduction <= 0)
 +      if(detailreduction <= 0)
        {
 -              if(autocvar_cl_playerdetailreduction <= -2)
 +              if(detailreduction <= -2)
                        this.modelindex = this.lodmodelindex2;
 -              else if(autocvar_cl_playerdetailreduction <= -1)
 +              else if(detailreduction <= -1)
                        this.modelindex = this.lodmodelindex1;
                else
                        this.modelindex = this.lodmodelindex0;
        }
        else
        {
 -              float distance = vlen(this.origin - view_origin);
 -              float f = (distance * current_viewzoom + 100.0) * autocvar_cl_playerdetailreduction;
 +              float distance = vlen(((isplayer) ? this.origin : NearestPointOnBox(this, view_origin)) - view_origin); // TODO: perhaps it should just use NearestPointOnBox all the time, player hitbox can potentially be huge
 +              float f = (distance * current_viewzoom + 100.0) * detailreduction;
                f *= 1.0 / bound(0.01, view_quality, 1);
                if(f > autocvar_cl_loddistance2)
                        this.modelindex = this.lodmodelindex2;
@@@ -236,18 -234,35 +236,35 @@@ void CSQCPlayer_ModelAppearance_Apply(e
        if(MUTATOR_CALLHOOK(ForcePlayercolors_Skip, this, islocalplayer))
                goto skipforcecolors;
  
+       bool forceplayercolors_enabled = false;
+       #define fpc autocvar_cl_forceplayercolors
+       if (gametype.m_1v1)
+       {
+               if ((myteam != NUM_SPECTATOR) && (fpc == 1 || fpc == 2 || fpc == 3 || fpc == 5))
+                       forceplayercolors_enabled = true;
+       }
+       else if (teamplay)
+       {
+               if ((team_count == 2) && (myteam != NUM_SPECTATOR) && (fpc == 2 || fpc == 4 || fpc == 5))
+                       forceplayercolors_enabled = true;
+       }
+       else
+       {
+               if (fpc == 1 || fpc == 2)
+                       forceplayercolors_enabled = true;
+       }
        // forceplayercolors too
        if(teamplay)
        {
                // own team's color is never forced
-               int forcecolor_friend = 0;
-               int forcecolor_enemy = 0;
+               int forcecolor_friend = 0, forcecolor_enemy = 0;
                entity tm;
  
                if(autocvar_cl_forcemyplayercolors)
                        forcecolor_friend = 1024 + autocvar_cl_forcemyplayercolors;
-               if((autocvar_cl_forceplayercolors == 2 && team_count == 2)
-                       || (autocvar_cl_forceplayercolors == 3 && IS_GAMETYPE(DUEL)))
+               if(forceplayercolors_enabled)
                        forcecolor_enemy = 1024 + autocvar__cl_color;
  
                if(forcecolor_enemy && !forcecolor_friend)
                                this.colormap = forcecolor_enemy;
                }
        }
-       else
+       else // if(!teamplay)
        {
                if(autocvar_cl_forcemyplayercolors && islocalplayer)
                        this.colormap = 1024 + autocvar_cl_forcemyplayercolors;
-               else if(autocvar_cl_forceplayercolors)
+               else if (autocvar_cl_forceuniqueplayercolors && !islocalplayer && !gametype.m_1v1)
+               {
+                       // Assign each enemy unique colors
+                       // pick colors from 0 to 14 since 15 is the rainbow color
+                       // pl01 0 1, pl02 1 2, ..., pl14 13 14, pl15 14 0
+                       // pl16 0 2, pl17 1 3, ..., pl29 13  0, pl30 14 1
+                       int num = this.entnum - 1;
+                       int c1 = num % 15;
+                       int q = floor(num / 15);
+                       int c2 = (c1 + 1 + q) % 15;
+                       this.colormap = 1024 + (c1 << 4) + c2;
+               }
+               else if(forceplayercolors_enabled)
                        this.colormap = player_localnum + 1;
        }
  
@@@ -636,7 -663,7 +665,7 @@@ void CSQCModel_Hook_PreDraw(entity this
        if((this.isplayermodel & ISPLAYER_MODEL) && this.drawmask) // this checks if it's a player MODEL!
        {
                CSQCPlayer_ModelAppearance_Apply(this, (this.isplayermodel & ISPLAYER_LOCAL));
 -              CSQCPlayer_LOD_Apply(this);
 +              CSQCPlayer_LOD_Apply(this, true);
  
                if(!isplayer)
                {
                        }
                }
        }
 +      else
 +              CSQCPlayer_LOD_Apply(this, false);
  
        CSQCModel_AutoTagIndex_Apply(this);
  
@@@ -742,6 -767,7 +771,7 @@@ void CSQCModel_Hook_PostUpdate(entity t
        bool is_playermodel = (substring(this.model, 0, 14) == "models/player/" || substring(this.model, 0, 17) == "models/ok_player/" || 
                                                        (substring(this.model, 0, 16) == "models/monsters/" && (this.isplayermodel & BIT(1))));
        this.isplayermodel = BITSET(this.isplayermodel, ISPLAYER_MODEL, is_playermodel);
+       this.csqcmodel_isdead = false; // workaround for dead players who become a spectator
  
        // save values set by server
        if((this.isplayermodel & ISPLAYER_MODEL))
index d58b7fec8d1ba09fb13d7cde8d7cdba37fb26e2b,5132b4998916f9e9a60f6bb86c6523560b0e2149..604799ca7850cd9cd6695b79e11550639eeb105f
@@@ -88,17 -88,14 +88,14 @@@ void Projectile_Draw(entity this
                drawn = (this.iflags & IFLAG_VALID);
                t = time;
        }
+       bool is_nade = Projectile_isnade(this.cnt);
  
        if (!(f & FL_ONGROUND))
        {
                rot = '0 0 0';
-               switch (this.cnt)
+               if (is_nade) rot = this.avelocity;
+               else switch (this.cnt)
                {
-                       /*
-                       case PROJECTILE_GRENADE:
-                           rot = '-2000 0 0'; // forward
-                           break;
-                       */
                        case PROJECTILE_GRENADE_BOUNCING:
                                rot = '0 -1000 0'; // sideways
                                break;
                        case PROJECTILE_ROCKET:
                                rot = '0 0 720'; // spinning
                                break;
-                       default:
-                               break;
                }
  
-               if (Projectile_isnade(this.cnt))
-                       rot = this.avelocity;
-               this.angles = AnglesTransform_ToAngles(AnglesTransform_Multiply(AnglesTransform_FromAngles(this.angles), rot * (t - this.spawntime)));
+               if (rot)
+               {
+                       if (!rot.x && !rot.y)
+                       {
+                               // cheaper z-only rotation formula
+                               this.angles.z = (rot.z * (t - this.spawntime)) % 360;
+                               if (this.angles.z < 0)
+                                       this.angles.z += 360;
+                       }
+                       else
+                               this.angles = AnglesTransform_ToAngles(AnglesTransform_Multiply(AnglesTransform_FromAngles(this.angles), rot * (t - this.spawntime)));
+               }
        }
  
-       vector ang;
-       ang = this.angles;
-       ang.x = -ang.x;
-       makevectors(ang);
        a = 1 - (time - this.fade_time) * this.fade_rate;
        this.alpha = bound(0, this.alphamod * a, 1);
        if (this.alpha <= 0)
                drawn = 0;
        this.renderflags = 0;
  
+       vector ang = this.angles;
+       ang.x = -ang.x;
        trailorigin = this.origin;
-       switch (this.cnt)
+       if (is_nade)
+       {
+               makevectors(ang);
+               trailorigin += v_up * 4;
+       }
+       else switch (this.cnt)
        {
                case PROJECTILE_GRENADE:
                case PROJECTILE_GRENADE_BOUNCING:
+                       makevectors(ang);
                        trailorigin += v_right * 1 + v_forward * -10;
                        break;
-               default:
-                       break;
        }
  
-       if (Projectile_isnade(this.cnt))
-               trailorigin += v_up * 4;
        if (drawn)
                Projectile_DrawTrail(this, trailorigin);
        else
@@@ -316,7 -317,7 +317,7 @@@ NET_HANDLE(ENT_CLIENT_PROJECTILE, bool 
                        HANDLE(SEEKER)             this.traileffect = EFFECT_SEEKER_TRAIL.m_id; break;
  
                        HANDLE(MAGE_SPIKE)         this.traileffect = EFFECT_TR_VORESPIKE.m_id; break;
 -                      HANDLE(SHAMBLER_LIGHTNING) this.traileffect = EFFECT_TR_NEXUIZPLASMA.m_id; break;
 +                      HANDLE(GOLEM_LIGHTNING) this.traileffect = EFFECT_TR_NEXUIZPLASMA.m_id; break;
  
                        HANDLE(RAPTORBOMB)         this.gravity = 1; this.avelocity = '0 0 180'; this.traileffect = EFFECT_Null.m_id; break;
                        HANDLE(RAPTORBOMBLET)      this.gravity = 1; this.avelocity = '0 0 180'; this.traileffect = EFFECT_Null.m_id; break;
                                this.bouncefactor = WEP_CVAR(mortar, bouncefactor);
                                this.bouncestop = WEP_CVAR(mortar, bouncestop);
                                break;
 -                      case PROJECTILE_SHAMBLER_LIGHTNING:
 +                      case PROJECTILE_GOLEM_LIGHTNING:
                                this.mins = '-8 -8 -8';
                                this.maxs = '8 8 8';
                                this.scale = 2.5;
index d3046ffec358506bd189d9c9d3faf6bff88f33c8,7b47ecec6980f086bfabd7e7d1f7475c3071fdd9..1b6f326b6982ab088067fa30d549452bfb838f96
@@@ -4,20 -4,12 +4,12 @@@
  #include <common/monsters/sv_spawn.qh>
  #include <common/monsters/sv_spawner.qh>
  #include <common/monsters/sv_monsters.qh>
+ #include <common/mutators/mutator/status_effects/_mod.qh>
  #include <server/bot/api.qh>
  #include <server/world.qh>
  #include <server/teamplay.qh>
  
- IntrusiveList g_invasion_roundends;
- IntrusiveList g_invasion_waves;
- IntrusiveList g_invasion_spawns;
- STATIC_INIT(g_invasion)
- {
-       g_invasion_roundends = IL_NEW();
-       g_invasion_waves = IL_NEW();
-       g_invasion_spawns = IL_NEW();
- }
  float autocvar_g_invasion_round_timelimit;
  float autocvar_g_invasion_spawnpoint_spawn_delay;
  float autocvar_g_invasion_warmup;
@@@ -98,7 -90,7 +90,7 @@@ int WinningCondition_Invasion(
                        if(it.winning)
                        {
                                bprint("Invasion: round completed.\n");
 -                              // winners already set (TODO: teamplay support)
 +                              // winners already set
  
                                status = WINNING_YES;
                                break;
@@@ -137,8 -129,8 +129,8 @@@ Monster invasion_PickMonster(int superm
  
        FOREACH(Monsters, it != MON_Null,
        {
 -              if((it.spawnflags & MON_FLAG_HIDDEN) || (it.spawnflags & MONSTER_TYPE_PASSIVE) || (it.spawnflags & MONSTER_TYPE_FLY) || (it.spawnflags & MONSTER_TYPE_SWIM) ||
 -                      (it.spawnflags & MONSTER_SIZE_QUAKE) || ((it.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
 +              if((it.spawnflags & MON_FLAG_HIDDEN) || (it.spawnflags & MONSTER_TYPE_PASSIVE) || (it.spawnflags & MONSTER_TYPE_FLY) || (it.spawnflags & MONSTER_TYPE_SWIM)
 +                      || (it.spawnflags & MONSTER_SIZE_QUAKE) || ((it.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
                        continue;
                if(autocvar_g_invasion_zombies_only && !(it.spawnflags & MONSTER_TYPE_UNDEAD))
                        continue;
@@@ -208,10 -200,7 +200,10 @@@ void invasion_SpawnChosenMonster(Monste
                setsize(e, mon.m_mins, mon.m_maxs);
  
                if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
 +              {
                        monster = spawnmonster(e, tospawn, mon, NULL, NULL, e.origin, false, false, 2);
 +                      monster.angles_x = monster.angles_z = 0;
 +              }
                else
                {
                        delete(e);
        if(!monster)
                return;
  
-       monster.spawnshieldtime = time;
+       StatusEffects_remove(STATUSEFFECT_SpawnShield, monster, STATUSEFFECT_REMOVE_NORMAL);
  
        if(spawn_point)
        {
                monster.target2 = spawn_point.target2;
        }
  
 -      if(teamplay)
 -      {
 -              if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0)
 -                      monster.team = spawn_point.team;
 -              else
 -              {
 -                      RandomSelection_Init();
 -                      if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_AddFloat(NUM_TEAM_1, 1, 1);
 -                      if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_AddFloat(NUM_TEAM_2, 1, 1);
 -                      if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_AddFloat(NUM_TEAM_3, 1, 1); }
 -                      if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_AddFloat(NUM_TEAM_4, 1, 1); }
 -
 -                      monster.team = RandomSelection_chosen_float;
 -              }
 -
 -              monster_setupcolors(monster);
 -
 -              if(monster.sprite)
 -              {
 -                      WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0'));
 -
 -                      monster.sprite.team = 0;
 -                      monster.sprite.SendFlags |= 1;
 -              }
 -      }
 -
        if(monster.monster_attack)
                IL_REMOVE(g_monster_targets, monster);
        monster.monster_attack = false; // it's the player's job to kill all the monsters
@@@ -264,13 -279,22 +256,13 @@@ bool Invasion_CheckWinner(
                return 1;
        }
  
 -      float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;
 +      float total_alive_monsters = 0, supermonster_count = 0;
  
        IL_EACH(g_monsters, GetResource(it, RES_HEALTH) > 0,
        {
                if(it.monsterdef.spawnflags & MON_FLAG_SUPERMONSTER)
                        ++supermonster_count;
                ++total_alive_monsters;
 -
 -              if(teamplay)
 -              switch(it.team)
 -              {
 -                      case NUM_TEAM_1: ++red_alive; break;
 -                      case NUM_TEAM_2: ++blue_alive; break;
 -                      case NUM_TEAM_3: ++yellow_alive; break;
 -                      case NUM_TEAM_4: ++pink_alive; break;
 -              }
        });
  
        if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
        if(inv_numspawned < 1)
                return 0; // nothing has spawned yet
  
 -      if(teamplay)
 -      {
 -              if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1)
 -                      return 0;
 -      }
 -      else if(inv_numkilled < inv_maxspawned)
 +      if(inv_numkilled < inv_maxspawned)
                return 0;
  
        entity winner = NULL;
 -      float winning_score = 0, winner_team = 0;
 +      float winning_score = 0;
  
 -
 -      if(teamplay)
 -      {
 -              if(red_alive > 0) { winner_team = NUM_TEAM_1; }
 -              if(blue_alive > 0)
 -              {
 -                      if(winner_team) { winner_team = 0; }
 -                      else { winner_team = NUM_TEAM_2; }
 -              }
 -              if(yellow_alive > 0)
 -              {
 -                      if(winner_team) { winner_team = 0; }
 -                      else { winner_team = NUM_TEAM_3; }
 -              }
 -              if(pink_alive > 0)
 +      FOREACH_CLIENT(IS_PLAYER(it), {
 +              float cs = GameRules_scoring_add(it, KILLS, 0);
 +              if(cs > winning_score)
                {
 -                      if(winner_team) { winner_team = 0; }
 -                      else { winner_team = NUM_TEAM_4; }
 +                      winning_score = cs;
 +                      winner = it;
                }
 -      }
 -      else
 -      {
 -              FOREACH_CLIENT(IS_PLAYER(it), {
 -                      float cs = GameRules_scoring_add(it, KILLS, 0);
 -                      if(cs > winning_score)
 -                      {
 -                              winning_score = cs;
 -                              winner = it;
 -                      }
 -              });
 -      }
 +      });
  
        IL_EACH(g_monsters, true,
        {
        });
        IL_CLEAR(g_monsters);
  
 -      if(teamplay)
 -      {
 -              if(winner_team)
 -              {
 -                      Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
 -                      Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
 -              }
 -      }
 -      else if(winner)
 +      if(winner)
        {
                Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
@@@ -342,6 -402,15 +334,6 @@@ void Invasion_RoundStart(
        inv_numkilled = 0;
  
        inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
 -
 -      if(teamplay)
 -      {
 -              DistributeEvenly_Init(inv_maxspawned, invasion_teams);
 -              inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1);
 -              inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1);
 -              if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1);
 -              if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1);
 -      }
  }
  
  MUTATOR_HOOKFUNCTION(inv, MonsterDies)
                        inv_numkilled += 1;
                        inv_maxcurrent -= 1;
                }
 -              if(teamplay) { inv_monsters_perteam[frag_target.team] -= 1; }
  
                if(IS_PLAYER(frag_attacker))
                {
 -                      if(SAME_TEAM(frag_attacker, frag_target)) // in non-teamplay modes, same team = same player, so this works
 +                      if(SAME_TEAM(frag_attacker, frag_target))
                                GameRules_scoring_add(frag_attacker, KILLS, -1);
                        else
 -                      {
                                GameRules_scoring_add(frag_attacker, KILLS, +1);
 -                              if(teamplay)
 -                                      TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
 -                      }
                }
        }
  }
@@@ -473,28 -547,44 +465,28 @@@ MUTATOR_HOOKFUNCTION(inv, CheckRules_Wo
        return true;
  }
  
 -MUTATOR_HOOKFUNCTION(inv, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
 -{
 -      M_ARGV(0, float) = invasion_teams;
 -      return true;
 -}
 -
  MUTATOR_HOOKFUNCTION(inv, AllowMobButcher)
  {
        M_ARGV(0, string) = "This command does not work during an invasion!";
        return true;
  }
  
 -void invasion_ScoreRules(int inv_teams)
 +void invasion_ScoreRules()
  {
        GameRules_score_enabled(false);
 -      GameRules_scoring(inv_teams, 0, 0, {
 -          if (inv_teams) {
 -            field_team(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
 -          }
 -          field(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
 +      GameRules_scoring(0, 0, 0, {
 +          field(SP_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
        });
  }
  
 -void invasion_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
 +void invasion_DelayedInit(entity this)
  {
        if(autocvar_g_invasion_type == INV_TYPE_HUNT || autocvar_g_invasion_type == INV_TYPE_STAGE)
                cvar_set("fraglimit", "0");
  
 -      if(autocvar_g_invasion_teams)
 -      {
 -              invasion_teams = BITS(bound(2, autocvar_g_invasion_teams, 4));
 -      }
 -      else
 -              invasion_teams = 0;
 -
        independent_players = 1; // to disable extra useless scores
  
 -      invasion_ScoreRules(invasion_teams);
 +      invasion_ScoreRules();
  
        independent_players = 0;
  
index 2011a35982f30070ebb7058264d96a9bc8c0d17d,3c16e34e18dea1b1179760cadb65aea08c3a0ce9..f3da6b6782b72a532948654335bc0ed64f289b76
@@@ -2,8 -2,13 +2,11 @@@
  
  #include <common/mutators/base.qh>
  #define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
 -int autocvar_g_invasion_teams;
  int autocvar_g_invasion_type;
 -bool autocvar_g_invasion_team_spawns;
  bool g_invasion;
+ IntrusiveList g_invasion_roundends;
+ IntrusiveList g_invasion_waves;
+ IntrusiveList g_invasion_spawns;
  void invasion_Initialize();
  
  REGISTER_MUTATOR(inv, false)
      MUTATOR_STATIC();
        MUTATOR_ONADD
        {
+               g_invasion_roundends = IL_NEW();
+               g_invasion_waves = IL_NEW();
+               g_invasion_spawns = IL_NEW();
+               if (autocvar_g_invasion_teams >= 2) {
+                       GameRules_teams(true);
+                       GameRules_spawning_teams(autocvar_g_invasion_team_spawns);
+               }
          GameRules_limit_score(autocvar_g_invasion_point_limit);
  
                g_invasion = true;
        return 0;
  }
  
 -float inv_numspawned;
 -float inv_maxspawned;
 -float inv_roundcnt;
 -float inv_maxrounds;
 -float inv_numkilled;
 +int inv_numspawned;
 +int inv_maxspawned;
 +int inv_roundcnt;
 +int inv_maxrounds;
 +int inv_numkilled;
  float inv_lastcheck;
 -float inv_maxcurrent;
 -
 -float invasion_teams;
 -float inv_monsters_perteam[17];
 +int inv_maxcurrent;
  
  float inv_monsterskill;
  
 -const float ST_INV_KILLS = 1;
 -
  const int INV_TYPE_ROUND = 0; // round-based waves of enemies
  const int INV_TYPE_HUNT = 1; // clear the map of placed enemies
  const int INV_TYPE_STAGE = 2; // reach the end of the level
index a35c1b17346a4d241d36b29e6ea3aa0f1e1ab585,ea71499fdb6b634bcbe2b9d381cc2a3a9534b238..6064603591492332ac4089011059b7e6f708bcbd
@@@ -311,8 -311,7 +311,7 @@@ void func_breakable_reset(entity this
                func_breakable_behave_restore(this);
  }
  
- // destructible walls that can be used to trigger target_objective_decrease
- spawnfunc(func_breakable)
+ void func_breakable_setup(entity this)
  {
        float n, i;
        if(!GetResource(this, RES_HEALTH))
                this.takedamage = DAMAGE_NO;
                this.event_damage = func_null;
                this.bot_attack = false;
 +              this.monster_attack = false;
        }
  
        // precache all the models
        this.reset = func_breakable_reset;
        this.reset(this);
  
 +      if(this.monster_attack)
 +              IL_PUSH(g_monster_targets, this);
 +
        IL_PUSH(g_initforplayer, this);
        this.init_for_player = func_breakable_init_for_player;
  
  }
  
  // for use in maps with a "model" key set
- spawnfunc(misc_breakablemodel) {
-       spawnfunc_func_breakable(this);
- }
+ spawnfunc(misc_breakablemodel) { func_breakable_setup(this); }
+ // destructible walls that can be used to trigger target_objective_decrease
+ spawnfunc(func_breakable) { func_breakable_setup(this); }
  #endif
index 269630057329fe57652d5377e8acc9af98fa894d,0000000000000000000000000000000000000000..a9d9224dad59a66958caf4dd9ebdaa001138e405
mode 100644,000000..100644
--- /dev/null
@@@ -1,294 -1,0 +1,294 @@@
-     actor.spawnshieldtime = actor.spawn_time;
 +#include "golem.qh"
 +
 +#ifdef SVQC
 +float autocvar_g_monster_golem_health;
 +float autocvar_g_monster_golem_damageforcescale = 0.1;
 +float autocvar_g_monster_golem_attack_smash_damage;
 +float autocvar_g_monster_golem_attack_smash_force = 100;
 +float autocvar_g_monster_golem_attack_smash_range = 200;
 +float autocvar_g_monster_golem_attack_claw_damage;
 +float autocvar_g_monster_golem_attack_lightning_damage;
 +float autocvar_g_monster_golem_attack_lightning_damage_zap = 15;
 +float autocvar_g_monster_golem_attack_lightning_force;
 +float autocvar_g_monster_golem_attack_lightning_radius;
 +float autocvar_g_monster_golem_attack_lightning_radius_zap;
 +float autocvar_g_monster_golem_attack_lightning_speed;
 +float autocvar_g_monster_golem_attack_lightning_speed_up;
 +float autocvar_g_monster_golem_speed_stop;
 +float autocvar_g_monster_golem_speed_run;
 +float autocvar_g_monster_golem_speed_walk;
 +
 +/*
 +const float golem_anim_stand          = 0;
 +const float golem_anim_walk           = 1;
 +const float golem_anim_run            = 2;
 +const float golem_anim_smash          = 3;
 +const float golem_anim_swingr = 4;
 +const float golem_anim_swingl = 5;
 +const float golem_anim_magic          = 6;
 +const float golem_anim_pain           = 7;
 +const float golem_anim_death          = 8;
 +*/
 +
 +.float golem_lastattack; // delay attacks separately
 +
 +void M_Golem_Attack_Smash(entity this)
 +{
 +      makevectors(this.angles);
 +      Send_Effect(EFFECT_EXPLOSION_MEDIUM, (this.origin + (v_forward * 150)) - ('0 0 1' * this.maxs.z), '0 0 0', 1);
 +      sound(this, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
 +
 +      vector loc = this.origin + v_forward * 50;
 +
 +      entity dmgent = spawn();
 +      dmgent.owner = dmgent.realowner = this;
 +      setorigin(dmgent, loc);
 +      RadiusDamage (dmgent, this, (autocvar_g_monster_golem_attack_smash_damage) * MONSTER_SKILLMOD(this), (autocvar_g_monster_golem_attack_smash_damage * 0.5) * MONSTER_SKILLMOD(this), 
 +                                      autocvar_g_monster_golem_attack_smash_range, this, NULL, autocvar_g_monster_golem_attack_smash_force, DEATH_MONSTER_GOLEM_SMASH.m_id, DMG_NOWEP, NULL);
 +      delete(dmgent);
 +}
 +
 +void M_Golem_Attack_Swing(entity this)
 +{
 +      Monster_Attack_Melee(this, this.enemy, (autocvar_g_monster_golem_attack_claw_damage), ((random() >= 0.5) ? this.anim_melee2 : this.anim_melee3), this.attack_range, 0.8, DEATH_MONSTER_GOLEM_CLAW.m_id, true);
 +}
 +
 +#include <common/effects/qc/_mod.qh>
 +
 +void M_Golem_Attack_Lightning_Explode(entity this, entity directhitentity)
 +{
 +      sound(this, CH_SHOTS, SND_ELECTRO_IMPACT, VOL_BASE, ATTEN_NORM);
 +      Send_Effect(EFFECT_ELECTRO_IMPACT, this.origin, '0 0 0', 1);
 +
 +      this.event_damage = func_null;
 +      this.takedamage = DAMAGE_NO;
 +      set_movetype(this, MOVETYPE_NONE);
 +      this.velocity = '0 0 0';
 +
 +      if(this.move_movetype == MOVETYPE_NONE)
 +              this.velocity = this.oldvelocity;
 +
 +      RadiusDamage (this, this.realowner, (autocvar_g_monster_golem_attack_lightning_damage), (autocvar_g_monster_golem_attack_lightning_damage), (autocvar_g_monster_golem_attack_lightning_radius),
 +                                      NULL, NULL, (autocvar_g_monster_golem_attack_lightning_force), this.projectiledeathtype, DMG_NOWEP, directhitentity);
 +
 +      FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_monster_golem_attack_lightning_radius_zap, it != this.realowner && it.takedamage,
 +      {
 +              te_csqc_lightningarc(this.origin, it.origin);
 +              Damage(it, this, this.realowner, (autocvar_g_monster_golem_attack_lightning_damage_zap) * MONSTER_SKILLMOD(this), DEATH_MONSTER_GOLEM_ZAP.m_id, DMG_NOWEP, it.origin, '0 0 0');
 +      });
 +
 +      setthink(this, SUB_Remove);
 +      this.nextthink = time + 0.2;
 +}
 +
 +void M_Golem_Attack_Lightning_Explode_use(entity this, entity actor, entity trigger)
 +{
 +      M_Golem_Attack_Lightning_Explode(this, trigger);
 +}
 +
 +void M_Golem_Attack_Lightning_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 +{
 +      if (GetResource(this, RES_HEALTH) <= 0)
 +              return;
 +
 +      if (!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
 +              return; // g_projectiles_damage says to halt
 +
 +      TakeResource(this, RES_HEALTH, damage);
 +
 +      if (GetResource(this, RES_HEALTH) <= 0)
 +              W_PrepareExplosionByDamage(this, attacker, adaptor_think2use);
 +}
 +
 +void M_Golem_Attack_Lightning_Touch(entity this, entity toucher)
 +{
 +      PROJECTILE_TOUCH(this, toucher);
 +
 +      this.use(this, NULL, toucher);
 +}
 +
 +void M_Golem_Attack_Lightning_Think(entity this)
 +{
 +      this.nextthink = time;
 +      if (time > this.cnt)
 +      {
 +              M_Golem_Attack_Lightning_Explode(this, NULL);
 +              return;
 +      }
 +}
 +
 +void M_Golem_Attack_Lightning(entity this)
 +{
 +      entity gren;
 +
 +      monster_makevectors(this, this.enemy);
 +
 +      gren = new(grenade);
 +      gren.owner = gren.realowner = this;
 +      gren.bot_dodge = true;
 +      gren.bot_dodgerating = (autocvar_g_monster_golem_attack_lightning_damage);
 +      set_movetype(gren, MOVETYPE_BOUNCE);
 +      PROJECTILE_MAKETRIGGER(gren);
 +      gren.projectiledeathtype = DEATH_MONSTER_GOLEM_ZAP.m_id;
 +      setorigin(gren, CENTER_OR_VIEWOFS(this));
 +      setsize(gren, '-8 -8 -8', '8 8 8');
 +      gren.scale = 2.5;
 +
 +      gren.cnt = time + 5;
 +      gren.nextthink = time;
 +      setthink(gren, M_Golem_Attack_Lightning_Think);
 +      gren.use = M_Golem_Attack_Lightning_Explode_use;
 +      settouch(gren, M_Golem_Attack_Lightning_Touch);
 +
 +      gren.takedamage = DAMAGE_YES;
 +      SetResourceExplicit(gren, RES_HEALTH, 50);
 +      gren.damageforcescale = 0;
 +      gren.event_damage = M_Golem_Attack_Lightning_Damage;
 +      gren.damagedbycontents = true;
 +      IL_PUSH(g_damagedbycontents, gren);
 +      gren.missile_flags = MIF_SPLASH | MIF_ARC;
 +      W_SetupProjVelocity_Explicit(gren, v_forward, v_up, (autocvar_g_monster_golem_attack_lightning_speed), (autocvar_g_monster_golem_attack_lightning_speed_up), 0, 0, false);
 +
 +      gren.angles = vectoangles (gren.velocity);
 +      gren.flags = FL_PROJECTILE;
 +      IL_PUSH(g_projectiles, gren);
 +      IL_PUSH(g_bot_dodge, gren);
 +
 +      CSQCProjectile(gren, true, PROJECTILE_GOLEM_LIGHTNING, true);
 +}
 +
 +.int state;
 +
 +bool M_Golem_Attack(int attack_type, entity actor, entity targ, .entity weaponentity)
 +{
 +      switch(attack_type)
 +      {
 +              case MONSTER_ATTACK_MELEE:
 +              {
 +                      setanim(actor, ((random() >= 0.5) ? actor.anim_melee2 : actor.anim_melee3), false, true, true);
 +                      int swing_cnt = bound(1, floor(random() * 4), 3);
 +                      Monster_Delay(actor, swing_cnt, 0.5, M_Golem_Attack_Swing);
 +                      actor.anim_finished = actor.attack_finished_single[0] = time + (0.5 * swing_cnt); // set this for the delay
 +                      return true;
 +              }
 +              case MONSTER_ATTACK_RANGED:
 +              {
 +                      float randomness = random();
 +
 +                      if(time < actor.golem_lastattack || !IS_ONGROUND(actor))
 +                              return false;
 +
 +                      if(randomness <= 0.5 && vdist(actor.enemy.origin - actor.origin, <=, autocvar_g_monster_golem_attack_smash_range))
 +                      {
 +                              setanim(actor, actor.anim_melee1, false, true, true);
 +                              Monster_Delay(actor, 1, 1.1, M_Golem_Attack_Smash);
 +                              if(actor.animstate_endtime > time)
 +                                      actor.anim_finished = actor.animstate_endtime;
 +                              else
 +                                      actor.anim_finished = time + 1.2;
 +                              actor.attack_finished_single[0] = actor.anim_finished + 0.2;
 +                              actor.state = MONSTER_ATTACK_MELEE; // kinda a melee attack
 +                              actor.golem_lastattack = time + 3 + random() * 1.5;
 +                              return true;
 +                      }
 +                      else if(randomness <= 0.1 && vdist(actor.enemy.origin - actor.origin, >=, autocvar_g_monster_golem_attack_smash_range * 1.5)) // small chance, don't want this spammed
 +                      {
 +                              setanim(actor, actor.anim_melee2, true, true, false);
 +                              actor.state = MONSTER_ATTACK_MELEE; // maybe we should rename this to something more general
 +                              actor.attack_finished_single[0] = time + 1.1;
 +                              actor.anim_finished = 1.1;
 +                              actor.golem_lastattack = time + 3 + random() * 1.5;
 +                              Monster_Delay(actor, 1, 0.6, M_Golem_Attack_Lightning);
 +                              return true;
 +                      }
 +
 +                      return false;
 +              }
 +      }
 +
 +      return false;
 +}
 +
 +spawnfunc(monster_golem) { Monster_Spawn(this, true, MON_GOLEM); }
 +// compatibility
 +spawnfunc(monster_shambler) { spawnfunc_monster_golem(this); }
 +#endif // SVQC
 +
 +#ifdef SVQC
 +METHOD(Golem, mr_think, bool(Golem this, entity actor))
 +{
 +    TC(Golem, this);
 +    return true;
 +}
 +
 +METHOD(Golem, mr_pain, float(Golem this, entity actor, float damage_take, entity attacker, float deathtype))
 +{
 +    TC(Golem, this);
 +    actor.pain_finished = time + 0.5;
 +    setanim(actor, ((random() >= 0.5) ? actor.anim_pain2 : actor.anim_pain1), true, true, false);
 +    return damage_take;
 +}
 +
 +METHOD(Golem, mr_death, bool(Golem this, entity actor))
 +{
 +    TC(Golem, this);
 +    setanim(actor, actor.anim_die1, false, true, true);
 +    return true;
 +}
 +#endif
 +#ifdef GAMEQC
 +METHOD(Golem, mr_anim, bool(Golem this, entity actor))
 +{
 +    TC(Golem, this);
 +    vector none = '0 0 0';
 +    actor.anim_idle = animfixfps(actor, '0 1 1', none);
 +    actor.anim_walk = animfixfps(actor, '1 1 1', none);
 +    actor.anim_run = animfixfps(actor, '2 1 1', none);
 +    //actor.anim_melee1 = animfixfps(actor, '3 1 5', none); // analyze models and set framerate
 +    actor.anim_melee2 = animfixfps(actor, '4 1 5', none); // analyze models and set framerate
 +    actor.anim_melee3 = animfixfps(actor, '5 1 5', none); // analyze models and set framerate
 +    //actor.anim_melee4 = animfixfps(actor, '6 1 5', none); // analyze models and set framerate
 +    actor.anim_melee1 = animfixfps(actor, '6 1 5', none); // analyze models and set framerate
 +    actor.anim_pain1 = animfixfps(actor, '7 1 2', none); // 0.5 seconds
 +    actor.anim_pain2 = animfixfps(actor, '8 1 2', none); // 0.5 seconds
 +    //actor.anim_pain3 = animfixfps(actor, '9 1 2', none); // 0.5 seconds
 +    //actor.anim_pain4 = animfixfps(actor, '10 1 2', none); // 0.5 seconds
 +    //actor.anim_pain5 = animfixfps(actor, '11 1 2', none); // 0.5 seconds
 +    actor.anim_spawn = animfixfps(actor, '12 1 5', none); // analyze models and set framerate
 +    actor.anim_die1 = animfixfps(actor, '13 1 0.5', none); // 2 seconds
 +    //actor.anim_dead = animfixfps(actor, '14 1 0.5', none); // 2 seconds
 +    actor.anim_die2 = animfixfps(actor, '15 1 0.5', none); // 2 seconds
 +    // dead2 16
 +    //actor.anim_dieback = animfixfps(actor, '16 1 0.5', none); // 2 seconds
 +    //actor.anim_deadback = animfixfps(actor, '17 1 0.5', none); // 2 seconds
 +    //actor.anim_dead2 = animfixfps(actor, '18 1 0.5', none); // 2 seconds
 +    //actor.anim_dead3 = animfixfps(actor, '19 1 0.5', none); // 2 seconds
 +    //actor.anim_dead4 = animfixfps(actor, '20 1 0.5', none); // 2 seconds
 +    //actor.anim_dead5 = animfixfps(actor, '21 1 0.5', none); // 2 seconds
 +    //actor.anim_dead6 = animfixfps(actor, '22 1 0.5', none); // 2 seconds
 +    return true;
 +}
 +#endif
 +#ifdef SVQC
 +.float animstate_endtime;
 +METHOD(Golem, mr_setup, bool(Golem this, entity actor))
 +{
 +    TC(Golem, this);
 +    if(!GetResource(actor, RES_HEALTH)) SetResourceExplicit(actor, RES_HEALTH, autocvar_g_monster_golem_health);
 +    if(!actor.attack_range) actor.attack_range = 150;
 +    if(!actor.speed) { actor.speed = (autocvar_g_monster_golem_speed_walk); }
 +    if(!actor.speed2) { actor.speed2 = (autocvar_g_monster_golem_speed_run); }
 +    if(!actor.stopspeed) { actor.stopspeed = (autocvar_g_monster_golem_speed_stop); }
 +    if(!actor.damageforcescale) { actor.damageforcescale = (autocvar_g_monster_golem_damageforcescale); }
 +
 +    actor.monster_loot = ITEM_HealthMega;
 +    actor.weapon = WEP_ELECTRO.m_id; // matches attacks better than WEP_VORTEX
 +
 +    setanim(actor, actor.anim_spawn, false, true, true);
 +    actor.spawn_time = actor.animstate_endtime;
++    StatusEffects_apply(STATUSEFFECT_SpawnShield, actor, actor.spawn_time, 0);
 +    actor.monster_attackfunc = M_Golem_Attack;
 +
 +    return true;
 +}
 +#endif
index 70496905b43c98a3840a92079f99a3bb76ed3ae5,74a041b156c26784041019f6ee126c21658f920e..f307b8aa1da4d0972bc6c0ca38fe60db39b641c4
@@@ -47,22 -47,16 +47,22 @@@ float autocvar_g_monster_mage_attack_sp
  float autocvar_g_monster_mage_attack_spike_delay;
  float autocvar_g_monster_mage_attack_spike_accel;
  float autocvar_g_monster_mage_attack_spike_decel;
 +float autocvar_g_monster_mage_attack_spike_chance = 0.45;
  float autocvar_g_monster_mage_attack_spike_turnrate;
  float autocvar_g_monster_mage_attack_spike_speed_max;
  float autocvar_g_monster_mage_attack_spike_smart;
  float autocvar_g_monster_mage_attack_spike_smart_trace_min;
  float autocvar_g_monster_mage_attack_spike_smart_trace_max;
  float autocvar_g_monster_mage_attack_spike_smart_mindist;
 +float autocvar_g_monster_mage_attack_push_chance = 0.7;
  float autocvar_g_monster_mage_attack_push_damage;
  float autocvar_g_monster_mage_attack_push_radius;
  float autocvar_g_monster_mage_attack_push_delay;
  float autocvar_g_monster_mage_attack_push_force;
 +float autocvar_g_monster_mage_attack_teleport_chance = 0.2;
 +float autocvar_g_monster_mage_attack_teleport_delay = 2;
 +float autocvar_g_monster_mage_attack_teleport_random = 0.4;
 +float autocvar_g_monster_mage_attack_teleport_random_range = 1200;
  float autocvar_g_monster_mage_heal_self;
  float autocvar_g_monster_mage_heal_allies;
  float autocvar_g_monster_mage_heal_minhealth;
@@@ -89,7 -83,6 +89,6 @@@ void M_Mage_Defend_Shield(entity this)
  
  .entity mage_spike;
  .float mage_shield_delay;
- .float mage_shield_time;
  
  bool M_Mage_Defend_Heal_Check(entity this, entity targ)
  {
                return false;
        if(!IS_PLAYER(targ))
                return (IS_MONSTER(targ) && GetResource(targ, RES_HEALTH) < targ.max_health);
-       if(targ.items & ITEM_Shield.m_itemid)
+       if(StatusEffects_active(STATUSEFFECT_Shield, targ))
                return false;
  
        switch(this.skin)
@@@ -231,7 -224,7 +230,7 @@@ void M_Mage_Attack_Spike(entity this, v
  
  void M_Mage_Defend_Heal(entity this)
  {
 -      float washealed = false;
 +      bool washealed = false;
  
        FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_monster_mage_heal_range, M_Mage_Defend_Heal_Check(this, it),
        {
                                }
                                case 1:
                                {
 -                                      if(GetResource(this, RES_CELLS)) GiveResourceWithLimit(it, RES_CELLS, 1, g_pickup_cells_max);
 -                                      if(GetResource(this, RES_PLASMA)) GiveResourceWithLimit(it, RES_PLASMA, 1, g_pickup_plasma_max);
 -                                      if(GetResource(this, RES_ROCKETS)) GiveResourceWithLimit(it, RES_ROCKETS, 1, g_pickup_rockets_max);
 -                                      if(GetResource(this, RES_SHELLS)) GiveResourceWithLimit(it, RES_SHELLS, 2, g_pickup_shells_max);
 -                                      if(GetResource(this, RES_BULLETS)) GiveResourceWithLimit(it, RES_BULLETS, 5, g_pickup_nails_max);
 +                                      if(GetResource(it, RES_CELLS)) GiveResourceWithLimit(it, RES_CELLS, 1, g_pickup_cells_max);
 +                                      if(GetResource(it, RES_PLASMA)) GiveResourceWithLimit(it, RES_PLASMA, 1, g_pickup_plasma_max);
 +                                      if(GetResource(it, RES_ROCKETS)) GiveResourceWithLimit(it, RES_ROCKETS, 1, g_pickup_rockets_max);
 +                                      if(GetResource(it, RES_SHELLS)) GiveResourceWithLimit(it, RES_SHELLS, 2, g_pickup_shells_max);
 +                                      if(GetResource(it, RES_BULLETS)) GiveResourceWithLimit(it, RES_BULLETS, 5, g_pickup_nails_max);
                                        // TODO: fuel?
                                        fx = EFFECT_AMMO_REGEN;
                                        break;
  
        if(washealed)
        {
 -              setanim(this, this.anim_shoot, true, true, true);
 +              setanim(this, this.anim_melee, true, true, true);
                this.attack_finished_single[0] = time + (autocvar_g_monster_mage_heal_delay);
 +              this.state = MONSTER_ATTACK_MELEE;
                this.anim_finished = time + 1.5;
        }
  }
@@@ -299,10 -291,8 +298,10 @@@ void M_Mage_Attack_Push(entity this
                                                NULL, NULL, (autocvar_g_monster_mage_attack_push_force), DEATH_MONSTER_MAGE.m_id, DMG_NOWEP, this.enemy);
        Send_Effect(EFFECT_TE_EXPLOSION, this.origin, '0 0 0', 1);
  
 -      setanim(this, this.anim_shoot, true, true, true);
 +      setanim(this, this.anim_duckjump, true, true, true);
        this.attack_finished_single[0] = time + (autocvar_g_monster_mage_attack_push_delay);
 +      this.anim_finished = time + 1;
 +      this.state = MONSTER_ATTACK_MELEE; // prevent moving while firing spike
  }
  
  void M_Mage_Attack_Teleport(entity this, entity targ)
        if(!targ) return;
        if(vdist(targ.origin - this.origin, >, 1500)) return;
  
 +      if(autocvar_g_monster_mage_attack_teleport_random && random() <= autocvar_g_monster_mage_attack_teleport_random)
 +      {
 +              vector oldpos = this.origin;
 +              vector extrasize = '1 1 1' * autocvar_g_monster_mage_attack_teleport_random_range;
 +              if(MoveToRandomLocationWithinBounds(this, this.absmin - extrasize, this.absmax + extrasize,
 +                                                                                      DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, 
 +                                                                                      Q3SURFACEFLAG_SKY, 10, 64, 256, true))
 +              {
 +                      vector a = vectoangles(targ.origin - this.origin);
 +                      this.angles = '0 1 0' * a.y;
 +                      this.fixangle = true;
 +                      Send_Effect(EFFECT_SPAWN_NEUTRAL, oldpos, '0 0 0', 1);
 +                      Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
 +                      this.attack_finished_single[0] = time + autocvar_g_monster_mage_attack_teleport_delay;
 +                      return;
 +              }
 +      }
 +
 +      if(!IS_ONGROUND(targ)) return;
 +
        makevectors(targ.angles);
 -      tracebox(targ.origin + ((v_forward * -1) * 200), this.mins, this.maxs, this.origin, MOVE_NOMONSTERS, this);
 +      tracebox(CENTER_OR_VIEWOFS(targ), this.mins, this.maxs, CENTER_OR_VIEWOFS(targ) + ((v_forward * -1) * 200), MOVE_NOMONSTERS, this);
  
        if(trace_fraction < 1)
                return;
  
 -      vector newpos = targ.origin + ((v_forward * -1) * 200);
 +      vector newpos = trace_endpos;
  
        Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
        Send_Effect(EFFECT_SPAWN_NEUTRAL, newpos, '0 0 0', 1);
        this.fixangle = true;
        this.velocity *= 0.5;
  
 -      this.attack_finished_single[0] = time + 0.2;
 +      this.attack_finished_single[0] = time + autocvar_g_monster_mage_attack_teleport_delay;
  }
  
- void M_Mage_Defend_Shield_Remove(entity this)
- {
-       this.effects &= ~(EF_ADDITIVE | EF_BLUE);
-       SetResourceExplicit(this, RES_ARMOR, autocvar_g_monsters_armor_blockpercent);
- }
  void M_Mage_Defend_Shield(entity this)
  {
-       this.effects |= (EF_ADDITIVE | EF_BLUE);
+       StatusEffects_apply(STATUSEFFECT_Shield, this, time + autocvar_g_monster_mage_shield_time, 0);
        this.mage_shield_delay = time + (autocvar_g_monster_mage_shield_delay);
        SetResourceExplicit(this, RES_ARMOR, autocvar_g_monster_mage_shield_blockpercent);
-       this.mage_shield_time = time + (autocvar_g_monster_mage_shield_time);
        setanim(this, this.anim_shoot, true, true, true);
 -      this.attack_finished_single[0] = time + 1;
 +      this.attack_finished_single[0] = time + 1; // give just a short cooldown on attacking
        this.anim_finished = time + 1;
  }
  
@@@ -376,7 -339,7 +368,7 @@@ bool M_Mage_Attack(int attack_type, ent
        {
                case MONSTER_ATTACK_MELEE:
                {
 -                      if(random() <= 0.7)
 +                      if(random() <= autocvar_g_monster_mage_attack_push_chance)
                        {
                                Weapon wep = WEP_MAGE_SPIKE;
  
                }
                case MONSTER_ATTACK_RANGED:
                {
 -                      if(!actor.mage_spike)
 +                      if(random() <= autocvar_g_monster_mage_attack_teleport_chance)
                        {
 -                              if(random() <= 0.4)
 -                              {
 -                                      OffhandWeapon off = OFFHAND_MAGE_TELEPORT;
 -                                      off.offhand_think(off, actor, true);
 -                                      return true;
 -                              }
 -                              else
 -                              {
 -                                      setanim(actor, actor.anim_shoot, true, true, true);
 -                                      actor.attack_finished_single[0] = time + (autocvar_g_monster_mage_attack_spike_delay);
 -                                      actor.anim_finished = time + 1;
 -                                      Weapon wep = WEP_MAGE_SPIKE;
 -                                      wep.wr_think(wep, actor, weaponentity, 1);
 -                                      return true;
 -                              }
 +                              OffhandWeapon off = OFFHAND_MAGE_TELEPORT;
 +                              actor.OffhandMageTeleport_key_pressed = 0;
 +                              off.offhand_think(off, actor, 1);
 +                              return true;
                        }
 -
 -                      if(actor.mage_spike)
 +                      else if(!actor.mage_spike && random() <= autocvar_g_monster_mage_attack_spike_chance)
 +                      {
 +                              setanim(actor, actor.anim_shoot, true, true, true);
 +                              actor.attack_finished_single[0] = time + (autocvar_g_monster_mage_attack_spike_delay);
 +                              actor.anim_finished = time + 1;
 +                              actor.state = MONSTER_ATTACK_MELEE; // prevent moving while firing spike
 +                              Weapon wep = WEP_MAGE_SPIKE;
 +                              wep.wr_think(wep, actor, weaponentity, 1);
                                return true;
 -                      else
 -                              return false;
 +                      }
 +
 +                      return false;
                }
        }
  
@@@ -451,13 -418,8 +443,8 @@@ METHOD(Mage, mr_think, bool(Mage thismo
      if(random() < 0.5)
          M_Mage_Defend_Heal(actor);
  
-     if(time >= actor.mage_shield_time && GetResource(actor, RES_ARMOR))
-         M_Mage_Defend_Shield_Remove(actor);
-     if(actor.enemy)
-     if(GetResource(actor, RES_HEALTH) < actor.max_health)
-     if(time >= actor.mage_shield_delay)
-     if(random() < 0.5)
+     if(actor.enemy && time >= actor.mage_shield_delay && random() < 0.5)
+     if(GetResource(actor, RES_HEALTH) < actor.max_health && !StatusEffects_active(STATUSEFFECT_Shield, actor))
          M_Mage_Defend_Shield(actor);
  
      return true;
@@@ -472,7 -434,7 +459,7 @@@ METHOD(Mage, mr_pain, float(Mage this, 
  METHOD(Mage, mr_death, bool(Mage this, entity actor))
  {
      TC(Mage, this);
 -    setanim(actor, actor.anim_die1, false, true, true);
 +    setanim(actor, ((random() > 0.5) ? actor.anim_die2 : actor.anim_die1), false, true, true);
      return true;
  }
  
@@@ -482,22 -444,12 +469,22 @@@ METHOD(Mage, mr_anim, bool(Mage this, e
  {
      TC(Mage, this);
      vector none = '0 0 0';
 -    actor.anim_die1 = animfixfps(actor, '4 1 0.5', none); // 2 seconds
 -    actor.anim_walk = animfixfps(actor, '1 1 1', none);
      actor.anim_idle = animfixfps(actor, '0 1 1', none);
 -    actor.anim_pain1 = animfixfps(actor, '3 1 2', none); // 0.5 seconds
 +    actor.anim_walk = animfixfps(actor, '1 1 1', none);
 +    actor.anim_run = animfixfps(actor, '1 1 1', none);
      actor.anim_shoot = animfixfps(actor, '2 1 5', none); // analyze models and set framerate
 -    actor.anim_run = animfixfps(actor, '5 1 1', none);
 +    actor.anim_duckjump = animfixfps(actor, '4 1 5', none); // analyze models and set framerate
 +    actor.anim_melee = animfixfps(actor, '5 1 5', none); // analyze models and set framerate
 +    //actor.anim_fire1 = animfixfps(actor, '3 1 5', none); // analyze models and set framerate
 +    //actor.anim_fire2 = animfixfps(actor, '4 1 5', none); // analyze models and set framerate
 +    //actor.anim_fire3 = animfixfps(actor, '5 1 5', none); // analyze models and set framerate
 +    actor.anim_pain1 = animfixfps(actor, '6 1 2', none); // 0.5 seconds
 +    actor.anim_pain2 = animfixfps(actor, '7 1 2', none); // 0.5 seconds
 +    //actor.anim_pain3 = animfixfps(actor, '8 1 2', none); // 0.5 seconds
 +    actor.anim_die1 = animfixfps(actor, '9 1 0.5', none); // 2 seconds
 +    actor.anim_die2 = animfixfps(actor, '10 1 0.5', none); // 2 seconds
 +    //actor.anim_dead1 = animfixfps(actor, '11 1 0.5', none); // 2 seconds
 +    //actor.anim_dead2 = animfixfps(actor, '12 1 0.5', none); // 2 seconds
      return true;
  }
  #endif
index a29f20b398842b36b0a9d0ab53b60b5f3b0f3984,3f3e65d4a3dee63da2cb3d91bdf08871a7146552..90b1d3218a46b6474035cee00865b19ac75f3ce0
@@@ -6,15 -6,12 +6,13 @@@
  
  #ifdef SVQC
  
- .float spider_slowness; // effect time of slowness inflicted by spiders
  .float spider_web_delay;
  
  float autocvar_g_monster_spider_attack_web_damagetime;
  float autocvar_g_monster_spider_attack_web_speed;
  float autocvar_g_monster_spider_attack_web_speed_up;
  float autocvar_g_monster_spider_attack_web_delay;
 +float autocvar_g_monster_spider_attack_web_range = 800;
  
  float autocvar_g_monster_spider_attack_bite_damage;
  float autocvar_g_monster_spider_attack_bite_delay;
@@@ -27,7 -24,7 +25,7 @@@ MUTATOR_HOOKFUNCTION(spiderweb, PlayerP
  {
        entity player = M_ARGV(0, entity);
  
-       if(time < player.spider_slowness)
+       if(StatusEffects_active(STATUSEFFECT_Webbed, player))
                STAT(MOVEVARS_HIGHSPEED, player) *= 0.5;
  }
  
@@@ -35,43 -32,25 +33,28 @@@ MUTATOR_HOOKFUNCTION(spiderweb, Monster
  {
      entity mon = M_ARGV(0, entity);
  
-       if(time < mon.spider_slowness)
+       if(StatusEffects_active(STATUSEFFECT_Webbed, mon))
        {
                M_ARGV(1, float) *= 0.5; // run speed
                M_ARGV(2, float) *= 0.5; // walk speed
        }
  }
  
- MUTATOR_HOOKFUNCTION(spiderweb, PlayerSpawn)
- {
-       entity player = M_ARGV(0, entity);
-       player.spider_slowness = 0;
-       return false;
- }
- MUTATOR_HOOKFUNCTION(spiderweb, MonsterSpawn)
- {
-     entity mon = M_ARGV(0, entity);
-       mon.spider_slowness = 0;
- }
  SOUND(SpiderAttack_FIRE, W_Sound("electro_fire"));
  METHOD(SpiderAttack, wr_think, void(SpiderAttack thiswep, entity actor, .entity weaponentity, int fire))
  {
      TC(SpiderAttack, thiswep);
      bool isPlayer = IS_PLAYER(actor);
      if (fire & 1)
 -    if ((!isPlayer && time >= actor.spider_web_delay) || weapon_prepareattack(thiswep, actor, weaponentity, false, autocvar_g_monster_spider_attack_web_delay)) {
 +    if ((!isPlayer && time >= actor.spider_web_delay) || (isPlayer && weapon_prepareattack(thiswep, actor, weaponentity, false, autocvar_g_monster_spider_attack_web_delay))) {
                if (!isPlayer) {
 -                      actor.spider_web_delay = time + 3;
 +                      actor.spider_web_delay = time + autocvar_g_monster_spider_attack_web_delay;
                        setanim(actor, actor.anim_shoot, true, true, true);
 -                      actor.attack_finished_single[0] = time + (autocvar_g_monster_spider_attack_web_delay);
 -                      actor.anim_finished = time + 1;
 +                      if(actor.animstate_endtime > time)
 +                              actor.anim_finished = actor.animstate_endtime;
 +                      else
 +                              actor.anim_finished = time + 1;
 +                      actor.attack_finished_single[0] = actor.anim_finished + 0.2;
                }
          if (isPlayer) actor.enemy = Monster_FindTarget(actor);
          monster_makevectors(actor, actor.enemy);
@@@ -114,7 -93,7 +97,7 @@@ void M_Spider_Attack_Web_Explode(entit
  
                FOREACH_ENTITY_RADIUS(this.origin, 25, it != this && it.takedamage && !IS_DEAD(it) && GetResource(it, RES_HEALTH) > 0 && it.monsterdef != MON_SPIDER,
                {
-                       it.spider_slowness = time + (autocvar_g_monster_spider_attack_web_damagetime);
+                       StatusEffects_apply(STATUSEFFECT_Webbed, it, time + autocvar_g_monster_spider_attack_web_damagetime, 0);
                });
  
                delete(this);
@@@ -183,11 -162,8 +166,11 @@@ bool M_Spider_Attack(int attack_type, e
                }
                case MONSTER_ATTACK_RANGED:
                {
 -                      wep.wr_think(wep, actor, weaponentity, 1);
 -                      return true;
 +                      if(vdist(actor.enemy.origin - actor.origin, <=, autocvar_g_monster_spider_attack_web_range))
 +                      {
 +                              wep.wr_think(wep, actor, weaponentity, 1);
 +                              return true;
 +                      }
                }
        }
  
@@@ -207,15 -183,14 +190,15 @@@ METHOD(Spider, mr_think, bool(Spider th
  METHOD(Spider, mr_pain, float(Spider this, entity actor, float damage_take, entity attacker, float deathtype))
  {
      TC(Spider, this);
 +    setanim(actor, ((random() > 0.5) ? actor.anim_pain2 : actor.anim_pain1), true, true, false);
 +    actor.pain_finished = actor.animstate_endtime;
      return damage_take;
  }
  
  METHOD(Spider, mr_death, bool(Spider this, entity actor))
  {
      TC(Spider, this);
 -    setanim(actor, actor.anim_melee, false, true, true);
 -    actor.angles_x = 180;
 +    setanim(actor, ((random() > 0.5) ? actor.anim_die2 : actor.anim_die1), false, true, true);
      return true;
  }
  #endif
@@@ -224,25 -199,11 +207,25 @@@ METHOD(Spider, mr_anim, bool(Spider thi
  {
      TC(Spider, this);
      vector none = '0 0 0';
 -    actor.anim_walk = animfixfps(actor, '1 1 1', none);
 -    actor.anim_idle = animfixfps(actor, '0 1 1', none);
 -    actor.anim_melee = animfixfps(actor, '2 1 5', none); // analyze models and set framerate
 -    actor.anim_shoot = animfixfps(actor, '3 1 5', none); // analyze models and set framerate
 -    actor.anim_run = animfixfps(actor, '1 1 1', none);
 +    actor.anim_melee = animfixfps(actor, '0 1 5', none); // analyze models and set framerate
 +    actor.anim_die1 = animfixfps(actor, '1 1 1', none);
 +    actor.anim_die2 = animfixfps(actor, '2 1 1', none);
 +    actor.anim_shoot = animfixfps(actor, '3 1 1', none);
 +    //actor.anim_fire2 = animfixfps(actor, '4 1 1', none);
 +    actor.anim_idle = animfixfps(actor, '5 1 1', none);
 +    //actor.anim_sight = animfixfps(actor, '6 1 1', none);
 +    actor.anim_pain1 = animfixfps(actor, '7 1 1', none);
 +    actor.anim_pain2 = animfixfps(actor, '8 1 1', none);
 +    //actor.anim_pain3 = animfixfps(actor, '9 1 1', none);
 +    actor.anim_walk = animfixfps(actor, '10 1 1', none);
 +    actor.anim_run = animfixfps(actor, '10 1 1', none); // temp?
 +    //actor.anim_forwardright = animfixfps(actor, '11 1 1', none);
 +    //actor.anim_walkright = animfixfps(actor, '12 1 1', none);
 +    //actor.anim_walkbackright = animfixfps(actor, '13 1 1', none);
 +    //actor.anim_walkback = animfixfps(actor, '14 1 1', none);
 +    //actor.anim_walkbackleft = animfixfps(actor, '15 1 1', none);
 +    //actor.anim_walkleft = animfixfps(actor, '16 1 1', none);
 +    //actor.anim_forwardleft = animfixfps(actor, '17 1 1', none);
      return true;
  }
  #endif
index 93d226ad3e4a10f9820fe7049c6683e5fa2fd741,943833f9909777894290d181bc2b1b39cdae72e5..5db6a0b8e408f9f7868a068447c8df47627fa55f
@@@ -8,8 -8,8 +8,8 @@@ MODEL(MON_SPIDER, M_Model("spider.dpm")
  
  CLASS(Spider, Monster)
      ATTRIB(Spider, spawnflags, int, MON_FLAG_MELEE | MON_FLAG_RANGED | MON_FLAG_RIDE);
 -    ATTRIB(Spider, m_mins, vector, '-18 -18 -25');
 -    ATTRIB(Spider, m_maxs, vector, '18 18 30');
 +    ATTRIB(Spider, m_mins, vector, '-30 -30 -25');
 +    ATTRIB(Spider, m_maxs, vector, '30 30 30');
  #ifdef GAMEQC
      ATTRIB(Spider, m_model, Model, MDL_MON_SPIDER);
  #endif
@@@ -28,3 -28,19 +28,19 @@@ CLASS(SpiderAttack, PortoLaunch
  /* wepname   */ ATTRIB(SpiderAttack, m_name, string, _("Spider attack"));
  ENDCLASS(SpiderAttack)
  REGISTER_WEAPON(SPIDER_ATTACK, NEW(SpiderAttack));
+ #include <common/mutators/mutator/status_effects/all.qh>
+ CLASS(Webbed, StatusEffects)
+     ATTRIB(Webbed, netname, string, "webbed");
+ #if 0
+     // NOTE: status effect name and icon disabled as they are not displayed
+     // re-enable if status effects are given a visual element
+     ATTRIB(Webbed, m_name, string, _("Webbed"));
+     ATTRIB(Webbed, m_icon, string, "buff_disability");
+ #endif
+     ATTRIB(Webbed, m_color, vector, '0.94 0.3 1');
+     ATTRIB(Webbed, m_hidden, bool, true);
+     ATTRIB(Webbed, m_lifetime, float, 10);
+ ENDCLASS(Webbed)
+ REGISTER_STATUSEFFECT(Webbed, NEW(Webbed));
index d2078c9d70d13df241337adc64d4cfd970c87884,f99474e9ec2ac293e75b83ac129fd0f6426e3783..551a9b42a414f90bb75fd28ef4c28a9b49eee546
@@@ -141,8 -141,7 +141,8 @@@ METHOD(Zombie, mr_pain, float(Zombie th
  {
      TC(Zombie, this);
      actor.pain_finished = time + 0.34;
 -    setanim(actor, ((random() > 0.5) ? actor.anim_pain1 : actor.anim_pain2), true, true, false);
 +    if(time >= actor.spawn_time)
 +      setanim(actor, ((random() > 0.5) ? actor.anim_pain1 : actor.anim_pain2), true, true, false);
      return damage_take;
  }
  
@@@ -195,7 -194,7 +195,7 @@@ METHOD(Zombie, mr_setup, bool(Zombie th
  
      actor.monster_loot = ITEM_HealthMedium;
      actor.monster_attackfunc = M_Zombie_Attack;
-     actor.spawnshieldtime = actor.spawn_time;
+     StatusEffects_apply(STATUSEFFECT_SpawnShield, actor, actor.spawn_time, 0);
      actor.respawntime = 0.2;
      actor.damageforcescale = 0.0001; // no push while spawning
  
index da4da4eb48254dfe369a26a608ff1fd884038e1c,e3eaf6ecd1b5ea88706b1619bf51505026b8f809..244652ef95c6cf93b8f884a06dc67b06e4081e18
@@@ -7,6 -7,7 +7,7 @@@
  #include <common/mapobjects/triggers.qh>
  #include <common/monsters/all.qh>
  #include <common/mutators/mutator/nades/nades.qh>
+ #include <common/mutators/mutator/status_effects/_mod.qh>
  #include <common/physics/movelib.qh>
  #include <common/stats.qh>
  #include <common/teams.qh>
@@@ -62,21 -63,6 +63,21 @@@ void monster_dropitem(entity this, enti
        }
  }
  
 +bool monster_facing(entity this, entity targ)
 +{
 +      // relies on target having an origin
 +      makevectors(this.angles);
 +      vector targ_org = targ.origin, my_org = this.origin;
 +      if(autocvar_g_monsters_target_infront_2d)
 +      {
 +              targ_org = vec2(targ_org);
 +              my_org = vec2(my_org);
 +      }
 +      float dot = normalize(targ_org - my_org) * v_forward;
 +
 +      return !(dot <= autocvar_g_monsters_target_infront_range);
 +}
 +
  void monster_makevectors(entity this, entity targ)
  {
        if(IS_MONSTER(this))
  // Target handling
  // ===============
  
 -bool Monster_ValidTarget(entity this, entity targ)
 +bool Monster_ValidTarget(entity this, entity targ, bool skipfacing)
  {
        // ensure we're not checking nonexistent monster/target
        if(!this || !targ) { return false; }
  
        if((targ == this)
 -      || (autocvar_g_monsters_lineofsight && !checkpvs(this.origin + this.view_ofs, targ)) // enemy cannot be seen
        || (IS_VEHICLE(targ) && !(this.monsterdef.spawnflags & MON_FLAG_RANGED)) // melee vs vehicle is useless
        || (time < game_starttime) // monsters do nothing before match has started
        || (targ.takedamage == DAMAGE_NO)
        || (game_stopped)
-       || (targ.items & IT_INVISIBILITY)
        || (IS_SPEC(targ) || IS_OBSERVER(targ)) // don't attack spectators
        || (!IS_VEHICLE(targ) && (IS_DEAD(targ) || IS_DEAD(this) || GetResource(targ, RES_HEALTH) <= 0 || GetResource(this, RES_HEALTH) <= 0))
        || (this.monster_follow == targ || targ.monster_follow == this)
        || (SAME_TEAM(targ, this))
        || (STAT(FROZEN, targ))
        || (targ.alpha != 0 && targ.alpha < 0.5)
 +      || (autocvar_g_monsters_lineofsight && !checkpvs(this.origin + this.view_ofs, targ)) // enemy cannot be seen
        || (MUTATOR_CALLHOOK(MonsterValidTarget, this, targ))
        )
        {
        }
  
        vector targ_origin = ((targ.absmin + targ.absmax) * 0.5);
 -      traceline(this.origin + this.view_ofs, targ_origin, MOVE_NOMONSTERS, this);
 +      traceline(this.origin + this.view_ofs, targ_origin, MOVE_NOMONSTERS, this); // TODO: maybe we can rely a bit on PVS data instead?
  
        if(trace_fraction < 1 && trace_ent != targ)
                return false; // solid
  
 -      if(autocvar_g_monsters_target_infront || (this.spawnflags & MONSTERFLAG_INFRONT))
 +      if(!skipfacing && (autocvar_g_monsters_target_infront || (this.spawnflags & MONSTERFLAG_INFRONT)))
        if(this.enemy != targ)
        {
 -              makevectors (this.angles);
 -              float dot = normalize (targ.origin - this.origin) * v_forward;
 -
 -              if(dot <= autocvar_g_monsters_target_infront_range) { return false; }
 +              if(!monster_facing(this, targ))
 +                      return false;
        }
  
        return true; // this target is valid!
@@@ -144,27 -131,21 +144,27 @@@ entity Monster_FindTarget(entity this
        vector my_center = CENTER_OR_VIEWOFS(this);
  
        // find the closest acceptable target to pass to
 -      IL_EACH(g_monster_targets, it.monster_attack && vdist(it.origin - this.origin, <, this.target_range),
 +      IL_EACH(g_monster_targets, it.monster_attack,
        {
 -              if(Monster_ValidTarget(this, it))
 -              {
 -                      // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in damage.qc)
 -                      vector targ_center = CENTER_OR_VIEWOFS(it);
 +              float trange = this.target_range;
 +              if(PHYS_INPUT_BUTTON_CROUCH(it))
 +                      trange *= 0.75; // TODO cvar this
 +              vector theirmid = (it.absmin + it.absmax) * 0.5;
 +              if(vdist(theirmid - this.origin, >, trange))
 +                      continue;
 +              if(!Monster_ValidTarget(this, it, false))
 +                      continue;
  
 -                      if(closest_target)
 -                      {
 -                              vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
 -                              if(vlen2(my_center - targ_center) < vlen2(my_center - closest_target_center))
 -                                      { closest_target = it; }
 -                      }
 -                      else { closest_target = it; }
 +              // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
 +              vector targ_center = CENTER_OR_VIEWOFS(it);
 +
 +              if(closest_target)
 +              {
 +                      vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
 +                      if(vlen2(my_center - targ_center) < vlen2(my_center - closest_target_center))
 +                              { closest_target = it; }
                }
 +              else { closest_target = it; }
        });
  
        return closest_target;
@@@ -179,23 -160,18 +179,23 @@@ void monster_setupcolors(entity this
        else
        {
                if(this.monster_skill <= MONSTER_SKILL_EASY)
 -                      this.colormap = 1029;
 +                      this.colormap = 1126;
                else if(this.monster_skill <= MONSTER_SKILL_MEDIUM)
 -                      this.colormap = 1027;
 +                      this.colormap = 1075;
                else if(this.monster_skill <= MONSTER_SKILL_HARD)
 -                      this.colormap = 1038;
 +                      this.colormap = 1228;
                else if(this.monster_skill <= MONSTER_SKILL_INSANE)
 -                      this.colormap = 1028;
 +                      this.colormap = 1092;
                else if(this.monster_skill <= MONSTER_SKILL_NIGHTMARE)
 -                      this.colormap = 1032;
 +                      this.colormap = 1160;
                else
                        this.colormap = 1024;
        }
 +
 +      if(this.colormap > 0)
 +              this.glowmod = colormapPaletteColor(this.colormap & 0x0F, false);
 +      else
 +              this.glowmod = '1 1 1';
  }
  
  void monster_changeteam(entity this, int newteam)
  .void(entity) monster_delayedfunc;
  void Monster_Delay_Action(entity this)
  {
 -      if(Monster_ValidTarget(this.owner, this.owner.enemy))
 +      // TODO: maybe do check for facing here
 +      if(Monster_ValidTarget(this.owner, this.owner.enemy, false))
        {
                monster_makevectors(this.owner, this.owner.enemy);
                this.monster_delayedfunc(this.owner);
  void Monster_Delay(entity this, int repeat_count, float defer_amnt, void(entity) func)
  {
        // deferred attacking, checks if monster is still alive and target is still valid before attacking
-       entity e = new(Monster_Delay);
+       entity e = new_pure(Monster_Delay);
  
        setthink(e, Monster_Delay_Action);
        e.nextthink = time + defer_amnt;
@@@ -373,6 -348,7 +373,6 @@@ void Monster_Sound(entity this, .strin
        string sample = this.(samplefield);
        if (sample != "") sample = GlobalSound_sample(sample, random());
        float myscale = ((this.scale) ? this.scale : 1); // safety net
 -      // TODO: change volume depending on size too?
        sound7(this, chan, sample, VOL_BASE, ATTEN_NORM, 100 / myscale, 0);
  
        this.msound_delay = time + sound_delay;
@@@ -453,7 -429,6 +453,7 @@@ void Monster_Attack_Check(entity this, 
        if((!this || !targ)
        || (!this.monster_attackfunc)
        || (time < this.attack_finished_single[slot])
 +      || ((autocvar_g_monsters_target_infront || (this.spawnflags & MONSTERFLAG_INFRONT)) && !monster_facing(this, targ))
        ) { return; }
  
        if(vdist(targ.origin - this.origin, <=, this.attack_range))
@@@ -503,10 -478,12 +503,10 @@@ void Monster_UpdateModel(entity this
  
  void Monster_Touch(entity this, entity toucher)
  {
 -      if(toucher == NULL) { return; }
 +      if(!toucher) { return; }
  
 -      if(toucher.monster_attack)
 -      if(this.enemy != toucher)
 -      if(!IS_MONSTER(toucher))
 -      if(Monster_ValidTarget(this, toucher))
 +      if(toucher.monster_attack && this.enemy != toucher && !IS_MONSTER(toucher) && time >= this.spawn_time)
 +      if(Monster_ValidTarget(this, toucher, true))
                this.enemy = toucher;
  }
  
@@@ -579,9 -556,10 +579,9 @@@ void Monster_Dead_Fade(entity this
  
  void Monster_Use(entity this, entity actor, entity trigger)
  {
 -      if(Monster_ValidTarget(this, actor)) { this.enemy = actor; }
 +      if(Monster_ValidTarget(this, actor, true)) { this.enemy = actor; }
  }
  
 -.float pass_distance;
  vector Monster_Move_Target(entity this, entity targ)
  {
        // enemy is always preferred target
                WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
  
                // cases where the enemy may have changed their state (don't need to check everything here)
 -              if((!this.enemy)
 -                      || (IS_DEAD(this.enemy) || GetResource(this.enemy, RES_HEALTH) < 1)
 +              if(    (IS_DEAD(this.enemy) || GetResource(this.enemy, RES_HEALTH) < 1)
                        || (STAT(FROZEN, this.enemy))
                        || (this.enemy.flags & FL_NOTARGET)
                        || (this.enemy.alpha < 0.5 && this.enemy.alpha != 0)
                        || (this.enemy.takedamage == DAMAGE_NO)
                        || (vdist(this.origin - targ_origin, >, this.target_range))
 -                      || ((trace_fraction < 1) && (trace_ent != this.enemy)))
 +                      || ((trace_fraction < 1) && (trace_ent != this.enemy))
 +                      )
                {
                        this.enemy = NULL;
 -                      //this.pass_distance = 0;
                }
  
                if(this.enemy)
        }
  }
  
 -void Monster_CalculateVelocity(entity this, vector to, vector from, float turnrate, float movespeed)
 -{
 -      //float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis
 -      //float initial_height = 0; //min(50, (targ_distance * tanh(20)));
 -      //float current_height = (initial_height * min(1, (this.pass_distance) ? (current_distance / this.pass_distance) : current_distance));
 -      //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
 -
 -      vector targpos = to;
 -#if 0
 -      if(current_height) // make sure we can actually do this arcing path
 -      {
 -              targpos = (to + ('0 0 1' * current_height));
 -              WarpZone_TraceLine(this.origin, targpos, MOVE_NOMONSTERS, this);
 -              if(trace_fraction < 1)
 -              {
 -                      //print("normal arc line failed, trying to find new pos...");
 -                      WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, this);
 -                      targpos = (trace_endpos + '0 0 -10');
 -                      WarpZone_TraceLine(this.origin, targpos, MOVE_NOMONSTERS, this);
 -                      if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
 -                      /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
 -              }
 -      }
 -      else { targpos = to; }
 -#endif
 -
 -      //this.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
 -
 -      vector desired_direction = normalize(targpos - from);
 -      if(turnrate) { this.velocity = (normalize(normalize(this.velocity) + (desired_direction * 50)) * movespeed); }
 -      else { this.velocity = (desired_direction * movespeed); }
 -
 -      //this.steerto = steerlib_attract2(targpos, 0.5, 500, 0.95);
 -      //this.angles = vectoangles(this.velocity);
 -}
 -
  .entity draggedby;
  
  void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed)
        if(!(this.spawnflags & MONSTERFLAG_FLY_VERTICAL) && !(this.flags & FL_SWIM))
                this.moveto_z = this.origin_z;
  
 -      if(vdist(this.origin - this.moveto, >, 100))
 +      fixedmakevectors(this.angles);
 +      float vz = this.velocity_z;
 +
 +      if(!turret_closetotarget(this, this.moveto, 16))
        {
                bool do_run = (this.enemy || this.monster_moveto);
 -              if(IS_ONGROUND(this) || ((this.flags & FL_FLY) || (this.flags & FL_SWIM)))
 -                      Monster_CalculateVelocity(this, this.moveto, this.origin, true, ((do_run) ? runspeed : walkspeed));
 +              movelib_move_simple(this, v_forward, ((do_run) ? runspeed : walkspeed), 0.4);
  
 -              if(time > this.pain_finished && time > this.anim_finished) // TODO: use anim_finished instead!?
 +              if(time > this.pain_finished && time > this.anim_finished)
                if(!this.state)
                {
                        if(vdist(this.velocity, >, 10))
                        setanim(this, this.anim_idle, true, false, false);
        }
  
 +      if(!((this.flags & FL_FLY) || (this.flags & FL_SWIM)))
 +              this.velocity_z = vz;
 +
        this.steerto = steerlib_attract2(this, ((this.monster_face) ? this.monster_face : this.moveto), 0.5, 500, 0.95);
  
        vector real_angle = vectoangles(this.steerto) - this.angles;
@@@ -864,9 -874,6 +864,9 @@@ void Monster_Dead_Think(entity this
  {
        this.nextthink = time + this.ticrate;
  
 +      Monster mon = REGISTRY_GET(Monsters, this.monsterid);
 +      mon.mr_deadthink(mon, this);
 +
        if(this.monster_lifetime != 0)
        if(time >= this.monster_lifetime)
        {
@@@ -971,7 -978,6 +971,7 @@@ void Monster_Dead(entity this, entity a
        this.state                      = 0;
        this.attack_finished_single[0] = 0;
        this.effects = 0;
 +      this.dphitcontentsmask &= ~DPCONTENTS_BODY;
  
        if(!((this.flags & FL_FLY) || (this.flags & FL_SWIM)))
                this.velocity = '0 0 0';
@@@ -999,7 -1005,7 +999,7 @@@ void Monster_Damage(entity this, entit
        //if(time < this.pain_finished && deathtype != DEATH_KILL.m_id)
                //return;
  
-       if(time < this.spawnshieldtime && deathtype != DEATH_KILL.m_id)
+       if(StatusEffects_active(STATUSEFFECT_SpawnShield, this) && deathtype != DEATH_KILL.m_id)
                return;
  
        if(deathtype == DEATH_FALL.m_id && this.draggedby != NULL)
@@@ -1095,7 -1101,7 +1095,7 @@@ void Monster_Move_2D(entity this, floa
        bool reverse = false;
        if(trace_fraction != 1.0)
                reverse = true;
-       if(trace_ent && IS_PLAYER(trace_ent) && !(trace_ent.items & ITEM_Strength.m_itemid))
+       if(trace_ent && IS_PLAYER(trace_ent))
                reverse = false;
        if(trace_ent && IS_MONSTER(trace_ent))
                reverse = true;
@@@ -1200,20 -1206,21 +1200,20 @@@ void Monster_Frozen_Think(entity this
  
  void Monster_Enemy_Check(entity this)
  {
 -      if(!this.enemy)
 +      if(this.enemy)
 +              return;
 +
 +      this.enemy = Monster_FindTarget(this);
 +      if(this.enemy)
        {
 -              this.enemy = Monster_FindTarget(this);
 -              if(this.enemy)
 -              {
 -                      WarpZone_RefSys_Copy(this.enemy, this);
 -                      WarpZone_RefSys_AddInverse(this.enemy, this); // wz1^-1 ... wzn^-1 receiver
 -                      // update move target immediately?
 -                      this.moveto = WarpZone_RefSys_TransformOrigin(this.enemy, this, (0.5 * (this.enemy.absmin + this.enemy.absmax)));
 -                      this.monster_moveto = '0 0 0';
 -                      this.monster_face = '0 0 0';
 -
 -                      //this.pass_distance = vlen((('1 0 0' * this.enemy.origin_x) + ('0 1 0' * this.enemy.origin_y)) - (('1 0 0' *  this.origin_x) + ('0 1 0' *  this.origin_y)));
 -                      Monster_Sound(this, monstersound_sight, 0, false, CH_VOICE);
 -              }
 +              WarpZone_RefSys_Copy(this.enemy, this);
 +              WarpZone_RefSys_AddInverse(this.enemy, this); // wz1^-1 ... wzn^-1 receiver
 +              // update move target immediately?
 +              this.moveto = WarpZone_RefSys_TransformOrigin(this.enemy, this, (0.5 * (this.enemy.absmin + this.enemy.absmax)));
 +              this.monster_moveto = '0 0 0';
 +              this.monster_face = '0 0 0';
 +
 +              Monster_Sound(this, monstersound_sight, 0, false, CH_VOICE);
        }
  }
  
@@@ -1275,7 -1282,6 +1275,7 @@@ bool Monster_Spawn_Setup(entity this
  
        this.max_health = GetResource(this, RES_HEALTH);
        this.pain_finished = this.nextthink;
 +      this.last_enemycheck = this.spawn_time + random(); // slight delay
  
        if(IS_PLAYER(this.monster_follow))
                this.effects |= EF_DIMLIGHT;
@@@ -1354,9 -1360,14 +1354,17 @@@ bool Monster_Spawn(entity this, bool ch
        else
                setmodel(this, mon.m_model);
  
 +      if(!this.monster_name || this.monster_name == "")
 +              this.monster_name = mon.monster_name;
 +
+       if(this.statuseffects && this.statuseffects.owner == this)
+       {
+               StatusEffects_clearall(this.statuseffects);
+               StatusEffects_update(this);
+       }
+       else
+               this.statuseffects = NULL;
        this.flags                              = FL_MONSTER;
        this.classname                  = "monster";
        this.takedamage                 = DAMAGE_AIM;
        this.use                                = Monster_Use;
        this.solid                              = SOLID_BBOX;
        set_movetype(this, MOVETYPE_WALK);
-       this.spawnshieldtime    = time + autocvar_g_monsters_spawnshieldtime;
+       StatusEffects_apply(STATUSEFFECT_SpawnShield, this, time + autocvar_g_monsters_spawnshieldtime, 0);
        this.enemy                              = NULL;
        this.velocity                   = '0 0 0';
        this.moveto                             = this.origin;
        this.reset                              = Monster_Reset;
        this.netname                    = mon.netname;
        this.monster_attackfunc = mon.monster_attackfunc;
 -      this.monster_name               = mon.monster_name;
        this.candrop                    = true;
 -      this.view_ofs                   = '0 0 0.7' * (this.maxs_z * 0.5);
        this.oldtarget2                 = this.target2;
 -      //this.pass_distance            = 0;
        this.deadflag                   = DEAD_NO;
        this.spawn_time                 = time;
        this.gravity                    = 1;
        if(autocvar_g_nodepthtestplayers) { this.effects |= EF_NODEPTHTEST; }
        if(mon.spawnflags & MONSTER_TYPE_SWIM) { this.flags |= FL_SWIM; }
  
 -      if(autocvar_g_playerclip_collisions)
 +      if(autocvar_g_monsters_playerclip_collisions)
                this.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP;
  
        if(mon.spawnflags & MONSTER_TYPE_FLY)
                set_movetype(this, MOVETYPE_FLY);
        }
  
 -      if(!(this.spawnflags & MONSTERFLAG_RESPAWNED))
 -      {
 -              if(mon.spawnflags & MONSTER_SIZE_BROKEN)
 -                      this.scale *= 1.3;
 -
 -              if(mon.spawnflags & MONSTER_SIZE_QUAKE)
 -              if(autocvar_g_monsters_quake_resize)
 -                      this.scale *= 1.3;
 -      }
 +      if((mon.spawnflags & MONSTER_SIZE_QUAKE) && autocvar_g_monsters_quake_resize && !(this.spawnflags & MONSTERFLAG_RESPAWNED))
 +              this.scale *= 1.3;
  
        setsize(this, mon.m_mins * this.scale, mon.m_maxs * this.scale);
 +      this.view_ofs                   = '0 0 0.7' * (this.maxs_z * 0.5);
  
        this.ticrate = bound(sys_frametime, ((!this.ticrate) ? autocvar_g_monsters_think_delay : this.ticrate), 60);
  
index b4ee27384ce7882138709159a83edafb92f41e71,d34e929cb1f1cdcd1ffd18093057a5505eaa27d7..87398b11d222367c1aeafa752eecab84b02e3f0d
        If you send a notification with mismatching arguments, Send_Notification() will error.
  */
  
+ // NOTE: leaving BOLD_OPERATOR outside of translatable messages has 2 advantages:
+ // 1. translators don't have to deal with it
+ // 2. messages can be changed to bold / normal without the need to translate them again
+ #define BOLD(translatable_msg) strcat(BOLD_OPERATOR, translatable_msg)
  // default value for annce notification cvars (notification_ANNCE_*):
  // 0: off, 1: announce only if gentle mode is off, 2: announce always
  #define N___NEVER 0
      MSG_ANNCE_NOTIF(NUM_GAMESTART_9,            N___NEVER, "9",                 CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
      MSG_ANNCE_NOTIF(NUM_GAMESTART_10,           N___NEVER, "10",                CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
  
-     MSG_ANNCE_NOTIF(NUM_IDLE_1,                 N___NEVER, "1",                 CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
-     MSG_ANNCE_NOTIF(NUM_IDLE_2,                 N___NEVER, "2",                 CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
-     MSG_ANNCE_NOTIF(NUM_IDLE_3,                 N___NEVER, "3",                 CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
-     MSG_ANNCE_NOTIF(NUM_IDLE_4,                 N___NEVER, "4",                 CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
-     MSG_ANNCE_NOTIF(NUM_IDLE_5,                 N___NEVER, "5",                 CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
-     MSG_ANNCE_NOTIF(NUM_IDLE_6,                 N___NEVER, "6",                 CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
-     MSG_ANNCE_NOTIF(NUM_IDLE_7,                 N___NEVER, "7",                 CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
-     MSG_ANNCE_NOTIF(NUM_IDLE_8,                 N___NEVER, "8",                 CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
-     MSG_ANNCE_NOTIF(NUM_IDLE_9,                 N___NEVER, "9",                 CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
-     MSG_ANNCE_NOTIF(NUM_IDLE_10,                N___NEVER, "10",                CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
      MSG_ANNCE_NOTIF(NUM_KILL_1,                 N___NEVER, "1",                 CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
      MSG_ANNCE_NOTIF(NUM_KILL_2,                 N___NEVER, "2",                 CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
      MSG_ANNCE_NOTIF(NUM_KILL_3,                 N___NEVER, "3",                 CH_INFO, VOL_BASEVOICE, ATTEN_NONE)
@@@ -295,7 -289,7 +289,7 @@@ string multiteam_info_sprintf(string in
      MSG_INFO_NOTIF(DEATH_MURDER_VH_WAKI_ROCKET,             N_CONSOLE,  3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",               "notify_death",         _("^BG%s%s^K1 couldn't find shelter from ^BG%s^K1's Racer%s%s"), "")
      MSG_INFO_NOTIF(DEATH_MURDER_VOID,                       N_CONSOLE,  3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",               "notify_void",          _("^BG%s%s^K1 was thrown into a world of hurt by ^BG%s^K1%s%s"), "")
  
-     MSG_INFO_NOTIF(DEATH_SELF_AUTOTEAMCHANGE,               N_CONSOLE,  2, 1, "s1 s2loc death_team", "",        "",                     _("^BG%s^K1 was moved into the %s%s"), "")
+     MSG_INFO_NOTIF(DEATH_SELF_AUTOTEAMCHANGE,               N_CONSOLE,  2, 1, "s1 death_team s2loc", "",        "",                     _("^BG%s^K1 was moved into the %s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_BETRAYAL,                     N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_teamkill_red",  _("^BG%s^K1 became enemies with the Lord of Teamplay%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_CAMP,                         N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_camping",       _("^BG%s^K1 thought they found a nice camping ground%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_CHEAT,                        N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_selfkill",      _("^BG%s^K1 unfairly eliminated themself%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_GENERIC,                      N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_selfkill",      _("^BG%s^K1 died%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_LAVA,                         N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_lava",          _("^BG%s^K1 turned into hot slag%s%s"), _("^BG%s^K1 found a hot place%s%s"))
      MSG_INFO_NOTIF(DEATH_SELF_MON_MAGE,                     N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was exploded by a Mage%s%s"), "")
 -    MSG_INFO_NOTIF(DEATH_SELF_MON_SHAMBLER_CLAW,            N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1's innards became outwards by a Shambler%s%s"), "")
 -    MSG_INFO_NOTIF(DEATH_SELF_MON_SHAMBLER_SMASH,           N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was smashed by a Shambler%s%s"), "")
 -    MSG_INFO_NOTIF(DEATH_SELF_MON_SHAMBLER_ZAP,             N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was zapped to death by a Shambler%s%s"), "")
 +    MSG_INFO_NOTIF(DEATH_SELF_MON_GOLEM_CLAW,               N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1's innards became outwards by a Golem%s%s"), "")
 +    MSG_INFO_NOTIF(DEATH_SELF_MON_GOLEM_SMASH,              N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was smashed by a Golem%s%s"), "")
 +    MSG_INFO_NOTIF(DEATH_SELF_MON_GOLEM_ZAP,                N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was zapped to death by a Golem%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_MON_SPIDER,                   N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was bitten by a Spider%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_MON_WYVERN,                   N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was fireballed by a Wyvern%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_MON_ZOMBIE_JUMP,              N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 joins the Zombies%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_SLIME,                        N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_slime",         _("^BG%s^K1 was slimed%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_SUICIDE,                      N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_selfkill",      _("^BG%s^K1 couldn't take it anymore%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_SWAMP,                        N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_slime",         _("^BG%s^K1 is now preserved for centuries to come%s%s"), "")
-     MSG_INFO_NOTIF(DEATH_SELF_TEAMCHANGE,                   N_CONSOLE,  2, 1, "s1 s2loc death_team", "",        "",                     _("^BG%s^K1 switched to the %s%s"), "")
+     MSG_INFO_NOTIF(DEATH_SELF_TEAMCHANGE,                   N_CONSOLE,  2, 1, "s1 death_team s2loc", "",        "",                     _("^BG%s^K1 switched to the %s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_TOUCHEXPLODE,                 N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 died in an accident%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_TURRET,                       N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 ran into a turret%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_TURRET_EWHEEL,                N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was blasted away by an eWheel turret%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_VH_SPID_ROCKET,               N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was blasted to bits by a Spiderbot rocket%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_VH_WAKI_DEATH,                N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 got caught in the blast of a Racer explosion%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_VH_WAKI_ROCKET,               N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 couldn't find shelter from a Racer rocket%s%s"), "")
-     MSG_INFO_NOTIF(DEATH_SELF_VOID,                         N_CONSOLE,  3, 1, "s1 s2 s2loc spree_lost", "s1",   "notify_void",            "^BG%s^K1 %s^K1%s%s", "")
+     MSG_INFO_NOTIF(DEATH_SELF_VOID,                         N_CONSOLE,  3, 1, "s1 s2 s3loc spree_lost", "s1",   "notify_void",            "^BG%s^K1 %s^K1%s%s", "")
  
      MULTITEAM_INFO(DEATH_TEAMKILL,                          N_CONSOLE,  3, 1, "s1 s2 s3loc spree_end", "s2 s1",     "notify_teamkill_%s",   _("^BG%s^K1 was betrayed by ^BG%s^K1%s%s"), "", NAME)
  
      MULTITEAM_INFO(ONSLAUGHT_GENDESTROYED,                  N_CONSOLE,  0, 0, "", "",           "",     _("^TC^TT^BG generator has been destroyed"), "", GENERATOR)
      MULTITEAM_INFO(ONSLAUGHT_GENDESTROYED_OVERTIME,         N_CONSOLE,  0, 0, "", "",           "",     _("^TC^TT^BG generator spontaneously combusted due to overtime!"), "", GENERATOR)
  
-     MSG_INFO_NOTIF(POWERUP_INVISIBILITY,                    N_CONSOLE,  1, 0, "s1", "s1",       "strength",     _("^BG%s^K1 picked up Invisibility"), "")
-     MSG_INFO_NOTIF(POWERUP_SHIELD,                          N_CONSOLE,  1, 0, "s1", "s1",       "shield",       _("^BG%s^K1 picked up Shield"), "")
-     MSG_INFO_NOTIF(POWERUP_SPEED,                           N_CONSOLE,  1, 0, "s1", "s1",       "shield",       _("^BG%s^K1 picked up Speed"), "")
-     MSG_INFO_NOTIF(POWERUP_STRENGTH,                        N_CONSOLE,  1, 0, "s1", "s1",       "strength",     _("^BG%s^K1 picked up Strength"), "")
+     MSG_INFO_NOTIF(POWERUP_INVISIBILITY,                    N_CONSOLE,  1, 0, "s1", "s1",       "buff_invisible", _("^BG%s^K1 picked up Invisibility"), "")
+     MSG_INFO_NOTIF(POWERUP_SHIELD,                          N_CONSOLE,  1, 0, "s1", "s1",       "shield",         _("^BG%s^K1 picked up Shield"), "")
+     MSG_INFO_NOTIF(POWERUP_SPEED,                           N_CONSOLE,  1, 0, "s1", "s1",       "buff_speed",     _("^BG%s^K1 picked up Speed"), "")
+     MSG_INFO_NOTIF(POWERUP_STRENGTH,                        N_CONSOLE,  1, 0, "s1", "s1",       "strength",       _("^BG%s^K1 picked up Strength"), "")
  
      MSG_INFO_NOTIF(QUIT_DISCONNECT,                         N_CHATCON,  1, 0, "s1", "",         "",             _("^BG%s^F3 disconnected"), "")
-     MSG_INFO_NOTIF(QUIT_KICK_IDLING,                        N_CHATCON,  1, 0, "s1", "",         "",             _("^BG%s^F3 was kicked for idling"), "")
+     MSG_INFO_NOTIF(QUIT_KICK_IDLING,                        N_CHATCON,  1, 1, "s1 f1", "",      "",             _("^BG%s^F3 was kicked after idling for %s seconds"), "")
+     MSG_INFO_NOTIF(MOVETOSPEC_IDLING,                       N_CHATCON,  1, 1, "s1 f1", "",      "",             _("^BG%s^F3 was moved to^BG spectators^F3 after idling for %s seconds"), "")
      MSG_INFO_NOTIF(QUIT_KICK_SPECTATING,                    N_CONSOLE,  0, 0, "", "",           "",             _("^F2You were kicked from the server because you are a spectator and spectators aren't allowed at the moment."), "")
      MSG_INFO_NOTIF(QUIT_KICK_TEAMKILL,                      N_CHATCON,  1, 0, "s1", "",         "",             _("^BG%s^F3 was kicked for excessive teamkilling"), "")
-     MSG_INFO_NOTIF(QUIT_SPECTATE,                           N_CHATCON,  1, 0, "s1", "",         "",             _("^BG%s^F3 is now spectating"), "")
+     MSG_INFO_NOTIF(QUIT_SPECTATE,                           N_CHATCON,  1, 0, "s1", "",         "",             _("^BG%s^F3 is now^BG spectating"), "")
  
      MSG_INFO_NOTIF(RACE_ABANDONED,                          N_CONSOLE,  1, 0, "s1", "",                                                                     "",                         _("^BG%s^BG has abandoned the race"), "")
      MSG_INFO_NOTIF(RACE_FAIL_RANKED,                        N_CONSOLE,  1, 3, "s1 race_col f1ord race_col f3race_time race_diff", "s1 f3race_time",         "race_newfail",             _("^BG%s^BG couldn't break their %s%s^BG place record of %s%s %s"), "")
      MSG_CENTER_NOTIF(ASSAULT_DEFENDING,                 N_ENABLE,    0, 0, "",               CPID_ASSAULT_ROLE,      "0 0",  _("^BGYou are defending!"), "")
      MSG_CENTER_NOTIF(ASSAULT_OBJ_DESTROYED,             N_ENABLE,    0, 1, "f1time",         CPID_ASSAULT_ROLE,      "0 0",  _("^BGObjective destroyed in ^F4%s^BG!"), "")
  
-     MSG_CENTER_NOTIF(COUNTDOWN_BEGIN,                   N_ENABLE,    0, 0, "",               CPID_ROUND,             "2 0",  _("^F4Begin!"), "")
-     MSG_CENTER_NOTIF(COUNTDOWN_GAMESTART,               N_ENABLE,    0, 1, "",               CPID_ROUND,             "1 f1", _("^F4Game starts in ^COUNT"), "")
-     MSG_CENTER_NOTIF(COUNTDOWN_ROUNDSTART,              N_ENABLE,    0, 1, "",               CPID_ROUND,             "1 f1", _("^F4Round starts in ^COUNT"), "")
+     MSG_CENTER_NOTIF(COUNTDOWN_BEGIN,                   N_ENABLE,    0, 0, "",               CPID_ROUND,             "2 0",  BOLD(_("^BGBegin!")), "")
+     MSG_CENTER_NOTIF(COUNTDOWN_GAMESTART,               N_ENABLE,    0, 1, "",               CPID_ROUND,             "1 f1", strcat(_("^BGGame starts in"), "\n", BOLD("^COUNT")), "")
+     MSG_CENTER_NOTIF(COUNTDOWN_ROUNDSTART,              N_ENABLE,    0, 2, "f1",             CPID_ROUND,             "1 f2", strcat(_("^BGRound %s starts in"), "\n", BOLD("^COUNT")), "")
      MSG_CENTER_NOTIF(COUNTDOWN_ROUNDSTOP,               N_ENABLE,    0, 0, "",               CPID_ROUND,             "2 0",  _("^F4Round cannot start"), "")
  
      MSG_CENTER_NOTIF(ROUND_TIED,                        N_ENABLE,    0, 0, "",               CPID_ROUND,             "0 0",  _("^BGRound tied"), "")
      MSG_CENTER_NOTIF(CTF_PASS_REQUESTING,               N_ENABLE,    1, 0, "s1",             CPID_CTF_PASS,          "0 0",  _("^BGRequesting %s^BG to pass you the flag"), "")
      MULTITEAM_CENTER(CTF_PASS_SENT,                     N_ENABLE,    1, 0, "s1",             CPID_CTF_PASS,          "0 0",  _("^BGYou passed the ^TC^TT^BG flag to %s"), "", FLAG)
      MSG_CENTER_NOTIF(CTF_PASS_SENT_NEUTRAL,             N_ENABLE,    1, 0, "s1",             CPID_CTF_PASS,          "0 0",  _("^BGYou passed the flag to %s"), "")
-     MULTITEAM_CENTER(CTF_PICKUP,                        N_ENABLE,    0, 0, "",               CPID_CTF_LOWPRIO,       "0 0",  _("^BGYou got the ^TC^TT^BG flag!"), "", FLAG)
-     MSG_CENTER_NOTIF(CTF_PICKUP_NEUTRAL,                N_ENABLE,    0, 0, "",               CPID_CTF_LOWPRIO,       "0 0",  _("^BGYou got the flag!"), "")
-     MSG_CENTER_NOTIF(CTF_PICKUP_RETURN,                 N_ENABLE,    1, 0, "s1",             CPID_CTF_LOWPRIO,       "0 0",  _("^BGYou got your %steam^BG's flag, return it!"), "")
-     MSG_CENTER_NOTIF(CTF_PICKUP_RETURN_ENEMY,           N_ENABLE,    1, 0, "s1",             CPID_CTF_LOWPRIO,       "0 0",  _("^BGYou got the %senemy^BG's flag, return it!"), "")
+     MULTITEAM_CENTER(CTF_PICKUP,                        N_ENABLE,    0, 0, "",               CPID_CTF_LOWPRIO,       "0 0",  BOLD(_("^BGYou got the ^TC^TT^BG flag!")), "", FLAG)
+     MSG_CENTER_NOTIF(CTF_PICKUP_NEUTRAL,                N_ENABLE,    0, 0, "",               CPID_CTF_LOWPRIO,       "0 0",  BOLD(_("^BGYou got the flag!")), "")
+     MSG_CENTER_NOTIF(CTF_PICKUP_RETURN,                 N_ENABLE,    1, 0, "s1",             CPID_CTF_LOWPRIO,       "0 0",  BOLD(_("^BGYou got your %steam^BG's flag, return it!")), "")
+     MSG_CENTER_NOTIF(CTF_PICKUP_RETURN_ENEMY,           N_ENABLE,    1, 0, "s1",             CPID_CTF_LOWPRIO,       "0 0",  BOLD(_("^BGYou got the %senemy^BG's flag, return it!")), "")
      MSG_CENTER_NOTIF(CTF_PICKUP_ENEMY,                  N_ENABLE,    1, 0, "s1",             CPID_CTF_LOWPRIO,       "0 0",  _("^BGThe %senemy^BG got your flag! Retrieve it!"), "")
      MSG_CENTER_NOTIF(CTF_PICKUP_ENEMY_VERBOSE,          N_ENABLE,    2, 0, "s1 s2 s1",       CPID_CTF_LOWPRIO,       "0 0",  _("^BGThe %senemy (^BG%s%s)^BG got your flag! Retrieve it!"), "")
      MSG_CENTER_NOTIF(CTF_PICKUP_ENEMY_NEUTRAL,          N_ENABLE,    1, 0, "s1",             CPID_CTF_LOWPRIO,       "0 0",  _("^BGThe %senemy^BG got the flag! Retrieve it!"), "")
  
      #define VERBOSE_MURDER(type) strcat(MURDER_##type, "^BG%s")
  
-     #define MURDER_FRAG             strcat(BOLD_OPERATOR, _("^K3%sYou fragged ^BG%s"))
-     #define MURDER_FRAG2            strcat(BOLD_OPERATOR, _("^K3%sYou scored against ^BG%s"))
-     #define MURDER_FRAGGED          _("^K1%sYou were fragged by ^BG%s")
-     #define MURDER_FRAGGED2         _("^K1%sYou were scored against by ^BG%s")
+     #define MURDER_FRAG             BOLD(_("^K3%sYou fragged ^BG%s"))
+     #define MURDER_FRAG2            BOLD(_("^K3%sYou scored against ^BG%s"))
+     #define MURDER_FRAGGED          BOLD(_("^K1%sYou were fragged by ^BG%s"))
+     #define MURDER_FRAGGED2         BOLD(_("^K1%sYou were scored against by ^BG%s"))
      MSG_CENTER_NOTIF(DEATH_MURDER_FRAG,                   N_ENABLE,  1, 1, "spree_cen s1",               CPID_Null,  "0 0",  MURDER_FRAG,                    MURDER_FRAG2                   )
      MSG_CENTER_NOTIF(DEATH_MURDER_FRAGGED,                N_ENABLE,  1, 1, "spree_cen s1",               CPID_Null,  "0 0",  MURDER_FRAGGED,                 MURDER_FRAGGED2                )
      MSG_CENTER_NOTIF(DEATH_MURDER_FRAGGED_VERBOSE,        N_ENABLE,  1, 4, "spree_cen s1 frag_stats",    CPID_Null,  "0 0",  VERBOSE_MURDER(FRAGGED),        VERBOSE_MURDER(FRAGGED2)       )
      MSG_CENTER_NOTIF(DEATH_MURDER_FRAG_VERBOSE,           N_ENABLE,  1, 2, "spree_cen s1 frag_ping",     CPID_Null,  "0 0",  VERBOSE_MURDER(FRAG),           VERBOSE_MURDER(FRAG2)          )
  
-     #define MURDER_FRAG_FIRE        strcat(BOLD_OPERATOR, _("^K3%sYou burned ^BG%s"))
-     #define MURDER_FRAG_FIRE2       strcat(BOLD_OPERATOR, _("^K3%sYou scored against ^BG%s"))
-     #define MURDER_FRAGGED_FIRE     _("^K1%sYou were burned by ^BG%s")
-     #define MURDER_FRAGGED_FIRE2    _("^K1%sYou were scored against by ^BG%s")
+     #define MURDER_FRAG_FIRE        BOLD(_("^K3%sYou burned ^BG%s"))
+     #define MURDER_FRAG_FIRE2       BOLD(_("^K3%sYou scored against ^BG%s"))
+     #define MURDER_FRAGGED_FIRE     BOLD(_("^K1%sYou were burned by ^BG%s"))
+     #define MURDER_FRAGGED_FIRE2    BOLD(_("^K1%sYou were scored against by ^BG%s"))
      MSG_CENTER_NOTIF(DEATH_MURDER_FRAG_FIRE,              N_ENABLE,  1, 1, "spree_cen s1",               CPID_Null,  "0 0",  MURDER_FRAG_FIRE,               MURDER_FRAG_FIRE2              )
      MSG_CENTER_NOTIF(DEATH_MURDER_FRAGGED_FIRE,           N_ENABLE,  1, 1, "spree_cen s1",               CPID_Null,  "0 0",  MURDER_FRAGGED_FIRE,            MURDER_FRAGGED_FIRE2           )
      MSG_CENTER_NOTIF(DEATH_MURDER_FRAGGED_FIRE_VERBOSE,   N_ENABLE,  1, 4, "spree_cen s1 frag_stats",    CPID_Null,  "0 0",  VERBOSE_MURDER(FRAGGED_FIRE),   VERBOSE_MURDER(FRAGGED_FIRE2)  )
      MSG_CENTER_NOTIF(DEATH_MURDER_FRAG_FIRE_VERBOSE,      N_ENABLE,  1, 2, "spree_cen s1 frag_ping",     CPID_Null,  "0 0",  VERBOSE_MURDER(FRAG_FIRE),      VERBOSE_MURDER(FRAG_FIRE2)     )
  
-     #define MURDER_FRAG_FREEZE      strcat(BOLD_OPERATOR, _("^K3%sYou froze ^BG%s"))
-     #define MURDER_FRAG_FREEZE2     strcat(BOLD_OPERATOR, _("^K3%sYou scored against ^BG%s"))
-     #define MURDER_FRAGGED_FREEZE   _("^K1%sYou were frozen by ^BG%s")
-     #define MURDER_FRAGGED_FREEZE2  _("^K1%sYou were scored against by ^BG%s")
+     #define MURDER_FRAG_FREEZE      BOLD(_("^K3%sYou froze ^BG%s"))
+     #define MURDER_FRAG_FREEZE2     BOLD(_("^K3%sYou scored against ^BG%s"))
+     #define MURDER_FRAGGED_FREEZE   BOLD(_("^K1%sYou were frozen by ^BG%s"))
+     #define MURDER_FRAGGED_FREEZE2  BOLD(_("^K1%sYou were scored against by ^BG%s"))
      MSG_CENTER_NOTIF(DEATH_MURDER_FRAG_FREEZE,            N_ENABLE,  1, 1, "spree_cen s1",               CPID_Null,  "0 0",  MURDER_FRAG_FREEZE,             MURDER_FRAG_FREEZE2            )
      MSG_CENTER_NOTIF(DEATH_MURDER_FRAGGED_FREEZE,         N_ENABLE,  1, 1, "spree_cen s1",               CPID_Null,  "0 0",  MURDER_FRAGGED_FREEZE,          MURDER_FRAGGED_FREEZE2         )
      MSG_CENTER_NOTIF(DEATH_MURDER_FRAGGED_FREEZE_VERBOSE, N_ENABLE,  1, 4, "spree_cen s1 frag_stats",    CPID_Null,  "0 0",  VERBOSE_MURDER(FRAGGED_FREEZE), VERBOSE_MURDER(FRAGGED_FREEZE2))
      MSG_CENTER_NOTIF(DEATH_MURDER_FRAG_FREEZE_VERBOSE,    N_ENABLE,  1, 2, "spree_cen s1 frag_ping",     CPID_Null,  "0 0",  VERBOSE_MURDER(FRAG_FREEZE),    VERBOSE_MURDER(FRAG_FREEZE2)   )
  
-     #define MURDER_TYPEFRAG         strcat(BOLD_OPERATOR, _("^K1%sYou typefragged ^BG%s"))
-     #define MURDER_TYPEFRAG2        strcat(BOLD_OPERATOR, _("^K1%sYou scored against ^BG%s^K1 while they were typing"))
-     #define MURDER_TYPEFRAGGED      _("^K1%sYou were typefragged by ^BG%s")
-     #define MURDER_TYPEFRAGGED2     _("^K1%sYou were scored against by ^BG%s^K1 while typing")
+     #define MURDER_TYPEFRAG         BOLD(_("^K1%sYou typefragged ^BG%s"))
+     #define MURDER_TYPEFRAG2        BOLD(_("^K1%sYou scored against ^BG%s^K1 while they were typing"))
+     #define MURDER_TYPEFRAGGED      BOLD(_("^K1%sYou were typefragged by ^BG%s"))
+     #define MURDER_TYPEFRAGGED2     BOLD(_("^K1%sYou were scored against by ^BG%s^K1 while typing"))
      MSG_CENTER_NOTIF(DEATH_MURDER_TYPEFRAG,               N_ENABLE,  1, 1, "spree_cen s1",               CPID_Null,  "0 0",  MURDER_TYPEFRAG,                MURDER_TYPEFRAG2               )
      MSG_CENTER_NOTIF(DEATH_MURDER_TYPEFRAGGED,            N_ENABLE,  1, 1, "spree_cen s1",               CPID_Null,  "0 0",  MURDER_TYPEFRAGGED,             MURDER_TYPEFRAGGED2            )
      MSG_CENTER_NOTIF(DEATH_MURDER_TYPEFRAGGED_VERBOSE,    N_ENABLE,  1, 4, "spree_cen s1 frag_stats",    CPID_Null,  "0 0",  VERBOSE_MURDER(TYPEFRAGGED),    VERBOSE_MURDER(TYPEFRAGGED2)   )
      MSG_CENTER_NOTIF(NADE_THROW,                        N_ENABLE,    0, 0, "nade_key",       CPID_NADES,             "0 0",  _("^BGPress ^F2%s^BG again to toss the nade!"), "")
      MSG_CENTER_NOTIF(NADE_BONUS,                        N_ENABLE,    0, 0, "",               CPID_NADES,             "0 0",  _("^F2You got a ^K1BONUS GRENADE^F2!"), "")
  
-     MSG_CENTER_NOTIF(DEATH_SELF_AUTOTEAMCHANGE,         N_ENABLE,    0, 1, "death_team",     CPID_Null,              "0 0",  _("^BGYou have been moved into a different team\nYou are now on: %s"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_BETRAYAL,               N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1Don't shoot your team mates!"), _("^K1Don't go against your team mates!"))
-     MSG_CENTER_NOTIF(DEATH_SELF_CAMP,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1Die camper!"), _("^K1Reconsider your tactics, camper!"))
-     MSG_CENTER_NOTIF(DEATH_SELF_CHEAT,                  N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You unfairly eliminated yourself!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_CUSTOM,                 N_ENABLE,    2, 0, "s2",             CPID_Null,              "0 0",  _("^K1You were %s"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_DROWN,                  N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You couldn't catch your breath!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_FALL,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You hit the ground with a crunch!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_FIRE,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You got a little bit too crispy!"), _("^K1You felt a little too hot!"))
-     MSG_CENTER_NOTIF(DEATH_SELF_GENERIC,                N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You killed your own dumb self!"), _("^K1You need to be more careful!"))
-     MSG_CENTER_NOTIF(DEATH_SELF_LAVA,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You couldn't stand the heat!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_MONSTER,                N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You were killed by a monster!"), _("^K1You need to watch out for monsters!"))
-     MSG_CENTER_NOTIF(DEATH_SELF_NADE,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You forgot to put the pin back in!"), _("^K1Tastes like chicken!"))
-     MSG_CENTER_NOTIF(DEATH_SELF_NADE_NAPALM,            N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1Hanging around a napalm explosion is bad!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_NADE_ICE_FREEZE,        N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You got a little bit too cold!"), _("^K1You felt a little chilly!"))
-     MSG_CENTER_NOTIF(DEATH_SELF_NADE_HEAL,              N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1Your Healing Nade is a bit defective"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_NOAMMO,                 N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You were killed for running out of ammo..."), _("^K1You are respawning for running out of ammo..."))
-     MSG_CENTER_NOTIF(DEATH_SELF_ROT,                    N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You grew too old without taking your medicine"), _("^K1You need to preserve your health"))
-     MSG_CENTER_NOTIF(DEATH_SELF_SHOOTING_STAR,          N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You became a shooting star!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_SLIME,                  N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You melted away in slime!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_SUICIDE,                N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You committed suicide!"), _("^K1You ended it all!"))
-     MSG_CENTER_NOTIF(DEATH_SELF_SWAMP,                  N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You got stuck in a swamp!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_TEAMCHANGE,             N_ENABLE,    0, 1, "death_team",     CPID_Null,              "0 0",  _("^BGYou are now on: %s"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_TOUCHEXPLODE,           N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You died in an accident!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_TURRET,                 N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You were fragged by a turret!"), _("^K1You had an unfortunate run in with a turret!"))
-     MSG_CENTER_NOTIF(DEATH_SELF_TURRET_EWHEEL,          N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You were fragged by an eWheel turret!"), _("^K1You had an unfortunate run in with an eWheel turret!"))
-     MSG_CENTER_NOTIF(DEATH_SELF_TURRET_WALK,            N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You were fragged by a Walker turret!"), _("^K1You had an unfortunate run in with a Walker turret!"))
-     MSG_CENTER_NOTIF(DEATH_SELF_VH_BUMB_DEATH,          N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You got caught in the blast of a Bumblebee explosion!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_VH_CRUSH,               N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You were crushed by a vehicle!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_VH_RAPT_BOMB,           N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You were caught in a Raptor cluster bomb!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_VH_RAPT_DEATH,          N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You got caught in the blast of a Raptor explosion!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_VH_SPID_DEATH,          N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You got caught in the blast of a Spiderbot explosion!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_VH_SPID_ROCKET,         N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You were blasted to bits by a Spiderbot rocket!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_VH_WAKI_DEATH,          N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You got caught in the blast of a Racer explosion!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_VH_WAKI_ROCKET,         N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You couldn't find shelter from a Racer rocket!"), "")
-     MSG_CENTER_NOTIF(DEATH_SELF_VOID,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1Watch your step!"), "")
-     MSG_CENTER_NOTIF(DEATH_TEAMKILL_FRAG,               N_ENABLE,    1, 0, "s1",             CPID_Null,              "0 0",  _("^K1Moron! You fragged ^BG%s^K1, a team mate!"), _("^K1Moron! You went against ^BG%s^K1, a team mate!"))
-     MSG_CENTER_NOTIF(DEATH_TEAMKILL_FRAGGED,            N_ENABLE,    1, 0, "s1",             CPID_Null,              "0 0",  _("^K1You were fragged by ^BG%s^K1, a team mate"), _("^K1You were scored against by ^BG%s^K1, a team mate"))
-     MSG_CENTER_NOTIF(DISCONNECT_IDLING,                 N_ENABLE,    0, 1, "",               CPID_IDLING,            "1 f1", _("^K1Stop idling!\n^BGDisconnecting in ^COUNT..."), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_AUTOTEAMCHANGE,         N_ENABLE,    0, 1, "death_team",     CPID_Null,              "0 0",  BOLD(_("^BGYou have been moved into a different team\nYou are now on: %s")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_BETRAYAL,               N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You were punished for attacking your team mates!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_CAMP,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1Die camper!")), BOLD(_("^K1Reconsider your tactics, camper!")))
+     MSG_CENTER_NOTIF(DEATH_SELF_CHEAT,                  N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You unfairly eliminated yourself!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_CUSTOM,                 N_ENABLE,    2, 0, "s2",             CPID_Null,              "0 0",  BOLD(_("^K1You were %s")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_DROWN,                  N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You couldn't catch your breath!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_FALL,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You hit the ground with a crunch!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_FIRE,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You got a little bit too crispy!")), BOLD(_("^K1You felt a little too hot!")))
+     MSG_CENTER_NOTIF(DEATH_SELF_GENERIC,                N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You fragged yourself!")), BOLD(_("^K1You need to be more careful!")))
+     MSG_CENTER_NOTIF(DEATH_SELF_LAVA,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You couldn't stand the heat!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_MONSTER,                N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You were killed by a monster!")), BOLD(_("^K1You need to watch out for monsters!")))
+     MSG_CENTER_NOTIF(DEATH_SELF_NADE,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You forgot to put the pin back in!")), BOLD(_("^K1Tastes like chicken!")))
+     MSG_CENTER_NOTIF(DEATH_SELF_NADE_NAPALM,            N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1Hanging around a napalm explosion is bad!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_NADE_ICE_FREEZE,        N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You got a little bit too cold!")), BOLD(_("^K1You felt a little chilly!")))
+     MSG_CENTER_NOTIF(DEATH_SELF_NADE_HEAL,              N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1Your Healing Nade is a bit defective")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_NOAMMO,                 N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You were killed for running out of ammo...")), BOLD(_("^K1You are respawning for running out of ammo...")))
+     MSG_CENTER_NOTIF(DEATH_SELF_ROT,                    N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You grew too old without taking your medicine")), BOLD(_("^K1You need to preserve your health")))
+     MSG_CENTER_NOTIF(DEATH_SELF_SHOOTING_STAR,          N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You became a shooting star!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_SLIME,                  N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You melted away in slime!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_SUICIDE,                N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You committed suicide!")), BOLD(_("^K1You ended it all!")))
+     MSG_CENTER_NOTIF(DEATH_SELF_SWAMP,                  N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You got stuck in a swamp!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_TEAMCHANGE,             N_ENABLE,    0, 1, "death_team",     CPID_Null,              "0 0",  BOLD(_("^BGYou are now on: %s")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_TOUCHEXPLODE,           N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You died in an accident!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_TURRET,                 N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You were fragged by a turret!")), BOLD(_("^K1You had an unfortunate run in with a turret!")))
+     MSG_CENTER_NOTIF(DEATH_SELF_TURRET_EWHEEL,          N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You were fragged by an eWheel turret!")), BOLD(_("^K1You had an unfortunate run in with an eWheel turret!")))
+     MSG_CENTER_NOTIF(DEATH_SELF_TURRET_WALK,            N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You were fragged by a Walker turret!")), BOLD(_("^K1You had an unfortunate run in with a Walker turret!")))
+     MSG_CENTER_NOTIF(DEATH_SELF_VH_BUMB_DEATH,          N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You got caught in the blast of a Bumblebee explosion!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_VH_CRUSH,               N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You were crushed by a vehicle!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_VH_RAPT_BOMB,           N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You were caught in a Raptor cluster bomb!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_VH_RAPT_DEATH,          N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You got caught in the blast of a Raptor explosion!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_VH_SPID_DEATH,          N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You got caught in the blast of a Spiderbot explosion!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_VH_SPID_ROCKET,         N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You were blasted to bits by a Spiderbot rocket!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_VH_WAKI_DEATH,          N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You got caught in the blast of a Racer explosion!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_VH_WAKI_ROCKET,         N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You couldn't find shelter from a Racer rocket!")), "")
+     MSG_CENTER_NOTIF(DEATH_SELF_VOID,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1Watch your step!")), "")
+     MSG_CENTER_NOTIF(DEATH_TEAMKILL_FRAG,               N_ENABLE,    1, 0, "s1",             CPID_Null,              "0 0",  BOLD(_("^K1Traitor! You team killed ^BG%s")), BOLD(_("^K1Traitor! You betrayed team mate ^BG%s")))
+     MSG_CENTER_NOTIF(DEATH_TEAMKILL_FRAGGED,            N_ENABLE,    1, 0, "s1",             CPID_Null,              "0 0",  BOLD(_("^K1You were team killed by ^BG%s")), BOLD(_("^K1You were betrayed by team mate ^BG%s")))
+     MSG_CENTER_NOTIF(DISCONNECT_IDLING,                 N_ENABLE,    0, 1, "",               CPID_IDLING,            "1 f1", BOLD(_("^K1Stop idling!\n^BGDisconnecting in ^COUNT...")), "")
+     MSG_CENTER_NOTIF(MOVETOSPEC_IDLING,                 N_ENABLE,    0, 1, "",               CPID_IDLING,            "1 f1", BOLD(_("^K1Stop idling!\n^BGMoving to spectators in ^COUNT...")), "")
  
      MSG_CENTER_NOTIF(DOOR_LOCKED_NEED,                  N_ENABLE,    1, 0, "s1",             CPID_Null,              "0 0",  _("^BGYou need %s^BG!"), "")
      MSG_CENTER_NOTIF(DOOR_LOCKED_ALSONEED,              N_ENABLE,    1, 0, "s1",             CPID_Null,              "0 0",  _("^BGYou also need %s^BG!"), "")
      MULTITEAM_CENTER(KEYHUNT_START,                     N_ENABLE,    0, 0, "",               CPID_KEYHUNT,           "0 0",  _("^BGYou are starting with the ^TC^TT Key"), "", KEY)
  
      MSG_CENTER_NOTIF(LMS_NOLIVES,                       N_ENABLE,    0, 0, "",               CPID_LMS,               "0 0",  _("^BGYou have no lives left, you must wait until the next match"), "")
+     MSG_CENTER_NOTIF(LMS_SPECWARN,                      N_ENABLE,    0, 0, "",               CPID_LMS,               "0 0",  _("^F4WARNING:^BG you can't rejoin this match after spectating.\nUse the same command again to spectate anyway."), "")
  
      MSG_CENTER_NOTIF(MISSING_TEAMS,                     N_ENABLE,    0, 1, "missing_teams",  CPID_MISSING_TEAMS,     "-1 0", _("^BGWaiting for players to join...\nNeed active players for: %s"), "")
      MSG_CENTER_NOTIF(MISSING_PLAYERS,                   N_ENABLE,    0, 1, "f1",             CPID_MISSING_PLAYERS,   "-1 0", _("^BGWaiting for %s player(s) to join..."), "")
      MSG_CENTER_NOTIF(INSTAGIB_FINDAMMO_FIRST,           N_ENABLE,    0, 0, "",               CPID_INSTAGIB_FINDAMMO, "1 10", _("^BGGet some ammo or you'll be dead in ^F4^COUNT^BG!"), _("^BGGet some ammo! ^F4^COUNT^BG left!"))
      MSG_CENTER_NOTIF(INSTAGIB_LIVES_REMAINING,          N_ENABLE,    0, 1, "f1",             CPID_Null,              "0 0",  _("^F2Extra lives remaining: ^K1%s"), "")
  
-     MSG_CENTER_NOTIF(CAMPAIGN_MESSAGE,                  N_ENABLE,    1, 1, "f1 s1 join_key", CPID_CAMPAIGN_MESSAGE,  "-1 0", strcat(_("Level %s: "), "^BG%s\n^3\n", _("^BGPress ^F2%s^BG to enter the game")), "")
      MSG_CENTER_NOTIF(MOTD,                              N_ENABLE,    1, 0, "s1",             CPID_MOTD,              "-1 0", "^BG%s", "")
  
      MSG_CENTER_NOTIF(NIX_COUNTDOWN,                     N_ENABLE,    0, 2, "item_wepname",   CPID_NIX,               "1 f2", _("^F2^COUNT^BG until weapon change...\nNext weapon: ^F1%s"), "")
      MSG_MULTI_NOTIF(DEATH_SELF_GENERIC,                 N_ENABLE,  NULL,           INFO_DEATH_SELF_GENERIC,                CENTER_DEATH_SELF_GENERIC)
      MSG_MULTI_NOTIF(DEATH_SELF_LAVA,                    N_ENABLE,  NULL,           INFO_DEATH_SELF_LAVA,                   CENTER_DEATH_SELF_LAVA)
      MSG_MULTI_NOTIF(DEATH_SELF_MON_MAGE,                N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_MAGE,               CENTER_DEATH_SELF_MONSTER)
 -    MSG_MULTI_NOTIF(DEATH_SELF_MON_SHAMBLER_CLAW,       N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_SHAMBLER_CLAW,      CENTER_DEATH_SELF_MONSTER)
 -    MSG_MULTI_NOTIF(DEATH_SELF_MON_SHAMBLER_SMASH,      N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_SHAMBLER_SMASH,     CENTER_DEATH_SELF_MONSTER)
 -    MSG_MULTI_NOTIF(DEATH_SELF_MON_SHAMBLER_ZAP,        N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_SHAMBLER_ZAP,       CENTER_DEATH_SELF_MONSTER)
 +    MSG_MULTI_NOTIF(DEATH_SELF_MON_GOLEM_CLAW,          N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_GOLEM_CLAW,         CENTER_DEATH_SELF_MONSTER)
 +    MSG_MULTI_NOTIF(DEATH_SELF_MON_GOLEM_SMASH,         N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_GOLEM_SMASH,        CENTER_DEATH_SELF_MONSTER)
 +    MSG_MULTI_NOTIF(DEATH_SELF_MON_GOLEM_ZAP,           N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_GOLEM_ZAP,          CENTER_DEATH_SELF_MONSTER)
      MSG_MULTI_NOTIF(DEATH_SELF_MON_SPIDER,              N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_SPIDER,             CENTER_DEATH_SELF_MONSTER)
      MSG_MULTI_NOTIF(DEATH_SELF_MON_WYVERN,              N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_WYVERN,             CENTER_DEATH_SELF_MONSTER)
      MSG_MULTI_NOTIF(DEATH_SELF_MON_ZOMBIE_JUMP,         N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_ZOMBIE_JUMP,        CENTER_DEATH_SELF_MONSTER)
index f3c6d0e24ecfb0a654982fe73e273d5e0dd24651,1b5e7cdb84ed9e098cd0f0dfff71931bbbb48ec0..2e6068bf56ac823cbefc287b163941447c19a7b8
@@@ -1245,9 -1245,9 +1245,9 @@@ void turret_initparams(entity tur
        #undef TRY
  }
  
 -bool turret_closetotarget(entity this, vector targ)
 +bool turret_closetotarget(entity this, vector targ, float range)
  {
 -      vector path_extra_size = '64 64 64';
 +      vector path_extra_size = '1 1 1' * range;
        return boxesoverlap(targ - path_extra_size, targ + path_extra_size, this.absmin - path_extra_size, this.absmax + path_extra_size);
  }
  
@@@ -1256,7 -1256,7 +1256,7 @@@ void turret_findtarget(entity this
        entity e = find(NULL, classname, "turret_manager");
        if(!e)
        {
-               e = new(turret_manager);
+               e = new_pure(turret_manager);
                setthink(e, turrets_manager_think);
                e.nextthink = time + 2;
        }
index bb7ba61cab8f44928869ff6575f7b18f89b0bb27,691404ac117d183c95162716a60c886d405017cc..ab2712db8a1dc9000a29de15feccb22283be2f1d
@@@ -26,7 -26,7 +26,7 @@@ void forAllDescendants(entity root, voi
        depthfirst(root, parent, firstChild, nextSibling, funcPre, funcPost, pass);
  }
  
- .string cvarName;
+ .string controlledCvar;
  void SUB_Null_ee(entity e1, entity e2)
  {
  }
@@@ -53,12 -53,12 +53,12 @@@ void loadAllCvars(entity root
        forAllDescendants(root, loadCvarsOf, SUB_Null_ee, NULL);
  }
  
- .string cvarNames_Multi;
+ .string controlledCvars_Multi;
  .void(entity me) saveCvars_Multi;
  string getCvarsMulti(entity me)
  {
-       if (me.cvarNames_Multi)
-               return me.cvarNames_Multi;
+       if (me.controlledCvars_Multi)
+               return me.controlledCvars_Multi;
        return string_null;
  }
  void saveCvarsMulti(entity me)
@@@ -67,9 -67,9 +67,9 @@@
        string s, cvarname;
  
        me.saveCvars_Multi(me);
-       s = cvar_string(me.cvarName);
+       s = cvar_string(me.controlledCvar);
  
-       n = tokenize_console(me.cvarNames_Multi);
+       n = tokenize_console(me.controlledCvars_Multi);
        for(i = 0; i < n; ++i)
        {
                // cvars prefixed with ! get saved with the inverted value
@@@ -89,7 -89,7 +89,7 @@@
  }
  void makeMulti(entity e, string otherCvars)
  {
-       e.cvarNames_Multi = otherCvars;
+       e.controlledCvars_Multi = otherCvars;
        e.saveCvars_Multi = e.saveCvars;
        e.saveCvars = saveCvarsMulti;
  }
@@@ -352,6 -352,7 +352,7 @@@ void UpdateNotification_URI_Get_Callbac
        string s;
  
        string un_version = "";
+       string un_tosversion = "";
        string un_download = "";
        string un_url = "";
        string un_bannedservers = "";
                                un_version = s;
                                break;
                        }
+                       case "T":
+                       {
+                               un_tosversion = s;
+                               break;
+                       }
                        case "C":
                        {
                                un_compatexpire = s;
                }
        }
  
+       if(un_tosversion != "")
+       {
+               _Nex_ExtResponseSystem_NewToS = stof(un_tosversion);
+       }
        if(un_bannedservers != "")
        {
                _Nex_ExtResponseSystem_BannedServers = strzone(un_bannedservers);
@@@ -464,15 -475,8 +475,8 @@@ void updateCheck(
        if(!_Nex_ExtResponseSystem_Queried)
        {
                _Nex_ExtResponseSystem_Queried = 1;
-               float startcnt;
-               string uri;
-               cvar_set("cl_startcount", ftos(startcnt = cvar("cl_startcount") + 1));
-               // for privacy, munge the start count a little
-               startcnt = floor((floor(startcnt / 10) + random()) * 10);
-               uri = sprintf("http://update.xonotic.org/checkupdate.txt?version=%s&cnt=%d", uri_escape(cvar_string("g_xonoticversion")), startcnt);
-               uri_get(uri, URI_GET_UPDATENOTIFICATION);
+               cvar_set("cl_startcount", ftos(cvar("cl_startcount") + 1));
+               uri_get("https://update.xonotic.org/checkupdate.txt", URI_GET_UPDATENOTIFICATION);
        }
  
        if(_Nex_ExtResponseSystem_PacksStep > 0)
@@@ -582,6 -586,7 +586,7 @@@ void preMenuDraw(
                draw_CenterText(mid - 1 * line, l1, fs, '1 0 0', 1, 0);
                draw_CenterText(mid - 0 * line, l2, fs, '0 0 1', 1, 0);
        }
        if (!campaign_name_previous)
                campaign_name_previous = strzone(strcat(campaign_name, "x")); // force unequal
        if(campaign_name == campaign_name_previous)
@@@ -682,13 -687,13 +687,13 @@@ float updateCompression(
        GAMETYPE(MAPINFO_TYPE_ONSLAUGHT) \
        GAMETYPE(MAPINFO_TYPE_ASSAULT) \
        /* GAMETYPE(MAPINFO_TYPE_DUEL) */ \
 -      /* GAMETYPE(MAPINFO_TYPE_INVASION) */ \
        /**/
  
  // hidden gametypes come last so indexing always works correctly
  #define HIDDEN_GAMETYPES \
        GAMETYPE(MAPINFO_TYPE_RACE) \
        GAMETYPE(MAPINFO_TYPE_CTS) \
 +      GAMETYPE(MAPINFO_TYPE_INVASION) \
        /**/
  
  Gametype GameType_GetID(int cnt)
@@@ -817,6 -822,18 +822,18 @@@ void dialog_hudpanel_main_settings(enti
                                e.configureXonoticTextSliderValues(e);
  }
  
+ bool isServerSingleplayer()
+ {
+       return (cvar_string("net_address") == "127.0.0.1" && cvar_string("net_address_ipv6") == "::1");
+ }
+ void makeServerSingleplayer()
+ {
+       // it doesn't allow clients to connect from different machines
+       localcmd("defer 0.1 \"sv_cmd settemp net_address 127.0.0.1\"\n");
+       localcmd("defer 0.1 \"sv_cmd settemp net_address_ipv6 ::1\"\n");
+ }
  float getFadedAlpha(float currentAlpha, float startAlpha, float targetAlpha)
  {
        if(startAlpha < targetAlpha)
index c08b4c898f694bc8154d86ffad3c8fbe00c20d5b,056a8e9e6ec7dae7efb4356cc4a2cf8b63f047d3..af7aeb059a63f9c438a65b06dd7cb8375a833b75
@@@ -168,17 -168,6 +168,6 @@@ entity GetFilteredEntity(string input
        return selection;
  }
  
- // same thing, but instead return their edict number
- float GetFilteredNumber(string input)
- {
-       entity selection = GetFilteredEntity(input);
-       float output;
-       output = etof(selection);
-       return output;
- }
  // switch between sprint and print depending on whether the receiver is the server or a player
  void print_to(entity to, string input)
  {
@@@ -358,7 -347,7 +347,7 @@@ void CommonCommand_editmob(int request
                                        string mon_oldname = mon.monster_name;
  
                                        mon.monster_name = argument;
 -                                      if (mon.sprite)   WaypointSprite_UpdateSprites(mon.sprite, WP_Monster, WP_Null, WP_Null);
 +                                      if (mon.sprite)   WaypointSprite_UpdateSprites(mon.sprite, WP_Monster, WP_Null, WP_Null); // TODO: the new name is never actually sent to CSQC!
                                        print_to(caller, sprintf("Your pet '%s' is now known as '%s'", mon_oldname, mon.monster_name));
                                        return;
                                }
                default:
                case CMD_REQUEST_USAGE:
                {
-                       print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " editmob command [arguments]"));
-                       print_to(caller, "  Where 'command' can be butcher spawn skin movetarget kill name");
-                       print_to(caller, "  spawn, skin, movetarget and name require 'arguments'");
+                       print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " editmob <command> [<arguments>]"));
+                       print_to(caller, "  Where <command> can be butcher spawn skin movetarget kill name");
+                       print_to(caller, "  spawn, skin, movetarget and name require <arguments>");
                        print_to(caller, "  spawn also takes arguments list and random");
                        print_to(caller, "  Monster will follow owner if third argument of spawn command is not defined");
                        return;
@@@ -490,8 -479,8 +479,8 @@@ void CommonCommand_info(int request, en
                default:
                case CMD_REQUEST_USAGE:
                {
-                       print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " info request"));
-                       print_to(caller, "  Where 'request' is the suffixed string appended onto the request for cvar.");
+                       print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " info <request>"));
+                       print_to(caller, "  Where <request> is the suffixed string appended onto the request for cvar.");
                        return;
                }
        }
@@@ -598,8 -587,9 +587,9 @@@ void CommonCommand_records(int request
                default:
                case CMD_REQUEST_USAGE:
                {
-                       print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " records"));
-                       print_to(caller, "  No arguments required.");
+                       print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " records [<pagenum>]"));
+                       print_to(caller, "  Without arguments it prints all records (all pages) for the current gametype,");
+                       print_to(caller, "  otherwise if there are multiple pages it only prints page <pagenum> (1..10),");
                        return;
                }
        }
@@@ -674,6 -664,7 +664,7 @@@ void CommonCommand_timein(int request, 
                                                        timeout_status = TIMEOUT_INACTIVE;
                                                        timeout_time = 0;
                                                        timeout_handler.nextthink = time;  // timeout_handler has to take care of it immediately
+                                                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_TIMEOUT);
                                                        bprint(strcat("^7The timeout was aborted by ", GetCallerName(caller), " !\n"));
                                                        return;
                                                }
@@@ -831,8 -822,8 +822,8 @@@ void CommonCommand_who(int request, ent
                default:
                case CMD_REQUEST_USAGE:
                {
-                       print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " who [separator]"));
-                       print_to(caller, "  Where 'separator' is the optional string to separate the values with, default is a space.");
+                       print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " who [<separator>]"));
+                       print_to(caller, "  Where <separator> is the optional string to separate the values with, default is a space.");
                        return;
                }
        }
diff --combined qcsrc/server/world.qc
index 598f47a17bf042f0f5ae6bb9e6ab4fcc0215ef9a,fca7c1741497e7a092245ad376744a1b3e4b630a..ca395209759edfa394a1d08b5157391239beb559
@@@ -69,7 -69,7 +69,7 @@@ void PingPLReport_Think(entity this
        {
                WriteHeader(MSG_BROADCAST, TE_CSQC_PINGPLREPORT);
                WriteByte(MSG_BROADCAST, this.cnt);
-               WriteShort(MSG_BROADCAST, bound(1, CS(e).ping, 65535));
+               WriteShort(MSG_BROADCAST, bound(1, rint(CS(e).ping), 32767));
                WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_packetloss * 255), 255));
                WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_movementloss * 255), 255));
  
@@@ -100,7 -100,6 +100,6 @@@ void PingPLReport_Spawn(
  }
  
  const float SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS = 1;
- float world_initialized;
  
  void SetDefaultAlpha()
  {
@@@ -171,6 -170,7 +170,7 @@@ void cvar_changes_init(
  #define BADPREFIX(p) if(substring(k, 0, strlen(p)) == p) continue
  #define BADPRESUFFIX(p,s) if(substring(k, 0, strlen(p)) == p && substring(k, -strlen(s), -1) == s) continue
  #define BADCVAR(p) if(k == p) continue
+ #define BADVALUE(p, val) if (k == p && v == val) continue
  
                // general excludes and namespaces for server admin used cvars
                BADPREFIX("help_"); // PN's server has this listed as changed, let's not rat him out for THAT
                BADCVAR("g_duel_not_dm_maps");
                BADCVAR("g_freezetag");
                BADCVAR("g_freezetag_teams");
 -              BADCVAR("g_invasion_teams");
                BADCVAR("g_invasion_type");
                BADCVAR("g_jailbreak");
                BADCVAR("g_jailbreak_teams");
                BADCVAR("g_chatsounds");
                BADCVAR("g_ca_point_leadlimit");
                BADCVAR("g_ca_point_limit");
+               BADCVAR("g_ca_spectate_enemies");
                BADCVAR("g_ctf_captimerecord_always");
                BADCVAR("g_ctf_flag_glowtrails");
                BADCVAR("g_ctf_dynamiclights");
                BADCVAR("g_ctf_flag_pickup_verbosename");
+               BADCVAR("g_ctf_flagcarrier_auto_helpme_damage");
                BADPRESUFFIX("g_ctf_flag_", "_model");
                BADPRESUFFIX("g_ctf_flag_", "_skin");
                BADCVAR("g_domination_point_leadlimit");
                BADCVAR("sv_minigames");
                BADCVAR("sv_namechangetimer");
                BADCVAR("sv_precacheplayermodels");
+               BADCVAR("sv_qcphysics");
                BADCVAR("sv_radio");
                BADCVAR("sv_stepheight");
                BADCVAR("sv_timeout");
                BADPREFIX("skill_");
                BADPREFIX("sv_allow_");
                BADPREFIX("sv_cullentities_");
-               BADPREFIX("sv_maxidle_");
+               BADPREFIX("sv_maxidle");
                BADPREFIX("sv_minigames_");
                BADPREFIX("sv_radio_");
                BADPREFIX("sv_timeout_");
                BADCVAR("g_ctf_leaderboard");
                BADCVAR("g_domination_point_limit");
                BADCVAR("g_domination_teams_override");
+               BADCVAR("g_freezetag_revive_spawnshield");
                BADCVAR("g_freezetag_teams_override");
                BADCVAR("g_friendlyfire");
                BADCVAR("g_fullbrightitems");
                BADCVAR("g_physics_clientselect");
                BADCVAR("g_pinata");
                BADCVAR("g_powerups");
+               BADCVAR("g_powerups_drop_ondeath");
                BADCVAR("g_player_brightness");
                BADCVAR("g_rocket_flying");
                BADCVAR("g_rocket_flying_disabledelays");
-               BADCVAR("g_spawnshieldtime");
+               BADPREFIX("g_spawnshield");
                BADCVAR("g_start_delay");
                BADCVAR("g_superspectate");
                BADCVAR("g_tdm_teams_override");
                BADCVAR("sv_defaultplayercolors");
                BADCVAR("sv_defaultplayermodel");
                BADCVAR("sv_defaultplayerskin");
-               BADCVAR("sv_maxidle");
                BADCVAR("sv_maxrate");
                BADCVAR("sv_motd");
                BADCVAR("sv_public");
-               BADCVAR("sv_ready_restart");
+               BADCVAR("sv_showfps");
+               BADCVAR("sv_showspectators");
                BADCVAR("sv_status_privacy");
                BADCVAR("sv_taunt");
                BADCVAR("sv_vote_call");
                BADCVAR("sv_vote_master_commands");
                BADCVAR("sv_vote_master_password");
                BADCVAR("sv_vote_simple_majority_factor");
+               BADVALUE("sys_ticrate", "0.0166667");
+               BADVALUE("sys_ticrate", "0.0333333");
                BADCVAR("teamplay_mode");
                BADCVAR("timelimit_override");
                BADPREFIX("g_warmup_");
                BADCVAR("g_lms_weaponarena");
                BADCVAR("g_ctf_stalemate_time");
  
-               if(cvar_string("g_mod_balance") == "Testing")
-               {
-                       // (temporary) while using the Testing balance, any weapon balance cvars are allowed to be changed
-                       BADPREFIX("g_balance_");
-               }
  #undef BADPRESUFFIX
  #undef BADPREFIX
  #undef BADCVAR
+ #undef BADVALUE
  
                if(pureadding)
                {
@@@ -687,6 -690,15 +689,15 @@@ spawnfunc(worldspawn
  {
        server_is_dedicated = boolean(stof(cvar_defstring("is_dedicated")));
  
+       if (autocvar_sv_termsofservice_url && autocvar_sv_termsofservice_url != "")
+       {
+               strcpy(sv_termsofservice_url_escaped, strreplace(":", "|", autocvar_sv_termsofservice_url));
+       }
+       else
+       {
+               strcpy(sv_termsofservice_url_escaped, "INVALID");
+       }
        bool wantrestart = false;
        {
                if (!server_is_dedicated)
  
        cvar_changes_init(); // do this very early now so it REALLY matches the server config
  
+       // default to RACE_RECORD, can be overwritten by gamemodes
+       record_type = RACE_RECORD;
        // needs to be done so early because of the constants they create
        static_init();
  
  
        if(autocvar_g_campaign)
                CampaignPreInit();
+       else
+               PlayerStats_GameReport_Init(); // we need this to be initiated before InitGameplayMode
  
        Map_MarkAsRecent(mapname);
  
-       PlayerStats_GameReport_Init(); // we need this to be initiated before InitGameplayMode
        InitGameplayMode();
        static_init_late();
        static_init_precache();
        // character set: ASCII 33-126 without the following characters: : ; ' " \ $
        if(autocvar_sv_eventlog)
        {
-               string s = sprintf("%s.%s.%06d", itos(autocvar_sv_eventlog_files_counter), strftime(false, "%s"), floor(random() * 1000000));
+               string num = strftime_s(); // strftime(false, "%s") isn't reliable, see strftime_s description
+               string s = sprintf("%s.%s.%06d", itos(autocvar_sv_eventlog_files_counter), num, floor(random() * 1000000));
                matchid = strzone(s);
  
                GameLogEcho(strcat(":gamestart:", GetGametype(), "_", GetMapname(), ":", s));
                if(autocvar_g_norecoil)
                        s = strcat(s, ":norecoil");
  
-               // TODO to mutator system
-               if(autocvar_g_powerups == 0)
-                       s = strcat(s, ":no_powerups");
-               if(autocvar_g_powerups > 0)
-                       s = strcat(s, ":powerups");
                GameLogEcho(s);
                GameLogEcho(":gameinfo:end");
        }
        MapInfo_Enumerate();
        MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1);
  
-       if(fexists(strcat("scripts/", mapname, ".arena")))
-               cvar_settemp("sv_q3acompat_machineshotgunswap", "1");
-       if(fexists(strcat("scripts/", mapname, ".defi")))
-               cvar_settemp("sv_q3defragcompat", "1");
+       q3compat = BITSET(q3compat, Q3COMPAT_ARENA, fexists(strcat("scripts/", mapname, ".arena")));
+       q3compat = BITSET(q3compat, Q3COMPAT_DEFI, fexists(strcat("scripts/", mapname, ".defi")));
  
        if(whichpack(strcat("maps/", mapname, ".cfg")) != "")
        {
        maplist_reply = strzone(getmaplist());
        lsmaps_reply = strzone(getlsmaps());
        monsterlist_reply = strzone(getmonsterlist());
+       bool records_available = false;
        for(int i = 0; i < 10; ++i)
        {
                string s = getrecords(i);
-               if (s)
+               if (s != "")
+               {
                        records_reply[i] = strzone(s);
+                       records_available = true;
+               }
        }
+       if (!records_available)
+               records_reply[0] = "No records available for the current game mode.\n";
        ladder_reply = strzone(getladder());
        rankings_reply = strzone(getrankings());
  
@@@ -1020,7 -1033,7 +1032,7 @@@ spawnfunc(light
        delete(this);
  }
  
 -bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance)
 +bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance, bool frompos)
  {
      float m = e.dphitcontentsmask;
      e.dphitcontentsmask = goodcontents | badcontents;
  
                // rule 4: we must "see" some spawnpoint or item
            entity sp = NULL;
 -          IL_EACH(g_spawnpoints, checkpvs(mstart, it),
 +          if(frompos)
            {
 -              if((traceline(mstart, it.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
 -              {
 -                      sp = it;
 -                      break;
 -              }
 -          });
 +              if((traceline(mstart, e.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
 +                      sp = e;
 +          }
 +          if(!sp)
 +          {
 +                  IL_EACH(g_spawnpoints, checkpvs(mstart, it),
 +                  {
 +                      if((traceline(mstart, it.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
 +                      {
 +                              sp = it;
 +                              break;
 +                      }
 +                  });
 +              }
                if(!sp)
                {
                        int items_checked = 0;
  
  float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
  {
 -      return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance);
 +      return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance, false);
  }
  
  /*
@@@ -1193,7 -1198,7 +1205,7 @@@ void DumpStats(float final
        s = strcat(s, GetGametype(), "_", GetMapname(), ":", ftos(rint(time)));
  
        if(to_console)
-               LOG_INFO(s);
+               LOG_HELP(s);
        if(to_eventlog)
                GameLogEcho(s);
  
  
        s = strcat(":labels:player:", GetPlayerScoreString(NULL, 0));
        if(to_console)
-               LOG_INFO(s);
+               LOG_HELP(s);
        if(to_eventlog)
                GameLogEcho(s);
        if(to_file)
        FOREACH_CLIENT(IS_REAL_CLIENT(it) || (IS_BOT_CLIENT(it) && autocvar_sv_logscores_bots), {
                s = strcat(":player:see-labels:", GetPlayerScoreString(it, 0), ":");
                s = strcat(s, ftos(rint(time - CS(it).jointime)), ":");
-               if(IS_PLAYER(it) || MUTATOR_CALLHOOK(GetPlayerStatus, it))
+               if(IS_PLAYER(it) || INGAME_JOINED(it))
                        s = strcat(s, ftos(it.team), ":");
                else
                        s = strcat(s, "spectator:");
  
                if(to_console)
-                       LOG_INFO(s, playername(it.netname, it.team, false));
+                       LOG_HELP(s, playername(it.netname, it.team, false));
                if(to_eventlog)
                        GameLogEcho(strcat(s, ftos(it.playerid), ":", playername(it.netname, it.team, false)));
                if(to_file)
        {
                s = strcat(":labels:teamscores:", GetTeamScoreString(0, 0));
                if(to_console)
-                       LOG_INFO(s);
+                       LOG_HELP(s);
                if(to_eventlog)
                        GameLogEcho(s);
                if(to_file)
                        s = strcat(":teamscores:see-labels:", GetTeamScoreString(i, 0));
                        s = strcat(s, ":", ftos(i));
                        if(to_console)
-                               LOG_INFO(s);
+                               LOG_HELP(s);
                        if(to_eventlog)
                                GameLogEcho(s);
                        if(to_file)
        }
  
        if(to_console)
-               LOG_INFO(":end");
+               LOG_HELP(":end");
        if(to_eventlog)
                GameLogEcho(":end");
        if(to_file)
@@@ -1271,8 -1276,9 +1283,9 @@@ only called if a time or frag limit ha
  */
  void NextLevel()
  {
+       cvar_set("_endmatch", "0");
        game_stopped = true;
-       intermission_running = 1; // game over
+       intermission_running = true; // game over
  
        // enforce a wait time before allowing changelevel
        if(player_count > 0)
  
        GameLogClose();
  
-       FOREACH_CLIENT(IS_PLAYER(it), {
+       int winner_team = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), {
                FixIntermissionClient(it);
                if(it.winning)
-                       bprint(playername(it.netname, it.team, false), " ^7wins.\n");
+               {
+                       if (teamplay && !winner_team)
+                       {
+                               winner_team = it.team;
+                               bprint(Team_ColorCode(winner_team), Team_ColorName_Upper(winner_team), "^7 team wins the match\n");
+                       }
+                       bprint(playername(it.netname, it.team, false), " ^7wins\n");
+               }
        });
  
        target_music_kill();
  }
  
  
float InitiateSuddenDeath()
int InitiateSuddenDeath()
  {
        // Check first whether normal overtimes could be added before initiating suddendeath mode
        // - for this timelimit_overtime needs to be >0 of course
                if(!checkrules_suddendeathend)
                {
                        if(autocvar_g_campaign)
+                       {
                                checkrules_suddendeathend = time; // no suddendeath in campaign
+                       }
                        else
+                       {
                                checkrules_suddendeathend = time + 60 * autocvar_timelimit_suddendeath;
+                               overtimes = -1;
+                       }
                        if(g_race && !g_race_qualifying)
                                race_StartCompleting();
                }
  void InitiateOvertime() // ONLY call this if InitiateSuddenDeath returned true
  {
        ++checkrules_overtimesadded;
+       overtimes = checkrules_overtimesadded;
        //add one more overtime by simply extending the timelimit
        cvar_set("timelimit", ftos(autocvar_timelimit + autocvar_timelimit_overtime));
        Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_TIME, autocvar_timelimit_overtime * 60);
@@@ -1387,13 -1407,13 +1414,13 @@@ float GetWinningCode(float fraglimitrea
  // set the .winning flag for exactly those players with a given field value
  void SetWinners(.float field, float value)
  {
-       FOREACH_CLIENT(IS_PLAYER(it), { it.winning = (it.(field) == value); });
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), { it.winning = (it.(field) == value); });
  }
  
  // set the .winning flag for those players with a given field value
  void AddWinners(.float field, float value)
  {
-       FOREACH_CLIENT(IS_PLAYER(it), {
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), {
                if(it.(field) == value)
                        it.winning = 1;
        });
  // clear the .winning flags
  void ClearWinners()
  {
-       FOREACH_CLIENT(IS_PLAYER(it), { it.winning = 0; });
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), { it.winning = 0; });
  }
  
  int fragsleft_last;
@@@ -1601,19 -1621,18 +1628,18 @@@ void CheckRules_World(
                leadlimit = 0; // no leadlimit for now
        }
  
-       if(timelimit > 0)
-       {
-               timelimit += game_starttime;
-       }
-       else if (timelimit < 0)
+       if (autocvar__endmatch || timelimit < 0)
        {
                // endmatch
                NextLevel();
                return;
        }
  
-       float wantovertime;
-       wantovertime = 0;
+       if(timelimit > 0)
+               timelimit += game_starttime;
+       int overtimes_prev = overtimes;
+       int wantovertime = 0;
  
        if(checkrules_suddendeathend)
        {
                                if(readyplayers || playerswithlaps >= 2)
                                {
                                        checkrules_suddendeathend = 0;
-                                       ReadyRestart(); // go to race
+                                       ReadyRestart(true); // go to race
                                        return;
                                }
                                else
  
        if(checkrules_status == WINNING_YES)
        {
+               if (overtimes == -1 && overtimes != overtimes_prev)
+               {
+                       // if suddendeathend overtime has just begun, revert it
+                       checkrules_suddendeathend = 0;
+                       overtimes = overtimes_prev;
+               }
                //print("WINNING\n");
                NextLevel();
        }
@@@ -1836,7 -1861,7 +1868,7 @@@ void readplayerstartcvars(
        start_ammo_plasma = 0;
        if (random_start_ammo == NULL)
        {
-               random_start_ammo = new(random_start_ammo);
+               random_start_ammo = new_pure(random_start_ammo);
        }
        start_health = cvar("g_balance_health_start");
        start_armorvalue = cvar("g_balance_armor_start");
        else if (s == "all" || s == "1")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "All Weapons";
+               g_weaponarena_list = "All Weapons Arena";
                g_weaponarena_weapons = weapons_all();
        }
        else if (s == "devall")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "Dev All Weapons";
+               g_weaponarena_list = "Dev All Weapons Arena";
                g_weaponarena_weapons = weapons_devall();
        }
        else if (s == "most")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "Most Weapons";
+               g_weaponarena_list = "Most Weapons Arena";
                g_weaponarena_weapons = weapons_most();
        }
        else if (s == "all_available")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "All Available Weapons";
+               g_weaponarena_list = "All Available Weapons Arena";
  
                // this needs to run after weaponsInMapAll is initialized
                InitializeEntity(NULL, weaponarena_available_all_update, INITPRIO_FINDTARGET);
        else if (s == "devall_available")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "Dev All Available Weapons";
+               g_weaponarena_list = "Dev All Available Weapons Arena";
  
                // this needs to run after weaponsInMapAll is initialized
                InitializeEntity(NULL, weaponarena_available_devall_update, INITPRIO_FINDTARGET);
        else if (s == "most_available")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "Most Available Weapons";
+               g_weaponarena_list = "Most Available Weapons Arena";
  
                // this needs to run after weaponsInMapAll is initialized
                InitializeEntity(NULL, weaponarena_available_most_update, INITPRIO_FINDTARGET);
        else if (s == "none")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "No Weapons";
+               g_weaponarena_list = "No Weapons Arena";
        }
        else
        {
                        if(wep != WEP_Null)
                        {
                                g_weaponarena_weapons |= (wep.m_wepset);
-                               g_weaponarena_list = strcat(g_weaponarena_list, wep.m_name, " & ");
+                               g_weaponarena_list = strcat(g_weaponarena_list, wep.netname, " & ");
                        }
                }
-               g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
+               if (g_weaponarena_list != "") // remove trailing " & "
+                       g_weaponarena_list = substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3);
+               else // no valid weapon found
+                       g_weaponarena_list = "No Weapons Arena";
        }
  
        if (g_weaponarena)
                g_weapon_stay = 0; // incompatible
                start_weapons = g_weaponarena_weapons;
                start_items |= IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS;
+               g_weaponarena_list = strzone(g_weaponarena_list);
        }
        else
        {
                random_start_weapons_count = cvar("g_random_start_weapons_count");
                SetResource(random_start_ammo, RES_SHELLS, cvar("g_random_start_shells"));
                SetResource(random_start_ammo, RES_BULLETS, cvar("g_random_start_bullets"));
-               SetResource(random_start_ammo, RES_ROCKETS,cvar("g_random_start_rockets"));
+               SetResource(random_start_ammo, RES_ROCKETS, cvar("g_random_start_rockets"));
                SetResource(random_start_ammo, RES_CELLS, cvar("g_random_start_cells"));
                SetResource(random_start_ammo, RES_PLASMA, cvar("g_random_start_plasma"));
        }
        start_ammo_cells = max(0, start_ammo_cells);
        start_ammo_plasma = max(0, start_ammo_plasma);
        start_ammo_fuel = max(0, start_ammo_fuel);
-       SetResource(random_start_ammo, RES_SHELLS,
-               max(0, GetResource(random_start_ammo, RES_SHELLS)));
-       SetResource(random_start_ammo, RES_BULLETS,
-               max(0, GetResource(random_start_ammo, RES_BULLETS)));
-       SetResource(random_start_ammo, RES_ROCKETS,
-               max(0, GetResource(random_start_ammo, RES_ROCKETS)));
-       SetResource(random_start_ammo, RES_CELLS,
-               max(0, GetResource(random_start_ammo, RES_CELLS)));
-       SetResource(random_start_ammo, RES_PLASMA,
-               max(0, GetResource(random_start_ammo, RES_PLASMA)));
+       SetResource(random_start_ammo, RES_SHELLS, max(0, GetResource(random_start_ammo, RES_SHELLS)));
+       SetResource(random_start_ammo, RES_BULLETS, max(0, GetResource(random_start_ammo, RES_BULLETS)));
+       SetResource(random_start_ammo, RES_ROCKETS, max(0, GetResource(random_start_ammo, RES_ROCKETS)));
+       SetResource(random_start_ammo, RES_CELLS, max(0, GetResource(random_start_ammo, RES_CELLS)));
+       SetResource(random_start_ammo, RES_PLASMA, max(0, GetResource(random_start_ammo, RES_PLASMA)));
  
        warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
        warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
@@@ -2122,7 -2146,7 +2153,7 @@@ void readlevelcvars(
  
      MUTATOR_CALLHOOK(ReadLevelCvars);
  
-       if (!warmup_stage)
+       if (!warmup_stage && !autocvar_g_campaign)
                game_starttime = time + cvar("g_start_delay");
  
        FOREACH(Weapons, it != WEP_Null, { it.wr_init(it); });
@@@ -2214,11 -2238,11 +2245,11 @@@ void droptofloor(entity this
  }
  
  bool autocvar_sv_gameplayfix_multiplethinksperframe = true;
- void RunThink(entity this)
+ void RunThink(entity this, float dt)
  {
        // don't let things stay in the past.
        // it is possible to start that way by a trigger with a local time.
-       if(this.nextthink <= 0 || this.nextthink > time + frametime)
+       if(this.nextthink <= 0 || this.nextthink > time + dt)
                return;
  
        float oldtime = time; // do we need to save this?
                // we don't want to loop in that case, so exit if the new nextthink is
                // <= the time the qc was told, also exit if it is past the end of the
                // frame
-               if(this.nextthink <= time || this.nextthink > oldtime + frametime || !autocvar_sv_gameplayfix_multiplethinksperframe)
+               if(this.nextthink <= time || this.nextthink > oldtime + dt || !autocvar_sv_gameplayfix_multiplethinksperframe)
                        break;
        }
  
@@@ -2264,8 -2288,8 +2295,8 @@@ void Physics_Frame(
                        if(it.move_movetype == MOVETYPE_PUSH || it.move_movetype == MOVETYPE_FAKEPUSH)
                                continue; // these movetypes have no regular think function
                        // handle thinking here
-                       if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + frametime)
-                               RunThink(it);
+                       if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + PHYS_INPUT_TIMELENGTH)
+                               RunThink(it, PHYS_INPUT_TIMELENGTH);
                }
        });
  
@@@ -2296,9 -2320,10 +2327,10 @@@ void EndFrame(
                        STAT(TYPEHIT_TIME, it) = time;
                } else if (e.killsound) {
                        STAT(KILL_TIME, it) = time;
-               } else if (e.damage_dealt) {
+               } else if (e.hitsound_damage_dealt) {
                        STAT(HIT_TIME, it) = time;
-                       STAT(DAMAGE_DEALT_TOTAL, it) += ceil(e.damage_dealt);
+                       // NOTE: this is not accurate as client code doesn't need so much accuracy for its purposes
+                       STAT(HITSOUND_DAMAGE_DEALT_TOTAL, it) += ceil(e.hitsound_damage_dealt);
                }
        });
        // add 1 frametime because after this, engine SV_Physics
        float altime = time + frametime * (1 + autocvar_g_antilag_nudge);
        FOREACH_CLIENT(true, {
                it.typehitsound = false;
-               it.damage_dealt = 0;
+               it.hitsound_damage_dealt = 0;
                it.killsound = false;
                antilag_record(it, CS(it), altime);
        });
@@@ -2431,6 -2456,8 +2463,8 @@@ void Shutdown(
  
                WeaponStats_Shutdown();
                MapInfo_Shutdown();
+               strfree(sv_termsofservice_url_escaped);
        }
        else if(world_initialized == 0)
        {
diff --combined qcsrc/server/world.qh
index f92322f9cf04a874e85df95f656249b3185ee37b,242b3369470546ff8c0a1c4b0169e14605237044..e74ab2dc7589733b4ca9e4034ab587c49daa78d1
@@@ -3,6 -3,7 +3,7 @@@
  #include <common/weapons/_all.qh>
  
  bool autocvar__sv_init;
+ bool autocvar__endmatch;
  bool autocvar_g_use_ammunition;
  bool autocvar_g_jetpack;
  bool autocvar_g_warmup_allguns;
@@@ -30,11 -31,13 +31,13 @@@ float autocvar_timelimit_suddendeath
  float checkrules_equality;
  float checkrules_suddendeathwarning;
  float checkrules_suddendeathend;
float checkrules_overtimesadded; //how many overtimes have been already added
int checkrules_overtimesadded; //how many overtimes have been already added
  
  // flag set on worldspawn so that the code knows if it is dedicated or not
  bool server_is_dedicated;
  
+ int world_initialized;
  string cvar_changes;
  string cvar_purechanges;
  float cvar_purechanges_count;
@@@ -43,6 -46,12 +46,12 @@@ string modname
  
  string gamemode_name;
  
+ string record_type;
+ string autocvar_sv_termsofservice_url;
+ // only escape the terms of service url on map change
+ string sv_termsofservice_url_escaped;
  string clientstuff;
  
  string matchid;
@@@ -130,11 -139,11 +139,11 @@@ const int WINNING_STARTSUDDENDEATHOVERT
  
  float WinningCondition_Scores(float limit, float leadlimit);
  void SetWinners(.float field, float value);
- void ReadyRestart();
+ void ReadyRestart(bool forceWarmupEnd);
  
  void DumpStats(float final);
  
 -bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance);
 +bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance, bool frompos);
  
  float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance);