]> 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 --cc gamemodes.cfg
Simple merge
diff --cc mutators.cfg
Simple merge
Simple merge
Simple merge
Simple merge
index 940f1f9328f5d80341490c195355f413d62b2a4f,b7281c562124b2c8dc0c20be64254d23095fa8da..86a8b43395e199d83a1dc1676f5a7d234cf1a27f
@@@ -16,7 -17,7 +17,8 @@@ Defs.q
  
  ../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
Simple merge
Simple merge
Simple merge
Simple merge
index cc3379ee36a9e94c657d898a9ac6f91ef4676251,34e7ceb9901ebbb30583e88b3862ba3204cd28fb..6d06de50ebb30e9c8ff98ee6c5fefcb5542dbcf7
@@@ -104,10 -94,10 +94,10 @@@ float monster_isvalidtarget (entity tar
        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;
@@@ -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)
@@@ -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);
  
  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;
  
Simple merge
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;
Simple merge
index 038799df5086e72d566756c3f67678706ebb1fba,4f6e29ceacd84ded7d04e289920bb067eee0c909..b3c6b65775d5121c8a6394cd199b51ff3b08363a
@@@ -1593,10 -1550,15 +1558,16 @@@ 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;
index 5a0ff5872da20d0f2dfad49a75e0519387f49f49,50518040b7ce07649d804af7504f15b1a2c87787..14747fa99aabe535d9074e7e03ae6391adcebf80
@@@ -19,12 -19,14 +19,17 @@@ 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;
  
Simple merge
Simple merge
index 505dba251d708531e606f7506537f04039ff436e,78424484aa34f4ff826c794a5c893383414c600f..51a04b59135e01e3ac0fd9b6bd62c1d25271651f
@@@ -283,10 -289,10 +289,10 @@@ void ClientCommand_mobspawn(float reque
                        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 != "")
                        {
Simple merge
index b5b52a379b253d5c6ff901961e79fd72ad9007e1,7cc48be005590a39402254691d529d4b53b22a39..47443e6d70f34acfdc175d3a16efb71a6823ef18
@@@ -1353,7 -1212,7 +1348,7 @@@ void Fire_ApplyDamage(entity e
        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))
Simple merge
Simple merge
index 626e4fed8af58d9721a8d66c15686c50346c999a,c9c9b4584b14af089b437c5b81f4c26e105dae95..967d8d656fdf72b43a54e7fdff077c3d111c4d50
@@@ -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
index 52d0fd4921f5d74cd9a638675fb21ddbcb5dce58,26e19910ad0f3f571a19f779bec902872b9de8e7..ffdc1612dde1235608c2874da85eefc60dd37c15
@@@ -520,8 -658,15 +520,8 @@@ 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 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
@@@ -27,446 -38,17 +27,446 @@@ void nade_spawn(entity _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()
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
@@@ -215,7 -210,7 +211,8 @@@ target_music.q
  
  ../common/items.qc
  
 +../common/nades.qc
+ ../common/buffs.qc
  
  
  accuracy.qc
Simple merge
Simple merge
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)
                {