]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/nade_updates
authorMario <zacjardine@y7mail.com>
Thu, 13 Nov 2014 14:52:28 +0000 (01:52 +1100)
committerMario <zacjardine@y7mail.com>
Thu, 13 Nov 2014 14:52:28 +0000 (01:52 +1100)
Conflicts:
qcsrc/client/progs.src
qcsrc/common/constants.qh
qcsrc/common/monsters/sv_monsters.qc
qcsrc/server/cl_physics.qc
qcsrc/server/command/cmd.qc
qcsrc/server/g_damage.qc
qcsrc/server/mutators/gamemode_freezetag.qc
qcsrc/server/progs.src

33 files changed:
1  2 
gamemodes.cfg
mutators.cfg
qcsrc/client/Main.qc
qcsrc/client/autocvars.qh
qcsrc/client/hud.qc
qcsrc/client/progs.src
qcsrc/client/waypointsprites.qc
qcsrc/common/constants.qh
qcsrc/common/deathtypes.qh
qcsrc/common/monsters/monster/mage.qc
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/notifications.qh
qcsrc/common/stats.qh
qcsrc/server/autocvars.qh
qcsrc/server/cl_client.qc
qcsrc/server/cl_physics.qc
qcsrc/server/cl_player.qc
qcsrc/server/cl_weapons.qc
qcsrc/server/command/cmd.qc
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/g_world.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/mutators/gamemode_ctf.qc
qcsrc/server/mutators/gamemode_freezetag.qc
qcsrc/server/mutators/gamemode_keepaway.qc
qcsrc/server/mutators/mutator_buffs.qc
qcsrc/server/mutators/mutator_nades.qc
qcsrc/server/mutators/mutator_spawn_near_teammate.qc
qcsrc/server/progs.src
qcsrc/server/sv_main.qc
qcsrc/server/teamplay.qc
qcsrc/server/w_minelayer.qc

diff --combined gamemodes.cfg
index 1916ddca944472c37c95f40dc84d37313d59e0fa,2b8874cd2e10fa22fa273e6aaebdf2bce8f5ed21..dadd41013ae8dfee7fd3a0f3cc0e2c3f469fd062
@@@ -62,6 -62,28 +62,28 @@@ alias sv_hook_gamerestar
  alias sv_hook_gameend
  
  
+ // =====================
+ //  gametype vote hooks
+ // =====================
+ // these are called when the mode is switched via gametype vote screen, earlier than gamestart hooks (useful for enabling per-gamemode mutators)
+ alias sv_vote_gametype_hook_all 
+ alias sv_vote_gametype_hook_as
+ alias sv_vote_gametype_hook_ca
+ alias sv_vote_gametype_hook_ctf
+ alias sv_vote_gametype_hook_cts
+ alias sv_vote_gametype_hook_dm
+ alias sv_vote_gametype_hook_dom
+ alias sv_vote_gametype_hook_ft
+ alias sv_vote_gametype_hook_inv
+ alias sv_vote_gametype_hook_ka
+ alias sv_vote_gametype_hook_kh
+ alias sv_vote_gametype_hook_lms
+ alias sv_vote_gametype_hook_nb
+ alias sv_vote_gametype_hook_ons
+ alias sv_vote_gametype_hook_rc
+ alias sv_vote_gametype_hook_tdm
  // ===========
  //  leadlimit
  // ===========
@@@ -80,7 -102,7 +102,7 @@@ seta g_keyhunt_point_leadlimit -1   "Keyh
  seta g_race_laps_limit -1     "Race laps limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
  seta g_nexball_goallimit -1 "Nexball goal limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
  seta g_nexball_goalleadlimit -1 "Nexball goal lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
- seta g_invasion_round_limit -1 "Invasion round limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+ seta g_invasion_point_limit -1 "Invasion point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
  
  
  // =================================
@@@ -327,12 -349,9 +349,12 @@@ seta g_freezetag_point_leadlimit -1      "Fr
  set g_freezetag_revive_speed 0.4 "Speed for reviving a frozen teammate"
  set g_freezetag_revive_clearspeed 1.6 "Speed at which reviving progress gets lost when out of range"
  set g_freezetag_revive_extra_size 100 "Distance in qu that you can stand from a frozen teammate to keep reviving him"
 +set g_freezetag_revive_nade 1 "Enable reviving from own nade explosion"
 +set g_freezetag_revive_nade_health 40 "Amount of health player has if they revived from their own nade explosion"
  set g_freezetag_revive_falldamage 0 "Enable reviving from this amount of fall damage"
  set g_freezetag_revive_falldamage_health 40 "Amount of health player has if they revived from falling"
  set g_freezetag_round_timelimit 180 "round time limit in seconds"
 +set g_freezetag_frozen_damage_trigger 1 "if 1, frozen players falling into the void will die instead of teleporting to spawn"
  set g_freezetag_frozen_force 0.6 "How much to multiply the force on a frozen player with"
  set g_freezetag_frozen_maxtime 60 "frozen players will be automatically unfrozen after this time in seconds"
  seta g_freezetag_teams_override 0
@@@ -478,4 -497,7 +500,7 @@@ set g_invasion_round_timelimit 120 "max
  set g_invasion_warmup 10 "time between waves to prepare for battle"
  set g_invasion_monster_count 10 "number of monsters on first wave (increments)"
  set g_invasion_zombies_only 0 "only spawn zombies"
- set g_invasion_spawn_delay 2 "spawn more monsters after this delay"
+ set g_invasion_spawn_delay 0.25
+ set g_invasion_spawnpoint_spawn_delay 0.5
+ set g_invasion_teams 0 "number of teams in invasion (note: use mapinfo to set this)"
+ set g_invasion_team_spawns 1 "use team spawns in teamplay invasion mode"
diff --combined mutators.cfg
index f75113e37ffc04d4bbc758e94d236f2f38992574,8ee7751e3f7e6e1fda870bb73e5e2296ba99cab5..1f66279a32ecc85b96b729a6d8ea3f2f148bf5d6
@@@ -138,11 -138,10 +138,11 @@@ set g_random_gravity_negative 1000 "neg
  
  
  // =======
 -//  nades
 +//  Nades
  // =======
  set g_nades 0 "enable off-hand grenades"
  set g_nades_spawn 1 "give nades right away when player spawns rather than delaying entire refire"
 +set g_nades_client_select 0 "allow client side selection of nade type"
  set g_nades_nade_lifetime 3.5
  set g_nades_nade_minforce 400
  set g_nades_nade_maxforce 2000
@@@ -153,73 -152,6 +153,73 @@@ set g_nades_nade_edgedamage 9
  set g_nades_nade_radius 300
  set g_nades_nade_force 650
  set g_nades_nade_newton_style 0
 +set g_nades_nade_type 1 "Type of the off-hand grenade. 1:normal 2:napalm 3:ice 4:translocate 5:spawn 6:heal 7:pokenade"
 +
 +seta cl_nade_timer 1 "show a visual timer for nades, 1 = only circle, 2 = circle with text"
 +seta cl_nade_type 3
 +seta cl_pokenade_type "zombie"
 +
 +// ------------
 +//  Nade bonus
 +// ------------
 +//
 +// How the nade bonus system works:
 +// Each player has a score counter that is increased by some actions (eg: capping, fragging...)
 +// Once this counter reaches its maximum, the player will receive a bonus grenade and the score counter resets
 +// If the player dies all the bonus nades will be lost and the score counter resets
 +// If g_nades_bonus_score_time is not zero, this score will increase or decrease over time
 +//
 +set g_nades_bonus 0 "Enable bonus grenades"
 +set g_nades_bonus_client_select 0 "Allow client side selection of bonus nade type"
 +set g_nades_bonus_type 2 "Type of the bonus grenade. 1:normal 2:napalm 3:ice 4:translocate 5:spawn 6:heal 7:pokenade"
 +set g_nades_bonus_onstrength 1 "Always give bonus grenades to players that have the strength powerup"
 +set g_nades_bonus_max 3 "Maximum number of bonus grenades"
 +// Bonus score
 +set g_nades_bonus_score_max   120 "Score value that will give a bonus nade"
 +set g_nades_bonus_score_minor   5 "Score given for minor actions (pickups, regular frags etc.)"
 +set g_nades_bonus_score_low    20 "Score given for frags and unfreezes"
 +set g_nades_bonus_score_medium 30 "Score given for flag returns and flag carrier kills"
 +set g_nades_bonus_score_high   60 "Score given for flag captures"
 +set g_nades_bonus_score_spree  40 "Score given every spree of this many frags"
 +set g_nades_bonus_score_time   -1 "Bonus nade score given per second (negative to have the score decay)"
 +set g_nades_bonus_score_time_flagcarrier 2 "Bonus nade score given per second as flag carrier (negative to have the score decay)"
 +
 +// Napalm (2)
 +set g_nades_napalm_blast 1 "Whether the napalm grenades also give damage with the usual grenade explosion"
 +set g_nades_napalm_burntime 0.5 "Time that the fire from napalm will stick to the player"
 +set g_nades_napalm_selfdamage 1 "Whether the player that tossed the nade can be harmed by its fire"
 +// Napalm fireballs
 +set g_nades_napalm_ball_count 6 "Number of fireballs emitted during the explosion"
 +set g_nades_napalm_ball_spread 500 "Maximum force which the fireballs will have on explosion"
 +set g_nades_napalm_ball_damageforcescale 4
 +set g_nades_napalm_ball_damage 40
 +set g_nades_napalm_ball_lifetime 7
 +set g_nades_napalm_ball_radius 100 "Distance from the fireball within which you may get burned"
 +// Napalm Fire fountain
 +set g_nades_napalm_fountain_lifetime 3 "Time period during which extra fire mines are ejected"
 +set g_nades_napalm_fountain_delay 0.5 "Delay between emissions by the fountain"
 +set g_nades_napalm_fountain_damage 50 "Damage caused by the center of the fountain"
 +set g_nades_napalm_fountain_edgedamage 20 "Damage caused by the edge of the fountain"
 +set g_nades_napalm_fountain_radius 130
 +
 +// Ice (3)
 +set g_nades_ice_freeze_time 3 "How long the ice field will last"
 +set g_nades_ice_health      0 "How much health the player will have after being unfrozen"
 +set g_nades_ice_explode     0 "Whether the ice nade should explode again once the ice field dissipated"
 +set g_nades_ice_teamcheck   0 "Don't freeze teammates"
 +
 +// Spawn (5)
 +set g_nades_spawn_count 3 "Number of times player will spawn at their spawn nade explosion location"
 +
 +// Heal (6)
 +set g_nades_heal_time 5 "How long the heling field will last"
 +set g_nades_heal_rate 30 "Health given per second"
 +set g_nades_heal_friend 1 "Multiplier of health given to team mates"
 +set g_nades_heal_foe   -2 "Multiplier of health given to enemies"
 +
 +// Pokenade (7)
 +set g_nades_pokenade_monster_lifetime 20 "How long pokenade monster will survive"
 +set g_nades_pokenade_monster_type "zombie" "Monster to spawn"
  
  
  // ============
@@@ -230,3 -162,50 +230,50 @@@ set g_campcheck_interval 1
  set g_campcheck_damage 100
  set g_campcheck_distance 1800
  
+ // =======
+ //  buffs
+ // =======
+ set cl_buffs_autoreplace 1 "automatically drop current buff when picking up another"
+ set g_buffs 0 "enable buffs (requires buff items or powerups)"
+ set g_buffs_waypoint_distance 1024 "maximum distance at which buff waypoint can be seen from item"
+ set g_buffs_randomize 1 "randomize buff type when player drops buff"
+ set g_buffs_random_lifetime 30 "re-spawn the buff again if it hasn't been touched after this time in seconds"
+ set g_buffs_random_location 0 "randomize buff location on start and when reset"
+ set g_buffs_random_location_attempts 10 "number of random locations a single buff will attempt to respawn at before giving up"
+ set g_buffs_spawn_count 5 "how many buffs to spawn on the map if none exist already"
+ set g_buffs_replace_powerups 1 "replace powerups on the map with random buffs"
+ set g_buffs_cooldown_activate 5 "cooldown period when buff is first activated"
+ set g_buffs_cooldown_respawn 3 "cooldown period when buff is reloading"
+ set g_buffs_ammo 1 "ammo buff: infinite ammunition"
+ set g_buffs_resistance 1 "resistance buff: greatly reduces damage taken"
+ set g_buffs_resistance_blockpercent 0.7 "damage reduction multiplier, higher values mean less damage"
+ set g_buffs_medic 1 "medic buff: increased regeneration speed, extra health, chance to survive a fatal attack"
+ set g_buffs_medic_survive_chance 0.6 "multiplier chance of player surviving a fatal hit"
+ set g_buffs_medic_survive_health 5 "amount of health player survives with after taking a fatal hit"
+ set g_buffs_medic_rot 0.7 "health rot rate multiplier"
+ set g_buffs_medic_max 1.5 "stable health medic limit multiplier"
+ set g_buffs_medic_regen 1.7 "health medic rate multiplier"
+ set g_buffs_vengeance 1 "vengeance buff: attackers also take damage"
+ set g_buffs_vengeance_damage_multiplier 0.6 "amount of damage dealt the attacker takes when hitting a target with vengeance"
+ set g_buffs_bash 1 "bash buff: increased knockback force and immunity to knockback"
+ set g_buffs_bash_force 2 "bash force multiplier"
+ set g_buffs_bash_force_self 1.2 "bash self force multiplier"
+ set g_buffs_disability 1 "disability buff: attacks to players and monsters deal slowness (decreased movement/attack speed) for a few seconds"
+ set g_buffs_disability_time 3 "time in seconds for target disability"
+ set g_buffs_disability_speed 0.5 "player speed multiplier while disabled"
+ set g_buffs_disability_rate 1.7 "player weapon rate multiplier while disabled"
+ set g_buffs_speed 1 "speed buff: increased movement/attack/health regeneration speed, carrier takes slightly more damage"
+ set g_buffs_speed_speed 1.7 "player speed multiplier while holding speed buff"
+ set g_buffs_speed_rate 0.8 "player weapon rate multiplier while holding speed buff"
+ set g_buffs_speed_damage_take 1.2 "damage taken multiplier while holding speed buff"
+ set g_buffs_speed_regen 1.2 "regeneration speed multiplier while holding speed buff"
+ set g_buffs_vampire 1 "vampire buff: attacks to players and monsters heal the carrier"
+ set g_buffs_vampire_damage_steal 0.6 "damage stolen multiplier while holding vampire buff"
+ set g_buffs_jump 1 "jump buff: greatly increased jump height"
+ set g_buffs_jump_height 600 "jump height while holding jump buff"
+ set g_buffs_flight 1 "flight buff: greatly decreased gravity"
+ set g_buffs_flight_gravity 0.3 "player gravity multiplier while holding flight buff"
+ set g_buffs_invisible 1 "invisible buff: carrier becomes invisible"
+ set g_buffs_invisible_alpha 0.4 "player invisibility multiplier while holding invisible buff"
diff --combined qcsrc/client/Main.qc
index 820171e75e92df1a1f65add6361a487c89d0ba93,c948c9a70e1e7f9c34d7f1ba8d6009fd221691a9..fea677cbbcec9dcfe0dabfbbecf827d17796a113
@@@ -87,9 -87,6 +87,9 @@@ void CSQC_Init(void
        registercvar("hud_usecsqc", "1");
        registercvar("scoreboard_columns", "default");
  
 +      registercvar("cl_nade_type", "3");
 +      registercvar("cl_pokenade_type", "zombie");
 +
        gametype = 0;
  
        // hud_fields uses strunzone on the titles!
        CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
        CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
        CALL_ACCUMULATED_FUNCTION(RegisterHUD_Panels);
+       CALL_ACCUMULATED_FUNCTION(RegisterBuffs);
  
        WaypointSprite_Load();
  
index 17813e3f2e33b2f490b32d92d179e2254919b40a,8516d32f6debeaf4faaa7fa539f5483a6e17eb86..28ef157e50d74bfde213a46b1c62d47b24adc0db
@@@ -268,6 -268,7 +268,7 @@@ float autocvar_hud_panel_notify_fadetim
  float autocvar_hud_panel_notify_flip;
  float autocvar_hud_panel_notify_fontsize;
  float autocvar_hud_panel_notify_time;
+ float autocvar_hud_panel_notify_icon_aspect;
  float autocvar_hud_panel_physics;
  float autocvar_hud_panel_physics_acceleration_progressbar_mode;
  float autocvar_hud_panel_physics_acceleration_progressbar_scale;
@@@ -290,6 -291,8 +291,8 @@@ float autocvar_hud_panel_powerups_baral
  float autocvar_hud_panel_powerups_flip;
  float autocvar_hud_panel_powerups_iconalign;
  float autocvar_hud_panel_powerups_progressbar;
+ float autocvar_hud_panel_buffs;
+ //float autocvar_hud_panel_buffs_iconalign;
  string autocvar_hud_panel_powerups_progressbar_shield;
  string autocvar_hud_panel_powerups_progressbar_strength;
  string autocvar_hud_panel_powerups_progressbar_superweapons;
@@@ -440,4 -443,3 +443,4 @@@ string autocvar__cl_playermodel
  float autocvar_cl_deathglow;
  float autocvar_developer_csqcentities;
  float autocvar_g_jetpack_attenuation;
 +float autocvar_cl_nade_timer;
diff --combined qcsrc/client/hud.qc
index 774d6ebc00405d2da57de72bc71fdbd49f3b395d,e1c95025892e272c556f33edbc86275df630a21a..ab61eb3c3eb4c071c549467e1b9b556deefadc76
@@@ -886,54 -886,6 +886,54 @@@ string GetAmmoPicture(float i
        }
  }
  
 +void DrawNadeScoreBar(vector myPos, vector mySize, vector color)
 +{
 +      
 +      HUD_Panel_DrawProgressBar(
 +              myPos + eX * autocvar_hud_panel_ammo_progressbar_xoffset * mySize_x, 
 +              mySize - eX * autocvar_hud_panel_ammo_progressbar_xoffset * mySize_x, 
 +              autocvar_hud_panel_ammo_progressbar_name, 
 +              getstatf(STAT_NADE_BONUS_SCORE), 0, 0, color, 
 +              autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
 +
 +}
 +
 +void DrawAmmoNades(vector myPos, vector mySize, float draw_expanding, float expand_time)
 +{
 +      float theAlpha = 1, a, b;
 +      vector nade_color, picpos, numpos;
 +      
 +      nade_color = Nade_Color(getstati(STAT_NADE_BONUS_TYPE));
 +      
 +      a = getstatf(STAT_NADE_BONUS);
 +      b = getstatf(STAT_NADE_BONUS_SCORE);
 +      
 +      if(autocvar_hud_panel_ammo_iconalign)
 +      {
 +              numpos = myPos;
 +              picpos = myPos + eX * 2 * mySize_y;
 +      }
 +      else
 +      {
 +              numpos = myPos + eX * mySize_y;
 +              picpos = myPos;
 +      }
 +
 +      DrawNadeScoreBar(myPos, mySize, nade_color);
 +
 +      if(b > 0 || a > 0)
 +      {
 +              if(autocvar_hud_panel_ammo_text)
 +                      drawstring_aspect(numpos, ftos(a), eX * (2/3) * mySize_x + eY * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL);
 +              
 +              if(draw_expanding)
 +                      drawpic_aspect_skin_expanding(picpos, "nade_nbg", '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL, expand_time);
 +                      
 +              drawpic_aspect_skin(picpos, "nade_bg" , '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL);
 +              drawpic_aspect_skin(picpos, "nade_nbg" , '1 1 0' * mySize_y, nade_color, panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL);
 +      }
 +}
 +
  void DrawAmmoItem(vector myPos, vector mySize, float itemcode, float currently_selected, float infinite_ammo)
  {
        float a;
                drawpic_aspect_skin(picpos, GetAmmoPicture(itemcode), '1 1 0' * mySize_y, '0 0 0', panel_fg_alpha * theAlpha * 0.5, DRAWFLAG_NORMAL);
  }
  
 +float nade_prevstatus;
 +float nade_prevframe;
 +float nade_statuschange_time;
  void HUD_Ammo(void)
  {
        if(hud != HUD_NORMAL) return;
                mySize -= '2 2 0' * panel_bg_padding;
        }
  
 -      const float AMMO_COUNT = 4;
        float rows = 0, columns, row, column;
 +      float nade_cnt = getstatf(STAT_NADE_BONUS), nade_score = getstatf(STAT_NADE_BONUS_SCORE);
 +      float draw_nades = (nade_cnt > 0 || nade_score > 0), nade_statuschange_elapsedtime;
 +      float total_ammo_count;
 +
        vector ammo_size;
 +      float AMMO_COUNT = 4;
        if (autocvar_hud_panel_ammo_onlycurrent)
 -              ammo_size = mySize;
 +              total_ammo_count = 1;
        else
 +              total_ammo_count = AMMO_COUNT - 1; // fuel
 +
 +      if(draw_nades)
        {
 -              rows = mySize_y/mySize_x;
 -              rows = bound(1, floor((sqrt(4 * (3/1) * rows * AMMO_COUNT + rows * rows) + rows + 0.5) / 2), AMMO_COUNT);
 -              //                               ^^^ ammo item aspect goes here
 +              ++total_ammo_count;
 +              if (nade_cnt != nade_prevframe)
 +              {
 +                      nade_statuschange_time = time;
 +                      nade_prevstatus = nade_prevframe;
 +                      nade_prevframe = nade_cnt;
 +              }
 +      }
 +      else
 +              nade_prevstatus = nade_prevframe = nade_statuschange_time = 0;
  
 -              columns = ceil(AMMO_COUNT/rows);
 +      rows = mySize_y/mySize_x;
 +      rows = bound(1, floor((sqrt(4 * (3/1) * rows * (total_ammo_count) + rows * rows) + rows + 0.5) / 2), (total_ammo_count));
 +      //                               ^^^ ammo item aspect goes here
  
 -              ammo_size = eX * mySize_x*(1/columns) + eY * mySize_y*(1/rows);
 -      }
 +      columns = ceil((total_ammo_count)/rows);
 +
 +      ammo_size = eX * mySize_x*(1/columns) + eY * mySize_y*(1/rows);
 +      
  
        local vector offset = '0 0 0'; // fteqcc sucks
        float newSize;
  
        float i, stat_items, currently_selected, infinite_ammo;
        infinite_ammo = FALSE;
 +
 +      row = column = 0;
 +
        if (autocvar_hud_panel_ammo_onlycurrent)
        {
                if(autocvar__hud_configure)
                                }
                        }
                }
 +
 +              ++row;
 +              if(row >= rows)
 +              {
 +                      row = 0;
 +                      column = column + 1;
 +              }
        }
        else
        {
                stat_items = getstati(STAT_ITEMS, 0, 24);
                if (stat_items & IT_UNLIMITED_WEAPON_AMMO)
                        infinite_ammo = TRUE;
 -              row = column = 0;
                for (i = 0; i < AMMO_COUNT; ++i) {
                        currently_selected = stat_items & GetAmmoItemCode(i);
                        DrawAmmoItem(pos + eX * column * (ammo_size_x + offset_x) + eY * row * (ammo_size_y + offset_y), ammo_size, i, currently_selected, infinite_ammo);
                }
        }
  
 +      if (draw_nades)
 +      {
 +              nade_statuschange_elapsedtime = time - nade_statuschange_time;
 +
 +              float f = bound(0, nade_statuschange_elapsedtime*2, 1);
 +
 +              DrawAmmoNades(pos + eX * column * (ammo_size_x + offset_x) + eY * row * (ammo_size_y + offset_y), ammo_size, nade_prevstatus < nade_cnt && nade_cnt != 0 && f < 1, f);
 +      }
 +
        draw_endBoldFont();
  }
  
@@@ -1688,148 -1601,154 +1688,154 @@@ void HUD_HealthArmor(void
  
  void HUD_Notify_Push(string icon, string attacker, string victim)
  {
-       if(icon != "")
-       {
-               --kn_index;
-               if (kn_index == -1) { kn_index = KN_MAX_ENTRIES-1; }
-               notify_times[kn_index] = time;
+       if (icon == "")
+               return;
+       ++notify_count;
+       --notify_index;
+       if (notify_index == -1)
+               notify_index = NOTIFY_MAX_ENTRIES-1;
+       // Free old strings
+       if (notify_attackers[notify_index])
+               strunzone(notify_attackers[notify_index]);
  
-               // icon
-               if(notify_icon[kn_index]) { strunzone(notify_icon[kn_index]); }
-               notify_icon[kn_index] = strzone(icon);
+       if (notify_victims[notify_index])
+               strunzone(notify_victims[notify_index]);
  
-               // attacker
-               if(notify_attackers[kn_index]) { strunzone(notify_attackers[kn_index]); }
-               notify_attackers[kn_index] = strzone(attacker);
+       if (notify_icons[notify_index])
+               strunzone(notify_icons[notify_index]);
  
-               // victim
-               if(notify_victims[kn_index]) { strunzone(notify_victims[kn_index]); }
-               notify_victims[kn_index] = strzone(victim);
+       // Allocate new strings
+       if (victim != "")
+       {
+               notify_attackers[notify_index] = strzone(attacker);
+               notify_victims[notify_index] = strzone(victim);
        }
+       else
+       {
+               // In case of a notification without a victim, the attacker
+               // is displayed on the victim's side. Instead of special
+               // treatment later on, we can simply switch them here.
+               notify_attackers[notify_index] = string_null;
+               notify_victims[notify_index] = strzone(attacker);
+       }
+       notify_icons[notify_index] = strzone(icon);
+       notify_times[notify_index] = time;
  }
  
  void HUD_Notify(void)
  {
-       if(!autocvar__hud_configure)
-       {
-               if(!autocvar_hud_panel_notify) return;
-       }
+       if (!autocvar__hud_configure)
+               if (!autocvar_hud_panel_notify)
+                       return;
  
        HUD_Panel_UpdateCvars();
-       vector pos, mySize;
-       pos = panel_pos;
-       mySize = panel_size;
        HUD_Panel_DrawBg(1);
-       if(panel_bg_padding)
+       if (!autocvar__hud_configure)
+               if (notify_count == 0)
+                       return;
+       vector pos, size;
+       pos  = panel_pos;
+       size = panel_size;
+       if (panel_bg_padding)
        {
-               pos += '1 1 0' * panel_bg_padding;
-               mySize -= '2 2 0' * panel_bg_padding;
+               pos  += '1 1 0' * panel_bg_padding;
+               size -= '2 2 0' * panel_bg_padding;
        }
  
-       float entries, height;
-       entries = bound(1, floor(KN_MAX_ENTRIES * mySize_y/mySize_x), KN_MAX_ENTRIES);
-       height = mySize_y/entries;
+       float fade_start = max(0, autocvar_hud_panel_notify_time);
+       float fade_time = max(0, autocvar_hud_panel_notify_fadetime);
+       float icon_aspect = max(1, autocvar_hud_panel_notify_icon_aspect);
  
-       vector fontsize;
-       float fontheight = height * autocvar_hud_panel_notify_fontsize;
-       fontsize = '0.5 0.5 0' * fontheight;
+       float entry_count = bound(1, floor(NOTIFY_MAX_ENTRIES * size_y / size_x), NOTIFY_MAX_ENTRIES);
+       float entry_height = size_y / entry_count;
  
-       float a;
-       float when;
-       when = autocvar_hud_panel_notify_time;
-       float fadetime;
-       fadetime = autocvar_hud_panel_notify_fadetime;
+       float panel_width_half = size_x * 0.5;
+       float icon_width_half = entry_height * icon_aspect / 2;
+       float name_maxwidth = panel_width_half - icon_width_half - size_x * NOTIFY_ICON_MARGIN;
  
-       vector pos_attacker, pos_victim, pos_icon;
-       float width_attacker;
+       vector font_size = '0.5 0.5 0' * entry_height * autocvar_hud_panel_notify_fontsize;
+       vector icon_size = (eX * icon_aspect + eY) * entry_height;
+       vector icon_left = eX * (panel_width_half - icon_width_half);
+       vector attacker_right = eX * name_maxwidth;
+       vector victim_left = eX * (size_x - name_maxwidth);
+       vector attacker_pos, victim_pos, icon_pos;
        string attacker, victim, icon;
+       float i, j, count, step, limit, alpha;
  
-       float i, j, step, limit;
-       if(autocvar_hud_panel_notify_flip) //order items from the top down
+       if (autocvar_hud_panel_notify_flip)
        {
+               // Order items from the top down
                i = 0;
                step = +1;
-               limit = entries;
+               limit = entry_count;
        }
-       else //order items from the bottom up
+       else
        {
-               i = entries - 1;
+               // Order items from the bottom up
+               i = entry_count - 1;
                step = -1;
                limit = -1;
        }
  
-       for(j = kn_index;  i != limit;  i += step, ++j)
+       for (j = notify_index, count = 0; i != limit; i += step, ++j, ++count)
        {
                if(autocvar__hud_configure)
                {
-                       if (step == +1)
-                               a = i;
-                       else // inverse order
-                               a = entries - 1 - i;
-                       attacker = textShortenToWidth(sprintf(_("Player %d"), a+1), 0.48 * mySize_x - height, fontsize, stringwidth_colors);
-                       victim = textShortenToWidth(sprintf(_("Player %d"), a+2), 0.48 * mySize_x - height, fontsize, stringwidth_colors);
-                       icon = strcat("weapon", get_weaponinfo(WEP_FIRST + mod(floor(a*2.4), WEP_LAST)).netname);
-                       a = bound(0, (when - a) / 4, 1);
-                       goto hud_config_notifyprint;
+                       attacker = sprintf(_("Player %d"), count + 1);
+                       victim = sprintf(_("Player %d"), count + 2);
+                       icon = strcat("weapon", get_weaponinfo(min(WEP_FIRST + count * 2, WEP_LAST)).netname);
+                       alpha = bound(0, 1.2 - count / entry_count, 1);
                }
                else
                {
-                       if (j == KN_MAX_ENTRIES)
+                       if (j == NOTIFY_MAX_ENTRIES)
                                j = 0;
  
-                       if(notify_times[j] + when > time)
-                               a = 1;
-                       else if(fadetime)
+                       if (notify_times[j] + fade_start > time)
+                               alpha = 1;
+                       else if (fade_time != 0)
                        {
-                               a = bound(0, (notify_times[j] + when + fadetime - time) / fadetime, 1);
-                               if(!a)
-                               {
+                               alpha = bound(0, (notify_times[j] + fade_start + fade_time - time) / fade_time, 1);
+                               if (alpha == 0)
                                        break;
-                               }
                        }
                        else
-                       {
                                break;
-                       }
  
                        attacker = notify_attackers[j];
                        victim = notify_victims[j];
-                       icon = notify_icon[j];
+                       icon = notify_icons[j];
                }
  
-               //type = notify_deathtype[j];
-               //w = DEATH_WEAPONOF(type);
-               if(icon != "")
+               if (icon != "" && victim != "")
                {
-                       if((attacker != "") && (victim == ""))
-                       {
-                               // Y [used by] X
-                               attacker = textShortenToWidth(attacker, 0.73 * mySize_x - height, fontsize, stringwidth_colors);
-                               pos_attacker = pos + eX * (0.27 * mySize_x + height) + eY * ((0.5 * fontsize_y + i * height) + (0.5 * (height - fontheight)));
-                               pos_icon = pos + eX * 0.25 * mySize_x - eX * height + eY * i * height;
+                       vector name_top = eY * (i * entry_height + 0.5 * (entry_height - font_size_y));
  
-                               drawpic_aspect_skin(pos_icon, icon, '2 1 0' * height, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
-                               drawcolorcodedstring(pos_attacker, attacker, fontsize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
-                       }
-                       else if((attacker != "") && (victim != ""))
+                       icon_pos = pos + icon_left + eY * i * entry_height;
+                       drawpic_aspect_skin(icon_pos, icon, icon_size, '1 1 1', panel_fg_alpha * alpha, DRAWFLAG_NORMAL);
+                       victim = textShortenToWidth(victim, name_maxwidth, font_size, stringwidth_colors);
+                       victim_pos = pos + victim_left + name_top;
+                       drawcolorcodedstring(victim_pos, victim, font_size, panel_fg_alpha * alpha, DRAWFLAG_NORMAL);
+                       if (attacker != "")
                        {
-                               // X [did action to] Y
-                               attacker = textShortenToWidth(attacker, 0.48 * mySize_x - height, fontsize, stringwidth_colors);
-                               victim = textShortenToWidth(victim, 0.48 * mySize_x - height, fontsize, stringwidth_colors);
- :hud_config_notifyprint
-                               width_attacker = stringwidth(attacker, TRUE, fontsize);
-                               pos_attacker = pos + eX * (0.48 * mySize_x - height - width_attacker) + eY * ((0.5 * fontsize_y + i * height) + (0.5 * (height - fontheight)));
-                               pos_victim = pos + eX * (0.52 * mySize_x + height) + eY * ((0.5 * fontsize_y + i * height) + (0.5 * (height - fontheight)));
-                               pos_icon = pos + eX * 0.5 * mySize_x - eX * height + eY * i * height;
-                               drawpic_aspect_skin(pos_icon, icon, '2 1 0' * height, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
-                               drawcolorcodedstring(pos_attacker, attacker, fontsize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
-                               drawcolorcodedstring(pos_victim, victim, fontsize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
+                               attacker = textShortenToWidth(attacker, name_maxwidth, font_size, stringwidth_colors);
+                               attacker_pos = pos + attacker_right - eX * stringwidth(attacker, TRUE, font_size) + name_top;
+                               drawcolorcodedstring(attacker_pos, attacker, font_size, panel_fg_alpha * alpha, DRAWFLAG_NORMAL);
                        }
                }
        }
+       notify_count = count;
  }
  
  // Timer (#5)
@@@ -4453,6 -4372,66 +4459,66 @@@ void HUD_CenterPrint (void
        }
  }
  
+ // Buffs (#18)
+ //
+ void HUD_Buffs(void)
+ {
+       float buffs = getstati(STAT_BUFFS, 0, 24);
+       if(!autocvar__hud_configure)
+       {
+               if(!autocvar_hud_panel_buffs) return;
+               if(spectatee_status == -1) return;
+               if(getstati(STAT_HEALTH) <= 0) return;
+               if(!buffs) return;
+       }
+       else
+       {
+               buffs = Buff_Type_first.items; // force first buff
+       }
+       
+       float b = 0; // counter to tell other functions that we have buffs
+       entity e;
+       string s = "";
+       for(e = Buff_Type_first; e; e = e.enemy) if(buffs & e.items)
+       {
+               ++b;
+               string o = strcat(rgb_to_hexcolor(Buff_Color(e.items)), Buff_PrettyName(e.items));
+               if(s == "")
+                       s = o;
+               else
+                       s = strcat(s, " ", o);
+       }
+       HUD_Panel_UpdateCvars();
+       draw_beginBoldFont();
+       vector pos, mySize;
+       pos = panel_pos;
+       mySize = panel_size;
+       HUD_Panel_DrawBg(bound(0, b, 1));
+       if(panel_bg_padding)
+       {
+               pos += '1 1 0' * panel_bg_padding;
+               mySize -= '2 2 0' * panel_bg_padding;
+       }
+       //float panel_ar = mySize_x/mySize_y;
+       //float is_vertical = (panel_ar < 1);
+       //float buff_iconalign = autocvar_hud_panel_buffs_iconalign;
+       vector buff_offset = '0 0 0';
+       
+       for(e = Buff_Type_first; e; e = e.enemy) if(buffs & e.items)
+       {
+               //DrawNumIcon(pos + buff_offset, mySize, shield, "shield", is_vertical, buff_iconalign, '1 1 1', 1);
+               drawcolorcodedstring_aspect(pos + buff_offset, s, mySize, panel_fg_alpha * 0.5, DRAWFLAG_NORMAL);
+       }
+       draw_endBoldFont();
+ }
  /*
  ==================
  Main HUD system
diff --combined qcsrc/client/progs.src
index 940f1f9328f5d80341490c195355f413d62b2a4f,b7281c562124b2c8dc0c20be64254d23095fa8da..86a8b43395e199d83a1dc1676f5a7d234cf1a27f
@@@ -8,6 -8,7 +8,7 @@@ sys-post.q
  Defs.qc
  ../dpdefs/keycodes.qc
  ../common/constants.qh
+ ../common/stats.qh
  
  ../warpzonelib/anglestransform.qh
  ../warpzonelib/mathlib.qh
@@@ -16,7 -17,7 +17,8 @@@
  
  ../common/teams.qh
  ../common/util.qh
 +../common/nades.qh
+ ../common/buffs.qh
  ../common/test.qh
  ../common/counting.qh
  ../common/items.qh
@@@ -118,7 -119,7 +120,8 @@@ command/cl_cmd.q
  
  ../common/monsters/monsters.qc
  
 +../common/nades.qc
+ ../common/buffs.qc
  
  ../warpzonelib/anglestransform.qc
  ../warpzonelib/mathlib.qc
index faed3bdb2a7ba8028352b9bd37e3a7517e53859b,38d1579282eed5650d36d556b21a4d4041cb6206..7ee34672c5caa087b242ef27aad7b8d3e64f8363
@@@ -241,6 -241,7 +241,7 @@@ vector spritelookupcolor(string s, vect
  }
  string spritelookuptext(string s)
  {
+       if(substring(s, 0, 5) == "buff-") { return Buff_PrettyName(Buff_Type_FromSprite(s)); }
        switch(s)
        {
                case "as-push": return _("Push");
                case "item-shield": return _("Shield");
                case "item-fuelregen": return _("Fuel regen");
                case "item-jetpack": return _("Jet Pack");
 -              case "freezetag_frozen": return _("Frozen!");
 +              case "frozen": return _("Frozen!");
                case "tagged-target": return _("Tagged");
                case "vehicle": return _("Vehicle");
                default: return s;
index c0b56652fac72ec854f45ec947648fef03988d59,7c8e9b35d32ec342abc38aa5860edac3edef9433..fb6d781c0c1e16e10e5cfb4fa3a591537b311050
@@@ -101,8 -101,6 +101,8 @@@ const float ENT_CLIENT_TURRET = 40
  const float ENT_CLIENT_AUXILIARYXHAIR = 50;
  const float ENT_CLIENT_VEHICLE = 60;
  
 +const float ENT_CLIENT_HEALING_ORB = 80;
 +
  const float SPRITERULE_DEFAULT = 0;
  const float SPRITERULE_TEAMPLAY = 1;
  
@@@ -141,86 -139,6 +141,6 @@@ const float CVAR_READONLY = 4
  ///////////////////////////
  // csqc communication stuff
  
- const float STAT_KH_KEYS = 32;
- const float STAT_CTF_STATE = 33;
- const float STAT_WEAPONS = 35;
- const float STAT_SWITCHWEAPON = 36;
- const float STAT_GAMESTARTTIME = 37;
- const float STAT_STRENGTH_FINISHED = 38;
- const float STAT_INVINCIBLE_FINISHED = 39;
- const float STAT_PRESSED_KEYS = 42;
- const float STAT_ALLOW_OLDNEXBEAM = 43; // this stat could later contain some other bits of info, like, more server-side particle config
- const float STAT_FUEL = 44;
- const float STAT_NB_METERSTART = 45;
- const float STAT_SHOTORG = 46; // compressShotOrigin
- const float STAT_LEADLIMIT = 47;
- const float STAT_WEAPON_CLIPLOAD = 48;
- const float STAT_WEAPON_CLIPSIZE = 49;
- const float STAT_NEX_CHARGE = 50;
- const float STAT_LAST_PICKUP = 51;
- const float STAT_HUD = 52;
- const float STAT_NEX_CHARGEPOOL = 53;
- const float STAT_HIT_TIME = 54;
- const float STAT_TYPEHIT_TIME = 55;
- const float STAT_LAYED_MINES = 56;
- const float STAT_HAGAR_LOAD = 57;
- const float STAT_SWITCHINGWEAPON = 58;
- const float STAT_SUPERWEAPONS_FINISHED = 59;
- const float STAT_VEHICLESTAT_HEALTH = 60;
- const float STAT_VEHICLESTAT_SHIELD = 61;
- const float STAT_VEHICLESTAT_ENERGY = 62;
- const float STAT_VEHICLESTAT_AMMO1 = 63;
- const float STAT_VEHICLESTAT_RELOAD1 = 64;
- const float STAT_VEHICLESTAT_AMMO2 = 65;
- const float STAT_VEHICLESTAT_RELOAD2 = 66;
- const float STAT_NADE_TIMER = 69;
- const float STAT_SECRETS_TOTAL = 70;
- const float STAT_SECRETS_FOUND = 71;
- const float STAT_RESPAWN_TIME = 72;
- const float STAT_ROUNDSTARTTIME = 73;
- const float STAT_WEAPONS2 = 74;
- const float STAT_WEAPONS3 = 75;
- const float STAT_MONSTERS_TOTAL = 76;
- const float STAT_MONSTERS_KILLED = 77;
- const float STAT_NADE_BONUS = 80;
- const float STAT_NADE_BONUS_TYPE = 81;
- const float STAT_NADE_BONUS_SCORE = 82;
- const float STAT_HEALING_ORB = 83;
- const float STAT_HEALING_ORB_ALPHA = 84;
- // mod stats (1xx)
- const float STAT_REDALIVE = 100;
- const float STAT_BLUEALIVE = 101;
- const float STAT_YELLOWALIVE = 102;
- const float STAT_PINKALIVE = 103;
- // freeze tag
- const float STAT_FROZEN = 104;
- const float STAT_REVIVE_PROGRESS = 105;
- // domination
- const float STAT_DOM_TOTAL_PPS = 100;
- const float STAT_DOM_PPS_RED = 101;
- const float STAT_DOM_PPS_BLUE = 102;
- const float STAT_DOM_PPS_PINK = 103;
- const float STAT_DOM_PPS_YELLOW = 104;
- //const float STAT_SPIDERBOT_AIM 53 // compressShotOrigin
- //const float STAT_SPIDERBOT_TARGET 54 // compressShotOrigin
- // see DP source, quakedef.h
- const float STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW = 222;
- const float STAT_MOVEVARS_AIRSTRAFEACCEL_QW = 223;
- const float STAT_MOVEVARS_MAXSPEED = 244;
- const float STAT_MOVEVARS_AIRACCEL_QW = 254;
  const float CTF_STATE_ATTACK = 1;
  const float CTF_STATE_DEFEND = 2;
  const float CTF_STATE_COMMANDER = 3;
@@@ -240,7 -158,7 +160,7 @@@ const vector eZ = '0 0 1'
  
  // moved that here so the client knows the max.
  // # of maps, I'll use arrays for them :P
- #define MAPVOTE_COUNT 10
+ #define MAPVOTE_COUNT 30
  
  /**
   * Lower scores are better (e.g. suicides)
  #define SP_SCORE 3
  // game mode specific indices are not in common/, but in server/scores_rules.qc!
  
- #ifdef COMPAT_XON010_CHANNELS
- const float CH_INFO = 0; // only on world and csqc
- const float CH_TRIGGER = 0; // only on players; compat: FALSELY CONTROLLED BY "Info"
- const float CH_WEAPON_A = 1; // only on players and entities
- const float CH_WEAPON_SINGLE = 5; // only on players and entities
- const float CH_VOICE = 2; // only on players
- const float CH_BGM_SINGLE = 2; // only on csqc; compat: FALSELY CONTROLLED BY "Voice"
- const float CH_AMBIENT = 2; // only on csqc; compat: FALSELY CONTROLLED BY "Voice"
- const float CH_TRIGGER_SINGLE = 3; // only on players, entities, csqc
- const float CH_SHOTS = 4; // only on players, entities, csqc
- const float CH_SHOTS_SINGLE = 4; // only on players, entities, csqc
- const float CH_WEAPON_B = 5; // only on players and entities
- const float CH_PAIN = 6; // only on players and csqc
- const float CH_PAIN_SINGLE = 6; // only on players and csqc
- const float CH_PLAYER = 7; // only on players and entities
- const float CH_TUBA = 5; // only on csqc
- #else
  const float CH_INFO = 0;
  const float CH_TRIGGER = -3;
  const float CH_WEAPON_A = -1;
@@@ -321,8 -222,8 +224,8 @@@ const float CH_WEAPON_B = -1
  const float CH_PAIN = -6;
  const float CH_PAIN_SINGLE = 6;
  const float CH_PLAYER = -7;
- const float CH_TUBA = 5;
- #endif
+ const float CH_PLAYER_SINGLE = 7;
+ const float CH_TUBA_SINGLE = 5;
  
  const float ATTEN_NONE = 0;
  const float ATTEN_MIN = 0.015625;
@@@ -370,6 -271,17 +273,6 @@@ const float PROJECTILE_BUMBLE_BEAM = 31
  const float PROJECTILE_MAGE_SPIKE = 32;
  const float PROJECTILE_SHAMBLER_LIGHTNING = 33;
  
 -const float PROJECTILE_NADE_RED = 50;
 -const float PROJECTILE_NADE_RED_BURN = 51;
 -const float PROJECTILE_NADE_BLUE = 52;
 -const float PROJECTILE_NADE_BLUE_BURN = 53;
 -const float PROJECTILE_NADE_YELLOW = 54;
 -const float PROJECTILE_NADE_YELLOW_BURN = 55;
 -const float PROJECTILE_NADE_PINK = 56;
 -const float PROJECTILE_NADE_PINK_BURN = 57;
 -const float PROJECTILE_NADE = 58;
 -const float PROJECTILE_NADE_BURN = 59;
 -
  const float SPECIES_HUMAN = 0;
  const float SPECIES_ROBOT_SOLID = 1;
  const float SPECIES_ALIEN = 2;
@@@ -454,3 -366,8 +357,8 @@@ noref var vector autocvar_sv_player_hea
  #define URI_GET_UPDATENOTIFICATION 33
  #define URI_GET_URLLIB 128
  #define URI_GET_URLLIB_END 191
+ // gametype votes
+ #define GTV_AVAILABLE 0
+ // for later use in per-map gametype filtering
+ #define GTV_FORBIDDEN 2
index 0bdea312fa20a74d5270b5d6d8b5be27d21bdd90,595cad7eea387401d507ee998b6031b663f91e1d..c8512cdcf25c7039bc352946e328bdb0566c19a8
@@@ -4,6 -4,7 +4,7 @@@
  
  #define DEATHTYPES \
        DEATHTYPE(DEATH_AUTOTEAMCHANGE,         DEATH_SELF_AUTOTEAMCHANGE,          NO_MSG,                        DEATH_SPECIAL_START) \
+       DEATHTYPE(DEATH_BUFF_VENGEANCE,         NO_MSG,                             DEATH_MURDER_VENGEANCE,        NORMAL_POS) \
        DEATHTYPE(DEATH_CAMP,                   DEATH_SELF_CAMP,                    NO_MSG,                        NORMAL_POS) \
        DEATHTYPE(DEATH_CHEAT,                  DEATH_SELF_CHEAT,                   DEATH_MURDER_CHEAT,            NORMAL_POS) \
        DEATHTYPE(DEATH_CUSTOM,                 DEATH_SELF_CUSTOM,                  NO_MSG,                        NORMAL_POS) \
        DEATHTYPE(DEATH_MONSTER_WYVERN,                 DEATH_SELF_MON_WYVERN,                          DEATH_MURDER_MONSTER,              NORMAL_POS) \
        DEATHTYPE(DEATH_MONSTER_ZOMBIE_JUMP,    DEATH_SELF_MON_ZOMBIE_JUMP,                     DEATH_MURDER_MONSTER,              NORMAL_POS) \
        DEATHTYPE(DEATH_MONSTER_ZOMBIE_MELEE,   DEATH_SELF_MON_ZOMBIE_MELEE,            DEATH_MURDER_MONSTER,              DEATH_MONSTER_LAST) \
 -      DEATHTYPE(DEATH_NADE,                                   DEATH_SELF_NADE,                                        DEATH_MURDER_NADE,                         NORMAL_POS) \
 +      DEATHTYPE(DEATH_NADE,                   DEATH_SELF_NADE,                    DEATH_MURDER_NADE,             NORMAL_POS) \
 +      DEATHTYPE(DEATH_NADE_NAPALM,            DEATH_SELF_NADE_NAPALM,             DEATH_MURDER_NADE_NAPALM,      NORMAL_POS) \
 +      DEATHTYPE(DEATH_NADE_ICE,               DEATH_SELF_NADE_ICE,                DEATH_MURDER_NADE_ICE,         NORMAL_POS) \
 +      DEATHTYPE(DEATH_NADE_ICE_FREEZE,        DEATH_SELF_NADE_ICE_FREEZE,         DEATH_MURDER_NADE_ICE_FREEZE,  NORMAL_POS) \
 +      DEATHTYPE(DEATH_NADE_HEAL,              DEATH_SELF_NADE_HEAL,               DEATH_MURDER_NADE_HEAL,        NORMAL_POS) \
        DEATHTYPE(DEATH_NOAMMO,                 DEATH_SELF_NOAMMO,                  NO_MSG,                        NORMAL_POS) \
        DEATHTYPE(DEATH_ROT,                    DEATH_SELF_ROT,                     NO_MSG,                        NORMAL_POS) \
        DEATHTYPE(DEATH_SHOOTING_STAR,          DEATH_SELF_SHOOTING_STAR,           DEATH_MURDER_SHOOTING_STAR,    NORMAL_POS) \
index 08e995e2e4102f6c8e3940e1d6c0cf7fafb2ea3a,bda48d5cb91dee7ae2fd20b5c1cb3d001c17d1b7..26bb0a9afda23c20909e5e35c5c7ed03a5df10a6
@@@ -60,7 -60,7 +60,7 @@@ float friend_needshelp(entity e
                return FALSE;
        if(DIFF_TEAM(e, self) && e != self.monster_owner)
                return FALSE;
 -      if(e.freezetag_frozen)
 +      if(e.frozen)
                return FALSE;
        if(!IS_PLAYER(e))
                return ((e.flags & FL_MONSTER) && e.health < e.max_health);
@@@ -233,7 -233,8 +233,8 @@@ void mage_heal(
                {
                        pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
                        head.health = bound(0, head.health + (autocvar_g_monster_mage_heal_allies), head.max_health);
-                       WaypointSprite_UpdateHealth(head.sprite, head.health);
+                       if(!(head.spawnflags & MONSTERFLAG_INVINCIBLE))
+                               WaypointSprite_UpdateHealth(head.sprite, head.health);
                }
        }
  
@@@ -335,12 -336,7 +336,7 @@@ void spawnfunc_monster_mage(
  {
        self.classname = "monster_mage";
  
-       self.monster_spawnfunc = spawnfunc_monster_mage;
-       if(Monster_CheckAppearFlags(self))
-               return;
-       if(!monster_initialize(MON_MAGE, FALSE)) { remove(self); return; }
+       if(!monster_initialize(MON_MAGE)) { remove(self); return; }
  }
  
  // compatibility with old spawns
@@@ -397,7 -393,7 +393,7 @@@ float m_mage(float req
                }
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/mage.dpm");
+                       precache_model("models/monsters/mage.dpm");
                        precache_sound ("weapons/grenade_impact.wav");
                        precache_sound ("weapons/tagexp1.wav");
                        return TRUE;
@@@ -415,7 -411,6 +411,6 @@@ float m_mage(float req
        {
                case MR_PRECACHE:
                {
-                       precache_model ("models/monsters/mage.dpm");
                        return TRUE;
                }
        }
index cc3379ee36a9e94c657d898a9ac6f91ef4676251,34e7ceb9901ebbb30583e88b3862ba3204cd28fb..6d06de50ebb30e9c8ff98ee6c5fefcb5542dbcf7
@@@ -3,30 -3,13 +3,13 @@@
  // =========================
  
  
- void monster_item_spawn()
- {
-       if(self.monster_loot)
-               self.monster_loot();
-       self.gravity = 1;
-       self.reset = SUB_Remove;
-       self.noalign = TRUE;
-       self.velocity = randomvec() * 175 + '0 0 325';
-       self.classname = "droppedweapon"; // hax
-       self.item_spawnshieldtime = time + 0.7;
-       SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1);
- }
  void monster_dropitem()
  {
        if(!self.candrop || !self.monster_loot)
                return;
  
        vector org = self.origin + ((self.mins + self.maxs) * 0.5);
-       entity e = spawn();
-       setorigin(e, org);
+       entity e = spawn(), oldself = self;
  
        e.monster_loot = self.monster_loot;
  
        MUTATOR_CALLHOOK(MonsterDropItem);
        e = other;
  
-       if(e)
+       if(e && e.monster_loot)
        {
-               e.think = monster_item_spawn;
-               e.nextthink = time + 0.3;
+               self = e;
+               e.noalign = TRUE;
+               e.monster_loot();
+               e.gravity = 1;
+               e.movetype = MOVETYPE_TOSS;
+               e.reset = SUB_Remove;
+               setorigin(e, org);
+               e.velocity = randomvec() * 175 + '0 0 325';
+               e.item_spawnshieldtime = time + 0.7;
+               e.classname = "droppedweapon"; // use weapon handling to remove it on touch
+               SUB_SetFade(e, time + autocvar_g_monsters_drop_time, 1);
+               self = oldself;
        }
  }
  
@@@ -56,10 -49,10 +49,10 @@@ float monster_isvalidtarget (entity tar
        if(targ == ent)
                return FALSE; // don't attack ourselves
  
-       traceline(ent.origin, targ.origin, MOVE_NORMAL, ent);
+       //traceline(ent.origin, targ.origin, MOVE_NORMAL, ent);
  
-       if(trace_ent != targ)
-               return FALSE;
+       //if(trace_ent != targ)
+               //return FALSE;
  
        if(targ.vehicle_flags & VHF_ISVEHICLE)
        if(!((get_monsterinfo(ent.monsterid)).spawnflags & MON_FLAG_RANGED))
@@@ -68,9 -61,6 +61,6 @@@
        if(time < game_starttime)
                return FALSE; // monsters do nothing before the match has started
  
-       if(vlen(targ.origin - ent.origin) >= ent.target_range)
-               return FALSE; // enemy is too far away
        if(targ.takedamage == DAMAGE_NO)
                return FALSE; // enemy can't be damaged
  
        if(SAME_TEAM(targ, ent))
                return FALSE; // enemy is on our team
  
 -      if (targ.freezetag_frozen)
 +      if (targ.frozen)
                return FALSE; // ignore frozen
  
-       if(autocvar_g_monsters_target_infront || ent.spawnflags & MONSTERFLAG_INFRONT)
+       if(autocvar_g_monsters_target_infront || (ent.spawnflags & MONSTERFLAG_INFRONT))
        if(ent.enemy != targ)
        {
                float dot;
@@@ -128,6 -118,7 +118,7 @@@ entity FindTarget (entity ent
  
        entity head, closest_target = world;
        head = findradius(ent.origin, ent.target_range);
+       //head = WarpZone_FindRadius(ent.origin, ent.target_range, TRUE);
  
        while(head) // find the closest acceptable target to pass to
        {
                {
                        // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
                        vector head_center = CENTER_OR_VIEWOFS(head);
+                       //vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
                        vector ent_center = CENTER_OR_VIEWOFS(ent);
  
-                       //if(ctf_CheckPassDirection(head_center, ent_center, ent.v_angle, head.WarpZone_findradius_nearest))
+                       traceline(ent_center, head_center, MOVE_NORMAL, ent);
+                       if(trace_ent == head)
                        if(closest_target)
                        {
                                vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
+                               //vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
                                if(vlen(ent_center - head_center) < vlen(ent_center - closest_target_center))
                                        { closest_target = head; }
                        }
@@@ -279,7 -274,10 +274,10 @@@ void UpdateMonsterSounds(
  
  void MonsterSound(.string samplefield, float sound_delay, float delaytoo, float chan)
  {
-       if(delaytoo && time < self.msound_delay)
+       if(!autocvar_g_monsters_sounds) { return; }
+       if(delaytoo)
+       if(time < self.msound_delay)
                return; // too early
        GlobalSound(self.samplefield, chan, VOICETYPE_PLAYERSOUND);
  
@@@ -357,14 -355,21 +355,21 @@@ float Monster_CanRespawn(entity ent
        return TRUE;
  }
  
+ float monster_initialize(float mon_id);
+ void monster_respawn()
+ {
+       // is this function really needed?
+       monster_initialize(self.monsterid);
+ }
  void Monster_Fade ()
  {
        if(Monster_CanRespawn(self))
        {
                self.spawnflags |= MONSTERFLAG_RESPAWNED;
-               self.think = self.monster_spawnfunc;
+               self.think = monster_respawn;
                self.nextthink = time + self.respawntime;
-               self.ltime = 0;
+               self.monster_lifetime = 0;
                self.deadflag = DEAD_RESPAWNING;
                if(self.spawnflags & MONSTER_RESPAWN_DEATHPOINT)
                {
                // number of monsters spawned with mobspawn command
                totalspawned -= 1;
  
-               if(IS_CLIENT(self.realowner))
-                       self.realowner.monstercount -= 1;
                SUB_SetFade(self, time + 3, 1);
        }
  }
@@@ -472,10 -474,40 +474,40 @@@ vector monster_pickmovetarget(entity ta
        // enemy is always preferred target
        if(self.enemy)
        {
-               makevectors(self.angles);
+               vector targ_origin = ((self.enemy.absmin + self.enemy.absmax) * 0.5);
+               targ_origin = WarpZone_RefSys_TransformOrigin(self.enemy, self, targ_origin); // origin of target as seen by the monster (us)
+               WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
+               
+               if((self.enemy == world)
+                       || (self.enemy.deadflag != DEAD_NO || self.enemy.health < 1)
 -                      || (self.enemy.freezetag_frozen)
++                      || (self.enemy.frozen)
+                       || (self.enemy.flags & FL_NOTARGET)
+                       || (self.enemy.alpha < 0.5)
+                       || (self.enemy.takedamage == DAMAGE_NO)
+                       || (vlen(self.origin - targ_origin) > self.target_range)
+                       || ((trace_fraction < 1) && (trace_ent != self.enemy)))
+                       //|| (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit)) // TODO: chase timelimit?
+               {
+                       self.enemy = world;
+                       self.pass_distance = 0;
+               }
+               
+               if(self.enemy)
+               {
+                       /*WarpZone_TrailParticles(world, particleeffectnum("red_pass"), self.origin, targ_origin);
+                       print("Trace origin: ", vtos(targ_origin), "\n");
+                       print("Target origin: ", vtos(self.enemy.origin), "\n");
+                       print("My origin: ", vtos(self.origin), "\n"); */
+                       
+                       self.monster_movestate = MONSTER_MOVE_ENEMY;
+                       self.last_trace = time + 1.2;
+                       return targ_origin;
+               }
+       
+               /*makevectors(self.angles);
                self.monster_movestate = MONSTER_MOVE_ENEMY;
                self.last_trace = time + 1.2;
-               return self.enemy.origin;
+               return self.enemy.origin; */
        }
  
        switch(self.monster_moveflags)
                {
                        vector pos;
                        self.monster_movestate = MONSTER_MOVE_WANDER;
-                       self.last_trace = time + 2;
-                       self.angles_y = rint(random() * 500);
-                       makevectors(self.angles);
-                       pos = self.origin + v_forward * 600;
-                       if(self.flags & FL_FLY || self.flags & FL_SWIM)
-                       if(self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
-                       {
-                               pos_z = random() * 200;
-                               if(random() >= 0.5)
-                                       pos_z *= -1;
-                       }
  
                        if(targ)
                        {
                                self.last_trace = time + 0.5;
                                pos = targ.origin;
                        }
+                       else
+                       {
+                               self.last_trace = time + self.wander_delay;
+                               self.angles_y = rint(random() * 500);
+                               makevectors(self.angles);
+                               pos = self.origin + v_forward * self.wander_distance;
+                               if(((self.flags & FL_FLY) && (self.spawnflags & MONSTERFLAG_FLY_VERTICAL)) || (self.flags & FL_SWIM))
+                               {
+                                       pos_z = random() * 200;
+                                       if(random() >= 0.5)
+                                               pos_z *= -1;
+                               }
+                       }
  
                        return pos;
                }
        }
  }
  
+ void monster_CalculateVelocity(entity mon, 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, (current_distance / self.pass_distance)));
+       //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
+       vector targpos;
+       if(current_height) // make sure we can actually do this arcing path
+       {
+               targpos = (to + ('0 0 1' * current_height));
+               WarpZone_TraceLine(mon.origin, targpos, MOVE_NOMONSTERS, mon);
+               if(trace_fraction < 1)
+               {
+                       //print("normal arc line failed, trying to find new pos...");
+                       WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, mon);
+                       targpos = (trace_endpos + '0 0 -10');
+                       WarpZone_TraceLine(mon.origin, targpos, MOVE_NOMONSTERS, mon);
+                       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; }
+       //mon.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
+       vector desired_direction = normalize(targpos - from);
+       if(turnrate) { mon.velocity = (normalize(normalize(mon.velocity) + (desired_direction * 50)) * movespeed); }
+       else { mon.velocity = (desired_direction * movespeed); }
+       //mon.steerto = steerlib_attract2(targpos, 0.5, 500, 0.95);
+       //mon.angles = vectoangles(mon.velocity);
+ }
  void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle)
  {
-       fixedmakevectors(self.angles);
+       //fixedmakevectors(self.angles);
  
        if(self.target2)
                self.goalentity = find(world, targetname, self.target2);
  
        entity targ;
  
 +      if(self.frozen == 2)
 +      {
 +              self.revive_progress = bound(0, self.revive_progress + self.ticrate * self.revive_speed, 1);
 +              self.health = max(1, self.revive_progress * self.max_health);
 +              self.iceblock.alpha = bound(0.2, 1 - self.revive_progress, 1);
 +
 +              WaypointSprite_UpdateHealth(self.sprite, self.health);
 +
 +              movelib_beak_simple(stopspeed);
 +              self.frame = manim_idle;
 +
 +              self.enemy = world;
 +              self.nextthink = time + self.ticrate;
 +
 +              if(self.revive_progress >= 1)
 +                      Unfreeze(self);
 +
 +              return;
 +      }
 +      else if(self.frozen == 3)
 +      {
 +              self.revive_progress = bound(0, self.revive_progress - self.ticrate * self.revive_speed, 1);
 +              self.health = max(0, autocvar_g_nades_ice_health + (self.max_health-autocvar_g_nades_ice_health) * self.revive_progress );
 +
 +              WaypointSprite_UpdateHealth(self.sprite, self.health);
 +
 +              movelib_beak_simple(stopspeed);
 +              self.frame = manim_idle;
 +
 +              self.enemy = world;
 +              self.nextthink = time + self.ticrate;
 +
 +              if(self.health < 1)
 +              {
 +                      Unfreeze(self);
 +                      self.health = 0;
 +                      self.event_damage(self, self.frozen_by, 1, DEATH_NADE_ICE_FREEZE, self.origin, '0 0 0');
 +              }
 +
 +              else if ( self.revive_progress <= 0 )
 +                      Unfreeze(self);
 +
 +              return;
 +      }
 +
        if(self.flags & FL_SWIM)
        {
                if(self.waterlevel < WATERLEVEL_WETFEET)
        monster_speed_run = runspeed;
        monster_speed_walk = walkspeed;
  
-       if(MUTATOR_CALLHOOK(MonsterMove) || gameover || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time)
+       if(MUTATOR_CALLHOOK(MonsterMove) || gameover || self.draggedby != world || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time)
        {
                runspeed = walkspeed = 0;
                if(time >= self.spawn_time)
        if(DIFF_TEAM(self.monster_owner, self))
                self.monster_owner = world;
  
-       if(self.enemy && self.enemy.health < 1)
-               self.enemy = world; // enough!
        if(time >= self.last_enemycheck)
        {
-               if(!monster_isvalidtarget(self.enemy, self))
-                       self.enemy = world;
                if(!self.enemy)
                {
                        self.enemy = FindTarget(self);
                        if(self.enemy)
+                       {
+                               WarpZone_RefSys_Copy(self.enemy, self);
+                               WarpZone_RefSys_AddInverse(self.enemy, self); // wz1^-1 ... wzn^-1 receiver
+                               self.moveto = WarpZone_RefSys_TransformOrigin(self.enemy, self, (0.5 * (self.enemy.absmin + self.enemy.absmax)));
+                               
+                               self.pass_distance = vlen((('1 0 0' * self.enemy.origin_x) + ('0 1 0' * self.enemy.origin_y)) - (('1 0 0' *  self.origin_x) + ('0 1 0' *  self.origin_y)));
                                MonsterSound(monstersound_sight, 0, FALSE, CH_VOICE);
+                       }
                }
  
-               self.last_enemycheck = time + 0.5;
+               self.last_enemycheck = time + 1; // check for enemies every second
        }
  
        if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single)
        if(!self.enemy)
                MonsterSound(monstersound_idle, 7, TRUE, CH_VOICE);
  
-       if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
-               self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
        if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND))
        {
                self.state = 0;
                self.touch = MonsterTouch;
        }
  
-       //self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
-       float turny = 0;
-       vector real_angle = vectoangles(self.steerto) - self.angles;
-       if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
-               turny = 20;
-       if(self.flags & FL_SWIM)
-               turny = vlen(self.angles - self.moveto);
-       if(turny)
-       {
-               turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny);
-               self.angles_y += turny;
-       }
        if(self.state == MONSTER_STATE_ATTACK_MELEE)
                self.moveto = self.origin;
  
        if(self.enemy && self.enemy.vehicle)
                runspeed = 0;
  
-       if(((self.flags & FL_FLY) || (self.flags & FL_SWIM)) && self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
-               v_forward = normalize(self.moveto - self.origin);
-       else
+       if(!(((self.flags & FL_FLY) && (self.spawnflags & MONSTERFLAG_FLY_VERTICAL)) || (self.flags & FL_SWIM)))
+               //v_forward = normalize(self.moveto - self.origin);
+       //else
                self.moveto_z = self.origin_z;
  
        if(vlen(self.origin - self.moveto) > 64)
        {
-               if(self.flags & FL_FLY || self.flags & FL_SWIM)
+               if((self.flags & FL_ONGROUND) || ((self.flags & FL_FLY) || (self.flags & FL_SWIM)))
+                       monster_CalculateVelocity(self, self.moveto, self.origin, TRUE, ((self.enemy) ? runspeed : walkspeed));
+               
+               /*&if(self.flags & FL_FLY || self.flags & FL_SWIM)
                        movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
                else
-                       movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
+                       movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6); */
  
                if(time > self.pain_finished)
                if(time > self.attack_finished_single)
                if (vlen(self.velocity) <= 30)
                        self.frame = manim_idle;
        }
+       
+       self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
+       
+       vector real_angle = vectoangles(self.steerto) - self.angles;
+       float turny = 25;
+       if(self.state == MONSTER_STATE_ATTACK_MELEE)
+               turny = 0;
+       if(turny)
+       {
+               turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny);
+               self.angles_y += turny;
+       }
  
        monster_checkattack(self, self.enemy);
  }
@@@ -757,9 -776,6 +821,9 @@@ void monster_remove(entity mon
        if(mon.weaponentity)
                remove(mon.weaponentity);
  
 +      if(mon.iceblock)
 +              remove(mon.iceblock);
 +
        WaypointSprite_Kill(mon.sprite);
  
        remove(mon);
@@@ -771,8 -787,8 +835,8 @@@ void monster_dead_think(
  
        CSQCMODEL_AUTOUPDATE();
  
-       if(self.ltime != 0)
-       if(time >= self.ltime)
+       if(self.monster_lifetime != 0)
+       if(time >= self.monster_lifetime)
        {
                Monster_Fade();
                return;
@@@ -788,16 -804,17 +852,17 @@@ void monsters_setstatus(
  void Monster_Appear()
  {
        self.enemy = activator;
-       self.spawnflags &= ~MONSTERFLAG_APPEAR;
-       self.monster_spawnfunc();
+       self.spawnflags &= ~MONSTERFLAG_APPEAR; // otherwise, we get an endless loop
+       monster_initialize(self.monsterid);
  }
  
- float Monster_CheckAppearFlags(entity ent)
+ float Monster_CheckAppearFlags(entity ent, float monster_id)
  {
        if(!(ent.spawnflags & MONSTERFLAG_APPEAR))
                return FALSE;
  
        ent.think = func_null;
+       ent.monsterid = monster_id; // set so this monster is properly registered (otherwise, normal initialization is used)
        ent.nextthink = 0;
        ent.use = Monster_Appear;
        ent.flags = FL_MONSTER; // set so this monster can get butchered
@@@ -810,8 -827,6 +875,8 @@@ void monsters_reset(
        setorigin(self, self.pos1);
        self.angles = self.pos2;
  
 +      Unfreeze(self); // remove any icy remains
 +
        self.health = self.max_health;
        self.velocity = '0 0 0';
        self.enemy = world;
@@@ -833,11 -848,9 +898,9 @@@ void monsters_corpse_damage (entity inf
                // number of monsters spawned with mobspawn command
                totalspawned -= 1;
  
-               if(IS_CLIENT(self.realowner))
-                       self.realowner.monstercount -= 1;
                self.think = SUB_Remove;
                self.nextthink = time + 0.1;
+               self.event_damage = func_null;
        }
  }
  
@@@ -845,14 -858,8 +908,14 @@@ void monster_die(entity attacker, floa
  {
        self.think = monster_dead_think;
        self.nextthink = time;
-       self.ltime = time + 5;
+       self.monster_lifetime = time + 5;
  
 +      if(self.frozen)
 +      {
 +              Unfreeze(self); // remove any icy remains
 +              self.health = 0; // reset by Unfreeze
 +      }
 +
        monster_dropitem();
  
        MonsterSound(monstersound_death, 0, FALSE, CH_VOICE);
        {
                // number of monsters spawned with mobspawn command
                totalspawned -= 1;
-               if(IS_CLIENT(self.realowner))
-                       self.realowner.monstercount -= 1;
        }
  
        if(self.candrop && self.weapon)
                W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325');
  
-       self.event_damage       = monsters_corpse_damage;
+       self.event_damage       = ((gibbed) ? func_null : monsters_corpse_damage);
        self.solid                      = SOLID_CORPSE;
        self.takedamage         = DAMAGE_AIM;
        self.deadflag           = DEAD_DEAD;
        self.state                      = 0;
        self.attack_finished_single = 0;
  
-       if(!(self.flags & FL_FLY))
+       if(!((self.flags & FL_FLY) || (self.flags & FL_SWIM)))
                self.velocity = '0 0 0';
  
        MON_ACTION(self.monsterid, MR_DEATH);
  
  void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
  {
 +      if(self.frozen && deathtype != DEATH_KILL && deathtype != DEATH_NADE_ICE_FREEZE)
 +              return;
 +
+       if((self.spawnflags & MONSTERFLAG_INVINCIBLE) && deathtype != DEATH_KILL)
+               return;
        if(time < self.pain_finished && deathtype != DEATH_KILL)
                return;
  
        if(time < self.spawnshieldtime && deathtype != DEATH_KILL)
                return;
  
+       if(deathtype == DEATH_FALL && self.draggedby != world)
+               return;
        vector v;
        float take, save;
  
        }
  }
  
- void monster_setupcolors()
+ void monster_setupcolors(entity mon)
  {
-       if(IS_PLAYER(self.monster_owner))
-               self.colormap = self.monster_owner.colormap;
-       else if(teamplay && self.team)
-               self.colormap = 1024 + (self.team - 1) * 17;
+       if(IS_PLAYER(mon.monster_owner))
+               mon.colormap = mon.monster_owner.colormap;
+       else if(teamplay && mon.team)
+               mon.colormap = 1024 + (mon.team - 1) * 17;
        else
        {
-               if(self.monster_skill <= MONSTER_SKILL_EASY)
-                       self.colormap = 1029;
-               else if(self.monster_skill <= MONSTER_SKILL_MEDIUM)
-                       self.colormap = 1027;
-               else if(self.monster_skill <= MONSTER_SKILL_HARD)
-                       self.colormap = 1038;
-               else if(self.monster_skill <= MONSTER_SKILL_INSANE)
-                       self.colormap = 1028;
-               else if(self.monster_skill <= MONSTER_SKILL_NIGHTMARE)
-                       self.colormap = 1032;
+               if(mon.monster_skill <= MONSTER_SKILL_EASY)
+                       mon.colormap = 1029;
+               else if(mon.monster_skill <= MONSTER_SKILL_MEDIUM)
+                       mon.colormap = 1027;
+               else if(mon.monster_skill <= MONSTER_SKILL_HARD)
+                       mon.colormap = 1038;
+               else if(mon.monster_skill <= MONSTER_SKILL_INSANE)
+                       mon.colormap = 1028;
+               else if(mon.monster_skill <= MONSTER_SKILL_NIGHTMARE)
+                       mon.colormap = 1032;
                else
-                       self.colormap = 1024;
+                       mon.colormap = 1024;
+       }
+ }
+ void monster_changeteam(entity ent, float newteam)
+ {
+       if(!teamplay) { return; }
+       
+       ent.team = newteam;
+       ent.monster_attack = TRUE; // new team, activate attacking
+       monster_setupcolors(ent);
+       
+       if(ent.sprite)
+       {
+               WaypointSprite_UpdateTeamRadar(ent.sprite, RADARICON_DANGER, ((newteam) ? Team_ColorRGB(newteam) : '1 0 0'));
+               ent.sprite.team = newteam;
+               ent.sprite.SendFlags |= 1;
        }
  }
  
@@@ -989,8 -1013,8 +1072,8 @@@ void monster_think(
        self.think = monster_think;
        self.nextthink = self.ticrate;
  
-       if(self.ltime)
-       if(time >= self.ltime)
+       if(self.monster_lifetime)
+       if(time >= self.monster_lifetime)
        {
                Damage(self, self, self, self.health + self.max_health, DEATH_KILL, self.origin, self.origin);
                return;
@@@ -1024,6 -1048,9 +1107,9 @@@ float monster_spawn(
        if(!self.attack_range)
                self.attack_range = autocvar_g_monsters_attack_range;
  
+       if(!self.wander_delay) { self.wander_delay = 2; }
+       if(!self.wander_distance) { self.wander_distance = 600; }
        precache_monstersounds();
        UpdateMonsterSounds();
  
        MonsterSound(monstersound_spawn, 0, FALSE, CH_VOICE);
  
        WaypointSprite_Spawn(M_NAME(self.monsterid), 0, 1024, self, '0 0 1' * (self.maxs_z + 15), world, self.team, self, sprite, TRUE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0'));
-       WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
-       WaypointSprite_UpdateHealth(self.sprite, self.health);
+       if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE))
+       {
+               WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+               WaypointSprite_UpdateHealth(self.sprite, self.health);
+       }
  
        self.think = monster_think;
        self.nextthink = time + self.ticrate;
        return TRUE;
  }
  
- float monster_initialize(float mon_id, float nodrop)
+ float monster_initialize(float mon_id)
  {
-       if(!autocvar_g_monsters)
-               return FALSE;
+       if(!autocvar_g_monsters) { return FALSE; }
+       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) { MON_ACTION(mon_id, MR_PRECACHE); }
+       if(Monster_CheckAppearFlags(self, mon_id)) { return TRUE; } // return true so the monster isn't removed
  
        entity mon = get_monsterinfo(mon_id);
  
                self.team = 0;
  
        if(!(self.spawnflags & MONSTERFLAG_SPAWNED)) // naturally spawned monster
-       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
+       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) // don't count re-spawning monsters either
                monsters_total += 1;
  
        setmodel(self, mon.model);
-       setsize(self, mon.mins, mon.maxs);
+       //setsize(self, mon.mins, mon.maxs);
        self.flags                              = FL_MONSTER;
        self.takedamage                 = DAMAGE_AIM;
        self.bot_attack                 = TRUE;
        self.candrop                    = TRUE;
        self.view_ofs                   = '0 0 1' * (self.maxs_z * 0.5);
        self.oldtarget2                 = self.target2;
+       self.pass_distance              = 0;
        self.deadflag                   = DEAD_NO;
-       self.scale                              = 1;
-       self.noalign                    = nodrop;
+       self.noalign                    = ((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM));
        self.spawn_time                 = time;
        self.spider_slowness    = 0;
        self.gravity                    = 1;
        self.dphitcontentsmask  = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
  
+       if(!self.scale)
+               self.scale = 1;
+       if(autocvar_g_monsters_edit)
+               self.grab = 1; // owner may carry their monster
        if(autocvar_g_fullbrightplayers)
                self.effects |= EF_FULLBRIGHT;
  
        }
  
        if(mon.spawnflags & MONSTER_SIZE_BROKEN)
-               self.scale = 1.3;
+       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
+               self.scale *= 1.3;
+               
+       setsize(self, mon.mins * self.scale, mon.maxs * self.scale);
  
        if(!self.ticrate)
                self.ticrate = autocvar_g_monsters_think_delay;
                return FALSE;
  
        if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
-               monster_setupcolors();
+               monster_setupcolors(self);
  
        CSQCMODEL_AUTOINIT();
  
index 799efaf03374f5b04091cedd222b682dc28e121b,9c55e457b947cd479ba7fc6ac2d98d2f93913b35..963a7d89dd0b1b282c98b8969fed2977a1d4ea21
@@@ -360,11 -360,7 +360,11 @@@ void Send_Notification_WOCOVA
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_FIRE,              3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_death",         _("^BG%s%s^K1 was burnt up into a crisp by ^BG%s^K1%s%s"), _("^BG%s%s^K1 felt a little hot from ^BG%s^K1's fire^K1%s%s")) \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_LAVA,              3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_lava",          _("^BG%s%s^K1 was cooked by ^BG%s^K1%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_MONSTER,           3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_death",         _("^BG%s%s^K1 was pushed infront of a monster by ^BG%s^K1%s%s"), "") \
 -      MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_NADE,              3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_death",         _("^BG%s%s^K1 was blown up by ^BG%s^K1's Nade%s%s"), "") \
 +      MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_NADE,              3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_nade",          _("^BG%s%s^K1 was blown up by ^BG%s^K1's Nade%s%s"), "") \
 +      MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_NADE_NAPALM,       3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_nade_napalm",   _("^BG%s%s^K1 was burned to death by ^BG%s^K1's Napalm Nade%s%s"), _("^BG%s%s^K1 got too close to a napalm explosion%s%s")) \
 +      MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_NADE_ICE,          3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_nade_ice",      _("^BG%s%s^K1 was blown up by ^BG%s^K1's Ice Nade%s%s"), "") \
 +      MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_NADE_ICE_FREEZE,   3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_nade_ice",      _("^BG%s%s^K1 was frozen to death by ^BG%s^K1's Ice Nade%s%s"), "") \
 +      MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_NADE_HEAL,         3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_nade_heal",     _("^BG%s%s^K1 has not been healed by ^BG%s^K1's Healing Nade%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_SHOOTING_STAR,     3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_shootingstar",  _("^BG%s%s^K1 was shot into space by ^BG%s^K1%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_SLIME,             3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_slime",         _("^BG%s%s^K1 was slimed by ^BG%s^K1%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_SWAMP,             3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_slime",         _("^BG%s%s^K1 was preserved by ^BG%s^K1%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_WAKI_DEATH,     3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_death",         _("^BG%s%s^K1 got caught in the blast when ^BG%s^K1's Racer exploded%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_WAKI_GUN,       3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_death",         _("^BG%s%s^K1 was bolted down by ^BG%s^K1's Racer%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_WAKI_ROCKET,    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(1, INFO_DEATH_MURDER_VENGEANCE,         3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",  "notify_death",         _("^BG%s%s^K1 was destroyed by the vengeful ^BG%s^K1%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VOID,              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(1, INFO_DEATH_SELF_AUTOTEAMCHANGE,      2, 1, "s1 s2loc death_team", "",         "",                     _("^BG%s^K1 was moved into the %s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_BETRAYAL,            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(1, INFO_DEATH_SELF_FIRE,                2, 1, "s1 s2loc spree_lost", "s1",       "notify_death",         _("^BG%s^K1 became a bit too crispy%s%s"), _("^BG%s^K1 felt a little hot%s%s")) \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_GENERIC,             2, 1, "s1 s2loc spree_lost", "s1",       "notify_selfkill",      _("^BG%s^K1 died%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_LAVA,                2, 1, "s1 s2loc spree_lost", "s1",       "notify_lava",          _("^BG%s^K1 turned into hot slag%s%s"), _("^BG%s^K1 found a hot place%s%s")) \
 -      MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NADE,                2, 1, "s1 s2loc spree_lost", "s1",       "notify_death",         _("^BG%s^K1 mastered the art of self-nading%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_MAGE,                2, 1, "s1 s2loc spree_lost", "s1",           "notify_death",                 _("^BG%s^K1 was exploded by a Mage%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SHAMBLER_CLAW,   2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1's innards became outwards by a Shambler%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_SHAMBLER_SMASH,  2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was smashed by a Shambler%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_WYVERN,          2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was fireballed by a Wyvern%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_ZOMBIE_JUMP,     2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 joins the Zombies%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_MON_ZOMBIE_MELEE,    2, 1, "s1 s2loc spree_lost", "s1",               "notify_death",                 _("^BG%s^K1 was given kung fu lessons by a Zombie%s%s"), "") \
 +      MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NADE,                2, 1, "s1 s2loc spree_lost", "s1",       "notify_nade",          _("^BG%s^K1 mastered the art of self-nading%s%s"), "") \
 +      MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NADE_NAPALM,         2, 1, "s1 s2loc spree_lost", "s1",       "notify_nade_napalm",   _("^BG%s^K1 was burned to death by their own Napalm Nade%s%s"), _("^BG%s^K1 decided to take a look at the results of their napalm explosion%s%s")) \
 +      MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NADE_ICE,            2, 1, "s1 s2loc spree_lost", "s1",       "notify_nade_ice",      _("^BG%s^K1 mastered the art of self-nading%s%s"), "") \
 +      MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NADE_ICE_FREEZE,     2, 1, "s1 s2loc spree_lost", "s1",       "notify_nade_ice",      _("^BG%s^K1 was frozen to death by their own Ice Nade%s%s"), _("^BG%s^K1 felt a little chilly%s%s")) \
 +      MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NADE_HEAL,           2, 1, "s1 s2loc spree_lost", "s1",       "notify_nade_heal",     _("^BG%s^K1's Healing Nade didn't quite heal them%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_NOAMMO,              2, 1, "s1 s2loc spree_lost", "s1",       "notify_outofammo",     _("^BG%s^K1 died%s%s. What's the point of living without ammo?"), _("^BG%s^K1 ran out of ammo%s%s")) \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_ROT,                 2, 1, "s1 s2loc spree_lost", "s1",       "notify_death",         _("^BG%s^K1 rotted away%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_DEATH_SELF_SHOOTING_STAR,       2, 1, "s1 s2loc spree_lost", "s1",       "notify_shootingstar",  _("^BG%s^K1 became a shooting star%s%s"), "") \
        MSG_INFO_NOTIF(1, INFO_FREEZETAG_FREEZE,               2, 0, "s1 s2", "",                       "",                     _("^BG%s^K1 was frozen by ^BG%s"), "") \
        MSG_INFO_NOTIF(1, INFO_FREEZETAG_REVIVED,              2, 0, "s1 s2", "",                       "",                     _("^BG%s^K3 was revived by ^BG%s"), "") \
        MSG_INFO_NOTIF(1, INFO_FREEZETAG_REVIVED_FALL,         1, 0, "s1", "",                          "",                     _("^BG%s^K3 was revived by falling"), "") \
 +      MSG_INFO_NOTIF(1, INFO_FREEZETAG_REVIVED_NADE,         1, 0, "s1", "",                          "",                     _("^BG%s^K3 was revived by their Nade explosion"), "") \
        MSG_INFO_NOTIF(1, INFO_FREEZETAG_AUTO_REVIVED,         1, 1, "s1 f1", "",                       "",                     _("^BG%s^K3 was automatically revived after %s second(s)"), "") \
        MULTITEAM_INFO(1, INFO_ROUND_TEAM_WIN_, 4,             0, 0, "", "",                            "",                     _("^TC^TT^BG team wins the round"), "") \
        MSG_INFO_NOTIF(1, INFO_ROUND_PLAYER_WIN,               1, 0, "s1", "",                          "",                     _("^BG%s^BG wins the round"), "") \
        MSG_INFO_NOTIF(1, INFO_ROUND_OVER,                     0, 0, "", "",                            "",                     _("^BGRound over, there's no winner"), "") \
        MSG_INFO_NOTIF(1, INFO_FREEZETAG_SELF,                 1, 0, "s1", "",                          "",                     _("^BG%s^K1 froze themself"), "") \
        MSG_INFO_NOTIF(1, INFO_GODMODE_OFF,                    0, 1, "f1", "",                          "",                     _("^BGGodmode saved you %s units of damage, cheater!"), "") \
+       MSG_INFO_NOTIF(1, INFO_ITEM_BUFF,                      1, 1, "s1 item_buffname", "",            "",                     _("^BG%s^BG got the %s^BG Buff!"), "") \
+       MSG_INFO_NOTIF(1, INFO_ITEM_BUFF_LOST,                 1, 1, "s1 item_buffname", "",            "",                     _("^BG%s^BG lost the %s^BG Buff!"), "") \
+       MSG_INFO_NOTIF(1, INFO_ITEM_BUFF_DROP,                 0, 1, "item_buffname", "",               "",                     _("^BGYou dropped the %s^BG Buff!"), "") \
+       MSG_INFO_NOTIF(1, INFO_ITEM_BUFF_GOT,                  0, 1, "item_buffname", "",               "",                     _("^BGYou got the %s^BG Buff!"), "") \
        MSG_INFO_NOTIF(0, INFO_ITEM_WEAPON_DONTHAVE,           0, 1, "item_wepname", "",                      "",               _("^BGYou do not have the ^F1%s"), "") \
        MSG_INFO_NOTIF(0, INFO_ITEM_WEAPON_DROP,               1, 1, "item_wepname item_wepammo", "",         "",               _("^BGYou dropped the ^F1%s^BG%s"), "") \
        MSG_INFO_NOTIF(0, INFO_ITEM_WEAPON_GOT,                0, 1, "item_wepname", "",                      "",               _("^BGYou got the ^F1%s"), "") \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_MURDER_TYPEFRAGGED_VERBOSE,  1, 4, "spree_cen s1 frag_stats",  NO_CPID, "0 0", _("^K1%sYou were typefragged by ^BG%s^BG%s"), _("^K1%sYou were scored against by ^BG%s^K1 while typing^BG%s")) \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_MURDER_TYPEFRAG_VERBOSE,     1, 2, "spree_cen s1 frag_ping",   NO_CPID, "0 0", _("^K1%sYou typefragged ^BG%s^BG%s"), _("^K1%sYou scored against ^BG%s^K1 while they were typing^BG%s")) \
        MSG_CENTER_NOTIF(1, CENTER_NADE_THROW,                          0, 0, "",             CPID_NADES,          "0 0", _("^BGPress ^F2DROPWEAPON^BG again to toss the nade!"), "") \
 +      MSG_CENTER_NOTIF(1, CENTER_NADE_BONUS,                          0, 0, "",             CPID_NADES,          "0 0", _("^F2You got a ^K1BONUS GRENADE^F2!"), "") \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_AUTOTEAMCHANGE,   0, 1, "death_team",   NO_CPID,             "0 0", _("^BGYou have been moved into a different team\nYou are now on: %s"), "") \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_BETRAYAL,         0, 0, "",             NO_CPID,             "0 0", _("^K1Don't shoot your team mates!"), _("^K1Don't go against your team mates!")) \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_CAMP,             0, 0, "",             NO_CPID,             "0 0", _("^K1Die camper!"), _("^K1Reconsider your tactics, camper!")) \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_LAVA,             0, 0, "",             NO_CPID,             "0 0", _("^K1You couldn't stand the heat!"), "") \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_MONSTER,          0, 0, "",             NO_CPID,             "0 0", _("^K1You were killed by a monster!"), _("^K1You need to watch out for monsters!")) \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_NADE,                             0, 0, "",                         NO_CPID,                         "0 0", _("^K1You forgot to put the pin back in!"), _("^K1Tastes like chicken!")) \
 +      MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_NADE_NAPALM,              0, 0, "",                         NO_CPID,                         "0 0", _("^K1Hanging around a napalm explosion is bad!"), "") \
 +      MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_NADE_ICE_FREEZE,  0, 0, "",                         NO_CPID,                         "0 0", _("^K1You got a little bit too cold!"), _("^K1You felt a little chilly!")) \
 +      MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_NADE_HEAL,        0, 0, "",             NO_CPID,             "0 0", _("^K1Your Healing Nade is a bit defective"), "") \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_NOAMMO,           0, 0, "",             NO_CPID,             "0 0", _("^K1You were killed for running out of ammo..."), _("^K1You are respawning for running out of ammo...")) \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_ROT,              0, 0, "",             NO_CPID,             "0 0", _("^K1You grew too old without taking your medicine"), _("^K1You need to preserve your health")) \
        MSG_CENTER_NOTIF(1, CENTER_DEATH_SELF_SHOOTING_STAR,    0, 0, "",             NO_CPID,             "0 0", _("^K1You became a shooting star!"), "") \
        MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_FREEZE,            1, 0, "s1",           NO_CPID,             "0 0", _("^K3You froze ^BG%s"), "") \
        MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_FROZEN,            1, 0, "s1",           NO_CPID,             "0 0", _("^K1You were frozen by ^BG%s"), "") \
        MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_REVIVE,            1, 0, "s1",           NO_CPID,             "0 0", _("^K3You revived ^BG%s"), "") \
 -      MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_REVIVE_FALL,       0, 0, "",             NO_CPID,             "0 0", _("^K3You revived yourself"), "") \
 +      MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_REVIVE_SELF,       0, 0, "",             NO_CPID,             "0 0", _("^K3You revived yourself"), "") \
        MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_REVIVED,           1, 0, "s1",           NO_CPID,             "0 0", _("^K3You were revived by ^BG%s"), "") \
        MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_AUTO_REVIVED,      0, 1, "f1",           NO_CPID,             "0 0", _("^K3You were automatically revived after %s second(s)"), "") \
        MULTITEAM_CENTER(1, CENTER_ROUND_TEAM_WIN_, 4,          0, 0, "",             CPID_ROUND,          "0 0", _("^TC^TT^BG team wins the round"), "") \
        MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_SELF,              0, 0, "",             NO_CPID,             "0 0", _("^K1You froze yourself"), "") \
        MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_SPAWN_LATE,        0, 0, "",             NO_CPID,             "0 0", _("^K1Round already started, you spawn as frozen"), "") \
        MSG_CENTER_NOTIF(1, CENTER_INVASION_SUPERMONSTER,       1, 0, "s1",           NO_CPID,             "0 0", _("^K1A %s has arrived!"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_ITEM_BUFF_DROP,              0, 1, "item_buffname",                     CPID_ITEM, "item_centime 0", _("^BGYou dropped the %s^BG Buff!"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_ITEM_BUFF_GOT,               0, 1, "item_buffname",                     CPID_ITEM, "item_centime 0", _("^BGYou got the %s^BG Buff!"), "") \
        MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_DONTHAVE,        0, 1, "item_wepname",                      CPID_ITEM, "item_centime 0", _("^BGYou do not have the ^F1%s"), "") \
        MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_DROP,            1, 1, "item_wepname item_wepammo",         CPID_ITEM, "item_centime 0", _("^BGYou dropped the ^F1%s^BG%s"), "") \
        MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_GOT,             0, 1, "item_wepname",                      CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1%s"), "") \
        MSG_CENTER_NOTIF(1, CENTER_JOIN_PREVENT,                0, 0, "",              CPID_PREVENT_JOIN,     "0 0", _("^K1You may not join the game at this time.\nThe player limit reached maximum capacity."), "") \
        MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_DROPPED,            1, 0, "s1",            CPID_KEEPAWAY,         "0 0", _("^BG%s^BG has dropped the ball!"), "") \
        MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_PICKUP,             1, 0, "s1",            CPID_KEEPAWAY,         "0 0", _("^BG%s^BG has picked up the ball!"), "") \
+       MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_PICKUP_SELF,        0, 0, "",              CPID_KEEPAWAY,         "0 0", _("^BGYou picked up the ball"), "") \
        MSG_CENTER_NOTIF(1, CENTER_KEEPAWAY_WARN,               0, 0, "",              CPID_KEEPAWAY_WARN,    "0 0", _("^BGKilling people while you don't have the ball gives no points!"), "") \
        MSG_CENTER_NOTIF(1, CENTER_KEYHUNT_HELP,                0, 0, "",              CPID_KEYHUNT,          "0 0", _("^BGAll keys are in your team's hands!\nHelp the key carriers to meet!"), "") \
        MULTITEAM_CENTER(1, CENTER_KEYHUNT_INTERFERE_, 4,       0, 0, "",              CPID_KEYHUNT,          "0 0", _("^BGAll keys are in ^TC^TT team^BG's hands!\nInterfere ^F4NOW^BG!"), "") \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_LAVA,                    NO_MSG,        INFO_DEATH_MURDER_LAVA,                    NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_MONSTER,                 NO_MSG,        INFO_DEATH_MURDER_MONSTER,                 CENTER_DEATH_SELF_MONSTER) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_NADE,                    NO_MSG,        INFO_DEATH_MURDER_NADE,                    NO_MSG) \
 +      MSG_MULTI_NOTIF(1, DEATH_MURDER_NADE_NAPALM,             NO_MSG,        INFO_DEATH_MURDER_NADE_NAPALM,             NO_MSG) \
 +      MSG_MULTI_NOTIF(1, DEATH_MURDER_NADE_ICE,                NO_MSG,        INFO_DEATH_MURDER_NADE_ICE,                NO_MSG) \
 +      MSG_MULTI_NOTIF(1, DEATH_MURDER_NADE_ICE_FREEZE,         NO_MSG,        INFO_DEATH_MURDER_NADE_ICE_FREEZE,         NO_MSG) \
 +      MSG_MULTI_NOTIF(1, DEATH_MURDER_NADE_HEAL,               NO_MSG,        INFO_DEATH_MURDER_NADE_HEAL,               NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_SHOOTING_STAR,           NO_MSG,        INFO_DEATH_MURDER_SHOOTING_STAR,           NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_SLIME,                   NO_MSG,        INFO_DEATH_MURDER_SLIME,                   NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_SWAMP,                   NO_MSG,        INFO_DEATH_MURDER_SWAMP,                   NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_WAKI_DEATH,           NO_MSG,        INFO_DEATH_MURDER_VH_WAKI_DEATH,           NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_WAKI_GUN,             NO_MSG,        INFO_DEATH_MURDER_VH_WAKI_GUN,             NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_WAKI_ROCKET,          NO_MSG,        INFO_DEATH_MURDER_VH_WAKI_ROCKET,          NO_MSG) \
+       MSG_MULTI_NOTIF(1, DEATH_MURDER_VENGEANCE,               NO_MSG,        INFO_DEATH_MURDER_VENGEANCE,               NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_MURDER_VOID,                    NO_MSG,        INFO_DEATH_MURDER_VOID,                    NO_MSG) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_AUTOTEAMCHANGE,            NO_MSG,        INFO_DEATH_SELF_AUTOTEAMCHANGE,            CENTER_DEATH_SELF_AUTOTEAMCHANGE) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_BETRAYAL,                  NO_MSG,        INFO_DEATH_SELF_BETRAYAL,                  CENTER_DEATH_SELF_BETRAYAL) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ZOMBIE_JUMP,                   NO_MSG,        INFO_DEATH_SELF_MON_ZOMBIE_JUMP,                   CENTER_DEATH_SELF_MONSTER) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_MON_ZOMBIE_MELEE,                  NO_MSG,        INFO_DEATH_SELF_MON_ZOMBIE_MELEE,                  CENTER_DEATH_SELF_MONSTER) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_NADE,                                              NO_MSG,                INFO_DEATH_SELF_NADE,                                      CENTER_DEATH_SELF_NADE) \
 +      MSG_MULTI_NOTIF(1, DEATH_SELF_NADE_NAPALM,                               NO_MSG,                INFO_DEATH_SELF_NADE_NAPALM,                       CENTER_DEATH_SELF_NADE_NAPALM) \
 +      MSG_MULTI_NOTIF(1, DEATH_SELF_NADE_ICE,                                  NO_MSG,                INFO_DEATH_SELF_NADE_ICE,                                  CENTER_DEATH_SELF_NADE_ICE_FREEZE) \
 +      MSG_MULTI_NOTIF(1, DEATH_SELF_NADE_ICE_FREEZE,           NO_MSG,                INFO_DEATH_SELF_NADE_ICE_FREEZE,           CENTER_DEATH_SELF_NADE_ICE_FREEZE) \
 +      MSG_MULTI_NOTIF(1, DEATH_SELF_NADE_HEAL,                 NO_MSG,        INFO_DEATH_SELF_NADE_HEAL,                 CENTER_DEATH_SELF_NADE_HEAL) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_NOAMMO,                    NO_MSG,        INFO_DEATH_SELF_NOAMMO,                    CENTER_DEATH_SELF_NOAMMO) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_ROT,                       NO_MSG,        INFO_DEATH_SELF_ROT,                       CENTER_DEATH_SELF_ROT) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_SHOOTING_STAR,             NO_MSG,        INFO_DEATH_SELF_SHOOTING_STAR,             CENTER_DEATH_SELF_SHOOTING_STAR) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_VH_WAKI_DEATH,             NO_MSG,        INFO_DEATH_SELF_VH_WAKI_DEATH,             CENTER_DEATH_SELF_VH_WAKI_DEATH) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_VH_WAKI_ROCKET,            NO_MSG,        INFO_DEATH_SELF_VH_WAKI_ROCKET,            CENTER_DEATH_SELF_VH_WAKI_ROCKET) \
        MSG_MULTI_NOTIF(1, DEATH_SELF_VOID,                      NO_MSG,        INFO_DEATH_SELF_VOID,                      CENTER_DEATH_SELF_VOID) \
+       MSG_MULTI_NOTIF(1, ITEM_BUFF_DROP,                       NO_MSG,        INFO_ITEM_BUFF_DROP,                       CENTER_ITEM_BUFF_DROP) \
+       MSG_MULTI_NOTIF(1, ITEM_BUFF_GOT,                        NO_MSG,        INFO_ITEM_BUFF_GOT,                        CENTER_ITEM_BUFF_GOT) \
        MSG_MULTI_NOTIF(1, ITEM_WEAPON_DONTHAVE,                 NO_MSG,        INFO_ITEM_WEAPON_DONTHAVE,                 CENTER_ITEM_WEAPON_DONTHAVE) \
        MSG_MULTI_NOTIF(1, ITEM_WEAPON_DROP,                     NO_MSG,        INFO_ITEM_WEAPON_DROP,                     CENTER_ITEM_WEAPON_DROP) \
        MSG_MULTI_NOTIF(1, ITEM_WEAPON_GOT,                      NO_MSG,        INFO_ITEM_WEAPON_GOT,                      CENTER_ITEM_WEAPON_GOT) \
@@@ -954,6 -944,7 +965,7 @@@ var float autocvar_notification_show_sp
      item_wepname: return full name of a weapon from weaponid
      item_wepammo: ammo display for weapon from string
      item_centime: amount of time to display weapon message in centerprint
+     item_buffname: return full name of a buff from buffid
      death_team: show the full name of the team a player is switching from
  */
  
@@@ -1006,6 -997,7 +1018,7 @@@ string arg_slot[NOTIF_MAX_ARGS]
      ARG_CASE(ARG_CS_SV,     "spree_end",     (autocvar_notification_show_sprees ? notif_arg_spree_inf(-1, "", "", f1) : "")) \
      ARG_CASE(ARG_CS_SV,     "spree_lost",    (autocvar_notification_show_sprees ? notif_arg_spree_inf(-2, "", "", f1) : "")) \
      ARG_CASE(ARG_CS_SV,     "item_wepname",  W_Name(f1)) \
+     ARG_CASE(ARG_CS_SV,     "item_buffname", sprintf("%s%s", rgb_to_hexcolor(Buff_Color(f1)), Buff_PrettyName(f1))) \
      ARG_CASE(ARG_CS_SV,     "item_wepammo",  (s1 != "" ? sprintf(_(" with %s"), s1) : "")) \
      ARG_CASE(ARG_DC,        "item_centime",  ftos(autocvar_notification_item_centerprinttime)) \
      ARG_CASE(ARG_SV,        "death_team",    Team_ColoredFullName(f1)) \
diff --combined qcsrc/common/stats.qh
index 0000000000000000000000000000000000000000,7965597d1bc189ae75e6acacaa48d0d84245dab9..793582e1264b092483e2b7e9839dd27e847b61a1
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,285 +1,290 @@@
 -// 69 empty?
+ // Full list of all stat constants, icnluded in a single location for easy reference
+ // 255 is the current limit (MAX_CL_STATS - 1), engine will need to be modified if you wish to add more stats
+ const float MAX_CL_STATS                = 256;
+ const float STAT_HEALTH                 = 0;
+ // 1 empty?
+ const float STAT_WEAPON                 = 2;
+ const float STAT_AMMO                   = 3;
+ const float STAT_ARMOR                  = 4;
+ const float STAT_WEAPONFRAME            = 5;
+ const float STAT_SHELLS                 = 6;
+ const float STAT_NAILS                  = 7;
+ const float STAT_ROCKETS                = 8;
+ const float STAT_CELLS                  = 9;
+ const float STAT_ACTIVEWEAPON           = 10;
+ const float STAT_TOTALSECRETS           = 11;
+ const float STAT_TOTALMONSTERS          = 12;
+ const float STAT_SECRETS                = 13;
+ const float STAT_MONSTERS               = 14;
+ const float STAT_ITEMS                  = 15;
+ const float STAT_VIEWHEIGHT             = 16;
+ // 17 empty?
+ // 18 empty?
+ // 19 empty?
+ // 20 empty?
+ const float STAT_VIEWZOOM               = 21;
+ // 22 empty?
+ // 23 empty?
+ // 24 empty?
+ // 25 empty?
+ // 26 empty?
+ // 27 empty?
+ // 28 empty?
+ // 29 empty?
+ // 30 empty?
+ // 31 empty?
+ const float STAT_KH_KEYS                = 32;
+ const float STAT_CTF_STATE              = 33;
+ // 34 empty?
+ const float STAT_WEAPONS                = 35;
+ const float STAT_SWITCHWEAPON           = 36;
+ const float STAT_GAMESTARTTIME          = 37;
+ const float STAT_STRENGTH_FINISHED      = 38;
+ const float STAT_INVINCIBLE_FINISHED    = 39;
+ // 40 empty?
+ // 41 empty?
+ const float STAT_PRESSED_KEYS           = 42;
+ const float STAT_ALLOW_OLDNEXBEAM       = 43; // this stat could later contain some other bits of info, like, more server-side particle config
+ const float STAT_FUEL                   = 44;
+ const float STAT_NB_METERSTART          = 45;
+ const float STAT_SHOTORG                = 46; // compressShotOrigin
+ const float STAT_LEADLIMIT              = 47;
+ const float STAT_WEAPON_CLIPLOAD        = 48;
+ const float STAT_WEAPON_CLIPSIZE        = 49;
+ const float STAT_NEX_CHARGE             = 50;
+ const float STAT_LAST_PICKUP            = 51;
+ const float STAT_HUD                    = 52;
+ const float STAT_NEX_CHARGEPOOL         = 53;
+ const float STAT_HIT_TIME               = 54;
+ const float STAT_TYPEHIT_TIME           = 55;
+ const float STAT_LAYED_MINES            = 56;
+ const float STAT_HAGAR_LOAD             = 57;
+ const float STAT_SWITCHINGWEAPON        = 58;
+ const float STAT_SUPERWEAPONS_FINISHED  = 59;
+ const float STAT_VEHICLESTAT_HEALTH     = 60;
+ const float STAT_VEHICLESTAT_SHIELD     = 61;
+ const float STAT_VEHICLESTAT_ENERGY     = 62;
+ const float STAT_VEHICLESTAT_AMMO1      = 63;
+ const float STAT_VEHICLESTAT_RELOAD1    = 64;
+ const float STAT_VEHICLESTAT_AMMO2      = 65;
+ const float STAT_VEHICLESTAT_RELOAD2    = 66;
+ const float STAT_VEHICLESTAT_W2MODE     = 67;
+ // 68 empty?
 -// 79 empty?
 -// 80 empty?
++const float STAT_NADE_TIMER             = 69;
+ const float STAT_SECRETS_TOTAL          = 70;
+ const float STAT_SECRETS_FOUND          = 71;
+ const float STAT_RESPAWN_TIME           = 72;
+ const float STAT_ROUNDSTARTTIME         = 73;
+ const float STAT_WEAPONS2               = 74;
+ const float STAT_WEAPONS3               = 75;
+ const float STAT_MONSTERS_TOTAL         = 76;
+ const float STAT_MONSTERS_KILLED        = 77;
+ const float STAT_BUFFS                  = 78;
++const float STAT_NADE_BONUS             = 79;
++const float STAT_NADE_BONUS_TYPE        = 80;
++const float STAT_NADE_BONUS_SCORE       = 81;
++const float STAT_HEALING_ORB            = 82;
++const float STAT_HEALING_ORB_ALPHA      = 83;
++// 84 empty?
++// 85 empty?
+ // 86 empty?
+ // 87 empty?
+ // 88 empty?
+ // 89 empty?
+ // 90 empty?
+ // 91 empty?
+ // 92 empty?
+ // 93 empty?
+ // 94 empty?
+ // 95 empty?
+ // 96 empty?
+ // 97 empty?
+ // 98 empty?
+ // 99 empty?
+ /* The following stats change depending on the gamemode, so can share the same ID */
+ // IDs 100 to 104 reserved for gamemodes
+ // freeze tag, clan arena, jailbreak
+ const float STAT_REDALIVE               = 100;
+ const float STAT_BLUEALIVE              = 101;
+ const float STAT_YELLOWALIVE            = 102;
+ const float STAT_PINKALIVE              = 103;
+ // domination
+ const float STAT_DOM_TOTAL_PPS          = 100;
+ const float STAT_DOM_PPS_RED            = 101;
+ const float STAT_DOM_PPS_BLUE           = 102;
+ const float STAT_DOM_PPS_YELLOW         = 103;
+ const float STAT_DOM_PPS_PINK           = 104;
+ // vip
+ const float STAT_VIP                    = 100;
+ const float STAT_VIP_RED                = 101;
+ const float STAT_VIP_BLUE               = 102;
+ const float STAT_VIP_YELLOW             = 103;
+ const float STAT_VIP_PINK               = 104;
+ // key hunt
+ const float STAT_KH_REDKEY_TEAM         = 100;
+ const float STAT_KH_BLUEKEY_TEAM        = 101;
+ const float STAT_KH_YELLOWKEY_TEAM      = 102;
+ const float STAT_KH_PINKKEY_TEAM        = 103;
+ /* Gamemode-specific stats end here */
+ const float STAT_FROZEN                 = 105;
+ const float STAT_REVIVE_PROGRESS        = 106;
+ // 107 empty?
+ // 108 empty?
+ // 109 empty?
+ // 110 empty?
+ // 111 empty?
+ // 112 empty?
+ // 113 empty?
+ // 114 empty?
+ // 115 empty?
+ // 116 empty?
+ // 117 empty?
+ // 118 empty?
+ // 119 empty?
+ // 120 empty?
+ // 121 empty?
+ // 122 empty?
+ // 123 empty?
+ // 124 empty?
+ // 125 empty?
+ // 126 empty?
+ // 127 empty?
+ // 128 empty?
+ // 129 empty?
+ // 130 empty?
+ // 131 empty?
+ // 132 empty?
+ // 133 empty?
+ // 134 empty?
+ // 135 empty?
+ // 136 empty?
+ // 137 empty?
+ // 138 empty?
+ // 139 empty?
+ // 140 empty?
+ // 141 empty?
+ // 142 empty?
+ // 143 empty?
+ // 144 empty?
+ // 145 empty?
+ // 146 empty?
+ // 147 empty?
+ // 148 empty?
+ // 149 empty?
+ // 150 empty?
+ // 151 empty?
+ // 152 empty?
+ // 153 empty?
+ // 154 empty?
+ // 155 empty?
+ // 156 empty?
+ // 157 empty?
+ // 158 empty?
+ // 159 empty?
+ // 160 empty?
+ // 161 empty?
+ // 162 empty?
+ // 162 empty?
+ // 163 empty?
+ // 164 empty?
+ // 165 empty?
+ // 166 empty?
+ // 167 empty?
+ // 168 empty?
+ // 169 empty?
+ // 170 empty?
+ // 171 empty?
+ // 172 empty?
+ // 173 empty?
+ // 174 empty?
+ // 175 empty?
+ // 176 empty?
+ // 177 empty?
+ // 178 empty?
+ // 179 empty?
+ // 180 empty?
+ // 181 empty?
+ // 182 empty?
+ // 183 empty?
+ // 184 empty?
+ // 185 empty?
+ // 186 empty?
+ // 187 empty?
+ // 188 empty?
+ // 189 empty?
+ // 190 empty?
+ // 191 empty?
+ // 192 empty?
+ // 193 empty?
+ // 194 empty?
+ // 195 empty?
+ // 196 empty?
+ // 197 empty?
+ // 198 empty?
+ // 199 empty?
+ // 200 empty?
+ // 201 empty?
+ // 202 empty?
+ // 203 empty?
+ // 204 empty?
+ // 205 empty?
+ // 206 empty?
+ // 207 empty?
+ // 208 empty?
+ // 209 empty?
+ // 210 empty?
+ // 211 empty?
+ // 212 empty?
+ // 213 empty?
+ // 214 empty?
+ // 215 empty?
+ // 216 empty?
+ // 217 empty?
+ // 218 empty?
+ // 219 empty?
+ const float STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR     = 220;
+ const float STAT_MOVEVARS_AIRCONTROL_PENALTY            = 221;
+ const float STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW           = 222;
+ const float STAT_MOVEVARS_AIRSTRAFEACCEL_QW             = 223;
+ const float STAT_MOVEVARS_AIRCONTROL_POWER              = 224;
+ const float STAT_MOVEFLAGS                              = 225;
+ const float STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL   = 226;
+ const float STAT_MOVEVARS_WARSOWBUNNY_ACCEL             = 227;
+ const float STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED          = 228;
+ const float STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL         = 229;
+ const float STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO   = 230;
+ const float STAT_MOVEVARS_AIRSTOPACCELERATE             = 231;
+ const float STAT_MOVEVARS_AIRSTRAFEACCELERATE           = 232;
+ const float STAT_MOVEVARS_MAXAIRSTRAFESPEED             = 233;
+ const float STAT_MOVEVARS_AIRCONTROL                    = 234;
+ const float STAT_FRAGLIMIT                              = 235;
+ const float STAT_TIMELIMIT                              = 236;
+ const float STAT_MOVEVARS_WALLFRICTION                  = 237;
+ const float STAT_MOVEVARS_FRICTION                      = 238;
+ const float STAT_MOVEVARS_WATERFRICTION                 = 239;
+ const float STAT_MOVEVARS_TICRATE                       = 240;
+ const float STAT_MOVEVARS_TIMESCALE                     = 241;
+ const float STAT_MOVEVARS_GRAVITY                       = 242;
+ const float STAT_MOVEVARS_STOPSPEED                     = 243;
+ const float STAT_MOVEVARS_MAXSPEED                      = 244;
+ const float STAT_MOVEVARS_SPECTATORMAXSPEED             = 245;
+ const float STAT_MOVEVARS_ACCELERATE                    = 246;
+ const float STAT_MOVEVARS_AIRACCELERATE                 = 247;
+ const float STAT_MOVEVARS_WATERACCELERATE               = 248;
+ const float STAT_MOVEVARS_ENTGRAVITY                    = 249;
+ const float STAT_MOVEVARS_JUMPVELOCITY                  = 250;
+ const float STAT_MOVEVARS_EDGEFRICTION                  = 251;
+ const float STAT_MOVEVARS_MAXAIRSPEED                   = 252;
+ const float STAT_MOVEVARS_STEPHEIGHT                    = 253;
+ const float STAT_MOVEVARS_AIRACCEL_QW                   = 254;
+ const float STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION    = 255;
index 290277111549e80f2750bdccda9c2eaaeb7c5dcd,3df7c17630851ab470d8ca1350b434cf34237c02..94bd0523e1fe4b0a9b0b6077ce5f5f45b1120ec8
@@@ -260,6 -260,7 +260,7 @@@ float autocvar_g_balance_grapplehook_sp
  float autocvar_g_balance_grapplehook_speed_pull;
  float autocvar_g_balance_grapplehook_stretch;
  float autocvar_g_balance_grapplehook_damagedbycontents;
+ float autocvar_g_balance_grapplehook_refire;
  float autocvar_g_balance_grenadelauncher_bouncefactor;
  float autocvar_g_balance_grenadelauncher_bouncestop;
  float autocvar_g_balance_grenadelauncher_primary_ammo;
@@@ -800,13 -801,10 +801,13 @@@ string autocvar_g_forced_team_otherwise
  string autocvar_g_forced_team_pink;
  string autocvar_g_forced_team_red;
  string autocvar_g_forced_team_yellow;
 +float autocvar_g_freezetag_frozen_damage_trigger;
  float autocvar_g_freezetag_frozen_force;
  float autocvar_g_freezetag_frozen_maxtime;
  float autocvar_g_freezetag_revive_falldamage;
  float autocvar_g_freezetag_revive_falldamage_health;
 +float autocvar_g_freezetag_revive_nade;
 +float autocvar_g_freezetag_revive_nade_health;
  float autocvar_g_freezetag_point_leadlimit;
  float autocvar_g_freezetag_point_limit;
  float autocvar_g_freezetag_revive_extra_size;
@@@ -1108,7 -1106,6 +1109,7 @@@ float autocvar_sv_dodging_up_speed
  float autocvar_sv_dodging_wall_distance_threshold;
  float autocvar_sv_dodging_wall_dodging;
  float autocvar_sv_dodging_frozen;
 +float autocvar_sv_dodging_frozen_doubletap;
  float autocvar_sv_doublejump;
  float autocvar_sv_eventlog;
  float autocvar_sv_eventlog_console;
@@@ -1159,6 -1156,11 +1160,11 @@@ float autocvar_sv_timeout_resumetime
  float autocvar_sv_vote_call;
  float autocvar_sv_vote_change;
  string autocvar_sv_vote_commands;
+ float autocvar_sv_vote_gametype;
+ float autocvar_sv_vote_gametype_timeout;
+ string autocvar_sv_vote_gametype_options;
+ float autocvar_sv_vote_gametype_keeptwotime;
+ float autocvar_sv_vote_gametype_default_current;
  float autocvar_sv_vote_limit;
  float autocvar_sv_vote_majority_factor;
  float autocvar_sv_vote_majority_factor_of_voted;
@@@ -1225,6 -1227,7 +1231,7 @@@ float autocvar_g_physical_items_damagef
  float autocvar_g_physical_items_reset;
  float autocvar_g_monsters;
  float autocvar_g_monsters_edit;
+ float autocvar_g_monsters_sounds;
  float autocvar_g_monsters_think_delay;
  float autocvar_g_monsters_max;
  float autocvar_g_monsters_max_perplayer;
@@@ -1248,7 -1251,10 +1255,10 @@@ float autocvar_g_touchexplode_damage
  float autocvar_g_touchexplode_edgedamage;
  float autocvar_g_touchexplode_force;
  float autocvar_g_invasion_round_timelimit;
- #define autocvar_g_invasion_round_limit cvar("g_invasion_round_limit")
+ float autocvar_g_invasion_teams;
+ float autocvar_g_invasion_team_spawns;
+ float autocvar_g_invasion_spawnpoint_spawn_delay;
+ #define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
  float autocvar_g_invasion_warmup;
  float autocvar_g_invasion_monster_count;
  float autocvar_g_invasion_zombies_only;
@@@ -1262,8 -1268,6 +1272,8 @@@ float autocvar_g_random_gravity_negativ
  float autocvar_g_random_gravity_delay;
  float autocvar_g_nades;
  float autocvar_g_nades_spawn;
 +float autocvar_g_nades_spawn_count;
 +float autocvar_g_nades_client_select;
  float autocvar_g_nades_nade_lifetime;
  float autocvar_g_nades_nade_minforce;
  float autocvar_g_nades_nade_maxforce;
@@@ -1274,44 -1278,6 +1284,44 @@@ float autocvar_g_nades_nade_edgedamage
  float autocvar_g_nades_nade_radius;
  float autocvar_g_nades_nade_force;
  float autocvar_g_nades_nade_newton_style;
 +float autocvar_g_nades_napalm_ball_count;
 +float autocvar_g_nades_napalm_ball_spread;
 +float autocvar_g_nades_napalm_ball_damage;
 +float autocvar_g_nades_napalm_ball_damageforcescale;
 +float autocvar_g_nades_napalm_ball_lifetime;
 +float autocvar_g_nades_napalm_ball_radius;
 +float autocvar_g_nades_napalm_blast;
 +float autocvar_g_nades_napalm_fountain_lifetime;
 +float autocvar_g_nades_napalm_fountain_delay;
 +float autocvar_g_nades_napalm_fountain_radius;
 +float autocvar_g_nades_napalm_fountain_damage;
 +float autocvar_g_nades_napalm_fountain_edgedamage;
 +float autocvar_g_nades_napalm_burntime;
 +float autocvar_g_nades_napalm_selfdamage;
 +float autocvar_g_nades_nade_type;
 +float autocvar_g_nades_bonus_type;
 +float autocvar_g_nades_bonus;
 +float autocvar_g_nades_bonus_onstrength;
 +float autocvar_g_nades_bonus_client_select;
 +float autocvar_g_nades_bonus_max;
 +float autocvar_g_nades_bonus_score_max;
 +float autocvar_g_nades_bonus_score_time;
 +float autocvar_g_nades_bonus_score_time_flagcarrier;
 +float autocvar_g_nades_bonus_score_minor;
 +float autocvar_g_nades_bonus_score_low;
 +float autocvar_g_nades_bonus_score_high;
 +float autocvar_g_nades_bonus_score_medium;
 +float autocvar_g_nades_bonus_score_spree;
 +float autocvar_g_nades_ice_freeze_time;
 +float autocvar_g_nades_ice_health;
 +float autocvar_g_nades_ice_explode;
 +float autocvar_g_nades_ice_teamcheck;
 +float autocvar_g_nades_heal_time;
 +float autocvar_g_nades_heal_rate;
 +float autocvar_g_nades_heal_friend;
 +float autocvar_g_nades_heal_foe;
 +string autocvar_g_nades_pokenade_monster_type;
 +//float autocvar_g_nades_pokenade_monster_lifetime;
  float autocvar_g_campcheck_damage;
  float autocvar_g_campcheck_distance;
  float autocvar_g_campcheck_interval;
@@@ -1322,3 -1288,33 +1332,33 @@@ float autocvar_g_spawn_near_teammate_ig
  float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death;
  float autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health;
  float autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath;
+ float autocvar_g_buffs_waypoint_distance;
+ float autocvar_g_buffs_randomize;
+ float autocvar_g_buffs_random_lifetime;
+ float autocvar_g_buffs_random_location;
+ float autocvar_g_buffs_random_location_attempts;
+ float autocvar_g_buffs_spawn_count;
+ float autocvar_g_buffs_replace_powerups;
+ float autocvar_g_buffs_cooldown_activate;
+ float autocvar_g_buffs_cooldown_respawn;
+ float autocvar_g_buffs_resistance_blockpercent;
+ float autocvar_g_buffs_medic_survive_chance;
+ float autocvar_g_buffs_medic_survive_health;
+ float autocvar_g_buffs_medic_rot;
+ float autocvar_g_buffs_medic_max;
+ float autocvar_g_buffs_medic_regen;
+ float autocvar_g_buffs_vengeance_damage_multiplier;
+ float autocvar_g_buffs_bash_force;
+ float autocvar_g_buffs_bash_force_self;
+ float autocvar_g_buffs_disability_time;
+ float autocvar_g_buffs_disability_speed;
+ float autocvar_g_buffs_disability_rate;
+ float autocvar_g_buffs_speed_speed;
+ float autocvar_g_buffs_speed_rate;
+ float autocvar_g_buffs_speed_damage_take;
+ float autocvar_g_buffs_speed_regen;
+ float autocvar_g_buffs_vampire_damage_steal;
+ float autocvar_g_buffs_invisible_alpha;
+ float autocvar_g_buffs_flight_gravity;
+ float autocvar_g_buffs_jump_height;
index 038799df5086e72d566756c3f67678706ebb1fba,4f6e29ceacd84ded7d04e289920bb067eee0c909..b3c6b65775d5121c8a6394cd199b51ff3b08363a
@@@ -1,6 -1,3 +1,3 @@@
- void race_send_recordtime(float msg);
- void race_SendRankings(float pos, float prevpos, float del, float msg);
  void send_CSQC_teamnagger() {
        WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
        WriteByte(MSG_BROADCAST, TE_CSQC_TEAMNAGGER);
@@@ -140,7 -137,6 +137,6 @@@ void PutObserverInServer (void
  {
        entity  spot;
      self.hud = HUD_NORMAL;
-       race_PreSpawnObserver();
  
        spot = SelectSpawnPoint (TRUE);
        if(!spot)
                WriteEntity(MSG_ONE, self);
        }
  
-       if((g_race && g_race_qualifying) || g_cts)
-       {
-               if(PlayerScore_Add(self, SP_RACE_FASTEST, 0))
-                       self.frags = FRAGS_LMS_LOSER;
-               else
-                       self.frags = FRAGS_SPECTATOR;
-       }
-       else
-               self.frags = FRAGS_SPECTATOR;
+       self.frags = FRAGS_SPECTATOR;
  
        MUTATOR_CALLHOOK(MakePlayerObserver);
  
        Portal_ClearAll(self);
  
 +      Unfreeze(self);
 +
        if(self.alivetime)
        {
                if(!warmup_stage)
        self.angles_z = 0;
        self.fixangle = TRUE;
        self.crouch = FALSE;
 +      self.revival_time = 0;
  
        setorigin (self, (spot.origin + PL_VIEW_OFS)); // offset it so that the spectator spawns higher off the ground, looks better this way
        self.prevorigin = self.origin;
@@@ -286,7 -271,7 +274,7 @@@ void FixPlayermodel(
                if(teamplay)
                {
                        string s;
-                       s = Team_ColorName_Lower(self.team);
+                       s = Static_Team_ColorName_Lower(self.team);
                        if(s != "neutral")
                        {
                                defaultmodel = cvar_string(strcat("sv_defaultplayermodel_", s));
@@@ -394,8 -379,6 +382,6 @@@ void PutClientInServer (void
                if(self.team < 0)
                        JoinBestTeam(self, FALSE, TRUE);
  
-               race_PreSpawn();
                spot = SelectSpawnPoint (FALSE);
                if(!spot)
                {
                self.punchvector = '0 0 0';
                self.oldvelocity = self.velocity;
                self.fire_endtime = -1;
 +              self.revival_time = 0;
  
                entity spawnevent = spawn();
                spawnevent.owner = self;
                Net_LinkEntity(spawnevent, FALSE, 0.5, SpawnEvent_Send);
  
+               // Cut off any still running player sounds.
+               stopsound(self, CH_PLAYER_SINGLE);
                self.model = "";
                FixPlayermodel();
                self.drawonlytoclient = world;
  
                self.speedrunning = FALSE;
  
-               race_PostSpawn(spot);
                //stuffcmd(self, "chase_active 0");
                //stuffcmd(self, "set viewsize $tmpviewsize \n");
  
                        activator = world;
                self = oldself;
  
 +              Unfreeze(self);
 +
                spawn_spot = spot;
                MUTATOR_CALLHOOK(PlayerSpawn);
  
@@@ -947,7 -928,7 +934,7 @@@ void ClientKill (void
  {
        if(gameover) return;
        if(self.player_blocked) return;
 -      if(self.freezetag_frozen) return;
 +      if(self.frozen) return;
  
        ClientKill_TeamChange(0);
  }
@@@ -1063,8 -1044,6 +1050,6 @@@ void ClientConnect (void
  
        anticheat_init();
  
-       race_PreSpawnObserver();
        // identify the right forced team
        if(autocvar_g_campaign)
        {
        else
                self.hitplotfh = -1;
  
-       if(g_race || g_cts) {
-               string rr;
-               if(g_cts)
-                       rr = CTS_RECORD;
-               else
-                       rr = RACE_RECORD;
-               msg_entity = self;
-               race_send_recordtime(MSG_ONE);
-               race_send_speedaward(MSG_ONE);
-               speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
-               speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
-               race_send_speedaward_alltimebest(MSG_ONE);
-               float i;
-               for (i = 1; i <= RANKINGS_CNT; ++i) {
-                       race_SendRankings(i, 0, 0, MSG_ONE);
-               }
-       }
-       else if(autocvar_sv_teamnagger && !(autocvar_bot_vs_human && (c3==-1 && c4==-1)) && !g_ca) // teamnagger is currently bad for ca
+       if(autocvar_sv_teamnagger && !(autocvar_bot_vs_human && (c3==-1 && c4==-1)) && !g_ca && !g_cts && !g_race) // teamnagger is currently bad for ca, race & cts
                send_CSQC_teamnagger();
  
        CheatInitClient();
@@@ -1298,8 -1257,6 +1263,8 @@@ void ClientDisconnect (void
  
        Portal_ClearAll(self);
  
 +      Unfreeze(self);
 +
        RemoveGrapplingHook(self);
  
        // Here, everything has been done that requires this player to be a client.
@@@ -1593,18 -1550,26 +1558,27 @@@ float CalcRotRegen(float current, floa
  
  void player_regen (void)
  {
+       float max_mod, regen_mod, rot_mod, limit_mod;
+       max_mod = regen_mod = rot_mod = limit_mod = 1;
+       regen_mod_max = max_mod;
+       regen_mod_regen = regen_mod;
+       regen_mod_rot = rot_mod;
+       regen_mod_limit = limit_mod;
        if(!MUTATOR_CALLHOOK(PlayerRegen))
 +      if(!self.frozen)
        {
-               float minh, mina, maxh, maxa, limith, limita, max_mod, regen_mod, rot_mod, limit_mod;
+               float minh, mina, maxh, maxa, limith, limita;
                maxh = autocvar_g_balance_health_rotstable;
                maxa = autocvar_g_balance_armor_rotstable;
                minh = autocvar_g_balance_health_regenstable;
                mina = autocvar_g_balance_armor_regenstable;
                limith = autocvar_g_balance_health_limit;
                limita = autocvar_g_balance_armor_limit;
-               max_mod = regen_mod = rot_mod = limit_mod = 1;
+               
+               max_mod = regen_mod_max;
+               regen_mod = regen_mod_regen;
+               rot_mod = regen_mod_rot;
+               limit_mod = regen_mod_limit;
  
                maxh = maxh * max_mod;
                minh = minh * max_mod;
@@@ -1740,8 -1705,6 +1714,8 @@@ void SpectateCopy(entity spectatee) 
        self.dmg_inflictor = spectatee.dmg_inflictor;
        self.v_angle = spectatee.v_angle;
        self.angles = spectatee.v_angle;
 +      self.frozen = spectatee.frozen;
 +      self.revive_progress = spectatee.revive_progress;
        if(!self.BUTTON_USE)
                self.fixangle = TRUE;
        setorigin(self, spectatee.origin);
@@@ -1947,7 -1910,6 +1921,7 @@@ void LeaveSpectatorMode(
                if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || (self.wasplayer && autocvar_g_changeteam_banned) || self.team_forced > 0)
                {
                        self.classname = "player";
 +                      nades_RemoveBonus(self);
  
                        if(autocvar_g_campaign || autocvar_g_balance_teams)
                                { JoinBestTeam(self, FALSE, TRUE); }
@@@ -2253,30 -2215,6 +2227,30 @@@ void PlayerPreThink (void
                return;
  #endif
  
 +      if(self.frozen == 2)
 +      {
 +              self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1);
 +              self.health = max(1, self.revive_progress * start_health);
 +              self.iceblock.alpha = bound(0.2, 1 - self.revive_progress, 1);
 +
 +              if(self.revive_progress >= 1)
 +                      Unfreeze(self);
 +      }
 +      else if(self.frozen == 3)
 +      {
 +              self.revive_progress = bound(0, self.revive_progress - frametime * self.revive_speed, 1);
 +              self.health = max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * self.revive_progress );
 +              
 +              if(self.health < 1)
 +              {
 +                      if(self.vehicle)
 +                              vehicles_exit(VHEF_RELESE);
 +                      self.event_damage(self, self.frozen_by, 1, DEATH_NADE_ICE_FREEZE, self.origin, '0 0 0');
 +              }
 +              else if ( self.revive_progress <= 0 )
 +                      Unfreeze(self);
 +      }
 +
        MUTATOR_CALLHOOK(PlayerPreThink);
  
        if(!self.cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button
                        do_crouch = 0;
                if(self.vehicle)
                        do_crouch = 0;
 -              if(self.freezetag_frozen)
 +              if(self.frozen)
                        do_crouch = 0;
                if(self.weapon == WEP_SHOTGUN && self.weaponentity.wframe == WFRAME_FIRE2 && time < self.weapon_nextthink)
                        do_crouch = 0;
        if(self.spectatee_status != oldspectatee_status)
        {
                ClientData_Touch(self);
-               if(g_race || g_cts)
-                       race_InitSpectator();
        }
  
        if(self.teamkill_soundtime)
@@@ -2651,22 -2587,5 +2623,5 @@@ void PlayerPostThink (void
  
        playerdemo_write();
  
-       if((g_cts || g_race) && self.cvar_cl_allow_uidtracking == 1 && self.cvar_cl_allow_uid2name == 1)
-       {
-               if (!self.stored_netname)
-                       self.stored_netname = strzone(uid2name(self.crypto_idfp));
-               if(self.stored_netname != self.netname)
-               {
-                       db_put(ServerProgsDB, strcat("/uid2name/", self.crypto_idfp), self.netname);
-                       strunzone(self.stored_netname);
-                       self.stored_netname = strzone(self.netname);
-               }
-       }
-       /*
-       if(g_race)
-               dprintf("%f %.6f\n", time, race_GetFractionalLapCount(self));
-       */
        CSQCMODEL_AUTOUPDATE();
  }
index 5a0ff5872da20d0f2dfad49a75e0519387f49f49,50518040b7ce07649d804af7504f15b1a2c87787..14747fa99aabe535d9074e7e03ae6391adcebf80
@@@ -19,18 -19,19 +19,22 @@@ When you press the jump ke
  */
  void PlayerJump (void)
  {
 +      if(self.frozen)
 +              return; // no jumping in freezetag when frozen
 +
+       if(self.player_blocked)
+               return; // no jumping while blocked
        float doublejump = FALSE;
+       float mjumpheight = autocvar_sv_jumpvelocity;
  
        player_multijump = doublejump;
+       player_jumpheight = mjumpheight;
        if(MUTATOR_CALLHOOK(PlayerJump))
                return;
  
        doublejump = player_multijump;
-       float mjumpheight;
+       mjumpheight = player_jumpheight;
  
        if (autocvar_sv_doublejump)
        {
@@@ -47,7 -48,6 +51,6 @@@
                }
        }
  
-       mjumpheight = autocvar_sv_jumpvelocity;
        if (self.waterlevel >= WATERLEVEL_SWIMMING)
        {
                self.velocity_z = self.stat_sv_maxspeed * 0.7;
@@@ -792,28 -792,6 +795,28 @@@ void SV_PlayerPhysics(
                self.stat_sv_airspeedlimit_nonqw *= 0.5;
        }
  
 +      if(self.frozen)
 +      {
 +              if(autocvar_sv_dodging_frozen && IS_REAL_CLIENT(self))
 +              {
 +                      self.movement_x = bound(-5, self.movement_x, 5);
 +                      self.movement_y = bound(-5, self.movement_y, 5);
 +                      self.movement_z = bound(-5, self.movement_z, 5);
 +              }
 +              else
 +                      self.movement = '0 0 0';
 +              self.disableclientprediction = 1;
 +
 +              vector midpoint = ((self.absmin + self.absmax) * 0.5);
 +              if(pointcontents(midpoint) == CONTENT_WATER)
 +              {
 +                      self.velocity = self.velocity * 0.5;
 +
 +                      if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
 +                              { self.velocity_z = 200; }
 +              }
 +      }
 +
        MUTATOR_CALLHOOK(PlayerPhysics);
  
        if(self.player_blocked)
                        PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
                }
        }
 -      else if ((self.items & IT_JETPACK) && self.BUTTON_HOOK && (!autocvar_g_jetpack_fuel || self.ammo_fuel >= 0.01 || self.items & IT_UNLIMITED_WEAPON_AMMO) && !self.freezetag_frozen)
 +      else if ((self.items & IT_JETPACK) && self.BUTTON_HOOK && (!autocvar_g_jetpack_fuel || self.ammo_fuel >= 0.01 || self.items & IT_UNLIMITED_WEAPON_AMMO) && !self.frozen)
        {
                //makevectors(self.v_angle_y * '0 1 0');
                makevectors(self.v_angle);
                }
        }
  
-       if((g_cts || g_race) && !IS_OBSERVER(self)) {
-               if(vlen(self.velocity - self.velocity_z * '0 0 1') > speedaward_speed) {
+       if((g_cts || g_race) && !IS_OBSERVER(self))
+       {
+               if(vlen(self.velocity - self.velocity_z * '0 0 1') > speedaward_speed)
+               {
                        speedaward_speed = vlen(self.velocity - self.velocity_z * '0 0 1');
                        speedaward_holder = self.netname;
                        speedaward_uid = self.crypto_idfp;
                        speedaward_lastupdate = time;
                }
-               if(speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1) {
-                       string rr;
-                       if(g_cts)
-                               rr = CTS_RECORD;
-                       else
-                               rr = RACE_RECORD;
+               if(speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
+               {
+                       string rr = (g_cts) ? CTS_RECORD : RACE_RECORD;
                        race_send_speedaward(MSG_ALL);
                        speedaward_lastsent = speedaward_speed;
-                       if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "") {
+                       if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
+                       {
                                speedaward_alltimebest = speedaward_speed;
                                speedaward_alltimebest_holder = speedaward_holder;
                                speedaward_alltimebest_uid = speedaward_uid;
index 39f503dc4af05791c6196d64dfc39914baa18df6,3a687eca81ca5aa9f358f7816df5d3b75e37b2d2..c11e92051074b7fcbf15b208b774f14d96c00d88
@@@ -246,7 -246,7 +246,7 @@@ void player_anim (void
                else
                        deadbits = ANIMSTATE_DEAD2;
        float animbits = deadbits;
 -      if(self.freezetag_frozen)
 +      if(self.frozen)
                animbits |= ANIMSTATE_FROZEN;
        if(self.crouch)
                animbits |= ANIMSTATE_DUCK;
@@@ -414,7 -414,7 +414,7 @@@ void calculate_player_respawn_time(
        else
                self.respawn_countdown = -1; // do not count down
  
-       if(g_cts || autocvar_g_forced_respawn)
+       if(autocvar_g_forced_respawn)
                self.respawn_flags = self.respawn_flags | RESPAWN_FORCE;
  }
  
@@@ -652,7 -652,6 +652,6 @@@ void PlayerDamage (entity inflictor, en
  
                // print an obituary message
                Obituary (attacker, inflictor, self, deathtype);
-               race_PreDie();
  
          // increment frag counter for used weapon type
          float w;
  
                Portal_ClearAllLater(self);
  
-               if(IS_REAL_CLIENT(self))
-               {
-                       self.fixangle = TRUE;
-                       //msg_entity = self;
-                       //WriteByte (MSG_ONE, SVC_SETANGLE);
-                       //WriteAngle (MSG_ONE, self.v_angle_x);
-                       //WriteAngle (MSG_ONE, self.v_angle_y);
-                       //WriteAngle (MSG_ONE, 80);
-               }
+               self.fixangle = TRUE;
  
                if(defer_ClientKill_Now_TeamChange)
                        ClientKill_Now_TeamChange(); // can turn player into spectator
  
                // when we get here, player actually dies
  
 +              Unfreeze(self); // remove any icy remains
 +              self.health = 0; // Unfreeze resets health, so we need to set it back
 +
                // clear waypoints
                WaypointSprite_PlayerDead();
                // throw a weapon
@@@ -761,7 -749,7 +752,7 @@@ float Say(entity source, float teamsay
  //   0 = reject
  //  -1 = fake accept
  {
-       string msgstr, colorstr, cmsgstr, namestr, fullmsgstr, sourcemsgstr, fullcmsgstr, sourcecmsgstr;
+       string msgstr, colorstr, cmsgstr, namestr, fullmsgstr, sourcemsgstr, fullcmsgstr, sourcecmsgstr, colorprefix;
        float flood;
        var .float flood_field;
        entity head;
        else
                namestr = source.netname;
  
+       if(strdecolorize(namestr) == namestr)
+               colorprefix = "^3";
+       else
+               colorprefix = "^7";
        if(msgin != "")
        {
                if(privatesay)
                {
-                       msgstr = strcat("\{1}\{13}* ^3", namestr, "^3 tells you: ^7");
+                       msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
                        privatemsgprefixlen = strlen(msgstr);
                        msgstr = strcat(msgstr, msgin);
-                       cmsgstr = strcat(colorstr, "^3", namestr, "^3 tells you:\n^7", msgin);
+                       cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
                        if(autocvar_g_chat_teamcolors)
                                privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay), ": ^7");
                        else
                }
                else if(teamsay)
                {
-                       msgstr = strcat("\{1}\{13}", colorstr, "(^3", namestr, colorstr, ") ^7", msgin);
-                       cmsgstr = strcat(colorstr, "(^3", namestr, colorstr, ")\n^7", msgin);
+                       msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
+                       cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
                }
                else
                {
-                       msgstr = strcat("\{1}", namestr, "^7: ", msgin);
+                       msgstr = strcat("\{1}", colorprefix, namestr, "^7: ", msgin);
                        cmsgstr = "";
                }
                msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
index 540f5b83f4c18be93af072216519bc2942717316,1c05aca7858a64d141621a6b2c36e96e3b1eb7b7..41cdd2b05e289edff14ebd35f20fa8700056d287
@@@ -301,8 -301,6 +301,6 @@@ float W_IsWeaponThrowable(float w
                return 0;
        if (g_weaponarena)
                return 0;
-       if (g_cts)
-               return 0;
        if (g_nexball && w == WEP_GRENADE_LAUNCHER)
                return 0;
      if(w == 0)
@@@ -330,8 -328,6 +328,8 @@@ void W_ThrowWeapon(vector velo, vector 
        w = self.weapon;
        if (w == 0)
                return; // just in case
 +      if(self.frozen)
 +              return;
        if(MUTATOR_CALLHOOK(ForbidThrowCurrentWeapon))
                return;
        if(!autocvar_g_weapon_throwable)
@@@ -360,7 -356,7 +358,7 @@@ float forbidWeaponUse(
                return 1;
        if(self.player_blocked)
                return 1;
 -      if(self.freezetag_frozen)
 +      if(self.frozen)
                return 1;
        return 0;
  }
index 505dba251d708531e606f7506537f04039ff436e,78424484aa34f4ff826c794a5c893383414c600f..51a04b59135e01e3ac0fd9b6bd62c1d25271651f
@@@ -267,7 -267,7 +267,7 @@@ void ClientCommand_mobspawn(float reque
                {
                        entity e;
                        string tospawn;
-                       float moveflag;
+                       float moveflag, monstercount = 0;
                        
                        moveflag = (argv(2) ? stof(argv(2)) : 1); // follow owner if not defined
                        tospawn = strtolower(argv(1));
                                return;
                        }
                        
+                       FOR_EACH_MONSTER(e)
+                       {
+                               if(e.realowner == self)
+                                       ++monstercount;
+                       }
+                       
                        if(autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { sprint(self, "Monster spawning is disabled.\n"); return; }
                        else if(!IS_PLAYER(self)) { sprint(self, "You can't spawn monsters while spectating.\n"); return; }
                        else if(MUTATOR_CALLHOOK(AllowMobSpawning)) { sprint(self, "Monster spawning is currently disabled by a mutator.\n"); return; }
                        else if(!autocvar_g_monsters) { Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_MONSTERS_DISABLED); return; }
                        else if(self.vehicle) { sprint(self, "You can't spawn monsters while driving a vehicle.\n"); return; }
 -                      else if(self.freezetag_frozen) { sprint(self, "You can't spawn monsters while frozen.\n"); return; }
 +                      else if(self.frozen) { sprint(self, "You can't spawn monsters while frozen.\n"); return; }
                        else if(autocvar_g_campaign) { sprint(self, "You can't spawn monsters in campaign mode.\n"); return; }
                        else if(self.deadflag != DEAD_NO) { sprint(self, "You can't spawn monsters while dead.\n"); return; }
-                       else if(self.monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); return; }
+                       else if(monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); return; }
                        else if(totalspawned >= autocvar_g_monsters_max) { sprint(self, "The global maximum monster count has been reached, kill some before trying to spawn any more.\n"); return; }
                        else if(tospawn != "")
                        {
                                        }
                                }
  
-                               if(found)
+                               if(found || tospawn == "random")
                                {
-                                       self.monstercount += 1;
                                        totalspawned += 1;
                                
                                        makevectors(self.v_angle);
                                        WarpZone_TraceBox (CENTER_OR_VIEWOFS(self), PL_MIN, PL_MAX, CENTER_OR_VIEWOFS(self) + v_forward * 150, TRUE, self);
                                        //WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 150, MOVE_NORMAL, self);
                                
-                                       e = spawnmonster(tospawn, 0, self, self, trace_endpos, FALSE, moveflag);
+                                       e = spawnmonster(tospawn, 0, self, self, trace_endpos, FALSE, FALSE, moveflag);
                                        
                                        sprint(self, strcat("Spawned ", e.monster_name, "\n"));
                                        
@@@ -457,8 -462,9 +462,9 @@@ void ClientCommand_selectteam(float req
                                                                        {
                                                                                if(autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
                                                                                {
+                                                                                       CheckAllowedTeams(self);
                                                                                        GetTeamCounts(self);
-                                                                                       if(!TeamSmallerEqThanTeam(selection, self.team, self))
+                                                                                       if(!TeamSmallerEqThanTeam(Team_TeamToNumber(selection), Team_TeamToNumber(self.team), self))
                                                                                        {
                                                                                                sprint(self, "Cannot change to a larger/better/shinier team\n");
                                                                                                return;
diff --combined qcsrc/server/defs.qh
index d8e35db4ac7da251c9cb43db845eef997bed84ef,a972bbeacd9532aeab3bdb2d927a193f92f42589..dd0b5c22bb13792e6ffc89c4a98b302cbed6b02a
@@@ -20,7 -20,6 +20,6 @@@ float g_cloaked, g_footsteps, g_grappli
  float g_warmup_limit;
  float g_warmup_allguns;
  float g_warmup_allow_timeout;
- float g_race_qualifying;
  float warmup_stage;
  float g_pickup_respawntime_weapon;
  float g_pickup_respawntime_superweapon;
@@@ -584,12 -583,7 +583,12 @@@ float serverflags
  
  .float player_blocked;
  
 -.float freezetag_frozen;
 +.float frozen; // for freeze attacks
 +.float revive_progress;
 +.float revival_time; // time at which player was last revived
 +.float revive_speed; // NOTE: multiplier (anything above 1 is instaheal)
 +.entity iceblock;
 +.entity frozen_by; // for ice fields
  
  .entity muzzle_flash;
  .float misc_bulletcounter;    // replaces uzi & hlac bullet counter.
diff --combined qcsrc/server/g_damage.qc
index b5b52a379b253d5c6ff901961e79fd72ad9007e1,7cc48be005590a39402254691d529d4b53b22a39..47443e6d70f34acfdc175d3a16efb71a6823ef18
@@@ -549,86 -549,6 +549,86 @@@ void Obituary(entity attacker, entity i
        if(targ.killcount) { targ.killcount = 0; }
  }
  
 +void Ice_Think()
 +{
 +      if(!self.owner.frozen || self.owner.iceblock != self)
 +      {
 +              remove(self);
 +              return;
 +      }
 +      setorigin(self, self.owner.origin - '0 0 16');
 +      self.nextthink = time;
 +}
 +
 +void Freeze (entity targ, float freeze_time, float frozen_type, float show_waypoint)
 +{
 +      if(!IS_PLAYER(targ) && !(targ.flags & FL_MONSTER)) // only specified entities can be freezed
 +              return;
 +
 +      if(targ.frozen)
 +              return;
 +
 +      float targ_maxhealth = ((targ.flags & FL_MONSTER) ? targ.max_health : start_health);
 +
 +      targ.frozen = frozen_type;
 +      targ.revive_progress = ((frozen_type == 3) ? 1 : 0);
 +      targ.health = ((frozen_type == 3) ? targ_maxhealth : 1);
 +      targ.revive_speed = freeze_time;
 +
 +      entity ice, head;
 +      ice = spawn();
 +      ice.owner = targ;
 +      ice.classname = "ice";
 +      ice.scale = targ.scale;
 +      ice.think = Ice_Think;
 +      ice.nextthink = time;
 +      ice.frame = floor(random() * 21); // ice model has 20 different looking frames
 +      setmodel(ice, "models/ice/ice.md3");
 +      ice.alpha = 1;
 +      ice.colormod = Team_ColorRGB(targ.team);
 +      ice.glowmod = ice.colormod;
 +      targ.iceblock = ice;
 +      targ.revival_time = 0;
 +
 +      entity oldself;
 +      oldself = self;
 +      self = ice;
 +      Ice_Think();
 +      self = oldself;
 +
 +      RemoveGrapplingHook(targ);
 +
 +      FOR_EACH_PLAYER(head)
 +      if(head.hook.aiment == targ)
 +              RemoveGrapplingHook(head);
 +
 +      // add waypoint
 +      if(show_waypoint)       
 +              WaypointSprite_Spawn("frozen", 0, 0, targ, '0 0 64', world, targ.team, targ, waypointsprite_attached, TRUE, RADARICON_WAYPOINT, '0.25 0.90 1');
 +}
 +
 +void Unfreeze (entity targ)
 +{
 +      if(targ.frozen && targ.frozen != 3) // only reset health if target was frozen
 +              targ.health = ((IS_PLAYER(targ)) ? start_health : targ.max_health);
 +
 +      entity head;
 +      targ.frozen = 0;
 +      targ.revive_progress = 0;
 +      targ.revival_time = time;
 +      
 +      WaypointSprite_Kill(targ.waypointsprite_attached);
 +      
 +      FOR_EACH_PLAYER(head)
 +      if(head.hook.aiment == targ)
 +              RemoveGrapplingHook(head);
 +
 +      // remove the ice block
 +      if(targ.iceblock)
 +              remove(targ.iceblock);
 +      targ.iceblock = world;
 +}
 +
  // these are updated by each Damage call for use in button triggering and such
  entity damage_targ;
  entity damage_inflictor;
@@@ -770,63 -690,7 +770,63 @@@ void Damage (entity targ, entity inflic
                mirrordamage = frag_mirrordamage;
                force = frag_force;
  
 -              if (!g_minstagib)
 +              if(targ.frozen)
 +              if(deathtype != DEATH_HURTTRIGGER && deathtype != DEATH_TEAMCHANGE && deathtype != DEATH_AUTOTEAMCHANGE)
 +              {
 +                      if(autocvar_g_freezetag_revive_falldamage > 0)
 +                      if(deathtype == DEATH_FALL)
 +                      if(damage >= autocvar_g_freezetag_revive_falldamage)
 +                      {
 +                              Unfreeze(targ);
 +                              targ.health = autocvar_g_freezetag_revive_falldamage_health;
 +                              pointparticles(particleeffectnum("iceorglass"), targ.origin, '0 0 0', 3);
 +                              Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
 +                              Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
 +                      }
 +
 +                      damage = 0;
 +                      force *= autocvar_g_freezetag_frozen_force;
 +              }
 +              
 +              if(targ.frozen && deathtype == DEATH_HURTTRIGGER && !autocvar_g_freezetag_frozen_damage_trigger)
 +              {
 +                      pointparticles(particleeffectnum("teleport"), targ.origin, '0 0 0', 1);
 +              
 +                      entity oldself = self;
 +                      self = targ;
 +                      entity spot = SelectSpawnPoint (FALSE);
 +                      
 +                      if(spot)
 +                      {
 +                              damage = 0;
 +                              self.deadflag = DEAD_NO;
 +
 +                              self.angles = spot.angles;
 +                              
 +                              self.effects = 0;
 +                              self.effects |= EF_TELEPORT_BIT;
 +
 +                              self.angles_z = 0; // never spawn tilted even if the spot says to
 +                              self.fixangle = TRUE; // turn this way immediately
 +                              self.velocity = '0 0 0';
 +                              self.avelocity = '0 0 0';
 +                              self.punchangle = '0 0 0';
 +                              self.punchvector = '0 0 0';
 +                              self.oldvelocity = self.velocity;
 +                              
 +                              self.spawnorigin = spot.origin;
 +                              setorigin (self, spot.origin + '0 0 1' * (1 - self.mins_z - 24));
 +                              // don't reset back to last position, even if new position is stuck in solid
 +                              self.oldorigin = self.origin;
 +                              self.prevorigin = self.origin;
 +                              
 +                              pointparticles(particleeffectnum("teleport"), self.origin, '0 0 0', 1);
 +                      }
 +                      
 +                      self = oldself;
 +              }
 +
 +              if(!g_minstagib)
                {
                        // apply strength multiplier
                        if (attacker.items & IT_STRENGTH)
                }
  
                if (targ == attacker)
-               {
-                       if(g_cts && !autocvar_g_cts_selfdamage)
-                               damage = 0;
-                       else
-                               damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
-               }
+                       damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
  
                // count the damage
                if(attacker)
@@@ -1336,7 -1195,7 +1331,7 @@@ void Fire_ApplyDamage(entity e
                e.fire_endtime = 0;
  
        // ice stops fire
 -      if(e.freezetag_frozen)
 +      if(e.frozen)
                e.fire_endtime = 0;
  
        t = min(frametime, e.fire_endtime - time);
        e.fire_hitsound = TRUE;
  
        if (!IS_INDEPENDENT_PLAYER(e))
-       if (!e.frozen)
 -      if(!e.freezetag_frozen)
++      if(!e.frozen)
        FOR_EACH_PLAYER(other) if(e != other)
        {
                if(IS_PLAYER(other))
diff --combined qcsrc/server/g_world.qc
index 599abdad9fd79af85f56c196f699e186d8eed7dc,324d332887c07161aa39a16449240432dcfb77d9..a1460ded8aa0e5bf6f19952754ba83d3213ee5be
@@@ -55,7 -55,6 +55,6 @@@ const float SPAWNFLAG_NO_WAYPOINTS_FOR_
  string redirection_target;
  float world_initialized;
  
- string GetMapname();
  string GetGametype();
  void GotoNextMap(float reinit);
  void ShuffleMaplist();
@@@ -216,7 -215,6 +215,6 @@@ void cvar_changes_init(
                // private
                BADCVAR("developer");
                BADCVAR("log_dest_udp");
-               BADCVAR("log_file");
                BADCVAR("net_address");
                BADCVAR("net_address_ipv6");
                BADCVAR("port");
                BADPREFIX("g_playerstats_");
                BADPREFIX("g_respawn_ghosts");
                BADPREFIX("g_voice_flood_");
+               BADPREFIX("log_file");
                BADPREFIX("rcon_");
                BADPREFIX("sv_allowdownloads");
                BADPREFIX("sv_autodemo");
@@@ -546,6 -545,7 +545,7 @@@ void spawnfunc___init_dedicated_server(
        CALL_ACCUMULATED_FUNCTION(RegisterGametypes);
        CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
        CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
+       CALL_ACCUMULATED_FUNCTION(RegisterBuffs);
  
        MapInfo_Enumerate();
        MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0);
@@@ -595,6 -595,7 +595,7 @@@ void spawnfunc_worldspawn (void
        CALL_ACCUMULATED_FUNCTION(RegisterGametypes);
        CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
        CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
+       CALL_ACCUMULATED_FUNCTION(RegisterBuffs);
  
        ServerProgsDB = db_load(strcat("server.db", autocvar_sessionid));
  
  
        addstat(STAT_HAGAR_LOAD, AS_INT, hagar_load);
  
 +      // freeze attacks
 +      addstat(STAT_FROZEN, AS_INT, frozen);
 +      addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, revive_progress);
 +
        // g_movementspeed hack
        addstat(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW, AS_FLOAT, stat_sv_airspeedlimit_nonqw);
        addstat(STAT_MOVEVARS_MAXSPEED, AS_FLOAT, stat_sv_maxspeed);
@@@ -900,7 -897,6 +901,6 @@@ string GetGametype(
        return MapInfo_Type_ToString(MapInfo_LoadedGametype);
  }
  
- string getmapname_stored;
  string GetMapname()
  {
        return mapname;
@@@ -1225,13 -1221,27 +1225,27 @@@ float DoNextMapOverride(float reinit
                return TRUE;
        }
        if(autocvar_nextmap != "")
-               if(MapInfo_CheckMap(autocvar_nextmap))
+       {
+               string m;
+               m = GameTypeVote_MapInfo_FixName(autocvar_nextmap);
+               cvar_set("nextmap",m);
+       
+               if(!m || gametypevote)
+                       return FALSE;
+               if(autocvar_sv_vote_gametype)
                {
-                       Map_Goto_SetStr(autocvar_nextmap);
+                       Map_Goto_SetStr(m);
+                       return FALSE;
+               }
+               
+               if(MapInfo_CheckMap(m))
+               {
+                       Map_Goto_SetStr(m);
                        Map_Goto(reinit);
                        alreadychangedlevel = TRUE;
                        return TRUE;
                }
+       }
        if(!reinit && autocvar_lastlevel)
        {
                cvar_settemp_restore();
@@@ -1268,9 -1278,6 +1282,6 @@@ When the player presses attack or jump
  ============
  */
  .float autoscreenshot;
- void() MapVote_Start;
- void() MapVote_Think;
- float mapvote_initialized;
  void IntermissionThink()
  {
        FixIntermissionClient(self);
@@@ -1580,7 -1587,7 +1591,7 @@@ float InitiateSuddenDeath(
        // - for this timelimit_overtime needs to be >0 of course
        // - also check the winning condition calculated in the previous frame and only add normal overtime
        //   again, if at the point at which timelimit would be extended again, still no winner was found
-       if (!autocvar_g_campaign && (checkrules_overtimesadded >= 0) && (checkrules_overtimesadded < autocvar_timelimit_overtimes) && autocvar_timelimit_overtime && !(g_race && !g_race_qualifying))
+       if (!autocvar_g_campaign && (checkrules_overtimesadded >= 0) && (checkrules_overtimesadded < autocvar_timelimit_overtimes || autocvar_timelimit_overtimes < 0) && autocvar_timelimit_overtime && !(g_race && !g_race_qualifying))
        {
                return 1; // need to call InitiateOvertime later
        }
@@@ -2035,17 -2042,6 +2046,6 @@@ void CheckRules_World(
  
        SetDefaultAlpha();
  
-       /*
-       MapVote_Think should now do that part
-       if (intermission_running)
-               if (time >= intermission_exittime + 60)
-               {
-                       if(!DoNextMapOverride())
-                               GotoNextMap();
-                       return;
-               }
-       */
        if (gameover)   // someone else quit the game already
        {
                if(player_count == 0) // Nobody there? Then let's go to the next map
        }
  }
  
- float mapvote_nextthink;
- float mapvote_initialized;
- float mapvote_keeptwotime;
- float mapvote_timeout;
- string mapvote_message;
- #define MAPVOTE_SCREENSHOT_DIRS_COUNT 4
- string mapvote_screenshot_dirs[MAPVOTE_SCREENSHOT_DIRS_COUNT];
- float mapvote_screenshot_dirs_count;
- float mapvote_count;
- float mapvote_count_real;
- string mapvote_maps[MAPVOTE_COUNT];
- float mapvote_maps_screenshot_dir[MAPVOTE_COUNT];
- string mapvote_maps_pakfile[MAPVOTE_COUNT];
- float mapvote_maps_suggested[MAPVOTE_COUNT];
- string mapvote_suggestions[MAPVOTE_COUNT];
- float mapvote_suggestion_ptr;
- float mapvote_voters;
- float mapvote_selections[MAPVOTE_COUNT];
- float mapvote_run;
- float mapvote_detail;
- float mapvote_abstain;
- .float mapvote;
- void MapVote_ClearAllVotes()
- {
-       FOR_EACH_CLIENT(other)
-               other.mapvote = 0;
- }
- string MapVote_Suggest(string m)
+ string GotoMap(string m)
  {
-       float i;
-       if(m == "")
-               return "That's not how to use this command.";
-       if(!autocvar_g_maplist_votable_suggestions)
-               return "Suggestions are not accepted on this server.";
-       if(mapvote_initialized)
-               return "Can't suggest - voting is already in progress!";
-       m = MapInfo_FixName(m);
+       m = GameTypeVote_MapInfo_FixName(m);
        if (!m)
                return "The map you suggested is not available on this server.";
-       if(!autocvar_g_maplist_votable_suggestions_override_mostrecent)
-               if(Map_IsRecent(m))
-                       return "This server does not allow for recent maps to be played again. Please be patient for some rounds.";
+       if (!autocvar_sv_vote_gametype)
        if(!MapInfo_CheckMap(m))
                return "The map you suggested does not support the current game mode.";
-       for(i = 0; i < mapvote_suggestion_ptr; ++i)
-               if(mapvote_suggestions[i] == m)
-                       return "This map was already suggested.";
-       if(mapvote_suggestion_ptr >= MAPVOTE_COUNT)
-       {
-               i = floor(random() * mapvote_suggestion_ptr);
-       }
-       else
-       {
-               i = mapvote_suggestion_ptr;
-               mapvote_suggestion_ptr += 1;
-       }
-       if(mapvote_suggestions[i] != "")
-               strunzone(mapvote_suggestions[i]);
-       mapvote_suggestions[i] = strzone(m);
-       if(autocvar_sv_eventlog)
-               GameLogEcho(strcat(":vote:suggested:", m, ":", ftos(self.playerid)));
-       return strcat("Suggestion of ", m, " accepted.");
- }
- void MapVote_AddVotable(string nextMap, float isSuggestion)
- {
-       float j, i, o;
-       string pakfile, mapfile;
-       if(nextMap == "")
-               return;
-       for(j = 0; j < mapvote_count; ++j)
-               if(mapvote_maps[j] == nextMap)
-                       return;
-       // suggestions might be no longer valid/allowed after gametype switch!
-       if(isSuggestion)
-               if(!MapInfo_CheckMap(nextMap))
-                       return;
-       mapvote_maps[mapvote_count] = strzone(nextMap);
-       mapvote_maps_suggested[mapvote_count] = isSuggestion;
-       pakfile = string_null;
-       for(i = 0; i < mapvote_screenshot_dirs_count; ++i)
-       {
-               mapfile = strcat(mapvote_screenshot_dirs[i], "/", mapvote_maps[i]);
-               pakfile = whichpack(strcat(mapfile, ".tga"));
-               if(pakfile == "")
-                       pakfile = whichpack(strcat(mapfile, ".jpg"));
-               if(pakfile == "")
-                       pakfile = whichpack(strcat(mapfile, ".png"));
-               if(pakfile != "")
-                       break;
-       }
-       if(i >= mapvote_screenshot_dirs_count)
-               i = 0; // FIXME maybe network this error case, as that means there is no mapshot on the server?
-       for(o = strstr(pakfile, "/", 0)+1; o > 0; o = strstr(pakfile, "/", 0)+1)
-               pakfile = substring(pakfile, o, -1);
-       mapvote_maps_screenshot_dir[mapvote_count] = i;
-       mapvote_maps_pakfile[mapvote_count] = strzone(pakfile);
-       mapvote_count += 1;
- }
- void MapVote_Spawn();
- void MapVote_Init()
- {
-       float i;
-       float nmax, smax;
-       MapVote_ClearAllVotes();
-       mapvote_count = 0;
-       mapvote_detail = !autocvar_g_maplist_votable_nodetail;
-       mapvote_abstain = autocvar_g_maplist_votable_abstain;
-       if(mapvote_abstain)
-               nmax = min(MAPVOTE_COUNT - 1, autocvar_g_maplist_votable);
-       else
-               nmax = min(MAPVOTE_COUNT, autocvar_g_maplist_votable);
-       smax = min3(nmax, autocvar_g_maplist_votable_suggestions, mapvote_suggestion_ptr);
-       // we need this for AddVotable, as that cycles through the screenshot dirs
-       mapvote_screenshot_dirs_count = tokenize_console(autocvar_g_maplist_votable_screenshot_dir);
-       if(mapvote_screenshot_dirs_count == 0)
-               mapvote_screenshot_dirs_count = tokenize_console("maps levelshots");
-       mapvote_screenshot_dirs_count = min(mapvote_screenshot_dirs_count, MAPVOTE_SCREENSHOT_DIRS_COUNT);
-       for(i = 0; i < mapvote_screenshot_dirs_count; ++i)
-               mapvote_screenshot_dirs[i] = strzone(argv(i));
-       if(mapvote_suggestion_ptr)
-               for(i = 0; i < 100 && mapvote_count < smax; ++i)
-                       MapVote_AddVotable(mapvote_suggestions[floor(random() * mapvote_suggestion_ptr)], TRUE);
-       for(i = 0; i < 100 && mapvote_count < nmax; ++i)
-               MapVote_AddVotable(GetNextMap(), FALSE);
-       if(mapvote_count == 0)
-       {
-               bprint( "Maplist contains no single playable map!  Resetting it to default map list.\n" );
-               cvar_set("g_maplist", MapInfo_ListAllAllowedMaps(MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags()));
-               if(autocvar_g_maplist_shuffle)
-                       ShuffleMaplist();
-               localcmd("\nmenu_cmd sync\n");
-               for(i = 0; i < 100 && mapvote_count < nmax; ++i)
-                       MapVote_AddVotable(GetNextMap(), FALSE);
-       }
-       mapvote_count_real = mapvote_count;
-       if(mapvote_abstain)
-               MapVote_AddVotable("don't care", 0);
-       //dprint("mapvote count is ", ftos(mapvote_count), "\n");
-       mapvote_keeptwotime = time + autocvar_g_maplist_votable_keeptwotime;
-       mapvote_timeout = time + autocvar_g_maplist_votable_timeout;
-       if(mapvote_count_real < 3 || mapvote_keeptwotime <= time)
-               mapvote_keeptwotime = 0;
-       mapvote_message = "Choose a map and press its key!";
-       MapVote_Spawn();
- }
- void MapVote_SendPicture(float id)
- {
-       msg_entity = self;
-       WriteByte(MSG_ONE, SVC_TEMPENTITY);
-       WriteByte(MSG_ONE, TE_CSQC_PICTURE);
-       WriteByte(MSG_ONE, id);
-       WritePicture(MSG_ONE, strcat(mapvote_screenshot_dirs[mapvote_maps_screenshot_dir[id]], "/", mapvote_maps[id]), 3072);
- }
- float MapVote_GetMapMask()
- {
-       float mask, i, power;
-       mask = 0;
-       for(i = 0, power = 1; i < mapvote_count; ++i, power *= 2)
-               if(mapvote_maps[i] != "")
-                       mask |= power;
-       return mask;
- }
- entity mapvote_ent;
- float MapVote_SendEntity(entity to, float sf)
- {
-       float i;
-       if(sf & 1)
-               sf &= ~2; // if we send 1, we don't need to also send 2
-       WriteByte(MSG_ENTITY, ENT_CLIENT_MAPVOTE);
-       WriteByte(MSG_ENTITY, sf);
-       if(sf & 1)
-       {
-               // flag 1 == initialization
-               for(i = 0; i < mapvote_screenshot_dirs_count; ++i)
-                       WriteString(MSG_ENTITY, mapvote_screenshot_dirs[i]);
-               WriteString(MSG_ENTITY, "");
-               WriteByte(MSG_ENTITY, mapvote_count);
-               WriteByte(MSG_ENTITY, mapvote_abstain);
-               WriteByte(MSG_ENTITY, mapvote_detail);
-               WriteCoord(MSG_ENTITY, mapvote_timeout);
-               if(mapvote_count <= 8)
-                       WriteByte(MSG_ENTITY, MapVote_GetMapMask());
-               else
-                       WriteShort(MSG_ENTITY, MapVote_GetMapMask());
-               for(i = 0; i < mapvote_count; ++i)
-                       if(mapvote_maps[i] != "")
-                       {
-                               if(mapvote_abstain && i == mapvote_count - 1)
-                               {
-                                       WriteString(MSG_ENTITY, ""); // abstain needs no text
-                                       WriteString(MSG_ENTITY, ""); // abstain needs no pack
-                                       WriteByte(MSG_ENTITY, 0); // abstain needs no screenshot dir
-                               }
-                               else
-                               {
-                                       WriteString(MSG_ENTITY, mapvote_maps[i]);
-                                       WriteString(MSG_ENTITY, mapvote_maps_pakfile[i]);
-                                       WriteByte(MSG_ENTITY, mapvote_maps_screenshot_dir[i]);
-                               }
-                       }
-       }
-       if(sf & 2)
-       {
-               // flag 2 == update of mask
-               if(mapvote_count <= 8)
-                       WriteByte(MSG_ENTITY, MapVote_GetMapMask());
-               else
-                       WriteShort(MSG_ENTITY, MapVote_GetMapMask());
-       }
-       if(sf & 4)
-       {
-               if(mapvote_detail)
-                       for(i = 0; i < mapvote_count; ++i)
-                               if(mapvote_maps[i] != "")
-                                       WriteByte(MSG_ENTITY, mapvote_selections[i]);
-               WriteByte(MSG_ENTITY, to.mapvote);
-       }
-       return TRUE;
- }
- void MapVote_Spawn()
- {
-       Net_LinkEntity(mapvote_ent = spawn(), FALSE, 0, MapVote_SendEntity);
- }
- void MapVote_TouchMask()
- {
-       mapvote_ent.SendFlags |= 2;
- }
- void MapVote_TouchVotes(entity voter)
- {
-       mapvote_ent.SendFlags |= 4;
- }
- float MapVote_Finished(float mappos)
- {
-       string result;
-       float i;
-       float didntvote;
-       if(autocvar_sv_eventlog)
-       {
-               result = strcat(":vote:finished:", mapvote_maps[mappos]);
-               result = strcat(result, ":", ftos(mapvote_selections[mappos]), "::");
-               didntvote = mapvote_voters;
-               for(i = 0; i < mapvote_count; ++i)
-                       if(mapvote_maps[i] != "")
-                       {
-                               didntvote -= mapvote_selections[i];
-                               if(i != mappos)
-                               {
-                                       result = strcat(result, ":", mapvote_maps[i]);
-                                       result = strcat(result, ":", ftos(mapvote_selections[i]));
-                               }
-                       }
-               result = strcat(result, ":didn't vote:", ftos(didntvote));
-               GameLogEcho(result);
-               if(mapvote_maps_suggested[mappos])
-                       GameLogEcho(strcat(":vote:suggestion_accepted:", mapvote_maps[mappos]));
-       }
-       FOR_EACH_REALCLIENT(other)
-               FixClientCvars(other);
-       Map_Goto_SetStr(mapvote_maps[mappos]);
-       Map_Goto(0);
-       alreadychangedlevel = TRUE;
-       return TRUE;
- }
- void MapVote_CheckRules_1()
- {
-       float i;
-       for(i = 0; i < mapvote_count; ++i) if(mapvote_maps[i] != "")
-       {
-               //dprint("Map ", ftos(i), ": "); dprint(mapvote_maps[i], "\n");
-               mapvote_selections[i] = 0;
-       }
-       mapvote_voters = 0;
-       FOR_EACH_REALCLIENT(other)
-       {
-               ++mapvote_voters;
-               if(other.mapvote)
-               {
-                       i = other.mapvote - 1;
-                       //dprint("Player ", other.netname, " vote = ", ftos(other.mapvote - 1), "\n");
-                       mapvote_selections[i] = mapvote_selections[i] + 1;
-               }
-       }
- }
- float MapVote_CheckRules_2()
- {
-       float i;
-       float firstPlace, secondPlace;
-       float firstPlaceVotes, secondPlaceVotes;
-       float mapvote_voters_real;
-       string result;
-       if(mapvote_count_real == 1)
-               return MapVote_Finished(0);
-       mapvote_voters_real = mapvote_voters;
-       if(mapvote_abstain)
-               mapvote_voters_real -= mapvote_selections[mapvote_count - 1];
-       RandomSelection_Init();
-       for(i = 0; i < mapvote_count_real; ++i) if(mapvote_maps[i] != "")
-               RandomSelection_Add(world, i, string_null, 1, mapvote_selections[i]);
-       firstPlace = RandomSelection_chosen_float;
-       firstPlaceVotes = RandomSelection_best_priority;
-       //dprint("First place: ", ftos(firstPlace), "\n");
-       //dprint("First place votes: ", ftos(firstPlaceVotes), "\n");
-       RandomSelection_Init();
-       for(i = 0; i < mapvote_count_real; ++i) if(mapvote_maps[i] != "")
-               if(i != firstPlace)
-                       RandomSelection_Add(world, i, string_null, 1, mapvote_selections[i]);
-       secondPlace = RandomSelection_chosen_float;
-       secondPlaceVotes = RandomSelection_best_priority;
-       //dprint("Second place: ", ftos(secondPlace), "\n");
-       //dprint("Second place votes: ", ftos(secondPlaceVotes), "\n");
-       if(firstPlace == -1)
-               error("No first place in map vote... WTF?");
-       if(secondPlace == -1 || time > mapvote_timeout || (mapvote_voters_real - firstPlaceVotes) < firstPlaceVotes)
-               return MapVote_Finished(firstPlace);
-       if(mapvote_keeptwotime)
-               if(time > mapvote_keeptwotime || (mapvote_voters_real - firstPlaceVotes - secondPlaceVotes) < secondPlaceVotes)
-               {
-                       float didntvote;
-                       MapVote_TouchMask();
-                       mapvote_message = "Now decide between the TOP TWO!";
-                       mapvote_keeptwotime = 0;
-                       result = strcat(":vote:keeptwo:", mapvote_maps[firstPlace]);
-                       result = strcat(result, ":", ftos(firstPlaceVotes));
-                       result = strcat(result, ":", mapvote_maps[secondPlace]);
-                       result = strcat(result, ":", ftos(secondPlaceVotes), "::");
-                       didntvote = mapvote_voters;
-                       for(i = 0; i < mapvote_count; ++i)
-                               if(mapvote_maps[i] != "")
-                               {
-                                       didntvote -= mapvote_selections[i];
-                                       if(i != firstPlace)
-                                               if(i != secondPlace)
-                                               {
-                                                       result = strcat(result, ":", mapvote_maps[i]);
-                                                       result = strcat(result, ":", ftos(mapvote_selections[i]));
-                                                       if(i < mapvote_count_real)
-                                                       {
-                                                               strunzone(mapvote_maps[i]);
-                                                               mapvote_maps[i] = "";
-                                                               strunzone(mapvote_maps_pakfile[i]);
-                                                               mapvote_maps_pakfile[i] = "";
-                                                       }
-                                               }
-                               }
-                       result = strcat(result, ":didn't vote:", ftos(didntvote));
-                       if(autocvar_sv_eventlog)
-                               GameLogEcho(result);
-               }
-       return FALSE;
- }
- void MapVote_Tick()
- {
-       float keeptwo;
-       float totalvotes;
-       keeptwo = mapvote_keeptwotime;
-       MapVote_CheckRules_1(); // count
-       if(MapVote_CheckRules_2()) // decide
-               return;
-       totalvotes = 0;
-       FOR_EACH_REALCLIENT(other)
-       {
-               // hide scoreboard again
-               if(other.health != 2342)
-               {
-                       other.health = 2342;
-                       other.impulse = 0;
-                       if(IS_REAL_CLIENT(other))
-                       {
-                               msg_entity = other;
-                               WriteByte(MSG_ONE, SVC_FINALE);
-                               WriteString(MSG_ONE, "");
-                       }
-               }
-               // clear possibly invalid votes
-               if(mapvote_maps[other.mapvote - 1] == "")
-                       other.mapvote = 0;
-               // use impulses as new vote
-               if(other.impulse >= 1 && other.impulse <= mapvote_count)
-                       if(mapvote_maps[other.impulse - 1] != "")
-                       {
-                               other.mapvote = other.impulse;
-                               MapVote_TouchVotes(other);
-                       }
-               other.impulse = 0;
-               if(other.mapvote)
-                       ++totalvotes;
-       }
-       MapVote_CheckRules_1(); // just count
- }
- void MapVote_Start()
- {
-       if(mapvote_run)
-               return;
-       // wait for stats to be sent first
-       if(!playerstats_waitforme)
-               return;
-       MapInfo_Enumerate();
-       if(MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1))
-               mapvote_run = TRUE;
- }
- void MapVote_Think()
- {
-       if(!mapvote_run)
-               return;
-       if(alreadychangedlevel)
-               return;
-       if(time < mapvote_nextthink)
-               return;
-       //dprint("tick\n");
-       mapvote_nextthink = time + 0.5;
-       if(!mapvote_initialized)
-       {
-               if(autocvar_rescan_pending == 1)
-               {
-                       cvar_set("rescan_pending", "2");
-                       localcmd("fs_rescan\nrescan_pending 3\n");
-                       return;
-               }
-               else if(autocvar_rescan_pending == 2)
-               {
-                       return;
-               }
-               else if(autocvar_rescan_pending == 3)
-               {
-                       // now build missing mapinfo files
-                       if(!MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1))
-                               return;
-                       // we're done, start the timer
-                       cvar_set("rescan_pending", "0");
-               }
-               mapvote_initialized = TRUE;
-               if(DoNextMapOverride(0))
-                       return;
-               if(!autocvar_g_maplist_votable || player_count <= 0)
-               {
-                       GotoNextMap(0);
-                       return;
-               }
-               MapVote_Init();
-       }
-       MapVote_Tick();
- }
- string GotoMap(string m)
- {
-       if(!MapInfo_CheckMap(m))
-               return "The map you chose is not available on this server.";
        cvar_set("nextmap", m);
        cvar_set("timelimit", "-1");
        if(mapvote_initialized || alreadychangedlevel)
  
  void EndFrame()
  {
+       anticheat_endframe();
        float altime;
        FOR_EACH_REALCLIENT(self)
        {
index f3c26699db8003b97d42402334675b443376bfd0,64760a994eeb37363795bc6086f47a04e30f79a9..55e01b2453f1672fb72dcd9e68923d1641b9d920
@@@ -904,34 -904,11 +904,11 @@@ float sv_autotaunt
  float sv_taunt;
  
  string GetGametype(); // g_world.qc
+ void mutators_add(); // mutators.qc
  void readlevelcvars(void)
  {
        // load mutators
-       #define CHECK_MUTATOR_ADD(mut_cvar,mut_name,dependence) \
-               { if(cvar(mut_cvar) && dependence) { MUTATOR_ADD(mut_name); } }
-       CHECK_MUTATOR_ADD("g_dodging", mutator_dodging, 1);
-       CHECK_MUTATOR_ADD("g_spawn_near_teammate", mutator_spawn_near_teammate, teamplay);
-       CHECK_MUTATOR_ADD("g_physical_items", mutator_physical_items, 1);
-       CHECK_MUTATOR_ADD("g_touchexplode", mutator_touchexplode, 1);
-       CHECK_MUTATOR_ADD("g_minstagib", mutator_minstagib, 1);
-       CHECK_MUTATOR_ADD("g_invincible_projectiles", mutator_invincibleprojectiles, !cvar("g_minstagib"));
-       CHECK_MUTATOR_ADD("g_new_toys", mutator_new_toys, !cvar("g_minstagib"));
-       CHECK_MUTATOR_ADD("g_nix", mutator_nix, !cvar("g_minstagib"));
-       CHECK_MUTATOR_ADD("g_rocket_flying", mutator_rocketflying, !cvar("g_minstagib"));
-       CHECK_MUTATOR_ADD("g_vampire", mutator_vampire, !cvar("g_minstagib"));
-       CHECK_MUTATOR_ADD("g_superspectate", mutator_superspec, 1);
-       CHECK_MUTATOR_ADD("g_pinata", mutator_pinata, !cvar("g_minstagib"));
-       CHECK_MUTATOR_ADD("g_midair", mutator_midair, 1);
-       CHECK_MUTATOR_ADD("g_bloodloss", mutator_bloodloss, !cvar("g_minstagib"));
-       CHECK_MUTATOR_ADD("g_random_gravity", mutator_random_gravity, 1);
-       CHECK_MUTATOR_ADD("g_multijump", mutator_multijump, 1);
-       CHECK_MUTATOR_ADD("g_melee_only", mutator_melee_only, !cvar("g_minstagib"));
-       CHECK_MUTATOR_ADD("g_nades", mutator_nades, 1);
-       CHECK_MUTATOR_ADD("g_sandbox", sandbox, 1);
-       CHECK_MUTATOR_ADD("g_campcheck", mutator_campcheck, 1);
-       #undef CHECK_MUTATOR_ADD
+       mutators_add();
  
        if(cvar("sv_allow_fullbright"))
                serverflags |= SERVERFLAG_ALLOW_FULLBRIGHT;
        sv_clones = cvar("sv_clones");
        sv_foginterval = cvar("sv_foginterval");
        g_cloaked = cvar("g_cloaked");
-     if(g_cts)
-         g_cloaked = 1; // always enable cloak in CTS
        g_footsteps = cvar("g_footsteps");
        g_grappling_hook = cvar("g_grappling_hook");
        g_jetpack = cvar("g_jetpack");
@@@ -1082,15 -1057,6 +1057,6 @@@ float sound_allowed(float dest, entity 
      return TRUE;
  }
  
- #ifdef COMPAT_XON010_CHANNELS
- void(entity e, float chan, string samp, float vol, float atten) builtin_sound = #8;
- void sound(entity e, float chan, string samp, float vol, float atten)
- {
-     if (!sound_allowed(MSG_BROADCAST, e))
-         return;
-     builtin_sound(e, chan, samp, vol, atten);
- }
- #else
  #undef sound
  void sound(entity e, float chan, string samp, float vol, float atten)
  {
          return;
      sound7(e, chan, samp, vol, atten, 0, 0);
  }
- #endif
  
  void soundtoat(float dest, entity e, vector o, float chan, string samp, float vol, float atten)
  {
@@@ -1303,7 -1268,6 +1268,7 @@@ void precache(
  {
      // gamemode related things
      precache_model ("models/misc/chatbubble.spr");
 +      precache_model("models/ice/ice.md3");
  
  #ifdef TTURRETS_ENABLED
      if (autocvar_g_turrets)
@@@ -1827,82 -1791,6 +1792,6 @@@ string uid2name(string myuid) 
        return s;
  }
  
- float race_readTime(string map, float pos)
- {
-       string rr;
-       if(g_cts)
-               rr = CTS_RECORD;
-       else
-               rr = RACE_RECORD;
-       return stof(db_get(ServerProgsDB, strcat(map, rr, "time", ftos(pos))));
- }
- string race_readUID(string map, float pos)
- {
-       string rr;
-       if(g_cts)
-               rr = CTS_RECORD;
-       else
-               rr = RACE_RECORD;
-       return db_get(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(pos)));
- }
- float race_readPos(string map, float t) {
-       float i;
-       for (i = 1; i <= RANKINGS_CNT; ++i)
-               if (race_readTime(map, i) == 0 || race_readTime(map, i) > t)
-                       return i;
-       return 0; // pos is zero if unranked
- }
- void race_writeTime(string map, float t, string myuid)
- {
-       string rr;
-       if(g_cts)
-               rr = CTS_RECORD;
-       else
-               rr = RACE_RECORD;
-       float newpos;
-       newpos = race_readPos(map, t);
-       float i, prevpos = 0;
-       for(i = 1; i <= RANKINGS_CNT; ++i)
-       {
-               if(race_readUID(map, i) == myuid)
-                       prevpos = i;
-       }
-       if (prevpos) { // player improved his existing record, only have to iterate on ranks between new and old recs
-               for (i = prevpos; i > newpos; --i) {
-                       db_put(ServerProgsDB, strcat(map, rr, "time", ftos(i)), ftos(race_readTime(map, i - 1)));
-                       db_put(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(i)), race_readUID(map, i - 1));
-               }
-       } else { // player has no ranked record yet
-               for (i = RANKINGS_CNT; i > newpos; --i) {
-                       db_put(ServerProgsDB, strcat(map, rr, "time", ftos(i)), ftos(race_readTime(map, i - 1)));
-                       db_put(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(i)), race_readUID(map, i - 1));
-               }
-       }
-       // store new time itself
-       db_put(ServerProgsDB, strcat(map, rr, "time", ftos(newpos)), ftos(t));
-       db_put(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(newpos)), myuid);
- }
- string race_readName(string map, float pos)
- {
-       string rr;
-       if(g_cts)
-               rr = CTS_RECORD;
-       else
-               rr = RACE_RECORD;
-       return uid2name(db_get(ServerProgsDB, strcat(map, rr, "crypto_idfp", ftos(pos))));
- }
  float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
  {
      float m, i;
index 626e4fed8af58d9721a8d66c15686c50346c999a,c9c9b4584b14af089b437c5b81f4c26e105dae95..967d8d656fdf72b43a54e7fdff077c3d111c4d50
@@@ -388,8 -388,6 +388,8 @@@ void ctf_Handle_Capture(entity flag, en
  
        if (!player) { return; } // without someone to give the reward to, we can't possibly cap
  
 +      nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
 +
        // messages and sounds
        Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
        ctf_CaptureRecord(enemy_flag, player);
@@@ -450,8 -448,6 +450,8 @@@ void ctf_Handle_Return(entity flag, ent
        {
                PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
                PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
 +
 +              nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
        }
  
        TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
@@@ -504,7 -500,6 +504,7 @@@ void ctf_Handle_Pickup(entity flag, ent
  
        // scoring
        PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
 +      nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
        switch(pickuptype)
        {
                case PICKUP_BASE:
@@@ -661,7 -656,7 +661,7 @@@ void ctf_FlagThink(
        if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
                dprint("wtf the flag got squashed?\n");
                tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
-               if(!trace_startsolid) // can we resize it without getting stuck?
+               if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
                        setsize(self, FLAG_MIN, FLAG_MAX); }
  
        switch(self.ctf_status) // reset flag angles in case warpzones adjust it
@@@ -796,10 -791,9 +796,10 @@@ void ctf_FlagTouch(
        }
  
        // special touch behaviors
 -      if(toucher.vehicle_flags & VHF_ISVEHICLE)
 +      if(toucher.frozen) { return; }
 +      else if(toucher.vehicle_flags & VHF_ISVEHICLE)
        {
-               if(autocvar_g_ctf_allow_vehicle_touch)
+               if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
                        toucher = toucher.owner; // the player is actually the vehicle owner, not other
                else
                        return; // do nothing
@@@ -2188,7 -2182,7 +2188,7 @@@ MUTATOR_DEFINITION(gamemode_ctf
        MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
        MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
        MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
-       MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
+       MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
  
        MUTATOR_ONADD
        {
index 52d0fd4921f5d74cd9a638675fb21ddbcb5dce58,26e19910ad0f3f571a19f779bec902872b9de8e7..ffdc1612dde1235608c2874da85eefc60dd37c15
@@@ -1,6 -1,7 +1,6 @@@
  .float freezetag_frozen_time;
  .float freezetag_frozen_timeout;
  .float freezetag_revive_progress;
 -.entity freezetag_ice;
  #define ICE_MAX_ALPHA 1
  #define ICE_MIN_ALPHA 0.1
  float freezetag_teams;
@@@ -9,18 -10,29 +9,18 @@@ void freezetag_count_alive_players(
  {
        entity e;
        total_players = redalive = bluealive = yellowalive = pinkalive = 0;
 -      FOR_EACH_PLAYER(e) {
 -              if(e.team == NUM_TEAM_1 && e.health >= 1)
 -              {
 -                      ++total_players;
 -                      if (!e.freezetag_frozen) ++redalive;
 -              }
 -              else if(e.team == NUM_TEAM_2 && e.health >= 1)
 -              {
 -                      ++total_players;
 -                      if (!e.freezetag_frozen) ++bluealive;
 -              }
 -              else if(e.team == NUM_TEAM_3 && e.health >= 1)
 -              {
 -                      ++total_players;
 -                      if (!e.freezetag_frozen) ++yellowalive;
 -              }
 -              else if(e.team == NUM_TEAM_4 && e.health >= 1)
 +      FOR_EACH_PLAYER(e)
 +      {
 +              switch(e.team)
                {
 -                      ++total_players;
 -                      if (!e.freezetag_frozen) ++pinkalive;
 +                      case NUM_TEAM_1: ++total_players; if(e.health >= 1 && e.frozen != 1) ++redalive; break;
 +                      case NUM_TEAM_2: ++total_players; if(e.health >= 1 && e.frozen != 1) ++bluealive; break;
 +                      case NUM_TEAM_3: ++total_players; if(e.health >= 1 && e.frozen != 1) ++yellowalive; break;
 +                      case NUM_TEAM_4: ++total_players; if(e.health >= 1 && e.frozen != 1) ++pinkalive; break;
                }
        }
 -      FOR_EACH_REALCLIENT(e) {
 +      FOR_EACH_REALCLIENT(e)
 +      {
                e.redalive_stat = redalive;
                e.bluealive_stat = bluealive;
                e.yellowalive_stat = yellowalive;
@@@ -90,7 -102,7 +90,7 @@@ float freezetag_CheckWinner(
                FOR_EACH_PLAYER(e)
                {
                        e.freezetag_frozen_timeout = 0;
 -                      e.freezetag_revive_progress = 0;
 +                      nades_Clear(e);
                }
                round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
                return 1;
        FOR_EACH_PLAYER(e)
        {
                e.freezetag_frozen_timeout = 0;
 -              e.freezetag_revive_progress = 0;
 +              nades_Clear(e);
        }
        round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
        return 1;
  }
  
 -// this is needed to allow the player to turn his view around (fixangle can't
 -// be used to freeze his view, as that also changes the angles), while not
 -// turning that ice object with the player
 -void freezetag_Ice_Think()
 -{
 -      setorigin(self, self.owner.origin - '0 0 16');
 -      self.nextthink = time;
 -}
 -
  void freezetag_Add_Score(entity attacker)
  {
        if(attacker == self)
  
  void freezetag_Freeze(entity attacker)
  {
 -      if(self.freezetag_frozen)
 +      if(self.frozen)
                return;
 -      self.freezetag_frozen = 1;
 -      self.freezetag_frozen_time = time;
 -      self.freezetag_revive_progress = 0;
 -      self.health = 1;
 +
        if(autocvar_g_freezetag_frozen_maxtime > 0)
                self.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
  
 -      freezetag_count_alive_players();
 +      Freeze(self, 0, 1, TRUE);
  
 -      entity ice;
 -      ice = spawn();
 -      ice.owner = self;
 -      ice.classname = "freezetag_ice";
 -      ice.think = freezetag_Ice_Think;
 -      ice.nextthink = time;
 -      ice.frame = floor(random() * 21); // ice model has 20 different looking frames
 -      ice.alpha = ICE_MAX_ALPHA;
 -      ice.colormod = Team_ColorRGB(self.team);
 -      ice.glowmod = ice.colormod;
 -      setmodel(ice, "models/ice/ice.md3");
 -
 -      self.freezetag_ice = ice;
 -
 -      RemoveGrapplingHook(self);
 -
 -      // add waypoint
 -      WaypointSprite_Spawn("freezetag_frozen", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attached, TRUE, RADARICON_WAYPOINT, '0.25 0.90 1');
 +      freezetag_count_alive_players();
  
        freezetag_Add_Score(attacker);
  }
  
  void freezetag_Unfreeze(entity attacker)
  {
 -      self.freezetag_frozen = 0;
        self.freezetag_frozen_time = 0;
        self.freezetag_frozen_timeout = 0;
 -      self.freezetag_revive_progress = 0;
  
 -      remove(self.freezetag_ice);
 -      self.freezetag_ice = world;
 -
 -      if(self.waypointsprite_attached)
 -              WaypointSprite_Kill(self.waypointsprite_attached);
 +      Unfreeze(self);
  }
  
  
@@@ -180,7 -227,7 +180,7 @@@ void havocbot_goalrating_freeplayers(fl
        {
                if ((head != self) && (head.team == self.team))
                {
 -                      if (head.freezetag_frozen)
 +                      if (head.frozen == 1)
                        {
                                distance = vlen(head.origin - org);
                                if (distance > sradius)
@@@ -212,12 -259,12 +212,12 @@@ void havocbot_role_ft_offense(
        unfrozen = 0;
        FOR_EACH_PLAYER(head)
        {
 -              if ((head.team == self.team) && (!head.freezetag_frozen))
 +              if ((head.team == self.team) && (head.frozen != 1))
                        unfrozen++;
        }
  
        // If only one left on team or if role has timed out then start trying to free players.
 -      if (((unfrozen == 0) && (!self.freezetag_frozen)) || (time > self.havocbot_role_timeout))
 +      if (((unfrozen == 0) && (!self.frozen)) || (time > self.havocbot_role_timeout))
        {
                dprint("changing role to freeing\n");
                self.havocbot_role = havocbot_role_ft_freeing;
@@@ -285,7 -332,7 +285,7 @@@ MUTATOR_HOOKFUNCTION(freezetag_PlayerDi
        if(round_handler_IsActive())
        if(round_handler_CountdownRunning())
        {
 -              if(self.freezetag_frozen)
 +              if(self.frozen)
                        freezetag_Unfreeze(world);
                freezetag_count_alive_players();
                return 1; // let the player die so that he can respawn whenever he wants
                || frag_deathtype == DEATH_TEAMCHANGE || frag_deathtype == DEATH_AUTOTEAMCHANGE)
        {
                // let the player die, he will be automatically frozen when he respawns
 -              if(!self.freezetag_frozen)
 +              if(self.frozen != 1)
                {
                        freezetag_Add_Score(frag_attacker);
                        freezetag_count_alive_players();
                }
                else
                        freezetag_Unfreeze(world); // remove ice
 +              self.health = 0; // Unfreeze resets health
                self.freezetag_frozen_timeout = -2; // freeze on respawn
                return 1;
        }
  
 -      if(self.freezetag_frozen)
 +      if(self.frozen)
                return 1;
  
        freezetag_Freeze(frag_attacker);
                Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
        }
  
 -      frag_target.health = 1; // "respawn" the player :P
 -
        return 1;
  }
  
@@@ -360,6 -408,8 +360,6 @@@ MUTATOR_HOOKFUNCTION(freezetag_reset_ma
        FOR_EACH_PLAYER(self)
        {
                self.killcount = 0;
 -              if (self.freezetag_frozen)
 -                      freezetag_Unfreeze(world);
                self.freezetag_frozen_timeout = -1;
                PutClientInServer();
                self.freezetag_frozen_timeout = 0;
@@@ -382,7 -432,7 +382,7 @@@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPr
        if(gameover)
                return 1;
  
 -      if(self.freezetag_frozen)
 +      if(self.frozen == 1)
        {
                // keep health = 1
                self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
  
        entity o;
        o = world;
 -      if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout)
 -              self.freezetag_ice.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (self.freezetag_frozen_timeout - time) / (self.freezetag_frozen_timeout - self.freezetag_frozen_time);
 +      //if(self.frozen)
 +      //if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout)
 +              //self.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (self.freezetag_frozen_timeout - time) / (self.freezetag_frozen_timeout - self.freezetag_frozen_time);
  
        if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout)
                n = -1;
        {
                vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
                n = 0;
 -              FOR_EACH_PLAYER(other) if(self != other)
 +              FOR_EACH_PLAYER(other)
 +              if(self != other)
 +              if(other.frozen == 0)
 +              if(other.deadflag == DEAD_NO)
 +              if(SAME_TEAM(other, self))
 +              if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
                {
 -                      if(other.freezetag_frozen == 0)
 -                      {
 -                              if(other.team == self.team)
 -                              {
 -                                      if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
 -                                      {
 -                                              if(!o)
 -                                                      o = other;
 -                                              if(self.freezetag_frozen)
 -                                                      other.reviving = TRUE;
 -                                              ++n;
 -                                      }
 -                              }
 -                      }
 +                      if(!o)
 +                              o = other;
 +                      if(self.frozen == 1)
 +                              other.reviving = TRUE;
 +                      ++n;
                }
        }
  
 -      if(n && self.freezetag_frozen) // OK, there is at least one teammate reviving us
 +      if(n && self.frozen == 1) // OK, there is at least one teammate reviving us
        {
 -              self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
 -              if(warmup_stage)
 -                      self.health = max(1, self.freezetag_revive_progress * warmup_start_health);
 -              else
 -                      self.health = max(1, self.freezetag_revive_progress * start_health);
 +              self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
 +              self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health));
  
 -              if(self.freezetag_revive_progress >= 1)
 +              if(self.revive_progress >= 1)
                {
                        freezetag_Unfreeze(self);
                        freezetag_count_alive_players();
                                {
                                        PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1);
                                        PlayerScore_Add(other, SP_SCORE, +1);
 +
 +                                      nades_GiveBonus(other,autocvar_g_nades_bonus_score_low);
                                }
                        }
  
                {
                        if(other.reviving)
                        {
 -                              other.freezetag_revive_progress = self.freezetag_revive_progress;
 +                              other.revive_progress = self.revive_progress;
                                other.reviving = FALSE;
                        }
                }
        }
 -      else if(!n && self.freezetag_frozen) // only if no teammate is nearby will we reset
 -      {
 -              self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
 -              if(warmup_stage)
 -                      self.health = max(1, self.freezetag_revive_progress * warmup_start_health);
 -              else
 -                      self.health = max(1, self.freezetag_revive_progress * start_health);
 -      }
 -      else if(!n)
 +      else if(!n && self.frozen == 1) // only if no teammate is nearby will we reset
        {
 -              self.freezetag_revive_progress = 0; // thawing nobody
 +              self.revive_progress = bound(0, self.revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
 +              self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health));
        }
 -
 -      return 1;
 -}
 -
 -MUTATOR_HOOKFUNCTION(freezetag_PlayerPhysics)
 -{
 -      if(self.freezetag_frozen)
 +      else if(!n && !self.frozen)
        {
 -              if(autocvar_sv_dodging_frozen && IS_REAL_CLIENT(self))
 -              {
 -                      self.movement_x = bound(-5, self.movement_x, 5);
 -                      self.movement_y = bound(-5, self.movement_y, 5);
 -                      self.movement_z = bound(-5, self.movement_z, 5);
 -              }
 -              else
 -                      self.movement = '0 0 0';
 -
 -              self.disableclientprediction = 1;
 +              self.revive_progress = 0; // thawing nobody
        }
 -      return 1;
 -}
 -
 -MUTATOR_HOOKFUNCTION(freezetag_PlayerDamage_Calculate)
 -{
 -      if(frag_target.freezetag_frozen && frag_deathtype != DEATH_HURTTRIGGER)
 -      {
 -              if(autocvar_g_freezetag_revive_falldamage > 0)
 -              if(frag_deathtype == DEATH_FALL)
 -              if(frag_damage >= autocvar_g_freezetag_revive_falldamage)
 -              {
 -                      freezetag_Unfreeze(frag_target);
 -                      frag_target.health = autocvar_g_freezetag_revive_falldamage_health;
 -                      pointparticles(particleeffectnum("iceorglass"), frag_target.origin, '0 0 0', 3);
 -                      Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, frag_target.netname);
 -                      Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_FALL);
 -              }
  
 -              frag_damage = 0;
 -              frag_force = frag_force * autocvar_g_freezetag_frozen_force;
 -      }
        return 1;
  }
  
 -MUTATOR_HOOKFUNCTION(freezetag_PlayerJump)
 -{
 -      if(self.freezetag_frozen)
 -              return TRUE; // no jumping in freezetag when frozen
 -
 -      return FALSE;
 -}
 -
 -MUTATOR_HOOKFUNCTION(freezetag_ForbidThrowCurrentWeapon)
 -{
 -      if (self.freezetag_frozen)
 -              return 1;
 -      return 0;
 -}
 -
 -MUTATOR_HOOKFUNCTION(freezetag_ItemTouch)
 -{
 -      if (other.freezetag_frozen)
 -              return MUT_ITEMTOUCH_RETURN;
 -      return MUT_ITEMTOUCH_CONTINUE;
 -}
 -
  MUTATOR_HOOKFUNCTION(freezetag_BotRoles)
  {
        if (!self.deadflag)
        return TRUE;
  }
  
 -MUTATOR_HOOKFUNCTION(freezetag_SpectateCopy)
 -{
 -      self.freezetag_frozen = other.freezetag_frozen;
 -      self.freezetag_revive_progress = other.freezetag_revive_progress;
 -      return 0;
 -}
 -
  MUTATOR_HOOKFUNCTION(freezetag_GetTeamCount)
  {
        ret_float = freezetag_teams;
        return 0;
  }
  
 -MUTATOR_HOOKFUNCTION(freezetag_VehicleTouch)
 -{
 -      if(other.freezetag_frozen)
 -              return TRUE;
 -
 -      return FALSE;
 -}
 -
  void freezetag_Initialize()
  {
 -      precache_model("models/ice/ice.md3");
 -
        freezetag_teams = autocvar_g_freezetag_teams_override;
        if(freezetag_teams < 2)
                freezetag_teams = autocvar_g_freezetag_teams;
        addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
        addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
        addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
 -
 -      addstat(STAT_FROZEN, AS_INT, freezetag_frozen);
 -      addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, freezetag_revive_progress);
  }
  
  MUTATOR_DEFINITION(gamemode_freezetag)
        MUTATOR_HOOK(reset_map_players, freezetag_reset_map_players, CBC_ORDER_ANY);
        MUTATOR_HOOK(GiveFragsForKill, freezetag_GiveFragsForKill, CBC_ORDER_FIRST);
        MUTATOR_HOOK(PlayerPreThink, freezetag_PlayerPreThink, CBC_ORDER_FIRST);
-       MUTATOR_HOOK(HavocBot_ChooseRule, freezetag_BotRoles, CBC_ORDER_ANY);
 -      MUTATOR_HOOK(PlayerPhysics, freezetag_PlayerPhysics, CBC_ORDER_FIRST);
 -      MUTATOR_HOOK(PlayerDamage_Calculate, freezetag_PlayerDamage_Calculate, CBC_ORDER_ANY);
 -      MUTATOR_HOOK(PlayerJump, freezetag_PlayerJump, CBC_ORDER_ANY);
 -      MUTATOR_HOOK(ForbidThrowCurrentWeapon, freezetag_ForbidThrowCurrentWeapon, CBC_ORDER_ANY);
 -      MUTATOR_HOOK(ItemTouch, freezetag_ItemTouch, CBC_ORDER_ANY);
+       MUTATOR_HOOK(HavocBot_ChooseRole, freezetag_BotRoles, CBC_ORDER_ANY);
 -      MUTATOR_HOOK(SpectateCopy, freezetag_SpectateCopy, CBC_ORDER_ANY);
        MUTATOR_HOOK(GetTeamCount, freezetag_GetTeamCount, CBC_ORDER_EXCLUSIVE);
 -      MUTATOR_HOOK(VehicleTouch, freezetag_VehicleTouch, CBC_ORDER_ANY);
  
        MUTATOR_ONADD
        {
index 248712f15f45bc0e368aece371549f4805b2b703,79058b0cf0c806ec98f1d1796bc1c9eb3b83873d..7e1006ec1d92252e624fda40366e08dee47feb99
@@@ -25,28 -25,28 +25,28 @@@ void ka_RespawnBall() // runs whenever 
        if(gameover) { return; }
        vector oldballorigin = self.origin;
  
-       if(MoveToRandomMapLocation(self, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+       if(!MoveToRandomMapLocation(self, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
        {
-               makevectors(self.angles);
-               self.movetype = MOVETYPE_BOUNCE;
-               self.velocity = '0 0 200';
-               self.angles = '0 0 0';
-               self.effects = autocvar_g_keepawayball_effects;
-               self.think = ka_RespawnBall;
-               self.nextthink = time + autocvar_g_keepawayball_respawntime;
+               entity spot = SelectSpawnPoint(TRUE);
+               setorigin(self, spot.origin);
+               self.angles = spot.angles;
+       }
  
-               pointparticles(particleeffectnum("electro_combo"), oldballorigin, '0 0 0', 1);
-               pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 1);
+       makevectors(self.angles);
+       self.movetype = MOVETYPE_BOUNCE;
+       self.velocity = '0 0 200';
+       self.angles = '0 0 0';
+       self.effects = autocvar_g_keepawayball_effects;
+       self.think = ka_RespawnBall;
+       self.nextthink = time + autocvar_g_keepawayball_respawntime;
  
-               WaypointSprite_Spawn("ka-ball", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attachedforcarrier, FALSE, RADARICON_FLAGCARRIER, '0 1 1');
-               WaypointSprite_Ping(self.waypointsprite_attachedforcarrier);
+       pointparticles(particleeffectnum("electro_combo"), oldballorigin, '0 0 0', 1);
+       pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 1);
  
-               sound(self, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-       }
-       else
-       {
-               ka_RespawnBall(); // finding a location failed, retry
-       }
+       WaypointSprite_Spawn("ka-ball", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attachedforcarrier, FALSE, RADARICON_FLAGCARRIER, '0 1 1');
+       WaypointSprite_Ping(self.waypointsprite_attachedforcarrier);
+       sound(self, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
  }
  
  void ka_TimeScoring()
@@@ -71,7 -71,6 +71,7 @@@ void ka_TouchEvent() // runs any time t
                return;
        }
        if(other.deadflag != DEAD_NO) { return; }
 +      if(other.frozen) { return; }
        if (!IS_PLAYER(other))
        {  // The ball just touched an object, most likely the world
                pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
        // messages and sounds
        ka_EventLog("pickup", other);
        Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_KEEPAWAY_PICKUP, other.netname);
-       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, other.netname);
+       Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, other.netname);
+       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF);
        sound(self.owner, CH_TRIGGER, "keepaway/pickedup.wav", VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
  
        // scoring
@@@ -416,7 -416,7 +417,7 @@@ MUTATOR_DEFINITION(gamemode_keepaway
        MUTATOR_HOOK(PlayerDamage_Calculate, ka_PlayerDamage, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerPowerups, ka_PlayerPowerups, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerUseKey, ka_PlayerUseKey, CBC_ORDER_ANY);
-       MUTATOR_HOOK(HavocBot_ChooseRule, ka_BotRoles, CBC_ORDER_ANY);
+       MUTATOR_HOOK(HavocBot_ChooseRole, ka_BotRoles, CBC_ORDER_ANY);
  
        MUTATOR_ONADD
        {
index 0000000000000000000000000000000000000000,c71fe60ea9bf3076107fa829438df4957af09db6..5aa534c33367726ce895f99f114193087ac207da
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,787 +1,787 @@@
 -      || (other.freezetag_frozen)
+ float buffs_BuffModel_Customize()
+ {
+       entity player, myowner;
+       float same_team;
+       player = WaypointSprite_getviewentity(other);
+       myowner = self.owner;
+       same_team = (SAME_TEAM(player, myowner) || SAME_TEAM(player, myowner));
+       if(myowner.alpha <= 0.5 && !same_team && myowner.alpha != 0)
+               return FALSE;
+       if(player == myowner || (IS_SPEC(other) && other.enemy == myowner))
+       {
+               // somewhat hide the model, but keep the glow
+               self.effects = 0;
+               self.alpha = -1;
+       }
+       else
+       {
+               self.effects = EF_FULLBRIGHT | EF_LOWPRECISION;
+               self.alpha = 1;
+       }
+       return TRUE;
+ }
+ // buff item
+ float buff_Waypoint_visible_for_player(entity plr)
+ {
+     if(!self.owner.buff_active && !self.owner.buff_activetime)
+         return FALSE;
+       if(plr.buffs)
+       {
+               if(plr.cvar_cl_buffs_autoreplace)
+               {
+                       if(plr.buffs == self.owner.buffs)
+                               return FALSE;
+               }
+               else
+                       return FALSE;
+       }
+     return WaypointSprite_visible_for_player(plr);
+ }
+ void buff_Waypoint_Spawn(entity e)
+ {
+     WaypointSprite_Spawn(Buff_Sprite(e.buffs), 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs_z, world, e.team, e, buff_waypoint, TRUE, RADARICON_POWERUP, e.glowmod);
+     WaypointSprite_UpdateTeamRadar(e.buff_waypoint, RADARICON_POWERUP, e.glowmod);
+     e.buff_waypoint.waypointsprite_visible_for_player = buff_Waypoint_visible_for_player;
+ }
+ void buff_SetCooldown(float cd)
+ {
+       cd = max(0, cd);
+       if(!self.buff_waypoint)
+               buff_Waypoint_Spawn(self);
+       WaypointSprite_UpdateBuildFinished(self.buff_waypoint, time + cd);
+       self.buff_activetime = cd;
+       self.buff_active = !cd;
+ }
+ void buff_Respawn(entity ent)
+ {
+       if(gameover) { return; }
+       
+       vector oldbufforigin = ent.origin;
+       
+       if(!MoveToRandomMapLocation(ent, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, ((autocvar_g_buffs_random_location_attempts > 0) ? autocvar_g_buffs_random_location_attempts : 10), 1024, 256))
+       {
+               entity spot = SelectSpawnPoint(TRUE);
+               setorigin(ent, ((spot.origin + '0 0 200') + (randomvec() * 300)));
+               ent.angles = spot.angles;
+       }
+       
+       tracebox(ent.origin, ent.mins * 1.5, self.maxs * 1.5, ent.origin, MOVE_NOMONSTERS, ent);
+       
+       setorigin(ent, trace_endpos); // attempt to unstick
+       
+       ent.movetype = MOVETYPE_TOSS;
+       
+       makevectors(ent.angles);
+       ent.velocity = '0 0 200';
+       ent.angles = '0 0 0';
+       if(autocvar_g_buffs_random_lifetime > 0)
+               ent.lifetime = time + autocvar_g_buffs_random_lifetime;
+       pointparticles(particleeffectnum("electro_combo"), oldbufforigin + ((ent.mins + ent.maxs) * 0.5), '0 0 0', 1);
+       pointparticles(particleeffectnum("electro_combo"), CENTER_OR_VIEWOFS(ent), '0 0 0', 1);
+       
+       WaypointSprite_Ping(ent.buff_waypoint);
+       
+       sound(ent, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+ }
+ void buff_Touch()
+ {
+       if(gameover) { return; }
+       if(ITEM_TOUCH_NEEDKILL())
+       {
+               buff_Respawn(self);
+               return;
+       }
+       if((self.team && DIFF_TEAM(other, self))
 -      if(!self.owner || self.owner.freezetag_frozen || self.owner.deadflag != DEAD_NO || !self.owner.iscreature || !(self.owner.buffs & self.buffs))
++      || (other.frozen)
+       || (other.vehicle)
+       || (!IS_PLAYER(other))
+       || (!self.buff_active)
+       )
+       {
+               // can't touch this
+               return;
+       }
+       if(other.buffs)
+       {
+               if(other.cvar_cl_buffs_autoreplace && other.buffs != self.buffs)
+               {
+                       //Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_DROP, other.buffs);
+                       Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ITEM_BUFF_LOST, other.netname, other.buffs);
+                       other.buffs = 0;
+                       //sound(other, CH_TRIGGER, "relics/relic_effect.wav", VOL_BASE, ATTN_NORM);
+               }
+               else { return; } // do nothing
+       }
+               
+       self.owner = other;
+       self.buff_active = FALSE;
+       self.lifetime = 0;
+       
+       Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_GOT, self.buffs);
+       Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_INFO, INFO_ITEM_BUFF, other.netname, self.buffs);
+       pointparticles(particleeffectnum("item_pickup"), CENTER_OR_VIEWOFS(self), '0 0 0', 1);
+       sound(other, CH_TRIGGER, "misc/shield_respawn.wav", VOL_BASE, ATTN_NORM);
+       other.buffs |= (self.buffs);
+ }
+ float buff_Available(float buffid)
+ {
+       if(buffid == BUFF_AMMO && ((start_items & IT_UNLIMITED_WEAPON_AMMO) || (start_items & IT_UNLIMITED_AMMO) || (cvar("g_melee_only"))))
+               return FALSE;
+       if(buffid == BUFF_VAMPIRE && cvar("g_vampire"))
+               return FALSE;
+       if(!cvar(strcat("g_buffs_", Buff_Name(buffid))))
+               return FALSE;
+       return TRUE;
+ }
+ void buff_NewType(entity ent, float cb)
+ {
+       entity e;
+       RandomSelection_Init();
+       for(e = Buff_Type_first; e; e = e.enemy)
+       if(buff_Available(e.items))
+       {
+               RandomSelection_Add(world, e.items, string_null, 1, 1 / e.count); // if it's already been chosen, give it a lower priority
+               e.count += 1;
+       }
+       ent.buffs = RandomSelection_chosen_float;
+ }
+ void buff_Think()
+ {
+       if(self.buffs != self.oldbuffs)
+       {
+               self.color = Buff_Color(self.buffs);
+               self.glowmod = ((self.team) ? Team_ColorRGB(self.team) + '0.1 0.1 0.1' : self.color);
+               self.skin = Buff_Skin(self.buffs);
+               
+               setmodel(self, "models/relics/relic.md3");
+               if(self.buff_waypoint)
+               {
+                       //WaypointSprite_Disown(self.buff_waypoint, 1);
+                       WaypointSprite_Kill(self.buff_waypoint);
+                       buff_Waypoint_Spawn(self);
+                       if(self.buff_activetime)
+                               WaypointSprite_UpdateBuildFinished(self.buff_waypoint, time + self.buff_activetime - frametime);
+               }
+               self.oldbuffs = self.buffs;
+       }
+       if(!gameover)
+       if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime)
+       if(!self.buff_activetime_updated)
+       {
+               buff_SetCooldown(self.buff_activetime);
+               self.buff_activetime_updated = TRUE;
+     }
+       if(!self.buff_active && !self.buff_activetime)
 -      if(!frag_target.freezetag_frozen)
++      if(!self.owner || self.owner.frozen || self.owner.deadflag != DEAD_NO || !self.owner.iscreature || !(self.owner.buffs & self.buffs))
+       {
+               buff_SetCooldown(autocvar_g_buffs_cooldown_respawn + frametime);
+               self.owner = world;
+               if(autocvar_g_buffs_randomize)
+                       buff_NewType(self, self.buffs);
+                       
+               if(autocvar_g_buffs_random_location || (self.spawnflags & 1))
+                       buff_Respawn(self);
+       }
+       
+       if(self.buff_activetime)
+       if(!gameover)
+       if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime)
+       {
+               self.buff_activetime = max(0, self.buff_activetime - frametime);
+               if(!self.buff_activetime)
+               {
+                       self.buff_active = TRUE;
+                       sound(self, CH_TRIGGER, "misc/strength_respawn.wav", VOL_BASE, ATTN_NORM);
+                       pointparticles(particleeffectnum("item_respawn"), CENTER_OR_VIEWOFS(self), '0 0 0', 1);
+               }
+       }
+       if(!self.buff_active)
+       {
+               self.alpha = 0.3;
+               self.effects &= ~(EF_FULLBRIGHT);
+               self.pflags = 0;
+       }
+       else
+       {
+               self.alpha = 1;
+               self.effects |= EF_FULLBRIGHT;
+               self.light_lev = 220 + 36 * sin(time);
+               self.pflags = PFLAGS_FULLDYNAMIC;
+               if(self.team && !self.buff_waypoint)
+                       buff_Waypoint_Spawn(self);
+                       
+               if(self.lifetime)
+               if(time >= self.lifetime)
+                       buff_Respawn(self);
+       }
+     
+       self.nextthink = time;
+       //self.angles_y = time * 110.1;
+ }
+ void buff_Waypoint_Reset()
+ {
+       WaypointSprite_Kill(self.buff_waypoint);
+       if(self.buff_activetime) { buff_Waypoint_Spawn(self); }
+ }
+ void buff_Reset()
+ {
+       if(autocvar_g_buffs_randomize)
+               buff_NewType(self, self.buffs);
+       self.owner = world;
+       buff_SetCooldown(autocvar_g_buffs_cooldown_activate);
+       buff_Waypoint_Reset();
+       self.buff_activetime_updated = FALSE;
+       
+       if(autocvar_g_buffs_random_location || (self.spawnflags & 1))
+               buff_Respawn(self);
+ }
+ void buff_Init(entity ent)
+ {
+       if(!cvar("g_buffs")) { remove(self); return; }
+       
+       if(!teamplay && self.team) { self.team = 0; }
+       entity oldself = self;
+       self = ent;
+       if(!self.buffs || buff_Available(self.buffs))
+               buff_NewType(self, 0);
+       
+       self.classname = "item_buff";
+       self.solid = SOLID_TRIGGER;
+       self.flags = FL_ITEM;
+       self.think = buff_Think;
+       self.touch = buff_Touch;
+       self.reset = buff_Reset;
+       self.nextthink = time + 0.1;
+       self.gravity = 1;
+       self.movetype = MOVETYPE_TOSS;
+       self.scale = 1;
+       self.skin = Buff_Skin(self.buffs);
+       self.effects = EF_FULLBRIGHT | EF_STARDUST | EF_NOSHADOW;
+       self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY;
+       //self.gravity = 100;
+       self.color = Buff_Color(self.buffs);
+       self.glowmod = ((self.team) ? Team_ColorRGB(self.team) + '0.1 0.1 0.1' : self.color);
+       buff_SetCooldown(autocvar_g_buffs_cooldown_activate + game_starttime);
+       self.buff_active = !self.buff_activetime;
+       self.pflags = PFLAGS_FULLDYNAMIC;
+       
+       if(self.noalign)
+               self.movetype = MOVETYPE_NONE; // reset by random location
+       setmodel(self, "models/relics/relic.md3");
+       setsize(self, BUFF_MIN, BUFF_MAX);
+       
+       if(cvar("g_buffs_random_location") || (self.spawnflags & 1))
+               buff_Respawn(self);
+       
+       self = oldself;
+ }
+ void buff_Init_Compat(entity ent, float replacement)
+ {
+       if(ent.spawnflags & 2)
+               ent.team = NUM_TEAM_1;
+       else if(ent.spawnflags & 4)
+               ent.team = NUM_TEAM_2;
+       ent.buffs = replacement;
+       buff_Init(ent);
+ }
+ void buff_SpawnReplacement(entity ent, entity old)
+ {
+       setorigin(ent, old.origin);
+       ent.angles = old.angles;
+       ent.noalign = old.noalign;
+       
+       buff_Init(ent);
+ }
+ // mutator hooks
+ MUTATOR_HOOKFUNCTION(buffs_PlayerDamage_SplitHealthArmor)
+ {
+       if(frag_deathtype == DEATH_BUFF_VENGEANCE) { return FALSE; } // oh no you don't
+       if(frag_target.buffs & BUFF_RESISTANCE)
+       {
+               vector v = healtharmor_applydamage(50, autocvar_g_buffs_resistance_blockpercent, frag_deathtype, frag_damage);
+               damage_take = v_x;
+               damage_save = v_y;
+       }
+       return FALSE;
+ }
+ void buff_Vengeance_DelayedDamage()
+ {
+       if(self.enemy)
+               Damage(self.enemy, self.owner, self.owner, self.dmg, DEATH_BUFF_VENGEANCE, self.enemy.origin, '0 0 0');
+       
+       remove(self);
+       return;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_PlayerDamage_Calculate)
+ {
+       if(frag_deathtype == DEATH_BUFF_VENGEANCE) { return FALSE; } // oh no you don't
+       if(frag_target.buffs & BUFF_SPEED)
+       if(frag_target != frag_attacker)
+               frag_damage *= autocvar_g_buffs_speed_damage_take;
+       if(frag_target.buffs & BUFF_MEDIC)
+       if((frag_target.health - frag_damage) <= 0)
+       if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
+       if(frag_attacker)
+       if(random() <= autocvar_g_buffs_medic_survive_chance)
+       if(frag_target.health - autocvar_g_buffs_medic_survive_health > 0) // not if the final result would be less than 0, medic must get health
+               frag_damage = frag_target.health - autocvar_g_buffs_medic_survive_health;
+               
+       if(frag_target.buffs & BUFF_VENGEANCE)
+       if(frag_attacker)
+       if(frag_attacker != frag_target)
+       if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
+       {
+               entity dmgent = spawn();
+               dmgent.dmg = frag_damage * autocvar_g_buffs_vengeance_damage_multiplier;
+               dmgent.enemy = frag_attacker;
+               dmgent.owner = frag_target;
+               dmgent.think = buff_Vengeance_DelayedDamage;
+               dmgent.nextthink = time + 0.1;
+       }
+       if(frag_target.buffs & BUFF_BASH)
+       if(frag_attacker != frag_target)
+       if(vlen(frag_force))
+               frag_force = '0 0 0';
+       
+       if(frag_attacker.buffs & BUFF_BASH)
+       if(vlen(frag_force))
+       if(frag_attacker == frag_target)
+               frag_force *= autocvar_g_buffs_bash_force_self;
+       else
+               frag_force *= autocvar_g_buffs_bash_force;
+       
+       if(frag_attacker.buffs & BUFF_DISABILITY)
+       if(frag_target != frag_attacker)
+               frag_target.buff_disability_time = time + autocvar_g_buffs_disability_time;
+       if(frag_attacker.buffs & BUFF_MEDIC)
+       if(SAME_TEAM(frag_attacker, frag_target))
+       if(frag_attacker != frag_target)
+       {
+               frag_target.health = min(g_pickup_healthmega_max, frag_target.health + frag_damage);
+               frag_damage = 0;
+       }
+       // this... is ridiculous (TODO: fix!)
+       if(frag_attacker.buffs & BUFF_VAMPIRE)
+       if(!frag_target.vehicle)
+       if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
+       if(frag_target.deadflag == DEAD_NO)
+       if(IS_PLAYER(frag_target) || (frag_target.flags & FL_MONSTER))
+       if(frag_attacker != frag_target)
 -      if(self.freezetag_frozen)
++      if(!frag_target.frozen)
+       if(frag_target.takedamage)
+       if(DIFF_TEAM(frag_attacker, frag_target))
+               frag_attacker.health = bound(0, frag_attacker.health + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.health), g_pickup_healthsmall_max);
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_PlayerSpawn)
+ {
+       self.buffs = 0;
+       // reset timers here to prevent them continuing after re-spawn
+       self.buff_disability_time = 0;
+       self.buff_disability_effect_time = 0;
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_PlayerPhysics)
+ {
+       if(self.buffs & BUFF_SPEED)
+       {
+               self.stat_sv_maxspeed *= autocvar_g_buffs_speed_speed;
+               self.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_speed_speed;
+       }
+       
+       if(time < self.buff_disability_time)
+       {
+               self.stat_sv_maxspeed *= autocvar_g_buffs_disability_speed;
+               self.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_disability_speed;
+       }
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_PlayerJump)
+ {
+       if(self.buffs & BUFF_JUMP)
+               player_jumpheight = autocvar_g_buffs_jump_height;
+       self.stat_jumpheight = player_jumpheight;
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_MonsterMove)
+ {
+       if(time < self.buff_disability_time)
+       {
+               monster_speed_walk *= autocvar_g_buffs_disability_speed;
+               monster_speed_run *= autocvar_g_buffs_disability_speed;
+       }
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_PlayerDies)
+ {
+       if(self.buffs)
+       {
+               Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, self.buffs);
+               self.buffs = 0;
+               
+               if(self.buff_model)
+               {
+                       remove(self.buff_model);
+                       self.buff_model = world;
+               }
+       }
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_PlayerUseKey)
+ {
+       if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
+       if(self.buffs)
+       {
+               Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_BUFF_DROP, self.buffs);
+               Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, self.buffs);
+               self.buffs = 0;
+               sound(self, CH_TRIGGER, "relics/relic_effect.wav", VOL_BASE, ATTN_NORM);
+               return TRUE;
+       }
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_RemovePlayer)
+ {
+       if(self.buff_model)
+       {
+               remove(self.buff_model);
+               self.buff_model = world;
+       }
+       
+       // also reset timers here to prevent them continuing after spectating
+       self.buff_disability_time = 0;
+       self.buff_disability_effect_time = 0;
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_CustomizeWaypoint)
+ {
+       entity e = WaypointSprite_getviewentity(other);
+       // if you have the invisibility powerup, sprites ALWAYS are restricted to your team
+       // but only apply this to real players, not to spectators
+       if((self.owner.flags & FL_CLIENT) && (self.owner.buffs & BUFF_INVISIBLE) && (e == other))
+       if(DIFF_TEAM(self.owner, e))
+               return TRUE;
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_OnEntityPreSpawn)
+ {
+       if(autocvar_g_buffs_replace_powerups)
+       switch(self.classname)
+       {
+               case "item_strength":
+               case "item_invincible":
+               {
+                       entity e = spawn();
+                       buff_SpawnReplacement(e, self);
+                       return TRUE;
+               }
+       }
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_WeaponRate)
+ {
+       if(self.buffs & BUFF_SPEED)
+               weapon_rate *= autocvar_g_buffs_speed_rate;
+               
+       if(time < self.buff_disability_time)
+               weapon_rate *= autocvar_g_buffs_disability_rate;
+       
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_PlayerThink)
+ {
+       if(gameover || self.deadflag != DEAD_NO) { return FALSE; }
+       
+       if(time < self.buff_disability_time)
+       if(time >= self.buff_disability_effect_time)
+       {
+               pointparticles(particleeffectnum("smoking"), self.origin + ((self.mins + self.maxs) * 0.5), '0 0 0', 1);
+               self.buff_disability_effect_time = time + 0.5;
+       }
+       
++      if(self.frozen)
+       {
+               if(self.buffs)
+               {
+                       Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, self.buffs);
+                       self.buffs = 0;
+               }
+       }
+               
+       if((self.buffs & BUFF_INVISIBLE) && (self.oldbuffs & BUFF_INVISIBLE))
+       if(self.alpha != autocvar_g_buffs_invisible_alpha)
+               self.alpha = autocvar_g_buffs_invisible_alpha;
+       if(self.buffs != self.oldbuffs)
+       {
+               if(self.oldbuffs & BUFF_AMMO)
+               {
+                       if(self.buff_ammo_prev_infitems)
+                               self.items |= IT_UNLIMITED_WEAPON_AMMO;
+                       else
+                               self.items &= ~IT_UNLIMITED_WEAPON_AMMO;
+               }
+               else if(self.buffs & BUFF_AMMO)
+               {
+                       self.buff_ammo_prev_infitems = (self.items & IT_UNLIMITED_WEAPON_AMMO);
+                       self.items |= IT_UNLIMITED_WEAPON_AMMO;
+                       if(!self.ammo_shells) { self.ammo_shells = 20; }
+                       if(!self.ammo_cells) { self.ammo_cells = 20; }
+                       if(!self.ammo_rockets) { self.ammo_rockets = 20; }
+                       if(!self.ammo_nails) { self.ammo_nails = 20; }
+                       if(!self.ammo_fuel) { self.ammo_fuel = 20; }
+               }
+               
+               if(self.oldbuffs & BUFF_INVISIBLE)
+               {
+                       if(time < self.strength_finished && g_minstagib)
+                               self.alpha = autocvar_g_minstagib_invis_alpha;
+                       else
+                               self.alpha = self.buff_invisible_prev_alpha;
+               }
+               else if(self.buffs & BUFF_INVISIBLE)
+               {
+                       if(time < self.strength_finished && g_minstagib)
+                               self.buff_invisible_prev_alpha = default_player_alpha;
+                       else
+                               self.buff_invisible_prev_alpha = self.alpha;
+                       self.alpha = autocvar_g_buffs_invisible_alpha;
+               }
+               
+               if(self.oldbuffs & BUFF_FLIGHT)
+                       self.gravity = self.buff_flight_prev_gravity;
+               else if(self.buffs & BUFF_FLIGHT)
+               {
+                       self.buff_flight_prev_gravity = self.gravity;
+                       self.gravity = autocvar_g_buffs_flight_gravity;
+               }
+               self.oldbuffs = self.buffs;
+               if(self.buffs)
+               {
+                       if(!self.buff_model)
+                       {
+                               self.buff_model = spawn();
+                               setmodel(self.buff_model, "models/relics/relic.md3");
+                               setsize(self.buff_model, '0 0 -40', '0 0 40');
+                               setattachment(self.buff_model, self, "");
+                               setorigin(self.buff_model, '0 0 1' * (self.buff_model.maxs_z * 1));
+                               self.buff_model.owner = self;
+                               self.buff_model.scale = 0.7;
+                               self.buff_model.pflags = PFLAGS_FULLDYNAMIC;
+                               self.buff_model.light_lev = 200;
+                               self.buff_model.customizeentityforclient = buffs_BuffModel_Customize;
+                       }
+                       self.buff_model.color = Buff_Color(self.buffs);
+                       self.buff_model.glowmod = ((self.buff_model.team) ? Team_ColorRGB(self.buff_model.team) + '0.1 0.1 0.1' : self.buff_model.color);
+                       self.buff_model.skin = Buff_Skin(self.buffs);
+                       
+                       self.effects |= EF_NOSHADOW;
+               }
+               else
+               {
+                       remove(self.buff_model);
+                       self.buff_model = world;
+                       
+                       self.effects &= ~(EF_NOSHADOW);
+               }
+       }
+       if(self.buff_model)
+       {
+               self.buff_model.effects = self.effects;
+               self.buff_model.effects |= EF_LOWPRECISION;
+               self.buff_model.effects = self.buff_model.effects & EFMASK_CHEAP; // eat performance
+               
+               self.buff_model.alpha = self.alpha;
+       }
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_SpectateCopy)
+ {
+       self.buffs = other.buffs;
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_VehicleEnter)
+ {
+       vh_vehicle.buffs = vh_player.buffs;
+       vh_player.buffs = 0;
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_VehicleExit)
+ {
+       vh_player.buffs = vh_vehicle.buffs;
+       vh_vehicle.buffs = 0;
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_PlayerRegen)
+ {
+       if(self.buffs & BUFF_MEDIC)
+       {
+               regen_mod_rot = autocvar_g_buffs_medic_rot;
+               regen_mod_limit = regen_mod_max = autocvar_g_buffs_medic_max;
+               regen_mod_regen = autocvar_g_buffs_medic_regen;
+       }
+       
+       if(self.buffs & BUFF_SPEED)
+               regen_mod_regen = autocvar_g_buffs_speed_regen;
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_GetCvars)
+ {
+       GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_buffs_autoreplace, "cl_buffs_autoreplace");
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_BuildMutatorsString)
+ {
+       ret_string = strcat(ret_string, ":Buffs");
+       return FALSE;
+ }
+ MUTATOR_HOOKFUNCTION(buffs_BuildMutatorsPrettyString)
+ {
+       ret_string = strcat(ret_string, ", Buffs");
+       return FALSE;
+ }
+ void buffs_DelayedInit()
+ {
+       if(autocvar_g_buffs_spawn_count > 0)
+       if(find(world, classname, "item_buff") == world)
+       {
+               float i;
+               for(i = 0; i < autocvar_g_buffs_spawn_count; ++i)
+               {
+                       entity e = spawn();
+                       e.spawnflags |= 1; // always randomize
+                       e.velocity = randomvec() * 250; // this gets reset anyway if random location works
+                       buff_Init(e);
+               }
+       }
+ }
+ void buffs_Initialize()
+ {
+       precache_model("models/relics/relic.md3");
+       precache_sound("misc/strength_respawn.wav");
+       precache_sound("misc/shield_respawn.wav");
+       precache_sound("relics/relic_effect.wav");
+       precache_sound("weapons/rocket_impact.wav");
+       precache_sound("keepaway/respawn.wav");
+       addstat(STAT_BUFFS, AS_INT, buffs);
+       addstat(STAT_MOVEVARS_JUMPVELOCITY, AS_FLOAT, stat_jumpheight);
+       
+       InitializeEntity(world, buffs_DelayedInit, INITPRIO_FINDTARGET);
+ }
+ MUTATOR_DEFINITION(mutator_buffs)
+ {
+       MUTATOR_HOOK(PlayerDamage_SplitHealthArmor, buffs_PlayerDamage_SplitHealthArmor, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerDamage_Calculate, buffs_PlayerDamage_Calculate, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerSpawn, buffs_PlayerSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerPhysics, buffs_PlayerPhysics, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerJump, buffs_PlayerJump, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MonsterMove, buffs_MonsterMove, CBC_ORDER_ANY);
+       MUTATOR_HOOK(SpectateCopy, buffs_SpectateCopy, CBC_ORDER_ANY);
+       MUTATOR_HOOK(VehicleEnter, buffs_VehicleEnter, CBC_ORDER_ANY);
+       MUTATOR_HOOK(VehicleExit, buffs_VehicleExit, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerRegen, buffs_PlayerRegen, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerDies, buffs_PlayerDies, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerUseKey, buffs_PlayerUseKey, CBC_ORDER_ANY);
+       MUTATOR_HOOK(MakePlayerObserver, buffs_RemovePlayer, CBC_ORDER_ANY);
+       MUTATOR_HOOK(ClientDisconnect, buffs_RemovePlayer, CBC_ORDER_ANY);
+       MUTATOR_HOOK(OnEntityPreSpawn, buffs_OnEntityPreSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(CustomizeWaypoint, buffs_CustomizeWaypoint, CBC_ORDER_ANY);
+       MUTATOR_HOOK(WeaponRateFactor, buffs_WeaponRate, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerPreThink, buffs_PlayerThink, CBC_ORDER_ANY);
+       MUTATOR_HOOK(GetCvars, buffs_GetCvars, CBC_ORDER_ANY);
+       MUTATOR_HOOK(BuildMutatorsString, buffs_BuildMutatorsString, CBC_ORDER_ANY);
+       MUTATOR_HOOK(BuildMutatorsPrettyString, buffs_BuildMutatorsPrettyString, CBC_ORDER_ANY);
+       MUTATOR_ONADD
+       {
+               buffs_Initialize();
+       }
+       return FALSE;
+ }
index 7b65430d9a2087476fc38f9131bb83fd26b74102,2fad6022673c855e4fae529dfe8dc6158bba0954..13344bd2052588117c8b8553eed52242fedd744b
@@@ -1,20 -1,31 +1,20 @@@
 +.entity nade_spawnloc;
 +
  void nade_timer_think()
  {
        self.skin = 8 - (self.owner.wait - time) / (autocvar_g_nades_nade_lifetime / 10);
        self.nextthink = time;
        if(!self.owner || wasfreed(self.owner))
                remove(self);
 -
  }
  
  void nade_burn_spawn(entity _nade)
  {
 -      float p;
 -
 -      switch(_nade.realowner.team)
 -      {
 -              case NUM_TEAM_1: p = PROJECTILE_NADE_RED_BURN; break;
 -              case NUM_TEAM_2: p = PROJECTILE_NADE_BLUE_BURN; break;
 -              case NUM_TEAM_3: p = PROJECTILE_NADE_YELLOW_BURN; break;
 -              case NUM_TEAM_4: p = PROJECTILE_NADE_PINK_BURN; break;
 -              default:                 p = PROJECTILE_NADE_BURN; break;
 -      }
 -
 -      CSQCProjectile(_nade, TRUE, p, TRUE);
 +      CSQCProjectile(_nade, TRUE, Nade_ProjectileFromID(_nade.nade_type, TRUE), TRUE);
  }
  
  void nade_spawn(entity _nade)
  {
 -      float p;
        entity timer = spawn();
        setmodel(timer, "models/ok_nade_counter/ok_nade_counter.md3");
        setattachment(timer, _nade, "");
        timer.owner = _nade;
        timer.skin = 10;
  
 -      switch(_nade.realowner.team)
 +      _nade.effects |= EF_LOWPRECISION;
 +
 +      CSQCProjectile(_nade, TRUE, Nade_ProjectileFromID(_nade.nade_type, FALSE), TRUE);
 +}
 +
 +void napalm_damage(float dist, float damage, float edgedamage, float burntime)
 +{
 +      entity e;
 +      float d;
 +      vector p;
 +
 +      if ( damage < 0 )
 +              return;
 +
 +      RandomSelection_Init();
 +      for(e = WarpZone_FindRadius(self.origin, dist, TRUE); e; e = e.chain)
 +              if(e.takedamage == DAMAGE_AIM)
 +              if(self.realowner != e || autocvar_g_nades_napalm_selfdamage)
 +              if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self))
 +              if(!e.frozen)
 +              {
 +                      p = e.origin;
 +                      p_x += e.mins_x + random() * (e.maxs_x - e.mins_x);
 +                      p_y += e.mins_y + random() * (e.maxs_y - e.mins_y);
 +                      p_z += e.mins_z + random() * (e.maxs_z - e.mins_z);
 +                      d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p);
 +                      if(d < dist)
 +                      {
 +                              e.fireball_impactvec = p;
 +                              RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e));
 +                      }
 +              }
 +      if(RandomSelection_chosen_ent)
        {
 -              case NUM_TEAM_1: p = PROJECTILE_NADE_RED; break;
 -              case NUM_TEAM_2: p = PROJECTILE_NADE_BLUE; break;
 -              case NUM_TEAM_3: p = PROJECTILE_NADE_YELLOW; break;
 -              case NUM_TEAM_4: p = PROJECTILE_NADE_PINK; break;
 -              default:                 p = PROJECTILE_NADE; break;
 +              d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec);
 +              d = damage + (edgedamage - damage) * (d / dist);
 +              Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE);
 +              //trailparticles(self, particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec);
 +              pointparticles(particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1);
        }
 +}
  
 -      CSQCProjectile(_nade, TRUE, p, TRUE);
  
-       entity e = spawnmonster(self.pokenade_type, 0, self.realowner, self.realowner, self.origin, FALSE, 1);
 +void napalm_ball_think()
 +{
 +      if(round_handler_IsActive())
 +      if(!round_handler_IsRoundStarted())
 +      {
 +              remove(self);
 +              return;
 +      }
 +
 +      if(time > self.pushltime)
 +      {
 +              remove(self);
 +              return;
 +      }
 +
 +      vector midpoint = ((self.absmin + self.absmax) * 0.5);
 +      if(pointcontents(midpoint) == CONTENT_WATER)
 +      {
 +              self.velocity = self.velocity * 0.5;
 +
 +              if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
 +                      { self.velocity_z = 200; }
 +      }
 +
 +      self.angles = vectoangles(self.velocity);
 +
 +      napalm_damage(autocvar_g_nades_napalm_ball_radius,autocvar_g_nades_napalm_ball_damage,
 +                                autocvar_g_nades_napalm_ball_damage,autocvar_g_nades_napalm_burntime);
 +
 +      self.nextthink = time + 0.1;
 +}
 +
 +
 +void nade_napalm_ball()
 +{
 +      entity proj;
 +      vector kick;
 +
 +      spamsound(self, CH_SHOTS, "weapons/fireball_fire.wav", VOL_BASE, ATTEN_NORM);
 +
 +      proj = spawn ();
 +      proj.owner = self.owner;
 +      proj.realowner = self.realowner;
 +      proj.team = self.owner.team;
 +      proj.classname = "grenade";
 +      proj.bot_dodge = TRUE;
 +      proj.bot_dodgerating = autocvar_g_nades_napalm_ball_damage;
 +      proj.movetype = MOVETYPE_BOUNCE;
 +      proj.projectiledeathtype = DEATH_NADE_NAPALM;
 +      PROJECTILE_MAKETRIGGER(proj);
 +      setmodel(proj, "null");
 +      proj.scale = 1;//0.5;
 +      setsize(proj, '-4 -4 -4', '4 4 4');
 +      setorigin(proj, self.origin);
 +      proj.think = napalm_ball_think;
 +      proj.nextthink = time;
 +      proj.damageforcescale = autocvar_g_nades_napalm_ball_damageforcescale;
 +      proj.effects = EF_LOWPRECISION | EF_FLAME;
 +
 +      kick_x =(random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread;
 +      kick_y = (random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread;
 +      kick_z = (random()/2+0.5) * autocvar_g_nades_napalm_ball_spread;
 +      proj.velocity = kick;
 +
 +      proj.pushltime = time + autocvar_g_nades_napalm_ball_lifetime;
 +
 +      proj.angles = vectoangles(proj.velocity);
 +      proj.flags = FL_PROJECTILE;
 +      proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC;
 +
 +      //CSQCProjectile(proj, TRUE, PROJECTILE_NAPALM_FIRE, TRUE);
 +}
 +
 +
 +void napalm_fountain_think()
 +{
 +
 +      if(round_handler_IsActive())
 +      if(!round_handler_IsRoundStarted())
 +      {
 +              remove(self);
 +              return;
 +      }
 +
 +      if(time >= self.ltime)
 +      {
 +              remove(self);
 +              return;
 +      }
 +
 +      vector midpoint = ((self.absmin + self.absmax) * 0.5);
 +      if(pointcontents(midpoint) == CONTENT_WATER)
 +      {
 +              self.velocity = self.velocity * 0.5;
 +
 +              if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
 +                      { self.velocity_z = 200; }
 +
 +              UpdateCSQCProjectile(self);
 +      }
 +
 +      napalm_damage(autocvar_g_nades_napalm_fountain_radius, autocvar_g_nades_napalm_fountain_damage,
 +              autocvar_g_nades_napalm_fountain_edgedamage, autocvar_g_nades_napalm_burntime);
 +
 +      self.nextthink = time + 0.1;
 +      if(time >= self.nade_special_time)
 +      {
 +              self.nade_special_time = time + autocvar_g_nades_napalm_fountain_delay;
 +              nade_napalm_ball();
 +      }
 +}
 +
 +void nade_napalm_boom()
 +{
 +      entity fountain;
 +      local float c;
 +      for (c = 0; c < autocvar_g_nades_napalm_ball_count; c ++)
 +              nade_napalm_ball();
 +
 +
 +      fountain = spawn();
 +      fountain.owner = self.owner;
 +      fountain.realowner = self.realowner;
 +      fountain.origin = self.origin;
 +      setorigin(fountain, fountain.origin);
 +      fountain.think = napalm_fountain_think;
 +      fountain.nextthink = time;
 +      fountain.ltime = time + autocvar_g_nades_napalm_fountain_lifetime;
 +      fountain.pushltime = fountain.ltime;
 +      fountain.team = self.team;
 +      fountain.movetype = MOVETYPE_TOSS;
 +      fountain.projectiledeathtype = DEATH_NADE_NAPALM;
 +      fountain.bot_dodge = TRUE;
 +      fountain.bot_dodgerating = autocvar_g_nades_napalm_fountain_damage;
 +      fountain.nade_special_time = time;
 +      setsize(fountain, '-16 -16 -16', '16 16 16');
 +      CSQCProjectile(fountain, TRUE, PROJECTILE_NAPALM_FOUNTAIN, TRUE);
 +}
 +
 +void nade_ice_freeze(entity freezefield, entity frost_target, float freeze_time)
 +{
 +      frost_target.frozen_by = freezefield.realowner;
 +      pointparticles(particleeffectnum("electro_impact"), frost_target.origin, '0 0 0', 1);
 +      Freeze(frost_target, 1/freeze_time, 3, FALSE);
 +      if(frost_target.ballcarried)
 +      if(g_keepaway) { ka_DropEvent(frost_target); }
 +      else { DropBall(frost_target.ballcarried, frost_target.origin, frost_target.velocity);}
 +      if(frost_target.flagcarried) { ctf_Handle_Throw(frost_target, world, DROP_THROW); }
 +      if(frost_target.nade) { toss_nade(frost_target, '0 0 0', time + 0.05); }
 +      
 +      kh_Key_DropAll(frost_target, FALSE);
 +}
 +
 +void nade_ice_think()
 +{
 +
 +      if(round_handler_IsActive())
 +      if(!round_handler_IsRoundStarted())
 +      {
 +              remove(self);
 +              return;
 +      }
 +
 +      if(time >= self.ltime)
 +      {
 +              if ( autocvar_g_nades_ice_explode )
 +              {
 +                      string expef;
 +                      switch(self.realowner.team)
 +                      {
 +                              case NUM_TEAM_1: expef = "nade_red_explode"; break;
 +                              case NUM_TEAM_2: expef = "nade_blue_explode"; break;
 +                              case NUM_TEAM_3: expef = "nade_yellow_explode"; break;
 +                              case NUM_TEAM_4: expef = "nade_pink_explode"; break;
 +                              default:                 expef = "nade_neutral_explode"; break;
 +                      }
 +                      pointparticles(particleeffectnum(expef), self.origin + '0 0 1', '0 0 0', 1);
 +                      sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
 +
 +                      RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
 +                              autocvar_g_nades_nade_radius, self, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy);
 +                      Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
 +                              autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self);
 +              }
 +              remove(self);
 +              return;
 +      }
 +
 +
 +      self.nextthink = time+0.1;
 +
 +      // gaussian
 +      float randomr;
 +      randomr = random();
 +      randomr = exp(-5*randomr*randomr)*autocvar_g_nades_nade_radius;
 +      float randomw;
 +      randomw = random()*M_PI*2;
 +      vector randomp;
 +      randomp_x = randomr*cos(randomw);
 +      randomp_y = randomr*sin(randomw);
 +      randomp_z = 1;
 +      pointparticles(particleeffectnum("electro_muzzleflash"), self.origin + randomp, '0 0 0', 1);
 +
 +      if(time >= self.nade_special_time)
 +      {
 +              self.nade_special_time = time+0.7;
 +
 +
 +              pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1);
 +              pointparticles(particleeffectnum("icefield"), self.origin, '0 0 0', 1);
 +      }
 +
 +
 +      float current_freeze_time = self.ltime - time - 0.1;
 +
 +      entity e;
 +      for(e = findradius(self.origin, autocvar_g_nades_nade_radius); e; e = e.chain)
 +      if(e != self)
 +      if(!autocvar_g_nades_ice_teamcheck || (DIFF_TEAM(e, self.realowner) || e == self.realowner))
 +      if(e.takedamage && e.deadflag == DEAD_NO)
 +      if(e.health > 0)
 +      if(!e.revival_time || ((time - e.revival_time) >= 1.5))
 +      if(!e.frozen)
 +      if(current_freeze_time > 0)
 +              nade_ice_freeze(self, e, current_freeze_time);
 +}
 +
 +void nade_ice_boom()
 +{
 +      entity fountain;
 +      fountain = spawn();
 +      fountain.owner = self.owner;
 +      fountain.realowner = self.realowner;
 +      fountain.origin = self.origin;
 +      setorigin(fountain, fountain.origin);
 +      fountain.think = nade_ice_think;
 +      fountain.nextthink = time;
 +      fountain.ltime = time + autocvar_g_nades_ice_freeze_time;
 +      fountain.pushltime = fountain.wait = fountain.ltime;
 +      fountain.team = self.team;
 +      fountain.movetype = MOVETYPE_TOSS;
 +      fountain.projectiledeathtype = DEATH_NADE_ICE;
 +      fountain.bot_dodge = FALSE;
 +      setsize(fountain, '-16 -16 -16', '16 16 16');
 +      fountain.nade_special_time = time+0.3;
 +      fountain.angles = self.angles;
 +
 +      if ( autocvar_g_nades_ice_explode )
 +      {
 +              setmodel(fountain, "models/grenademodel.md3");
 +              entity timer = spawn();
 +              setmodel(timer, "models/ok_nade_counter/ok_nade_counter.md3");
 +              setattachment(timer, fountain, "");
 +              timer.classname = "nade_timer";
 +              timer.colormap = self.colormap;
 +              timer.glowmod = self.glowmod;
 +              timer.think = nade_timer_think;
 +              timer.nextthink = time;
 +              timer.wait = fountain.ltime;
 +              timer.owner = fountain;
 +              timer.skin = 10;
 +      }
 +      else
 +              setmodel(fountain, "null");
 +}
 +
 +void nade_translocate_boom()
 +{
 +      if(self.realowner.vehicle)
 +              return;
 +
 +      vector locout = self.origin + '0 0 1' * (1 - self.realowner.mins_z - 24);
 +
 +      makevectors(self.realowner.angles);
 +
 +      entity oldself = self;
 +      self = self.realowner;
 +      MUTATOR_CALLHOOK(PortalTeleport);
 +      self.realowner = self;
 +      self = oldself;
 +
 +      TeleportPlayer(self, self.realowner, locout, self.realowner.mangle, v_forward * vlen(self.realowner.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER);
 +}
 +
 +void nade_spawn_boom()
 +{
 +      entity spawnloc = spawn();
 +      setorigin(spawnloc, self.origin);
 +      setsize(spawnloc, self.realowner.mins, self.realowner.maxs);
 +      spawnloc.movetype = MOVETYPE_NONE;
 +      spawnloc.solid = SOLID_NOT;
 +      spawnloc.drawonlytoclient = self.realowner;
 +      spawnloc.effects = EF_STARDUST;
 +      spawnloc.cnt = autocvar_g_nades_spawn_count;
 +
 +      if(self.realowner.nade_spawnloc)
 +      {
 +              remove(self.realowner.nade_spawnloc);
 +              self.realowner.nade_spawnloc = world;
 +      }
 +
 +      self.realowner.nade_spawnloc = spawnloc;
 +}
 +
 +void nade_heal_think()
 +{
 +      if(time >= self.ltime)
 +      {
 +              remove(self);
 +              return;
 +      }
 +      
 +      self.nextthink = time;
 +      
 +      if(time >= self.nade_special_time)
 +      {
 +              self.nade_special_time = time+0.25;
 +              self.nade_show_particles = 1;
 +      }
 +      else
 +              self.nade_show_particles = 0;
 +}
 +
 +void nade_heal_touch()
 +{
 +      float maxhealth;
 +      float health_factor;
 +      if(IS_PLAYER(other) || (other.flags & FL_MONSTER))
 +      if(other.deadflag == DEAD_NO)
 +      if(!other.frozen)
 +      {
 +              health_factor = autocvar_g_nades_heal_rate*frametime/2;
 +              if ( other != self.realowner )
 +              {
 +                      if ( SAME_TEAM(other,self) )
 +                              health_factor *= autocvar_g_nades_heal_friend;
 +                      else
 +                              health_factor *= autocvar_g_nades_heal_foe;
 +              }
 +              if ( health_factor > 0 )
 +              {
 +                      maxhealth = (other.flags & FL_MONSTER) ? other.max_health : g_pickup_healthmega_max;
 +                      if ( other.health < maxhealth )
 +                      {
 +                              if ( self.nade_show_particles )
 +                                      pointparticles(particleeffectnum("healing_fx"), other.origin, '0 0 0', 1);
 +                              other.health = min(other.health+health_factor, maxhealth);
 +                      }
 +                      other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);  
 +              }
 +              else if ( health_factor < 0 )
 +              {
 +                      Damage(other,self,self.realowner,-health_factor,DEATH_NADE_HEAL,other.origin,'0 0 0');
 +              }
 +              
 +      }
 +      
 +      if ( IS_REAL_CLIENT(other) || (other.vehicle_flags & VHF_ISVEHICLE) )
 +      {
 +              entity show_red = (other.vehicle_flags & VHF_ISVEHICLE) ? other.owner : other;
 +              show_red.stat_healing_orb = time+0.1;
 +              show_red.stat_healing_orb_alpha = 0.75 * (self.ltime - time) / self.healer_lifetime;
 +      }
 +}
 +
 +void nade_heal_boom()
 +{
 +      entity healer;
 +      healer = spawn();
 +      healer.owner = self.owner;
 +      healer.realowner = self.realowner;
 +      setorigin(healer, self.origin);
 +      healer.healer_lifetime = autocvar_g_nades_heal_time; // save the cvar
 +      healer.ltime = time + healer.healer_lifetime;
 +      healer.team = self.realowner.team;
 +      healer.bot_dodge = FALSE;
 +      healer.solid = SOLID_TRIGGER;
 +      healer.touch = nade_heal_touch;
 +
 +      setmodel(healer, "models/ctf/shield.md3");
 +      healer.healer_radius = autocvar_g_nades_nade_radius;
 +      vector size = '1 1 1' * healer.healer_radius / 2;
 +      setsize(healer,-size,size);
 +      
 +      Net_LinkEntity(healer, TRUE, 0, healer_send);
 +      
 +      healer.think = nade_heal_think;
 +      healer.nextthink = time;
 +      healer.SendFlags |= 1;
 +}
 +
 +void nade_monster_boom()
 +{
++      entity e = spawnmonster(self.pokenade_type, 0, self.realowner, self.realowner, self.origin, FALSE, FALSE, 1);
 +      
 +      //e.monster_lifetime = time + autocvar_g_nades_pokenade_monster_lifetime;
 +      e.monster_skill = MONSTER_SKILL_INSANE;
  }
  
  void nade_boom()
  {
        string expef;
 +      float nade_blast = 1;
  
 -      switch(self.realowner.team)
 +      switch ( self.nade_type )
        {
 -              case NUM_TEAM_1: expef = "nade_red_explode"; break;
 -              case NUM_TEAM_2: expef = "nade_blue_explode"; break;
 -              case NUM_TEAM_3: expef = "nade_yellow_explode"; break;
 -              case NUM_TEAM_4: expef = "nade_pink_explode"; break;
 -              default:                 expef = "nade_explode"; break;
 +              case NADE_TYPE_NAPALM:
 +                      nade_blast = autocvar_g_nades_napalm_blast;
 +                      expef = "explosion_medium";
 +                      break;
 +              case NADE_TYPE_ICE:
 +                      nade_blast = 0;
 +                      expef = "electro_combo"; // hookbomb_explode electro_combo bigplasma_impact
 +                      break;
 +              case NADE_TYPE_TRANSLOCATE:
 +                      nade_blast = 0;
 +                      expef = "";
 +                      break;
 +              case NADE_TYPE_MONSTER:
 +              case NADE_TYPE_SPAWN:
 +                      nade_blast = 0;
 +                      switch(self.realowner.team)
 +                      {
 +                              case NUM_TEAM_1: expef = "spawn_event_red"; break;
 +                              case NUM_TEAM_2: expef = "spawn_event_blue"; break;
 +                              case NUM_TEAM_3: expef = "spawn_event_yellow"; break;
 +                              case NUM_TEAM_4: expef = "spawn_event_pink"; break;
 +                              default: expef = "spawn_event_neutral"; break;
 +                      }
 +                      break;
 +              case NADE_TYPE_HEAL:
 +                      nade_blast = 0;
 +                      expef = "spawn_event_red";
 +                      break;
 +
 +              default:
 +              case NADE_TYPE_NORMAL:
 +                      switch(self.realowner.team)
 +                      {
 +                              case NUM_TEAM_1: expef = "nade_red_explode"; break;
 +                              case NUM_TEAM_2: expef = "nade_blue_explode"; break;
 +                              case NUM_TEAM_3: expef = "nade_yellow_explode"; break;
 +                              case NUM_TEAM_4: expef = "nade_pink_explode"; break;
 +                              default:                 expef = "nade_neutral_explode"; break;
 +                      }
        }
  
 -      sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM);
 -      sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
        pointparticles(particleeffectnum(expef), self.origin + '0 0 1', '0 0 0', 1);
  
 -      Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self);
 +      sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM);
 +      sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
  
        self.takedamage = DAMAGE_NO;
 -      RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
 +
 +      if(nade_blast)
 +      {
 +              RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
                                 autocvar_g_nades_nade_radius, self, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy);
 +              Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self);
 +      }
 +
 +      switch ( self.nade_type )
 +      {
 +              case NADE_TYPE_NAPALM: nade_napalm_boom(); break;
 +              case NADE_TYPE_ICE: nade_ice_boom(); break;
 +              case NADE_TYPE_TRANSLOCATE: nade_translocate_boom(); break;
 +              case NADE_TYPE_SPAWN: nade_spawn_boom(); break;
 +              case NADE_TYPE_HEAL: nade_heal_boom(); break;
 +              case NADE_TYPE_MONSTER: nade_monster_boom(); break;
 +      }
  
        remove(self);
  }
@@@ -568,9 -101,6 +568,9 @@@ void nade_beep(
  
  void nade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
  {
 +      if(self.nade_type == NADE_TYPE_TRANSLOCATE || self.nade_type == NADE_TYPE_SPAWN)
 +              return;
 +
        if(DEATH_ISWEAPON(deathtype, WEP_LASER))
                return;
  
        if(DEATH_ISWEAPON(deathtype, WEP_UZI))
                damage = self.max_health * 0.1;
  
 -      if(DEATH_ISWEAPON(deathtype, WEP_SHOTGUN) && !(deathtype & HITTYPE_SECONDARY))
 -              damage = self.max_health * 1.1;
 -
 -      if(DEATH_ISWEAPON(deathtype, WEP_SHOTGUN) && (deathtype & HITTYPE_SECONDARY))
 +      if(DEATH_ISWEAPON(deathtype, WEP_SHOTGUN))
 +      if(deathtype & HITTYPE_SECONDARY)
        {
                damage = self.max_health * 0.1;
 -              force *= 15;
 +              force *= 10;
        }
 +      else
 +              damage = self.max_health * 1.1;
  
        self.velocity += force;
  
                self.think = nade_beep;
        }
  
 -      self.health   -= damage;
 -      self.realowner = attacker;
 +      self.health       -= damage;
 +      
 +      if ( self.nade_type != NADE_TYPE_HEAL || IS_PLAYER(attacker) )
 +              self.realowner = attacker;
  
        if(self.health <= 0)
                W_PrepareExplosionByDamage(attacker, nade_boom);
  
  void toss_nade(entity e, vector _velocity, float _time)
  {
 +      if(e.nade == world)
 +              return;
 +
        entity _nade = e.nade;
        e.nade = world;
  
  
        Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER_CPID, CPID_NADES);
  
 -      //setorigin(_nade, CENTER_OR_VIEWOFS(e) + (v_right * 10) * -1);
        setorigin(_nade, w_shotorg + (v_right * 25) * -1);
 -      setmodel(_nade, "models/weapons/v_ok_grenade.md3");
 -      setattachment(_nade, world, "");
 +      //setmodel(_nade, "models/weapons/v_ok_grenade.md3");
 +      //setattachment(_nade, world, "");
        PROJECTILE_MAKETRIGGER(_nade);
        setsize(_nade, '-16 -16 -16', '16 16 16');
        _nade.movetype = MOVETYPE_BOUNCE;
                _nade.velocity = _velocity;
        else
                _nade.velocity = W_CalculateProjectileVelocity(e.velocity, _velocity, TRUE);
 -              
 +
        _nade.touch = nade_touch;
        _nade.health = autocvar_g_nades_nade_health;
        _nade.max_health = _nade.health;
        _nade.takedamage = DAMAGE_AIM;
        _nade.event_damage = nade_damage;
 +      _nade.customizeentityforclient = func_null;
 +      _nade.exteriormodeltoclient = world;
 +      _nade.traileffectnum = 0;
        _nade.teleportable = TRUE;
        _nade.pushable = TRUE;
        _nade.gravity = 1;
        _nade.damagedbycontents = TRUE;
        _nade.angles = vectoangles(_nade.velocity);
        _nade.flags = FL_PROJECTILE;
 +      _nade.projectiledeathtype = DEATH_NADE;
 +      _nade.toss_time = time;
 +      _nade.solid = SOLID_CORPSE; //((_nade.nade_type == NADE_TYPE_TRANSLOCATE) ? SOLID_CORPSE : SOLID_BBOX);
  
        nade_spawn(_nade);
  
        }
  
        e.nade_refire = time + autocvar_g_nades_nade_refire;
 +      e.nade_timer = 0;
 +}
 +
 +void nades_GiveBonus(entity player, float score)
 +{
 +      if (autocvar_g_nades)
 +      if (autocvar_g_nades_bonus)
 +      if (IS_REAL_CLIENT(player))
 +      if (IS_PLAYER(player) && player.bonus_nades < autocvar_g_nades_bonus_max)
 +      if (player.frozen == 0)
 +      if (player.deadflag == DEAD_NO)
 +      {
 +              if ( player.bonus_nade_score < 1 )
 +                      player.bonus_nade_score += score/autocvar_g_nades_bonus_score_max;
 +
 +              if ( player.bonus_nade_score >= 1 )
 +              {
 +                      Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_NADE_BONUS);
 +                      play2(player,"kh/alarm.wav");
 +                      player.bonus_nades++;
 +                      player.bonus_nade_score -= 1;
 +              }
 +      }
 +}
 +
 +void nades_RemoveBonus(entity player)
 +{
 +      player.bonus_nades = player.bonus_nade_score = 0;
 +}
 +
 +float nade_customize()
 +{
 +      //if(IS_SPEC(other)) { return FALSE; }
 +      if(other == self.realowner || (IS_SPEC(other) && other.enemy == self.realowner))
 +      {
 +              // somewhat hide the model, but keep the glow
 +              //self.effects = 0;
 +              if(self.traileffectnum)
 +                      self.traileffectnum = 0;
 +              self.alpha = -1;
 +      }
 +      else
 +      {
 +              //self.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
 +              if(!self.traileffectnum)
 +                      self.traileffectnum = particleeffectnum(Nade_TrailEffect(Nade_ProjectileFromID(self.nade_type, FALSE), self.team));
 +              self.alpha = 1;
 +      }
 +      
 +      return TRUE;
  }
  
  void nade_prime()
        if(self.fake_nade)
                remove(self.fake_nade);
  
 -      self.nade = spawn();
 -      setmodel(self.nade, "null");
 -      setattachment(self.nade, self, "bip01 l hand");
 -      self.nade.classname = "nade";
 -      self.nade.realowner = self;
 -      self.nade.colormap = self.colormap;
 -      self.nade.glowmod = self.glowmod;
 -      self.nade.wait = time + autocvar_g_nades_nade_lifetime;
 -      self.nade.lifetime = time;
 -      self.nade.think = nade_beep;
 -      self.nade.nextthink = max(self.nade.wait - 3, time);
 -      self.nade.projectiledeathtype = DEATH_NADE;
 -
 -      self.fake_nade = spawn();
 -      setmodel(self.fake_nade, "models/weapons/h_ok_grenade.iqm");
 -      setattachment(self.fake_nade, self.weaponentity, "");
 -      self.fake_nade.classname = "fake_nade";
 -      //self.fake_nade.viewmodelforclient = self;
 -      self.fake_nade.realowner = self.fake_nade.owner = self;
 -      self.fake_nade.colormap = self.colormap;
 -      self.fake_nade.glowmod = self.glowmod;
 -      self.fake_nade.think = SUB_Remove;
 -      self.fake_nade.nextthink = self.nade.wait;
 +      entity n = spawn(), fn = spawn();
 +
 +      n.classname = "nade";
 +      fn.classname = "fake_nade";
 +
 +      if(self.items & IT_STRENGTH && autocvar_g_nades_bonus_onstrength)
 +              n.nade_type = self.nade_type;
 +      else if (self.bonus_nades >= 1)
 +      {
 +              n.nade_type = self.nade_type;
 +              n.pokenade_type = self.pokenade_type;
 +              self.bonus_nades -= 1;
 +      }
 +      else
 +      {
 +              n.nade_type = ((autocvar_g_nades_client_select) ? self.cvar_cl_nade_type : autocvar_g_nades_nade_type);
 +              n.pokenade_type = ((autocvar_g_nades_client_select) ? self.cvar_cl_pokenade_type : autocvar_g_nades_pokenade_monster_type);
 +      }
 +      
 +      n.nade_type = bound(1, n.nade_type, NADE_TYPE_LAST);
 +
 +      setmodel(n, "models/weapons/v_ok_grenade.md3");
 +      //setattachment(n, self, "bip01 l hand");
 +      n.exteriormodeltoclient = self;
 +      n.customizeentityforclient = nade_customize;
 +      n.traileffectnum = particleeffectnum(Nade_TrailEffect(Nade_ProjectileFromID(n.nade_type, FALSE), self.team));
 +      n.colormod = Nade_Color(n.nade_type);
 +      n.realowner = self;
 +      n.colormap = self.colormap;
 +      n.glowmod = self.glowmod;
 +      n.wait = time + autocvar_g_nades_nade_lifetime;
 +      n.lifetime = time;
 +      n.think = nade_beep;
 +      n.nextthink = max(n.wait - 3, time);
 +      n.projectiledeathtype = DEATH_NADE;
 +
 +      setmodel(fn, "models/weapons/h_ok_grenade.iqm");
 +      setattachment(fn, self.weaponentity, "");
 +      fn.realowner = fn.owner = self;
 +      fn.colormod = Nade_Color(n.nade_type);
 +      fn.colormap = self.colormap;
 +      fn.glowmod = self.glowmod;
 +      fn.think = SUB_Remove;
 +      fn.nextthink = n.wait;
 +
 +      self.nade = n;
 +      self.fake_nade = fn;
  }
  
  float CanThrowNade()
@@@ -839,55 -285,21 +839,55 @@@ void nades_CheckThrow(
        }
  }
  
 +void nades_Clear(entity player)
 +{
 +      if(player.nade)
 +              remove(player.nade);
 +      if(player.fake_nade)
 +              remove(player.fake_nade);
 +
 +      player.nade = player.fake_nade = world;
 +      player.nade_timer = 0;
 +}
 +
 +MUTATOR_HOOKFUNCTION(nades_CheckThrow)
 +{
 +      if(MUTATOR_RETURNVALUE) { nades_CheckThrow(); }
 +      return FALSE;
 +}
 +
  MUTATOR_HOOKFUNCTION(nades_VehicleEnter)
  {
 -      if(other.nade)
 -              toss_nade(other, '0 0 100', max(other.nade.wait, time + 0.05));
 +      if(vh_player.nade)
 +              toss_nade(vh_player, '0 0 100', max(vh_player.nade.wait, time + 0.05));
  
        return FALSE;
  }
  
  MUTATOR_HOOKFUNCTION(nades_PlayerPreThink)
  {
 -      float key_pressed = ((g_grappling_hook || client_hasweapon(self, WEP_HOOK, FALSE, FALSE) || (weaponsInMap & WEPSET_HOOK)) ? self.button16 : self.BUTTON_HOOK);
 +      if(!IS_PLAYER(self)) { return FALSE; }
 +
 +      float key_pressed = self.BUTTON_HOOK;
 +      float time_score;
  
 +      if(g_grappling_hook || client_hasweapon(self, WEP_HOOK, FALSE, FALSE) || (weaponsInMap & WEPSET_HOOK) || g_jetpack || self.items & IT_JETPACK)
 +              key_pressed = self.button16; // if hook/jetpack is enabled, use an alternate key
 +              
        if(self.nade)
 -              if(self.nade.wait - 0.1 <= time)
 -                      toss_nade(self, '0 0 0', time + 0.05);
 +      {
 +              self.nade_timer = bound(0, (time - self.nade.lifetime) / autocvar_g_nades_nade_lifetime, 1);
 +              //print(sprintf("%d %d\n", self.nade_timer, time - self.nade.lifetime));
 +              makevectors(self.angles);
 +              self.nade.velocity = self.velocity;
 +
 +              setorigin(self.nade, self.origin + self.view_ofs + v_forward * 8 + v_right * -8 + v_up * 0);
 +              self.nade.angles_y = self.angles_y;
 +      }
 +
 +      if(self.nade)
 +      if(self.nade.wait - 0.1 <= time)
 +              toss_nade(self, '0 0 0', time + 0.05);
  
        if(CanThrowNade())
        if(self.nade_refire < time)
                }
        }
  
 +      if(IS_PLAYER(self))
 +      {
 +              if ( autocvar_g_nades_bonus && autocvar_g_nades )
 +              {
 +                      entity key;
 +                      float key_count = 0;
 +                      FOR_EACH_KH_KEY(key) if(key.owner == self) { ++key_count; }
 +
 +                      if(self.flagcarried || self.ballcarried) // this player is important
 +                              time_score = autocvar_g_nades_bonus_score_time_flagcarrier;
 +                      else
 +                              time_score = autocvar_g_nades_bonus_score_time;
 +                              
 +                      if(key_count)
 +                              time_score = autocvar_g_nades_bonus_score_time_flagcarrier * key_count; // multiply by the number of keys the player is holding
 +
 +                      if(autocvar_g_nades_bonus_client_select)
 +                      {
 +                              self.nade_type = self.cvar_cl_nade_type;
 +                              self.pokenade_type = self.cvar_cl_pokenade_type;
 +                      }
 +                      else
 +                      {
 +                              self.nade_type = autocvar_g_nades_bonus_type;
 +                              self.pokenade_type = autocvar_g_nades_pokenade_monster_type;
 +                      }
 +                              
 +                      self.nade_type = bound(1, self.nade_type, NADE_TYPE_LAST);
 +
 +                      if(self.bonus_nade_score >= 0 && autocvar_g_nades_bonus_score_max)
 +                              nades_GiveBonus(self, time_score / autocvar_g_nades_bonus_score_max);
 +              }
 +              else
 +              {
 +                      self.bonus_nades = self.bonus_nade_score = 0;
 +              }
 +      }
 +
 +      float n = 0;
 +      entity o = world;
 +      if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout)
 +              n = -1;
 +      else
 +      {
 +              vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
 +              n = 0;
 +              FOR_EACH_PLAYER(other) if(self != other)
 +              {
 +                      if(other.deadflag == DEAD_NO)
 +                      if(other.frozen == 0)
 +                      if(SAME_TEAM(other, self))
 +                      if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
 +                      {
 +                              if(!o)
 +                                      o = other;
 +                              if(self.frozen == 1)
 +                                      other.reviving = TRUE;
 +                              ++n;
 +                      }
 +              }
 +      }
 +
 +      if(n && self.frozen == 3) // OK, there is at least one teammate reviving us
 +      {
 +              self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
 +              self.health = max(1, self.revive_progress * start_health);
 +
 +              if(self.revive_progress >= 1)
 +              {
 +                      Unfreeze(self);
 +
 +                      Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
 +                      Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname);
 +              }
 +
 +              FOR_EACH_PLAYER(other) if(other.reviving)
 +              {
 +                      other.revive_progress = self.revive_progress;
 +                      other.reviving = FALSE;
 +              }
 +      }
 +
        return FALSE;
  }
  
@@@ -1002,113 -332,24 +1002,113 @@@ MUTATOR_HOOKFUNCTION(nades_PlayerSpawn
        else
                self.nade_refire  = time + autocvar_g_nades_nade_refire;
  
 +      if(autocvar_g_nades_bonus_client_select)
 +              self.nade_type = self.cvar_cl_nade_type;
 +
 +      self.nade_timer = 0;
 +
 +      if(self.nade_spawnloc)
 +      {
 +              setorigin(self, self.nade_spawnloc.origin);
 +              self.nade_spawnloc.cnt -= 1;
 +              
 +              if(self.nade_spawnloc.cnt <= 0)
 +              {
 +                      remove(self.nade_spawnloc);
 +                      self.nade_spawnloc = world;
 +              }
 +      }
 +
        return FALSE;
  }
  
  MUTATOR_HOOKFUNCTION(nades_PlayerDies)
  {
 -      if(self.nade)
 -              toss_nade(self, '0 0 100', max(self.nade.wait, time + 0.05));
 +      if(frag_target.nade)
 +      if(!frag_target.frozen || !autocvar_g_freezetag_revive_nade)
 +              toss_nade(frag_target, '0 0 100', max(frag_target.nade.wait, time + 0.05));
 +
 +      float killcount_bonus = ((frag_attacker.killcount >= 1) ? bound(0, autocvar_g_nades_bonus_score_minor * frag_attacker.killcount, autocvar_g_nades_bonus_score_medium) : autocvar_g_nades_bonus_score_minor);
 +
 +      if(IS_PLAYER(frag_attacker))
 +      {
 +              if (SAME_TEAM(frag_attacker, frag_target) || frag_attacker == frag_target)
 +                      nades_RemoveBonus(frag_attacker);
 +              else if(frag_target.flagcarried)
 +                      nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_medium);
 +              else if(autocvar_g_nades_bonus_score_spree && frag_attacker.killcount > 1)
 +              {
 +                      #define SPREE_ITEM(counta,countb,center,normal,gentle) \
 +                              case counta: { nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_spree); break; }
 +                      switch(frag_attacker.killcount)
 +                      {
 +                              KILL_SPREE_LIST
 +                              default: nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); break;
 +                      }
 +                      #undef SPREE_ITEM
 +              }
 +              else
 +                      nades_GiveBonus(frag_attacker, killcount_bonus);
 +      }
 +
 +      nades_RemoveBonus(frag_target);
 +
 +      return FALSE;
 +}
 +
 +MUTATOR_HOOKFUNCTION(nades_PlayerDamage)
 +{
 +      if(frag_target.frozen)
 +      if(autocvar_g_freezetag_revive_nade)
 +      if(frag_attacker == frag_target)
 +      if(frag_deathtype == DEATH_NADE)
 +      if(time - frag_inflictor.toss_time <= 0.1)
 +      {
 +              Unfreeze(frag_target);
 +              frag_target.health = autocvar_g_freezetag_revive_nade_health;
 +              pointparticles(particleeffectnum("iceorglass"), frag_target.origin, '0 0 0', 3);
 +              frag_damage = 0;
 +              frag_force = '0 0 0';
 +              Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_NADE, frag_target.netname);
 +              Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
 +      }
 +      
 +      return FALSE;
 +}
 +
 +MUTATOR_HOOKFUNCTION(nades_MonsterDies)
 +{
 +      if(IS_PLAYER(frag_attacker))
 +      if(DIFF_TEAM(frag_attacker, self))
 +      if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
 +              nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor);
  
        return FALSE;
  }
  
  MUTATOR_HOOKFUNCTION(nades_RemovePlayer)
  {
 -      if(self.nade)
 -              remove(self.nade);
 +      nades_Clear(self);
 +      nades_RemoveBonus(self);
 +      return FALSE;
 +}
  
 -      if(self.fake_nade)
 -              remove(self.fake_nade);
 +MUTATOR_HOOKFUNCTION(nades_SpectateCopy)
 +{
 +      self.nade_timer = other.nade_timer;
 +      self.nade_type = other.nade_type;
 +      self.pokenade_type = other.pokenade_type;
 +      self.bonus_nades = other.bonus_nades;
 +      self.bonus_nade_score = other.bonus_nade_score;
 +      self.stat_healing_orb = other.stat_healing_orb;
 +      self.stat_healing_orb_alpha = other.stat_healing_orb_alpha;
 +      return FALSE;
 +}
 +
 +MUTATOR_HOOKFUNCTION(nades_GetCvars)
 +{
 +      GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_nade_type, "cl_nade_type");
 +      GetCvars_handleString(get_cvars_s, get_cvars_f, cvar_cl_pokenade_type, "cl_pokenade_type");
  
        return FALSE;
  }
@@@ -1125,50 -366,31 +1125,50 @@@ MUTATOR_HOOKFUNCTION(nades_BuildMutator
        return FALSE;
  }
  
 +void nades_Initialize()
 +{
 +      addstat(STAT_NADE_TIMER, AS_FLOAT, nade_timer);
 +      addstat(STAT_NADE_BONUS, AS_FLOAT, bonus_nades);
 +      addstat(STAT_NADE_BONUS_TYPE, AS_INT, nade_type);
 +      addstat(STAT_NADE_BONUS_SCORE, AS_FLOAT, bonus_nade_score);
 +      addstat(STAT_HEALING_ORB, AS_FLOAT, stat_healing_orb);
 +      addstat(STAT_HEALING_ORB_ALPHA, AS_FLOAT, stat_healing_orb_alpha);
 +      
 +      precache_model("models/ok_nade_counter/ok_nade_counter.md3");
 +      precache_model("models/weapons/h_ok_grenade.iqm");
 +      precache_model("models/weapons/v_ok_grenade.md3");
 +      precache_model("models/ctf/shield.md3");
 +
 +      precache_sound("weapons/rocket_impact.wav");
 +      precache_sound("weapons/grenade_bounce1.wav");
 +      precache_sound("weapons/grenade_bounce2.wav");
 +      precache_sound("weapons/grenade_bounce3.wav");
 +      precache_sound("weapons/grenade_bounce4.wav");
 +      precache_sound("weapons/grenade_bounce5.wav");
 +      precache_sound("weapons/grenade_bounce6.wav");
 +      precache_sound("overkill/grenadebip.ogg");
 +}
 +
  MUTATOR_DEFINITION(mutator_nades)
  {
 +      MUTATOR_HOOK(ForbidThrowCurrentWeapon, nades_CheckThrow, CBC_ORDER_LAST);
        MUTATOR_HOOK(VehicleEnter, nades_VehicleEnter, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerPreThink, nades_PlayerPreThink, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerSpawn, nades_PlayerSpawn, CBC_ORDER_ANY);
 -      MUTATOR_HOOK(PlayerDies, nades_PlayerDies, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(PlayerDies, nades_PlayerDies, CBC_ORDER_LAST);
 +      MUTATOR_HOOK(PlayerDamage_Calculate, nades_PlayerDamage, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(MonsterDies, nades_MonsterDies, CBC_ORDER_ANY);
        MUTATOR_HOOK(MakePlayerObserver, nades_RemovePlayer, CBC_ORDER_ANY);
        MUTATOR_HOOK(ClientDisconnect, nades_RemovePlayer, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(SpectateCopy, nades_SpectateCopy, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(GetCvars, nades_GetCvars, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(reset_map_global, nades_RemovePlayer, CBC_ORDER_ANY);
        MUTATOR_HOOK(BuildMutatorsString, nades_BuildMutatorsString, CBC_ORDER_ANY);
        MUTATOR_HOOK(BuildMutatorsPrettyString, nades_BuildMutatorsPrettyString, CBC_ORDER_ANY);
  
        MUTATOR_ONADD
        {
 -              precache_model("models/ok_nade_counter/ok_nade_counter.md3");
 -
 -              precache_model("models/weapons/h_ok_grenade.iqm");
 -              precache_model("models/weapons/v_ok_grenade.md3");
 -              precache_sound("weapons/rocket_impact.wav");
 -              precache_sound("weapons/grenade_bounce1.wav");
 -              precache_sound("weapons/grenade_bounce2.wav");
 -              precache_sound("weapons/grenade_bounce3.wav");
 -              precache_sound("weapons/grenade_bounce4.wav");
 -              precache_sound("weapons/grenade_bounce5.wav");
 -              precache_sound("weapons/grenade_bounce6.wav");
 -              precache_sound("overkill/grenadebip.ogg");
 +              nades_Initialize();
        }
  
        return FALSE;
index 2658c449371ac90eb6c932e3e1067093f00cdf6f,e53c80f848cca975344b6e2025076bdecfec6c33..ffae9543b95c663527fe29a6abe49f527453c94f
@@@ -41,6 -41,7 +41,7 @@@ MUTATOR_HOOKFUNCTION(msnt_Spawn_Score
  
  MUTATOR_HOOKFUNCTION(msnt_PlayerSpawn)
  {
+       // Note: when entering this, fixangle is already set.
        if(autocvar_g_spawn_near_teammate_ignore_spawnpoint)
        {
                if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death)
@@@ -56,7 -57,7 +57,7 @@@
                        if(team_mate.msnt_timer < time)
                        if(SAME_TEAM(self, team_mate))
                        if(time > team_mate.spawnshieldtime) // spawn shielding
 -                      if(team_mate.freezetag_frozen == 0)
 +                      if(team_mate.frozen == 0)
                        if(team_mate != self)
                        {
                                tracebox(team_mate.origin, PL_MIN, PL_MAX, team_mate.origin - '0 0 100', MOVE_WORLDONLY, team_mate);
                                                                                setorigin(self, trace_endpos);
                                                                                self.angles = team_mate.angles;
                                                                                self.angles_z = 0; // never spawn tilted even if the spot says to
-                                                                               self.fixangle = TRUE; // turn this way immediately
                                                                                team_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
                                                                                return 0;
                                                                        }
                        setorigin(self, best_spot);
                        self.angles = best_mate.angles;
                        self.angles_z = 0; // never spawn tilted even if the spot says to
-                       self.fixangle = TRUE; // turn this way immediately
                        best_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
                }
        }
                self.angles = vectoangles(spawn_spot.msnt_lookat.origin - self.origin);
                self.angles_x = -self.angles_x;
                self.angles_z = 0; // never spawn tilted even if the spot says to
-               self.fixangle = TRUE; // turn this way immediately
                /*
                sprint(self, "You should be looking at ", spawn_spot.msnt_lookat.netname, "^7.\n");
                sprint(self, "distance: ", vtos(spawn_spot.msnt_lookat.origin - self.origin), "\n");
diff --combined qcsrc/server/progs.src
index a4e431d143b9cd4d8ce9e0553f5629e43aa7f7f8,c71889ce1412f68dce5ab08a3ea2359c2db30113..288b2477597c00114d0a3b0246c296f3e1360b51
@@@ -13,9 -13,10 +13,11 @@@ sys-post.q
  ../warpzonelib/server.qh
  
  ../common/constants.qh
+ ../common/stats.qh
  ../common/teams.qh
  ../common/util.qh
 +../common/nades.qh
+ ../common/buffs.qh
  ../common/test.qh
  ../common/counting.qh
  ../common/items.qh
@@@ -38,19 -39,7 +40,7 @@@ defs.qh              // Should rename this, it has 
  ../common/notifications.qh // must be after autocvars
  ../common/deathtypes.qh // must be after notifications
  
- mutators/base.qh
- mutators/mutators.qh
- mutators/gamemode_assault.qh
- mutators/gamemode_ca.qh
- mutators/gamemode_ctf.qh
- mutators/gamemode_domination.qh
- mutators/gamemode_keyhunt.qh // TODO fix this
- mutators/gamemode_keepaway.qh
- mutators/gamemode_nexball.qh 
- mutators/gamemode_lms.qh
- mutators/gamemode_invasion.qh
- mutators/mutator_dodging.qh
- mutators/mutator_nades.qh
+ mutators/mutators_include.qh
  
  //// tZork Turrets ////
  tturrets/include/turrets_early.qh
@@@ -89,6 -78,8 +79,8 @@@ scores.q
  
  spawnpoints.qh
  
+ mapvoting.qh
  ipban.qh
  
  race.qh
@@@ -107,6 -98,8 +99,8 @@@ scores_rules.q
  
  miscfunctions.qc
  
+ mutators/mutators.qc
  waypointsprites.qc
  
  bot/bot.qc
@@@ -132,6 -125,8 +126,8 @@@ pathlib/pathlib.q
  g_world.qc
  g_casings.qc
  
+ mapvoting.qc
  t_jumppads.qc
  t_teleporters.qc
  
@@@ -215,7 -210,7 +211,8 @@@ target_music.q
  
  ../common/items.qc
  
 +../common/nades.qc
+ ../common/buffs.qc
  
  
  accuracy.qc
@@@ -237,38 -232,7 +234,7 @@@ round_handler.q
  
  ../common/monsters/spawn.qc
  
- mutators/base.qc
- mutators/gamemode_assault.qc
- mutators/gamemode_ca.qc
- mutators/gamemode_ctf.qc
- mutators/gamemode_domination.qc
- mutators/gamemode_freezetag.qc
- mutators/gamemode_keyhunt.qc
- mutators/gamemode_keepaway.qc
- mutators/gamemode_nexball.qc
- mutators/gamemode_onslaught.qc
- mutators/gamemode_lms.qc
- mutators/gamemode_invasion.qc
- mutators/mutator_invincibleproj.qc
- mutators/mutator_new_toys.qc
- mutators/mutator_nix.qc
- mutators/mutator_dodging.qc
- mutators/mutator_rocketflying.qc
- mutators/mutator_vampire.qc
- mutators/mutator_spawn_near_teammate.qc
- mutators/mutator_physical_items.qc
- mutators/sandbox.qc
- mutators/mutator_superspec.qc
- mutators/mutator_minstagib.qc
- mutators/mutator_touchexplode.qc
- mutators/mutator_pinata.qc
- mutators/mutator_midair.qc
- mutators/mutator_bloodloss.qc
- mutators/mutator_random_gravity.qc
- mutators/mutator_multijump.qc
- mutators/mutator_melee_only.qc
- mutators/mutator_nades.qc
- mutators/mutator_campcheck.qc
+ mutators/mutators_include.qc
  
  ../warpzonelib/anglestransform.qc
  ../warpzonelib/mathlib.qc
diff --combined qcsrc/server/sv_main.qc
index cf974d82ba2b5b4c87fbc93ba8593dcddf157501,c8a75032bffa6fbca95ea045ebd089baf1c8dc93..b25528e1645ccf1f635a20e5ae2866dc05eaf045
@@@ -62,7 -62,7 +62,7 @@@ void CreatureFrame (void
                                                if (self.watersound_finished < time)
                                                {
                                                        self.watersound_finished = time + 0.5;
-                                                       sound (self, CH_PLAYER, "player/lava.wav", VOL_BASE, ATTEN_NORM);
+                                                       sound (self, CH_PLAYER_SINGLE, "player/lava.wav", VOL_BASE, ATTEN_NORM);
                                                }
                                                Damage (self, world, world, autocvar_g_balance_contents_playerdamage_lava * autocvar_g_balance_contents_damagerate * self.waterlevel, DEATH_LAVA, self.origin, '0 0 0');
                                        }
@@@ -71,7 -71,7 +71,7 @@@
                                                if (self.watersound_finished < time)
                                                {
                                                        self.watersound_finished = time + 0.5;
-                                                       sound (self, CH_PLAYER, "player/slime.wav", VOL_BASE, ATTEN_NORM);
+                                                       sound (self, CH_PLAYER_SINGLE, "player/slime.wav", VOL_BASE, ATTEN_NORM);
                                                }
                                                Damage (self, world, world, autocvar_g_balance_contents_playerdamage_slime * autocvar_g_balance_contents_damagerate * self.waterlevel, DEATH_SLIME, self.origin, '0 0 0');
                                        }
@@@ -94,7 -94,7 +94,7 @@@
                {
                        // check for falling damage
                        float velocity_len = vlen(self.velocity);
-                       if(!self.hook.state && !(g_cts && !autocvar_g_cts_selfdamage))
+                       if(!self.hook.state)
                        {
                                dm = vlen(self.oldvelocity) - velocity_len; // dm is now the velocity DECREASE. Velocity INCREASE should never cause a sound or any damage.
                                if (self.deadflag)
@@@ -155,6 -155,7 +155,6 @@@ float game_delay
  float game_delay_last;
  
  float RedirectionThink();
 -entity SelectSpawnPoint (float anypoint);
  void StartFrame (void)
  {
        execute_next_frame();
        FOR_EACH_PLAYER(self)
                self.porto_forbidden = max(0, self.porto_forbidden - 1);
  
+       anticheat_startframe();
        MUTATOR_CALLHOOK(SV_StartFrame);
  }
  
diff --combined qcsrc/server/teamplay.qc
index e325ed8458127a7f29b119aae6dd120369626cd0,87ea68d7982ca4714084e0ae8b5a0ea6a5b8dcf6..33d3ebd3603a030a6eb6b934e2708478e544ebef
@@@ -159,7 -159,6 +159,6 @@@ void InitGameplayMode(
  
        if(g_race)
        {
                if(autocvar_g_race_teams)
                {
                        ActivateTeamplay();
                qualifying_override = autocvar_g_race_qualifying_timelimit_override;
                fraglimit_override = autocvar_g_race_laps_limit;
                leadlimit_override = 0; // currently not supported by race
+               MUTATOR_ADD(gamemode_race);
        }
  
        if(g_cts)
                g_race_qualifying = 1;
                fraglimit_override = 0;
                leadlimit_override = 0;
+               MUTATOR_ADD(gamemode_cts);
        }
  
        if(g_nexball)
  
        if(g_invasion)
        {
-               timelimit_override = 0; // no timelimit in invasion, round based
-               fraglimit_override = autocvar_g_invasion_round_limit;
+               fraglimit_override = autocvar_g_invasion_point_limit;
+               if(autocvar_g_invasion_teams >= 2)
+               {
+                       ActivateTeamplay();
+                       if(autocvar_g_invasion_team_spawns)
+                               have_team_spawns = -1; // request team spawns
+               }
                MUTATOR_ADD(gamemode_invasion);
        }
  
                else
                        g_race_qualifying = 0;
        }
-       
-       if(g_invasion)
-       {
-               inv_maxrounds = cvar("fraglimit");
-               cvar_set("fraglimit", "0");
-       }
  
        if(g_race || g_cts)
-       {
-               if(g_race_qualifying)
-                       independent_players = 1;
-               ScoreRules_race();
-       }
+       if(g_race_qualifying)
+               independent_players = 1;
  
        InitializeEntity(world, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK);
  }
@@@ -318,9 -315,6 +315,9 @@@ string getwelcomemessage(void
        if (g_grappling_hook)
                s = strcat(s, "\n\n^3grappling hook^8 is enabled, press 'e' to use it\n");
  
 +      if (cvar("g_nades"))
 +              s = strcat(s, "\n\n^3nades^8 are enabled, press 'g' to use them\n");
 +
        if(cache_lastmutatormsg != autocvar_g_mutatormsg)
        {
                if(cache_lastmutatormsg)
@@@ -424,10 -418,7 +421,7 @@@ void CheckAllowedTeams (entity for_whom
        else
        {
                // cover anything else by treating it like tdm with no teams spawned
-               if(g_race)
-                       dm = race_teams;
-               else
-                       dm = 2;
+               dm = 2;
  
                ret_float = dm;
                MUTATOR_CALLHOOK(GetTeamCount);
index 5d88df8b254ac08be41bcec0422d0af8a464a34c,ec36f5d32541cea2fbca1c8fa29572b0e1994d06..e91fb5fb6b77b70889548d314c7065893ddddb03
@@@ -195,7 -195,7 +195,7 @@@ void W_Mine_Think (void
  
        // a player's mines shall explode if he disconnects or dies
        // TODO: Do this on team change too -- Samual: But isn't a player killed when they switch teams?
-       if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO)
 -      if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO || self.realowner.freezetag_frozen)
++      if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO || self.realowner.frozen)
        {
                other = world;
                self.projectiledeathtype |= HITTYPE_BOUNCE;
        head = findradius(self.origin, autocvar_g_balance_minelayer_proximityradius);
        while(head)
        {
-               if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
 -              if(IS_PLAYER(head) && head.deadflag == DEAD_NO && !head.freezetag_frozen)
++              if(IS_PLAYER(head) && head.deadflag == DEAD_NO && !head.frozen)
                if(head != self.realowner && DIFF_TEAM(head, self.realowner)) // don't trigger for team mates
                if(!self.mine_time)
                {