]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Mutators: combine headers
authorTimePath <andrew.hardaker1995@gmail.com>
Sun, 18 Oct 2015 04:21:03 +0000 (15:21 +1100)
committerTimePath <andrew.hardaker1995@gmail.com>
Sun, 18 Oct 2015 04:22:11 +0000 (15:22 +1100)
130 files changed:
qcsrc/client/hud.qc
qcsrc/client/progs.inc
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/notifications.qc
qcsrc/common/weapons/all.qc
qcsrc/lib/csqcmodel/cl_player.qc
qcsrc/server/bot/aim.qc
qcsrc/server/bot/bot.qc
qcsrc/server/cheats.qc
qcsrc/server/command/cmd.qc
qcsrc/server/command/sv_cmd.qc
qcsrc/server/command/vote.qc
qcsrc/server/ent_cs.qc
qcsrc/server/g_damage.qc
qcsrc/server/g_damage.qh
qcsrc/server/g_world.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/miscfunctions.qh
qcsrc/server/mutators/all.inc [new file with mode: 0644]
qcsrc/server/mutators/all.qc [new file with mode: 0644]
qcsrc/server/mutators/all.qh [new file with mode: 0644]
qcsrc/server/mutators/gamemode.qh
qcsrc/server/mutators/gamemode_assault.qc [deleted file]
qcsrc/server/mutators/gamemode_assault.qh [deleted file]
qcsrc/server/mutators/gamemode_ca.qc [deleted file]
qcsrc/server/mutators/gamemode_ca.qh [deleted file]
qcsrc/server/mutators/gamemode_ctf.qc [deleted file]
qcsrc/server/mutators/gamemode_ctf.qh [deleted file]
qcsrc/server/mutators/gamemode_cts.qc [deleted file]
qcsrc/server/mutators/gamemode_cts.qh [deleted file]
qcsrc/server/mutators/gamemode_deathmatch.qc [deleted file]
qcsrc/server/mutators/gamemode_domination.qc [deleted file]
qcsrc/server/mutators/gamemode_domination.qh [deleted file]
qcsrc/server/mutators/gamemode_freezetag.qc [deleted file]
qcsrc/server/mutators/gamemode_freezetag.qh [deleted file]
qcsrc/server/mutators/gamemode_invasion.qc [deleted file]
qcsrc/server/mutators/gamemode_invasion.qh [deleted file]
qcsrc/server/mutators/gamemode_keepaway.qc [deleted file]
qcsrc/server/mutators/gamemode_keepaway.qh [deleted file]
qcsrc/server/mutators/gamemode_keyhunt.qc [deleted file]
qcsrc/server/mutators/gamemode_keyhunt.qh [deleted file]
qcsrc/server/mutators/gamemode_lms.qc [deleted file]
qcsrc/server/mutators/gamemode_lms.qh [deleted file]
qcsrc/server/mutators/gamemode_onslaught.qc [deleted file]
qcsrc/server/mutators/gamemode_onslaught.qh [deleted file]
qcsrc/server/mutators/gamemode_race.qc [deleted file]
qcsrc/server/mutators/gamemode_race.qh [deleted file]
qcsrc/server/mutators/gamemode_tdm.qc [deleted file]
qcsrc/server/mutators/mutator.qh
qcsrc/server/mutators/mutator/gamemode_assault.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/gamemode_ca.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/gamemode_ctf.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/gamemode_cts.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/gamemode_deathmatch.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/gamemode_domination.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/gamemode_freezetag.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/gamemode_invasion.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/gamemode_keepaway.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/gamemode_keyhunt.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/gamemode_lms.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/gamemode_onslaught.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/gamemode_race.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/gamemode_tdm.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_bloodloss.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_breakablehook.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_buffs.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_campcheck.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_dodging.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_hook.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_invincibleproj.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_melee_only.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_midair.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_multijump.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_nades.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_new_toys.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_nix.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_overkill.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_physical_items.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_pinata.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_random_gravity.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_rocketflying.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_rocketminsta.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_spawn_near_teammate.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_superspec.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_touchexplode.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_vampire.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/mutator_vampirehook.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/sandbox.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator_bloodloss.qc [deleted file]
qcsrc/server/mutators/mutator_breakablehook.qc [deleted file]
qcsrc/server/mutators/mutator_buffs.qc [deleted file]
qcsrc/server/mutators/mutator_buffs.qh [deleted file]
qcsrc/server/mutators/mutator_campcheck.qc [deleted file]
qcsrc/server/mutators/mutator_dodging.qc [deleted file]
qcsrc/server/mutators/mutator_dodging.qh [deleted file]
qcsrc/server/mutators/mutator_hook.qc [deleted file]
qcsrc/server/mutators/mutator_invincibleproj.qc [deleted file]
qcsrc/server/mutators/mutator_melee_only.qc [deleted file]
qcsrc/server/mutators/mutator_midair.qc [deleted file]
qcsrc/server/mutators/mutator_multijump.qc [deleted file]
qcsrc/server/mutators/mutator_nades.qc [deleted file]
qcsrc/server/mutators/mutator_nades.qh [deleted file]
qcsrc/server/mutators/mutator_new_toys.qc [deleted file]
qcsrc/server/mutators/mutator_nix.qc [deleted file]
qcsrc/server/mutators/mutator_overkill.qc [deleted file]
qcsrc/server/mutators/mutator_overkill.qh [deleted file]
qcsrc/server/mutators/mutator_physical_items.qc [deleted file]
qcsrc/server/mutators/mutator_pinata.qc [deleted file]
qcsrc/server/mutators/mutator_random_gravity.qc [deleted file]
qcsrc/server/mutators/mutator_rocketflying.qc [deleted file]
qcsrc/server/mutators/mutator_rocketminsta.qc [deleted file]
qcsrc/server/mutators/mutator_spawn_near_teammate.qc [deleted file]
qcsrc/server/mutators/mutator_superspec.qc [deleted file]
qcsrc/server/mutators/mutator_touchexplode.qc [deleted file]
qcsrc/server/mutators/mutator_vampire.qc [deleted file]
qcsrc/server/mutators/mutator_vampirehook.qc [deleted file]
qcsrc/server/mutators/mutators_include.qc [deleted file]
qcsrc/server/mutators/mutators_include.qh [deleted file]
qcsrc/server/mutators/sandbox.qc [deleted file]
qcsrc/server/portals.qc
qcsrc/server/progs.inc
qcsrc/server/scores.qc
qcsrc/server/spawnpoints.qc
qcsrc/server/sv_main.qc
qcsrc/server/t_items.qc
qcsrc/server/teamplay.qc
qcsrc/server/weapons/accuracy.qc
qcsrc/server/weapons/spawning.qc
qcsrc/server/weapons/throwing.qc
qcsrc/server/weapons/weaponsystem.qc

index 90be935df3b43528717b39aa53e0eefc1404ab4e..042536aa767e91250fbd49d7dc504e56869f057d 100644 (file)
@@ -13,7 +13,8 @@
 #include "../common/nades/all.qh"
 #include "../common/stats.qh"
 #include "../lib/csqcmodel/cl_player.qh"
-#include "../server/mutators/gamemode_ctf.qh"
+// TODO: remove
+#include "../server/mutators/mutator/gamemode_ctf.qc"
 
 
 /*
index 0c3884f6797a489efb9b1ddb3f0d5fc4d711993a..5c110e2b8986547e93e16b0e3bc1ba96b4cf24ce 100644 (file)
 #include "../lib/csqcmodel/cl_player.qc"
 #include "../lib/csqcmodel/interpolate.qc"
 
-#include "../server/mutators/mutator_multijump.qc"
+// TODO: move to common
+#include "../server/mutators/mutator/mutator_multijump.qc"
+#define IMPLEMENTATION
+#include "../server/mutators/mutator/mutator_multijump.qc"
+#undef IMPLEMENTATION
 
 #include "../lib/warpzone/anglestransform.qc"
 #include "../lib/warpzone/client.qc"
index bf74dcea0aa532126dfa72b66dd2f19f5a7cccbf..ac642fd3cbee0586793d5bf9639d6cadc02bdd23 100644 (file)
@@ -11,7 +11,7 @@
     #include "../../server/autocvars.qh"
     #include "../../server/defs.qh"
     #include "../deathtypes/all.qh"
-    #include "../../server/mutators/mutators_include.qh"
+    #include "../../server/mutators/all.qh"
        #include "../../server/steerlib.qh"
        #include "../turrets/sv_turrets.qh"
        #include "../turrets/util.qh"
index 600ddaa1b3237301833480c7ef92cb901333b1a0..811e7007c63e21b69548a607de73d3918698efa2 100644 (file)
@@ -7,7 +7,7 @@
     #include "../server/constants.qh"
     #include "../server/defs.qh"
     #include "notifications.qh"
-    #include "../server/mutators/mutators_include.qh"
+    #include "../server/mutators/all.qh"
 #endif
 
 // ================================================
index e603cb16c195a3ce3c3067e1f4f112979f0724b9..ffbc0712da186a0d5d73972c7f385ed3498b016e 100644 (file)
@@ -39,7 +39,7 @@
     #include "../../server/defs.qh"
     #include "../notifications.qh"
     #include "../deathtypes/all.qh"
-    #include "../../server/mutators/mutators_include.qh"
+    #include "../../server/mutators/all.qh"
     #include "../mapinfo.qh"
     #include "../../server/command/common.qh"
     #include "../../lib/csqcmodel/sv_model.qh"
index ae4e55ac1cad74e41fa261429444c545ce10f6fc..c39ee879708a8aba8f0d4fa2b6a9370a91d8c952 100644 (file)
@@ -28,6 +28,7 @@
 #include "../../client/defs.qh"
 #include "../../client/main.qh"
 #include "../../common/constants.qh"
+#include "../../common/physics.qh"
 #include "../../common/stats.qh"
 #include "../../common/triggers/trigger/viewloc.qh"
 #include "../../common/util.qh"
index 07ea9091dc3f605f0bb56b7c759285480a432aeb..50e1d456f56e497720c5d153958518c48fac39f5 100644 (file)
@@ -4,7 +4,7 @@
 
 #include "../weapons/weaponsystem.qh"
 
-#include "../mutators/mutators_include.qh"
+#include "../mutators/all.qh"
 
 // traces multiple trajectories to find one that will impact the target
 // 'end' vector is the place it aims for,
index 95262bcfa11769bef068c13b9be84cafa7fc3633..fb76623289bebc239bf8ad9f473a5edae883d65a 100644 (file)
@@ -19,7 +19,7 @@
 #include "../race.qh"
 #include "../t_items.qh"
 
-#include "../mutators/mutators_include.qh"
+#include "../mutators/all.qh"
 
 #include "../weapons/accuracy.qh"
 
index 64c7cec19e17ea2627faf89f40ca8d91bd95710a..8f34a7798b7fa90ceec7aeab4b0f3c1f74742f5d 100644 (file)
@@ -4,7 +4,7 @@
 #include "race.qh"
 #include "../common/triggers/teleporters.qh"
 
-#include "mutators/mutators_include.qh"
+#include "mutators/all.qh"
 
 #include "weapons/tracing.qh"
 
index dbb9e1a4858d2c7e66a7e438bee1b3d7bfa17f11..e4eee69834d4e53632952f8520d8649df34b7bb4 100644 (file)
@@ -12,7 +12,7 @@
 #include "../scores.qh"
 #include "../teamplay.qh"
 
-#include "../mutators/mutators_include.qh"
+#include "../mutators/all.qh"
 
 #ifdef SVQC
        #include "../../common/vehicles/all.qh"
index 21255e5ddf50e91d4bc12f44168340fd56ad8e40..cde2d6c45209150c5c61a4380bc09efe1a9d3f96 100644 (file)
@@ -20,7 +20,7 @@
 #include "../bot/navigation.qh"
 #include "../bot/scripting.qh"
 
-#include "../mutators/mutators_include.qh"
+#include "../mutators/all.qh"
 
 #include "../../common/constants.qh"
 #include "../../common/mapinfo.qh"
index 0fca5d2260b4e624f1624235e47ead70981920ea..77c0a35600dd12cee1428f7ae7ec8f53e3f3767b 100644 (file)
@@ -9,7 +9,7 @@
 #include "../round_handler.qh"
 #include "../scores.qh"
 
-#include "../mutators/mutators_include.qh"
+#include "../mutators/all.qh"
 
 #include "../../common/constants.qh"
 #include "../../common/mapinfo.qh"
index 548c60429b709b3064eb12491f4585d2a2b388be..cf275c0f89aa1b8eb49dd4ec55af3d238fe528b9 100644 (file)
@@ -1,7 +1,5 @@
 #include "ent_cs.qh"
 
-#include "mutators/gamemode_ca.qh"
-
 float entcs_customize()
 {
        SELFPARAM();
index 6a80f7e5e1abfcec5211a34a617e5edf016d1edb..8354007aa0fc2b3ac463cb5416354109e33a08c0 100644 (file)
@@ -2,7 +2,7 @@
 
 #include "bot/bot.qh"
 #include "g_hook.qh"
-#include "mutators/mutators_include.qh"
+#include "mutators/all.qh"
 #include "scores.qh"
 #include "spawnpoints.qh"
 #include "t_items.qh"
index 62a74be22d0289087b1fd1f45b6e689fb026e8a9..cee65e1643ad76bb00787468eeccb980d7115f14 100644 (file)
@@ -18,7 +18,7 @@
     #include "defs.qh"
     #include "../common/notifications.qh"
     #include "../common/deathtypes/all.qh"
-    #include "mutators/mutators_include.qh"
+    #include "mutators/all.qh"
     #include "../common/turrets/sv_turrets.qh"
     #include "../common/vehicles/all.qh"
     #include "../lib/csqcmodel/sv_model.qh"
index e121e6f965a336e27b7e259822ff21822446be79..06913e06aad7c37a7812124b6495d15a7157cde7 100644 (file)
@@ -13,7 +13,7 @@
 #include "g_hook.qh"
 #include "ipban.qh"
 #include "mapvoting.qh"
-#include "mutators/mutators_include.qh"
+#include "mutators/all.qh"
 #include "race.qh"
 #include "scores.qh"
 #include "teamplay.qh"
index c01aca47915748159bbc5eb370e30d3c1dca093d..95133eb2e4b591d4b8ba6861aa262b08e90f096b 100644 (file)
@@ -4,7 +4,7 @@
 #include "constants.qh"
 #include "g_hook.qh"
 #include "ipban.qh"
-#include "mutators/mutators_include.qh"
+#include "mutators/all.qh"
 #include "t_items.qh"
 #include "weapons/accuracy.qh"
 #include "weapons/csqcprojectile.qh"
index 1c9dfe689757308570a3b27bbcb347a89d78e200..050da2bc08b9af97fbcab4c1b0e953f00cd7839a 100644 (file)
@@ -4,7 +4,6 @@
 #include "t_items.qh"
 
 #include "mutators/events.qh"
-#include "mutators/gamemode_race.qh"
 
 #include "../common/constants.qh"
 #include "../common/mapinfo.qh"
diff --git a/qcsrc/server/mutators/all.inc b/qcsrc/server/mutators/all.inc
new file mode 100644 (file)
index 0000000..f67ceae
--- /dev/null
@@ -0,0 +1,41 @@
+#include "mutator/gamemode_assault.qc"
+#include "mutator/gamemode_ca.qc"
+#include "mutator/gamemode_ctf.qc"
+#include "mutator/gamemode_cts.qc"
+#include "mutator/gamemode_deathmatch.qc"
+#include "mutator/gamemode_domination.qc"
+#include "mutator/gamemode_freezetag.qc"
+#include "mutator/gamemode_invasion.qc"
+#include "mutator/gamemode_keepaway.qc"
+#include "mutator/gamemode_keyhunt.qc"
+#include "mutator/gamemode_lms.qc"
+#include "mutator/gamemode_onslaught.qc"
+#include "mutator/gamemode_race.qc"
+#include "mutator/gamemode_tdm.qc"
+
+#include "mutator/mutator_bloodloss.qc"
+#include "mutator/mutator_breakablehook.qc"
+#include "mutator/mutator_buffs.qc"
+#include "mutator/mutator_campcheck.qc"
+#include "mutator/mutator_dodging.qc"
+#include "mutator/mutator_hook.qc"
+#include "mutator/mutator_invincibleproj.qc"
+#include "mutator/mutator_melee_only.qc"
+#include "mutator/mutator_midair.qc"
+#include "mutator/mutator_multijump.qc"
+#include "mutator/mutator_nades.qc"
+#include "mutator/mutator_new_toys.qc"
+#include "mutator/mutator_nix.qc"
+#include "mutator/mutator_overkill.qc"
+#include "mutator/mutator_physical_items.qc"
+#include "mutator/mutator_pinata.qc"
+#include "mutator/mutator_random_gravity.qc"
+#include "mutator/mutator_rocketflying.qc"
+#include "mutator/mutator_rocketminsta.qc"
+#include "mutator/mutator_spawn_near_teammate.qc"
+#include "mutator/mutator_superspec.qc"
+#include "mutator/mutator_touchexplode.qc"
+#include "mutator/mutator_vampirehook.qc"
+#include "mutator/mutator_vampire.qc"
+
+#include "mutator/sandbox.qc"
diff --git a/qcsrc/server/mutators/all.qc b/qcsrc/server/mutators/all.qc
new file mode 100644 (file)
index 0000000..7ad3726
--- /dev/null
@@ -0,0 +1,82 @@
+#if defined(CSQC)
+#elif defined(MENUQC)
+#elif defined(SVQC)
+    #include "../../lib/warpzone/anglestransform.qh"
+    #include "../../lib/warpzone/common.qh"
+    #include "../../lib/warpzone/util_server.qh"
+    #include "../../lib/warpzone/server.qh"
+    #include "../../common/constants.qh"
+    #include "../../common/stats.qh"
+    #include "../../common/teams.qh"
+    #include "../../common/util.qh"
+    #include "../../common/nades/all.qh"
+    #include "../../common/buffs/all.qh"
+    #include "../../common/command/markup.qh"
+    #include "../../common/command/rpn.qh"
+    #include "../../common/command/generic.qh"
+    #include "../../common/command/command.qh"
+    #include "../../common/net_notice.qh"
+    #include "../../common/animdecide.qh"
+    #include "../../common/monsters/all.qh"
+    #include "../../common/monsters/sv_monsters.qh"
+    #include "../../common/monsters/spawn.qh"
+    #include "../../common/weapons/config.qh"
+    #include "../../common/weapons/all.qh"
+    #include "../weapons/accuracy.qh"
+    #include "../weapons/common.qh"
+    #include "../weapons/csqcprojectile.qh"
+    #include "../weapons/hitplot.qh"
+    #include "../weapons/selection.qh"
+    #include "../weapons/spawning.qh"
+    #include "../weapons/throwing.qh"
+    #include "../weapons/tracing.qh"
+    #include "../weapons/weaponstats.qh"
+    #include "../weapons/weaponsystem.qh"
+    #include "../t_items.qh"
+    #include "../autocvars.qh"
+    #include "../constants.qh"
+    #include "../defs.qh"
+    #include "../../common/notifications.qh"
+    #include "../../common/deathtypes/all.qh"
+    #include "all.qh"
+    #include "../../common/turrets/sv_turrets.qh"
+    #include "../../common/vehicles/all.qh"
+    #include "../campaign.qh"
+    #include "../../common/campaign_common.qh"
+    #include "../../common/mapinfo.qh"
+    #include "../command/common.qh"
+    #include "../command/banning.qh"
+    #include "../command/radarmap.qh"
+    #include "../command/vote.qh"
+    #include "../command/getreplies.qh"
+    #include "../command/cmd.qh"
+    #include "../command/sv_cmd.qh"
+    #include "../../common/csqcmodel_settings.qh"
+    #include "../../lib/csqcmodel/common.qh"
+    #include "../../lib/csqcmodel/sv_model.qh"
+    #include "../anticheat.qh"
+    #include "../cheats.qh"
+    #include "../../common/playerstats.qh"
+    #include "../portals.qh"
+    #include "../g_hook.qh"
+    #include "../scores.qh"
+    #include "../spawnpoints.qh"
+    #include "../mapvoting.qh"
+    #include "../ipban.qh"
+    #include "../race.qh"
+    #include "../antilag.qh"
+    #include "../playerdemo.qh"
+    #include "../round_handler.qh"
+    #include "../item_key.qh"
+    #include "../pathlib/pathlib.qh"
+    #include "../../common/vehicles/all.qh"
+#endif
+
+#include "all.qh"
+
+#include "mutator.qh"
+#include "gamemode.qh"
+
+#define IMPLEMENTATION
+#include "all.inc"
+#undef IMPLEMENTATION
diff --git a/qcsrc/server/mutators/all.qh b/qcsrc/server/mutators/all.qh
new file mode 100644 (file)
index 0000000..ad4a5b9
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef SERVER_MUTATORS_H
+#define SERVER_MUTATORS_H
+
+#include "all.inc"
+
+#endif
index 5e5675f40c81907054ed40dd68c86913101b007f..5e2c4635734d27cdd3717ccfe1d76b4df39e37d5 100644 (file)
@@ -1,8 +1,6 @@
 #ifndef GAMEMODE_H
 #define GAMEMODE_H
 
-#include "mutator_nades.qh"
-
 #include "../cl_client.qh"
 #include "../cl_player.qh"
 #include "../cl_impulse.qh"
diff --git a/qcsrc/server/mutators/gamemode_assault.qc b/qcsrc/server/mutators/gamemode_assault.qc
deleted file mode 100644 (file)
index 8558faa..0000000
+++ /dev/null
@@ -1,638 +0,0 @@
-#include "gamemode_assault.qh"
-
-#include "gamemode.qh"
-
-.entity sprite;
-
-// random functions
-void assault_objective_use()
-{SELFPARAM();
-       // activate objective
-       self.health = 100;
-       //print("^2Activated objective ", self.targetname, "=", etos(self), "\n");
-       //print("Activator is ", activator.classname, "\n");
-
-       for (entity e = world; (e = find(e, target, this.targetname)); )
-       {
-               if (e.classname == "target_objective_decrease")
-               {
-                       WITH(entity, self, e, target_objective_decrease_activate());
-               }
-       }
-
-       setself(this);
-}
-
-vector target_objective_spawn_evalfunc(entity player, entity spot, vector current)
-{SELFPARAM();
-       if(self.health < 0 || self.health >= ASSAULT_VALUE_INACTIVE)
-               return '-1 0 0';
-       return current;
-}
-
-// reset this objective. Used when spawning an objective
-// and when a new round starts
-void assault_objective_reset()
-{SELFPARAM();
-       self.health = ASSAULT_VALUE_INACTIVE;
-}
-
-// decrease the health of targeted objectives
-void assault_objective_decrease_use()
-{SELFPARAM();
-       if(activator.team != assault_attacker_team)
-       {
-               // wrong team triggered decrease
-               return;
-       }
-
-       if(other.assault_sprite)
-       {
-               WaypointSprite_Disown(other.assault_sprite, waypointsprite_deadlifetime);
-               if(other.classname == "func_assault_destructible")
-                       other.sprite = world;
-       }
-       else
-               return; // already activated! cannot activate again!
-
-       if(self.enemy.health < ASSAULT_VALUE_INACTIVE)
-       {
-               if(self.enemy.health - self.dmg > 0.5)
-               {
-                       PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.dmg);
-                       self.enemy.health = self.enemy.health - self.dmg;
-               }
-               else
-               {
-                       PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.enemy.health);
-                       PlayerTeamScore_Add(activator, SP_ASSAULT_OBJECTIVES, ST_ASSAULT_OBJECTIVES, 1);
-                       self.enemy.health = -1;
-
-                       entity oldactivator, head;
-
-                       setself(this.enemy);
-                       if(self.message)
-                       FOR_EACH_PLAYER(head)
-                               centerprint(head, self.message);
-
-                       oldactivator = activator;
-                       activator = this;
-                       SUB_UseTargets();
-                       activator = oldactivator;
-                       setself(this);
-               }
-       }
-}
-
-void assault_setenemytoobjective()
-{SELFPARAM();
-       entity objective;
-       for(objective = world; (objective = find(objective, targetname, self.target)); )
-       {
-               if(objective.classname == "target_objective")
-               {
-                       if(self.enemy == world)
-                               self.enemy = objective;
-                       else
-                               objerror("more than one objective as target - fix the map!");
-                       break;
-               }
-       }
-
-       if(self.enemy == world)
-               objerror("no objective as target - fix the map!");
-}
-
-float assault_decreaser_sprite_visible(entity e)
-{SELFPARAM();
-       entity decreaser;
-
-       decreaser = self.assault_decreaser;
-
-       if(decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE)
-               return false;
-
-       return true;
-}
-
-void target_objective_decrease_activate()
-{SELFPARAM();
-       entity ent, spr;
-       self.owner = world;
-       for(ent = world; (ent = find(ent, target, self.targetname)); )
-       {
-               if(ent.assault_sprite != world)
-               {
-                       WaypointSprite_Disown(ent.assault_sprite, waypointsprite_deadlifetime);
-                       if(ent.classname == "func_assault_destructible")
-                               ent.sprite = world;
-               }
-
-               spr = WaypointSprite_SpawnFixed(WP_Assault, 0.5 * (ent.absmin + ent.absmax), ent, assault_sprite, RADARICON_OBJECTIVE);
-               spr.assault_decreaser = self;
-               spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
-               spr.classname = "sprite_waypoint";
-               WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
-               if(ent.classname == "func_assault_destructible")
-               {
-                       WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
-                       WaypointSprite_UpdateMaxHealth(spr, ent.max_health);
-                       WaypointSprite_UpdateHealth(spr, ent.health);
-                       ent.sprite = spr;
-               }
-               else
-                       WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
-       }
-}
-
-void target_objective_decrease_findtarget()
-{
-       assault_setenemytoobjective();
-}
-
-void target_assault_roundend_reset()
-{SELFPARAM();
-       //print("round end reset\n");
-       self.cnt = self.cnt + 1; // up round counter
-       self.winning = 0; // up round
-}
-
-void target_assault_roundend_use()
-{SELFPARAM();
-       self.winning = 1; // round has been won by attackers
-}
-
-void assault_roundstart_use()
-{SELFPARAM();
-       activator = self;
-       SUB_UseTargets();
-
-       //(Re)spawn all turrets
-       for(entity ent = NULL; (ent = find(ent, classname, "turret_main")); ) {
-               // Swap turret teams
-               if(ent.team == NUM_TEAM_1)
-                       ent.team = NUM_TEAM_2;
-               else
-                       ent.team = NUM_TEAM_1;
-
-               // Dubbles as teamchange
-               WITH(entity, self, ent, turret_respawn());
-       }
-}
-
-void assault_wall_think()
-{SELFPARAM();
-       if(self.enemy.health < 0)
-       {
-               self.model = "";
-               self.solid = SOLID_NOT;
-       }
-       else
-       {
-               self.model = self.mdl;
-               self.solid = SOLID_BSP;
-       }
-
-       self.nextthink = time + 0.2;
-}
-
-// trigger new round
-// reset objectives, toggle spawnpoints, reset triggers, ...
-void vehicles_clearreturn(entity veh);
-void vehicles_spawn();
-void assault_new_round()
-{SELFPARAM();
-       //bprint("ASSAULT: new round\n");
-
-       // Eject players from vehicles
-       entity e;
-    FOR_EACH_PLAYER(e)
-    {
-        if(e.vehicle)
-        {
-               WITH(entity, self, e, vehicles_exit(VHEF_RELEASE));
-        }
-    }
-
-    for (entity e_ = findchainflags(vehicle_flags, VHF_ISVEHICLE); e_; e_ = e_.chain)
-    {
-       setself(e_);
-        vehicles_clearreturn(self);
-        vehicles_spawn();
-    }
-
-    setself(this);
-
-       // up round counter
-       self.winning = self.winning + 1;
-
-       // swap attacker/defender roles
-       if(assault_attacker_team == NUM_TEAM_1)
-               assault_attacker_team = NUM_TEAM_2;
-       else
-               assault_attacker_team = NUM_TEAM_1;
-
-       entity ent;
-       for(ent = world; (ent = nextent(ent)); )
-       {
-               if(clienttype(ent) == CLIENTTYPE_NOTACLIENT)
-               {
-                       if(ent.team_saved == NUM_TEAM_1)
-                               ent.team_saved = NUM_TEAM_2;
-                       else if(ent.team_saved == NUM_TEAM_2)
-                               ent.team_saved = NUM_TEAM_1;
-               }
-       }
-
-       // reset the level with a countdown
-       cvar_set("timelimit", ftos(ceil(time - game_starttime) / 60));
-       ReadyRestart_force(); // sets game_starttime
-}
-
-// spawnfuncs
-spawnfunc(info_player_attacker)
-{
-       if (!g_assault) { remove(self); return; }
-
-       self.team = NUM_TEAM_1; // red, gets swapped every round
-       spawnfunc_info_player_deathmatch(this);
-}
-
-spawnfunc(info_player_defender)
-{
-       if (!g_assault) { remove(self); return; }
-
-       self.team = NUM_TEAM_2; // blue, gets swapped every round
-       spawnfunc_info_player_deathmatch(this);
-}
-
-spawnfunc(target_objective)
-{
-       if (!g_assault) { remove(self); return; }
-
-       self.classname = "target_objective";
-       self.use = assault_objective_use;
-       assault_objective_reset();
-       self.reset = assault_objective_reset;
-       self.spawn_evalfunc = target_objective_spawn_evalfunc;
-}
-
-spawnfunc(target_objective_decrease)
-{
-       if (!g_assault) { remove(self); return; }
-
-       self.classname = "target_objective_decrease";
-
-       if(!self.dmg)
-               self.dmg = 101;
-
-       self.use = assault_objective_decrease_use;
-       self.health = ASSAULT_VALUE_INACTIVE;
-       self.max_health = ASSAULT_VALUE_INACTIVE;
-       self.enemy = world;
-
-       InitializeEntity(self, target_objective_decrease_findtarget, INITPRIO_FINDTARGET);
-}
-
-// destructible walls that can be used to trigger target_objective_decrease
-spawnfunc(func_breakable);
-spawnfunc(func_assault_destructible)
-{
-       if (!g_assault) { remove(self); return; }
-
-       self.spawnflags = 3;
-       self.classname = "func_assault_destructible";
-
-       if(assault_attacker_team == NUM_TEAM_1)
-               self.team = NUM_TEAM_2;
-       else
-               self.team = NUM_TEAM_1;
-
-       spawnfunc_func_breakable(this);
-}
-
-spawnfunc(func_assault_wall)
-{
-       if (!g_assault) { remove(self); return; }
-
-       self.classname = "func_assault_wall";
-       self.mdl = self.model;
-       _setmodel(self, self.mdl);
-       self.solid = SOLID_BSP;
-       self.think = assault_wall_think;
-       self.nextthink = time;
-       InitializeEntity(self, assault_setenemytoobjective, INITPRIO_FINDTARGET);
-}
-
-spawnfunc(target_assault_roundend)
-{
-       if (!g_assault) { remove(self); return; }
-
-       self.winning = 0; // round not yet won by attackers
-       self.classname = "target_assault_roundend";
-       self.use = target_assault_roundend_use;
-       self.cnt = 0; // first round
-       self.reset = target_assault_roundend_reset;
-}
-
-spawnfunc(target_assault_roundstart)
-{
-       if (!g_assault) { remove(self); return; }
-
-       assault_attacker_team = NUM_TEAM_1;
-       self.classname = "target_assault_roundstart";
-       self.use = assault_roundstart_use;
-       self.reset2 = assault_roundstart_use;
-       InitializeEntity(self, assault_roundstart_use, INITPRIO_FINDTARGET);
-}
-
-// legacy bot code
-void havocbot_goalrating_ast_targets(float ratingscale)
-{SELFPARAM();
-       entity ad, best, wp, tod;
-       float radius, found, bestvalue;
-       vector p;
-
-       ad = findchain(classname, "func_assault_destructible");
-
-       for (; ad; ad = ad.chain)
-       {
-               if (ad.target == "")
-                       continue;
-
-               if (!ad.bot_attack)
-                       continue;
-
-               found = false;
-               for(tod = world; (tod = find(tod, targetname, ad.target)); )
-               {
-                       if(tod.classname == "target_objective_decrease")
-                       {
-                               if(tod.enemy.health > 0 && tod.enemy.health < ASSAULT_VALUE_INACTIVE)
-                               {
-                               //      dprint(etos(ad),"\n");
-                                       found = true;
-                                       break;
-                               }
-                       }
-               }
-
-               if(!found)
-               {
-               ///     dprint("target not found\n");
-                       continue;
-               }
-               /// dprint("target #", etos(ad), " found\n");
-
-
-               p = 0.5 * (ad.absmin + ad.absmax);
-       //      dprint(vtos(ad.origin), " ", vtos(ad.absmin), " ", vtos(ad.absmax),"\n");
-       //      te_knightspike(p);
-       //      te_lightning2(world, '0 0 0', p);
-
-               // Find and rate waypoints around it
-               found = false;
-               best = world;
-               bestvalue = 99999999999;
-               for(radius=0; radius<1500 && !found; radius+=500)
-               {
-                       for(wp=findradius(p, radius); wp; wp=wp.chain)
-                       {
-                               if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
-                               if(wp.classname=="waypoint")
-                               if(checkpvs(wp.origin, ad))
-                               {
-                                       found = true;
-                                       if(wp.cnt<bestvalue)
-                                       {
-                                               best = wp;
-                                               bestvalue = wp.cnt;
-                                       }
-                               }
-                       }
-               }
-
-               if(best)
-               {
-               ///     dprint("waypoints around target were found\n");
-               //      te_lightning2(world, '0 0 0', best.origin);
-               //      te_knightspike(best.origin);
-
-                       navigation_routerating(best, ratingscale, 4000);
-                       best.cnt += 1;
-
-                       self.havocbot_attack_time = 0;
-
-                       if(checkpvs(self.view_ofs,ad))
-                       if(checkpvs(self.view_ofs,best))
-                       {
-                       //      dprint("increasing attack time for this target\n");
-                               self.havocbot_attack_time = time + 2;
-                       }
-               }
-       }
-}
-
-void havocbot_role_ast_offense()
-{SELFPARAM();
-       if(self.deadflag != DEAD_NO)
-       {
-               self.havocbot_attack_time = 0;
-               havocbot_ast_reset_role(self);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + 120;
-
-       if (time > self.havocbot_role_timeout)
-       {
-               havocbot_ast_reset_role(self);
-               return;
-       }
-
-       if(self.havocbot_attack_time>time)
-               return;
-
-       if (self.bot_strategytime < time)
-       {
-               navigation_goalrating_start();
-               havocbot_goalrating_enemyplayers(20000, self.origin, 650);
-               havocbot_goalrating_ast_targets(20000);
-               havocbot_goalrating_items(15000, self.origin, 10000);
-               navigation_goalrating_end();
-
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-       }
-}
-
-void havocbot_role_ast_defense()
-{SELFPARAM();
-       if(self.deadflag != DEAD_NO)
-       {
-               self.havocbot_attack_time = 0;
-               havocbot_ast_reset_role(self);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + 120;
-
-       if (time > self.havocbot_role_timeout)
-       {
-               havocbot_ast_reset_role(self);
-               return;
-       }
-
-       if(self.havocbot_attack_time>time)
-               return;
-
-       if (self.bot_strategytime < time)
-       {
-               navigation_goalrating_start();
-               havocbot_goalrating_enemyplayers(20000, self.origin, 3000);
-               havocbot_goalrating_ast_targets(20000);
-               havocbot_goalrating_items(15000, self.origin, 10000);
-               navigation_goalrating_end();
-
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-       }
-}
-
-void havocbot_role_ast_setrole(entity bot, float role)
-{
-       switch(role)
-       {
-               case HAVOCBOT_AST_ROLE_DEFENSE:
-                       bot.havocbot_role = havocbot_role_ast_defense;
-                       bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_AST_ROLE_OFFENSE:
-                       bot.havocbot_role = havocbot_role_ast_offense;
-                       bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-       }
-}
-
-void havocbot_ast_reset_role(entity bot)
-{SELFPARAM();
-       if(self.deadflag != DEAD_NO)
-               return;
-
-       if(bot.team == assault_attacker_team)
-               havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_OFFENSE);
-       else
-               havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_DEFENSE);
-}
-
-// mutator hooks
-MUTATOR_HOOKFUNCTION(as, PlayerSpawn)
-{SELFPARAM();
-       if(self.team == assault_attacker_team)
-               Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
-       else
-               Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(as, TurretSpawn)
-{SELFPARAM();
-       if(!self.team || self.team == MAX_SHOT_DISTANCE)
-               self.team = 5; // this gets reversed when match starts?
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(as, VehicleSpawn)
-{SELFPARAM();
-       self.nextthink = time + 0.5;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole)
-{SELFPARAM();
-       havocbot_ast_reset_role(self);
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, PlayHitsound)
-{
-       return (frag_victim.classname == "func_assault_destructible");
-}
-
-MUTATOR_HOOKFUNCTION(as, GetTeamCount)
-{
-       // assault always has 2 teams
-       c1 = c2 = 0;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, CheckRules_World)
-{
-       ret_float = WinningCondition_Assault();
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, ReadLevelCvars)
-{
-       // no assault warmups
-       warmup_stage = 0;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn)
-{
-       switch(self.classname)
-       {
-               case "info_player_team1":
-               case "info_player_team2":
-               case "info_player_team3":
-               case "info_player_team4":
-                       return true;
-       }
-
-       return false;
-}
-
-// scoreboard setup
-void assault_ScoreRules()
-{
-       ScoreRules_basics(2, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, true);
-       ScoreInfo_SetLabel_TeamScore(  ST_ASSAULT_OBJECTIVES,    "objectives",      SFL_SORT_PRIO_PRIMARY);
-       ScoreInfo_SetLabel_PlayerScore(SP_ASSAULT_OBJECTIVES,    "objectives",      SFL_SORT_PRIO_PRIMARY);
-       ScoreRules_basics_end();
-}
-
-REGISTER_MUTATOR(as, g_assault)
-{
-       ActivateTeamplay();
-       have_team_spawns = -1; // request team spawns
-
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This is a game type and it cannot be added at runtime.");
-               assault_ScoreRules();
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // we actually cannot roll back assault_Initialize here
-               // BUT: we don't need to! If this gets called, adding always
-               // succeeds.
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This is a game type and it cannot be removed at runtime.");
-               return -1;
-       }
-
-       return 0;
-}
diff --git a/qcsrc/server/mutators/gamemode_assault.qh b/qcsrc/server/mutators/gamemode_assault.qh
deleted file mode 100644 (file)
index 1266492..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-#ifndef GAMEMODE_ASSAULT_H
-#define GAMEMODE_ASSAULT_H
-// sprites
-.entity assault_decreaser;
-.entity assault_sprite;
-
-// legacy bot defs
-const int HAVOCBOT_AST_ROLE_NONE = 0;
-const int HAVOCBOT_AST_ROLE_DEFENSE = 2;
-const int HAVOCBOT_AST_ROLE_OFFENSE = 4;
-
-.int havocbot_role_flags;
-.float havocbot_attack_time;
-
-.void() havocbot_role;
-.void() havocbot_previous_role;
-
-void() havocbot_role_ast_defense;
-void() havocbot_role_ast_offense;
-.entity havocbot_ast_target;
-
-void(entity bot) havocbot_ast_reset_role;
-
-void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
-void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
-
-// scoreboard stuff
-const float ST_ASSAULT_OBJECTIVES = 1;
-const float SP_ASSAULT_OBJECTIVES = 4;
-
-// predefined spawnfuncs
-void target_objective_decrease_activate();
-#endif
diff --git a/qcsrc/server/mutators/gamemode_ca.qc b/qcsrc/server/mutators/gamemode_ca.qc
deleted file mode 100644 (file)
index 1a9b1da..0000000
+++ /dev/null
@@ -1,524 +0,0 @@
-#include "gamemode_ca.qh"
-
-#include "gamemode.qh"
-
-float autocvar_g_ca_damage2score_multiplier;
-int autocvar_g_ca_point_leadlimit;
-int autocvar_g_ca_point_limit;
-float autocvar_g_ca_round_timelimit;
-bool autocvar_g_ca_spectate_enemies;
-int autocvar_g_ca_teams;
-int autocvar_g_ca_teams_override;
-bool autocvar_g_ca_team_spawns;
-float autocvar_g_ca_warmup;
-
-float ca_teams;
-float allowed_to_spawn;
-
-const float ST_CA_ROUNDS = 1;
-void ca_ScoreRules(float teams)
-{
-       ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
-       ScoreInfo_SetLabel_TeamScore(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY);
-       ScoreRules_basics_end();
-}
-
-void CA_count_alive_players()
-{
-       entity e;
-       total_players = redalive = bluealive = yellowalive = pinkalive = 0;
-       FOR_EACH_PLAYER(e) {
-               if(e.team == NUM_TEAM_1)
-               {
-                       ++total_players;
-                       if (e.health >= 1) ++redalive;
-               }
-               else if(e.team == NUM_TEAM_2)
-               {
-                       ++total_players;
-                       if (e.health >= 1) ++bluealive;
-               }
-               else if(e.team == NUM_TEAM_3)
-               {
-                       ++total_players;
-                       if (e.health >= 1) ++yellowalive;
-               }
-               else if(e.team == NUM_TEAM_4)
-               {
-                       ++total_players;
-                       if (e.health >= 1) ++pinkalive;
-               }
-       }
-       FOR_EACH_REALCLIENT(e) {
-               e.redalive_stat = redalive;
-               e.bluealive_stat = bluealive;
-               e.yellowalive_stat = yellowalive;
-               e.pinkalive_stat = pinkalive;
-       }
-}
-
-float CA_GetWinnerTeam()
-{
-       float winner_team = 0;
-       if(redalive >= 1)
-               winner_team = NUM_TEAM_1;
-       if(bluealive >= 1)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_2;
-       }
-       if(yellowalive >= 1)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_3;
-       }
-       if(pinkalive >= 1)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_4;
-       }
-       if(winner_team)
-               return winner_team;
-       return -1; // no player left
-}
-
-#define CA_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
-#define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == ca_teams)
-float CA_CheckWinner()
-{
-       entity e;
-       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
-               allowed_to_spawn = false;
-               round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
-               FOR_EACH_PLAYER(e)
-                       nades_Clear(e);
-               return 1;
-       }
-
-       CA_count_alive_players();
-       if(CA_ALIVE_TEAMS() > 1)
-               return 0;
-
-       float winner_team = CA_GetWinnerTeam();
-       if(winner_team > 0)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
-               TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1);
-       }
-       else if(winner_team == -1)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
-       }
-
-       allowed_to_spawn = false;
-       round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
-
-       FOR_EACH_PLAYER(e)
-               nades_Clear(e);
-
-       return 1;
-}
-
-void CA_RoundStart()
-{
-       if(warmup_stage)
-               allowed_to_spawn = true;
-       else
-               allowed_to_spawn = false;
-}
-
-float CA_CheckTeams()
-{
-       static float prev_missing_teams_mask;
-       allowed_to_spawn = true;
-       CA_count_alive_players();
-       if(CA_ALIVE_TEAMS_OK())
-       {
-               if(prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               return 1;
-       }
-       if(total_players == 0)
-       {
-               if(prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               return 0;
-       }
-       float missing_teams_mask = (!redalive) + (!bluealive) * 2;
-       if(ca_teams >= 3) missing_teams_mask += (!yellowalive) * 4;
-       if(ca_teams >= 4) missing_teams_mask += (!pinkalive) * 8;
-       if(prev_missing_teams_mask != missing_teams_mask)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
-               prev_missing_teams_mask = missing_teams_mask;
-       }
-       return 0;
-}
-
-float ca_isEliminated(entity e)
-{
-       if(e.caplayer == 1 && (e.deadflag != DEAD_NO || e.frags == FRAGS_LMS_LOSER))
-               return true;
-       if(e.caplayer == 0.5)
-               return true;
-       return false;
-}
-
-// Returns next available player to spectate if g_ca_spectate_enemies == 0
-entity CA_SpectateNext(entity player, entity start)
-{
-       if(SAME_TEAM(start, player))
-               return start;
-
-       entity spec_other = start;
-       // continue from current player
-       while(spec_other && DIFF_TEAM(spec_other, player))
-               spec_other = find(spec_other, classname, "player");
-
-       if (!spec_other)
-       {
-               // restart from begining
-               spec_other = find(spec_other, classname, "player");
-               while(spec_other && DIFF_TEAM(spec_other, player))
-                       spec_other = find(spec_other, classname, "player");
-       }
-
-       return spec_other;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerSpawn)
-{SELFPARAM();
-       self.caplayer = 1;
-       if(!warmup_stage)
-               eliminatedPlayers.SendFlags |= 1;
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
-{SELFPARAM();
-       if(!allowed_to_spawn)
-       if(IS_PLAYER(self)) // this is true even when player is trying to join
-       {
-               self.classname = "observer";
-               if(self.jointime != time) //not when connecting
-               if(!self.caplayer)
-               {
-                       self.caplayer = 0.5;
-                       if(IS_REAL_CLIENT(self))
-                               Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CA_JOIN_LATE);
-               }
-       }
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, reset_map_players)
-{SELFPARAM();
-       entity e;
-       FOR_EACH_CLIENT(e)
-       {
-               setself(e);
-               self.killcount = 0;
-               if(!self.caplayer && IS_BOT_CLIENT(self))
-               {
-                       self.team = -1;
-                       self.caplayer = 1;
-               }
-               if(self.caplayer)
-               {
-                       self.classname = "player";
-                       self.caplayer = 1;
-                       PutClientInServer();
-               }
-       }
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientConnect)
-{SELFPARAM();
-       self.classname = "observer";
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, reset_map_global)
-{
-       allowed_to_spawn = true;
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, GetTeamCount, CBC_ORDER_EXCLUSIVE)
-{
-       ret_float = ca_teams;
-       return false;
-}
-
-entity ca_LastPlayerForTeam()
-{SELFPARAM();
-       entity pl, last_pl = world;
-       FOR_EACH_PLAYER(pl)
-       {
-               if(pl.health >= 1)
-               if(pl != self)
-               if(pl.team == self.team)
-               if(!last_pl)
-                       last_pl = pl;
-               else
-                       return world;
-       }
-       return last_pl;
-}
-
-void ca_LastPlayerForTeam_Notify()
-{
-       if(round_handler_IsActive())
-       if(round_handler_IsRoundStarted())
-       {
-               entity pl = ca_LastPlayerForTeam();
-               if(pl)
-                       Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerDies)
-{SELFPARAM();
-       ca_LastPlayerForTeam_Notify();
-       if(!allowed_to_spawn)
-               self.respawn_flags =  RESPAWN_SILENT;
-       if(!warmup_stage)
-               eliminatedPlayers.SendFlags |= 1;
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
-{SELFPARAM();
-       if(self.caplayer == 1)
-               ca_LastPlayerForTeam_Notify();
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ForbidPlayerScore_Clear)
-{
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
-{SELFPARAM();
-       if(self.caplayer == 1)
-               ca_LastPlayerForTeam_Notify();
-       if(self.killindicator_teamchange == -2)
-               self.caplayer = 0;
-       if(self.caplayer)
-               self.frags = FRAGS_LMS_LOSER;
-       if(!warmup_stage)
-               eliminatedPlayers.SendFlags |= 1;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon)
-{
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST)
-{
-       frag_score = 0; // score will be given to the winner team when the round ends
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SetStartItems)
-{
-       start_items &= ~IT_UNLIMITED_AMMO;
-       start_health       = warmup_start_health       = cvar("g_lms_start_health");
-       start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
-       start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
-       start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
-       start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
-       start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
-       start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
-       start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
-
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerDamage_Calculate)
-{
-       if(IS_PLAYER(frag_target))
-       if(frag_target.deadflag == DEAD_NO)
-       if(frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id)
-               frag_damage = 0;
-
-       frag_mirrordamage = 0;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ca, FilterItem)
-{SELFPARAM();
-       if(autocvar_g_powerups <= 0)
-       if(self.flags & FL_POWERUP)
-               return true;
-
-       if(autocvar_g_pickup_items <= 0)
-               return true;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor)
-{
-       float excess = max(0, frag_damage - damage_take - damage_save);
-
-       if(frag_target != frag_attacker && IS_PLAYER(frag_attacker))
-               PlayerTeamScore_Add(frag_attacker, SP_SCORE, ST_SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier);
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerRegen)
-{
-       // no regeneration in CA
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
-{
-       // announce remaining frags
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectateSet)
-{
-       if(!autocvar_g_ca_spectate_enemies && self.caplayer)
-       if(DIFF_TEAM(spec_player, self))
-               return true;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectateNext)
-{SELFPARAM();
-       if(!autocvar_g_ca_spectate_enemies && self.caplayer)
-       {
-               spec_player = CA_SpectateNext(self, spec_player);
-               return true;
-       }
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectatePrev)
-{SELFPARAM();
-       if(!autocvar_g_ca_spectate_enemies && self.caplayer)
-       {
-               do { spec_player = spec_player.chain; }
-               while(spec_player && DIFF_TEAM(spec_player, self));
-
-               if (!spec_player)
-               {
-                       spec_player = spec_first;
-                       while(spec_player && DIFF_TEAM(spec_player, self))
-                               spec_player = spec_player.chain;
-                       if(spec_player == self.enemy)
-                               return MUT_SPECPREV_RETURN;
-               }
-       }
-
-       return MUT_SPECPREV_FOUND;
-}
-
-MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
-{
-       entity head;
-       FOR_EACH_REALCLIENT(head)
-       {
-               if(IS_PLAYER(head) || head.caplayer == 1)
-                       ++bot_activerealplayers;
-               ++bot_realplayers;
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate)
-{
-       if(self.caplayer)
-       {
-               // they're going to spec, we can do other checks
-               if(autocvar_sv_spectate && (IS_SPEC(self) || IS_OBSERVER(self)))
-                       Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CA_LEAVE);
-               return MUT_SPECCMD_FORCE;
-       }
-
-       return MUT_SPECCMD_CONTINUE;
-}
-
-MUTATOR_HOOKFUNCTION(ca, WantWeapon)
-{
-       want_allguns = true;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus)
-{
-       if(set_player.caplayer == 1)
-               return true;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SetWeaponArena)
-{
-       // most weapons arena
-       if(ret_string == "0" || ret_string == "")
-               ret_string = "most";
-       return false;
-}
-
-void ca_Initialize()
-{
-       allowed_to_spawn = true;
-
-       ca_teams = autocvar_g_ca_teams_override;
-       if(ca_teams < 2)
-               ca_teams = autocvar_g_ca_teams;
-       ca_teams = bound(2, ca_teams, 4);
-       ret_float = ca_teams;
-       ca_ScoreRules(ca_teams);
-
-       round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart);
-       round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
-
-       addstat(STAT_REDALIVE, AS_INT, redalive_stat);
-       addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
-       addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
-       addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
-
-       EliminatedPlayers_Init(ca_isEliminated);
-}
-
-REGISTER_MUTATOR(ca, g_ca)
-{
-       ActivateTeamplay();
-       SetLimits(autocvar_g_ca_point_limit, autocvar_g_ca_point_leadlimit, -1, -1);
-
-       if(autocvar_g_ca_team_spawns)
-               have_team_spawns = -1; // request team spawns
-
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This is a game type and it cannot be added at runtime.");
-               ca_Initialize();
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This is a game type and it cannot be removed at runtime.");
-               return -1;
-       }
-
-       return 0;
-}
diff --git a/qcsrc/server/mutators/gamemode_ca.qh b/qcsrc/server/mutators/gamemode_ca.qh
deleted file mode 100644 (file)
index bf6686d..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-#ifndef GAMEMODE_CA_H
-#define GAMEMODE_CA_H
-// should be removed in the future, as other code should not have to care
-.float caplayer; // 0.5 if scheduled to join the next round
-#endif
diff --git a/qcsrc/server/mutators/gamemode_ctf.qc b/qcsrc/server/mutators/gamemode_ctf.qc
deleted file mode 100644 (file)
index 016f9bd..0000000
+++ /dev/null
@@ -1,2606 +0,0 @@
-#include "gamemode_ctf.qh"
-
-#include "gamemode.qh"
-
-#ifdef SVQC
-#include "../../common/vehicles/all.qh"
-#include "../teamplay.qh"
-#endif
-
-#include "../../lib/warpzone/common.qh"
-
-bool autocvar_g_ctf_allow_vehicle_carry;
-bool autocvar_g_ctf_allow_vehicle_touch;
-bool autocvar_g_ctf_allow_monster_touch;
-bool autocvar_g_ctf_throw;
-float autocvar_g_ctf_throw_angle_max;
-float autocvar_g_ctf_throw_angle_min;
-int autocvar_g_ctf_throw_punish_count;
-float autocvar_g_ctf_throw_punish_delay;
-float autocvar_g_ctf_throw_punish_time;
-float autocvar_g_ctf_throw_strengthmultiplier;
-float autocvar_g_ctf_throw_velocity_forward;
-float autocvar_g_ctf_throw_velocity_up;
-float autocvar_g_ctf_drop_velocity_up;
-float autocvar_g_ctf_drop_velocity_side;
-bool autocvar_g_ctf_oneflag_reverse;
-bool autocvar_g_ctf_portalteleport;
-bool autocvar_g_ctf_pass;
-float autocvar_g_ctf_pass_arc;
-float autocvar_g_ctf_pass_arc_max;
-float autocvar_g_ctf_pass_directional_max;
-float autocvar_g_ctf_pass_directional_min;
-float autocvar_g_ctf_pass_radius;
-float autocvar_g_ctf_pass_wait;
-bool autocvar_g_ctf_pass_request;
-float autocvar_g_ctf_pass_turnrate;
-float autocvar_g_ctf_pass_timelimit;
-float autocvar_g_ctf_pass_velocity;
-bool autocvar_g_ctf_dynamiclights;
-float autocvar_g_ctf_flag_collect_delay;
-float autocvar_g_ctf_flag_damageforcescale;
-bool autocvar_g_ctf_flag_dropped_waypoint;
-bool autocvar_g_ctf_flag_dropped_floatinwater;
-bool autocvar_g_ctf_flag_glowtrails;
-int autocvar_g_ctf_flag_health;
-bool autocvar_g_ctf_flag_return;
-float autocvar_g_ctf_flag_return_carried_radius;
-float autocvar_g_ctf_flag_return_time;
-bool autocvar_g_ctf_flag_return_when_unreachable;
-float autocvar_g_ctf_flag_return_damage;
-float autocvar_g_ctf_flag_return_damage_delay;
-float autocvar_g_ctf_flag_return_dropped;
-float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
-float autocvar_g_ctf_flagcarrier_auto_helpme_time;
-float autocvar_g_ctf_flagcarrier_selfdamagefactor;
-float autocvar_g_ctf_flagcarrier_selfforcefactor;
-float autocvar_g_ctf_flagcarrier_damagefactor;
-float autocvar_g_ctf_flagcarrier_forcefactor;
-//float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
-bool autocvar_g_ctf_fullbrightflags;
-bool autocvar_g_ctf_ignore_frags;
-int autocvar_g_ctf_score_capture;
-int autocvar_g_ctf_score_capture_assist;
-int autocvar_g_ctf_score_kill;
-int autocvar_g_ctf_score_penalty_drop;
-int autocvar_g_ctf_score_penalty_returned;
-int autocvar_g_ctf_score_pickup_base;
-int autocvar_g_ctf_score_pickup_dropped_early;
-int autocvar_g_ctf_score_pickup_dropped_late;
-int autocvar_g_ctf_score_return;
-float autocvar_g_ctf_shield_force;
-float autocvar_g_ctf_shield_max_ratio;
-int autocvar_g_ctf_shield_min_negscore;
-bool autocvar_g_ctf_stalemate;
-int autocvar_g_ctf_stalemate_endcondition;
-float autocvar_g_ctf_stalemate_time;
-bool autocvar_g_ctf_reverse;
-float autocvar_g_ctf_dropped_capture_delay;
-float autocvar_g_ctf_dropped_capture_radius;
-
-void ctf_FakeTimeLimit(entity e, float t)
-{
-       msg_entity = e;
-       WriteByte(MSG_ONE, 3); // svc_updatestat
-       WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
-       if(t < 0)
-               WriteCoord(MSG_ONE, autocvar_timelimit);
-       else
-               WriteCoord(MSG_ONE, (t + 1) / 60);
-}
-
-void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
-{
-       if(autocvar_sv_eventlog)
-               GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
-               //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void ctf_CaptureRecord(entity flag, entity player)
-{
-       float cap_record = ctf_captimerecord;
-       float cap_time = (time - flag.ctf_pickuptime);
-       string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
-
-       // notify about shit
-       if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
-       else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
-       else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
-       else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
-
-       // write that shit in the database
-       if(!ctf_oneflag) // but not in 1-flag mode
-       if((!ctf_captimerecord) || (cap_time < cap_record))
-       {
-               ctf_captimerecord = cap_time;
-               db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
-               db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
-               write_recordmarker(player, (time - cap_time), cap_time);
-       }
-}
-
-void ctf_FlagcarrierWaypoints(entity player)
-{
-       WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
-       WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
-       WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
-       WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
-}
-
-void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
-{
-       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 = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
-       float current_height = (initial_height * min(1, (current_distance / flag.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(flag.origin, targpos, MOVE_NOMONSTERS, flag);
-               if(trace_fraction < 1)
-               {
-                       //print("normal arc line failed, trying to find new pos...");
-                       WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
-                       targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
-                       WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
-                       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; }
-
-       //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
-
-       vector desired_direction = normalize(targpos - from);
-       if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
-       else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
-}
-
-bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
-{
-       if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
-       {
-               // directional tracing only
-               float spreadlimit;
-               makevectors(passer_angle);
-
-               // find the closest point on the enemy to the center of the attack
-               float h; // hypotenuse, which is the distance between attacker to head
-               float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
-
-               h = vlen(head_center - passer_center);
-               a = h * (normalize(head_center - passer_center) * v_forward);
-
-               vector nearest_on_line = (passer_center + a * v_forward);
-               float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
-
-               spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
-               spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
-
-               if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
-                       { return true; }
-               else
-                       { return false; }
-       }
-       else { return true; }
-}
-
-
-// =======================
-// CaptureShield Functions
-// =======================
-
-bool ctf_CaptureShield_CheckStatus(entity p)
-{
-       int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
-       entity e;
-       int players_worseeq, players_total;
-
-       if(ctf_captureshield_max_ratio <= 0)
-               return false;
-
-       s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
-       s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
-       s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
-       s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
-
-       sr = ((s - s2) + (s3 + s4));
-
-       if(sr >= -ctf_captureshield_min_negscore)
-               return false;
-
-       players_total = players_worseeq = 0;
-       FOR_EACH_PLAYER(e)
-       {
-               if(DIFF_TEAM(e, p))
-                       continue;
-               se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
-               se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
-               se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
-               se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
-
-               ser = ((se - se2) + (se3 + se4));
-
-               if(ser <= sr)
-                       ++players_worseeq;
-               ++players_total;
-       }
-
-       // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
-       // use this rule here
-
-       if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
-               return false;
-
-       return true;
-}
-
-void ctf_CaptureShield_Update(entity player, bool wanted_status)
-{
-       bool updated_status = ctf_CaptureShield_CheckStatus(player);
-       if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
-       {
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
-               player.ctf_captureshielded = updated_status;
-       }
-}
-
-bool ctf_CaptureShield_Customize()
-{SELFPARAM();
-       if(!other.ctf_captureshielded) { return false; }
-       if(CTF_SAMETEAM(self, other)) { return false; }
-
-       return true;
-}
-
-void ctf_CaptureShield_Touch()
-{SELFPARAM();
-       if(!other.ctf_captureshielded) { return; }
-       if(CTF_SAMETEAM(self, other)) { return; }
-
-       vector mymid = (self.absmin + self.absmax) * 0.5;
-       vector othermid = (other.absmin + other.absmax) * 0.5;
-
-       Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
-       if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
-}
-
-void ctf_CaptureShield_Spawn(entity flag)
-{SELFPARAM();
-       entity shield = spawn();
-
-       shield.enemy = self;
-       shield.team = self.team;
-       shield.touch = ctf_CaptureShield_Touch;
-       shield.customizeentityforclient = ctf_CaptureShield_Customize;
-       shield.classname = "ctf_captureshield";
-       shield.effects = EF_ADDITIVE;
-       shield.movetype = MOVETYPE_NOCLIP;
-       shield.solid = SOLID_TRIGGER;
-       shield.avelocity = '7 0 11';
-       shield.scale = 0.5;
-
-       setorigin(shield, self.origin);
-       setmodel(shield, MDL_CTF_SHIELD);
-       setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
-}
-
-
-// ====================
-// Drop/Pass/Throw Code
-// ====================
-
-void ctf_Handle_Drop(entity flag, entity player, int droptype)
-{
-       // declarations
-       player = (player ? player : flag.pass_sender);
-
-       // main
-       flag.movetype = MOVETYPE_TOSS;
-       flag.takedamage = DAMAGE_YES;
-       flag.angles = '0 0 0';
-       flag.health = flag.max_flag_health;
-       flag.ctf_droptime = time;
-       flag.ctf_dropper = player;
-       flag.ctf_status = FLAG_DROPPED;
-
-       // messages and sounds
-       Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
-       _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
-       ctf_EventLog("dropped", player.team, player);
-
-       // scoring
-       PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
-       PlayerScore_Add(player, SP_CTF_DROPS, 1);
-
-       // waypoints
-       if(autocvar_g_ctf_flag_dropped_waypoint) {
-               entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG);
-               wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
-       }
-
-       if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
-       {
-               WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
-               WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
-       }
-
-       player.throw_antispam = time + autocvar_g_ctf_pass_wait;
-
-       if(droptype == DROP_PASS)
-       {
-               flag.pass_distance = 0;
-               flag.pass_sender = world;
-               flag.pass_target = world;
-       }
-}
-
-void ctf_Handle_Retrieve(entity flag, entity player)
-{
-       entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
-       entity sender = flag.pass_sender;
-
-       // transfer flag to player
-       flag.owner = player;
-       flag.owner.flagcarried = flag;
-
-       // reset flag
-       if(player.vehicle)
-       {
-               setattachment(flag, player.vehicle, "");
-               setorigin(flag, VEHICLE_FLAG_OFFSET);
-               flag.scale = VEHICLE_FLAG_SCALE;
-       }
-       else
-       {
-               setattachment(flag, player, "");
-               setorigin(flag, FLAG_CARRY_OFFSET);
-       }
-       flag.movetype = MOVETYPE_NONE;
-       flag.takedamage = DAMAGE_NO;
-       flag.solid = SOLID_NOT;
-       flag.angles = '0 0 0';
-       flag.ctf_status = FLAG_CARRY;
-
-       // messages and sounds
-       _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
-       ctf_EventLog("receive", flag.team, player);
-
-       FOR_EACH_REALPLAYER(tmp_player)
-       {
-               if(tmp_player == sender)
-                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname);
-               else if(tmp_player == player)
-                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname);
-               else if(SAME_TEAM(tmp_player, sender))
-                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname);
-       }
-
-       // create new waypoint
-       ctf_FlagcarrierWaypoints(player);
-
-       sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
-       player.throw_antispam = sender.throw_antispam;
-
-       flag.pass_distance = 0;
-       flag.pass_sender = world;
-       flag.pass_target = world;
-}
-
-void ctf_Handle_Throw(entity player, entity receiver, int droptype)
-{
-       entity flag = player.flagcarried;
-       vector targ_origin, flag_velocity;
-
-       if(!flag) { return; }
-       if((droptype == DROP_PASS) && !receiver) { return; }
-
-       if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
-
-       // reset the flag
-       setattachment(flag, world, "");
-       setorigin(flag, player.origin + FLAG_DROP_OFFSET);
-       flag.owner.flagcarried = world;
-       flag.owner = world;
-       flag.solid = SOLID_TRIGGER;
-       flag.ctf_dropper = player;
-       flag.ctf_droptime = time;
-
-       flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
-
-       switch(droptype)
-       {
-               case DROP_PASS:
-               {
-                       // warpzone support:
-                       // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
-                       // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
-                       WarpZone_RefSys_Copy(flag, receiver);
-                       WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
-                       targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
-
-                       flag.pass_distance = vlen((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' *  player.origin.x) + ('0 1 0' *  player.origin.y))); // for the sake of this check, exclude Z axis
-                       ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
-
-                       // main
-                       flag.movetype = MOVETYPE_FLY;
-                       flag.takedamage = DAMAGE_NO;
-                       flag.pass_sender = player;
-                       flag.pass_target = receiver;
-                       flag.ctf_status = FLAG_PASSING;
-
-                       // other
-                       _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
-                       WarpZone_TrailParticles(world, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
-                       ctf_EventLog("pass", flag.team, player);
-                       break;
-               }
-
-               case DROP_THROW:
-               {
-                       makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
-
-                       flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
-                       flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
-                       ctf_Handle_Drop(flag, player, droptype);
-                       break;
-               }
-
-               case DROP_RESET:
-               {
-                       flag.velocity = '0 0 0'; // do nothing
-                       break;
-               }
-
-               default:
-               case DROP_NORMAL:
-               {
-                       flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
-                       ctf_Handle_Drop(flag, player, droptype);
-                       break;
-               }
-       }
-
-       // kill old waypointsprite
-       WaypointSprite_Ping(player.wps_flagcarrier);
-       WaypointSprite_Kill(player.wps_flagcarrier);
-
-       if(player.wps_enemyflagcarrier)
-               WaypointSprite_Kill(player.wps_enemyflagcarrier);
-
-       // captureshield
-       ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
-}
-
-
-// ==============
-// Event Handlers
-// ==============
-
-void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
-{
-       entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
-       entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
-       entity player_team_flag = world, tmp_entity;
-       float old_time, new_time;
-
-       if(!player) { return; } // without someone to give the reward to, we can't possibly cap
-       if(CTF_DIFFTEAM(player, flag)) { return; }
-
-       if(ctf_oneflag)
-       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
-       if(SAME_TEAM(tmp_entity, player))
-       {
-               player_team_flag = tmp_entity;
-               break;
-       }
-
-       nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
-
-       player.throw_prevtime = time;
-       player.throw_count = 0;
-
-       // messages and sounds
-       Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
-       ctf_CaptureRecord(enemy_flag, player);
-       _sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
-
-       switch(capturetype)
-       {
-               case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
-               case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
-               default: break;
-       }
-
-       // scoring
-       PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
-       PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
-
-       old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
-       new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
-       if(!old_time || new_time < old_time)
-               PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
-
-       // effects
-       Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
-       //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
-
-       // other
-       if(capturetype == CAPTURE_NORMAL)
-       {
-               WaypointSprite_Kill(player.wps_flagcarrier);
-               if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
-
-               if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
-                       { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
-       }
-
-       // reset the flag
-       player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
-       ctf_RespawnFlag(enemy_flag);
-}
-
-void ctf_Handle_Return(entity flag, entity player)
-{
-       // messages and sounds
-       if(IS_MONSTER(player))
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
-       }
-       else if(flag.team)
-       {
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
-       }
-       _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
-       ctf_EventLog("return", flag.team, player);
-
-       // scoring
-       if(IS_PLAYER(player))
-       {
-               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
-
-       if(flag.ctf_dropper)
-       {
-               PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
-               ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
-               flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
-       }
-
-       // other
-       if(player.flagcarried == flag)
-               WaypointSprite_Kill(player.wps_flagcarrier);
-
-       // reset the flag
-       ctf_RespawnFlag(flag);
-}
-
-void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
-{
-       // declarations
-       float pickup_dropped_score; // used to calculate dropped pickup score
-       entity tmp_entity; // temporary entity
-
-       // attach the flag to the player
-       flag.owner = player;
-       player.flagcarried = flag;
-       if(player.vehicle)
-       {
-               setattachment(flag, player.vehicle, "");
-               setorigin(flag, VEHICLE_FLAG_OFFSET);
-               flag.scale = VEHICLE_FLAG_SCALE;
-       }
-       else
-       {
-               setattachment(flag, player, "");
-               setorigin(flag, FLAG_CARRY_OFFSET);
-       }
-
-       // flag setup
-       flag.movetype = MOVETYPE_NONE;
-       flag.takedamage = DAMAGE_NO;
-       flag.solid = SOLID_NOT;
-       flag.angles = '0 0 0';
-       flag.ctf_status = FLAG_CARRY;
-
-       switch(pickuptype)
-       {
-               case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
-               case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
-               default: break;
-       }
-
-       // messages and sounds
-       Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
-       if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
-       if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
-       else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
-       else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team)); }
-
-       Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname);
-
-       if(!flag.team)
-       FOR_EACH_PLAYER(tmp_entity)
-       if(tmp_entity != player)
-       if(DIFF_TEAM(player, tmp_entity))
-               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
-
-       if(flag.team)
-       FOR_EACH_PLAYER(tmp_entity)
-       if(tmp_entity != player)
-       if(CTF_SAMETEAM(flag, tmp_entity))
-       if(SAME_TEAM(player, tmp_entity))
-               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
-       else
-               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
-
-       _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
-
-       // scoring
-       PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
-       nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
-       switch(pickuptype)
-       {
-               case PICKUP_BASE:
-               {
-                       PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
-                       ctf_EventLog("steal", flag.team, player);
-                       break;
-               }
-
-               case PICKUP_DROPPED:
-               {
-                       pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
-                       pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
-                       LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
-                       PlayerTeamScore_AddScore(player, pickup_dropped_score);
-                       ctf_EventLog("pickup", flag.team, player);
-                       break;
-               }
-
-               default: break;
-       }
-
-       // speedrunning
-       if(pickuptype == PICKUP_BASE)
-       {
-               flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
-               if((player.speedrunning) && (ctf_captimerecord))
-                       ctf_FakeTimeLimit(player, time + ctf_captimerecord);
-       }
-
-       // effects
-       Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
-
-       // waypoints
-       if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
-       ctf_FlagcarrierWaypoints(player);
-       WaypointSprite_Ping(player.wps_flagcarrier);
-}
-
-
-// ===================
-// Main Flag Functions
-// ===================
-
-void ctf_CheckFlagReturn(entity flag, int returntype)
-{
-       if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
-       {
-               if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
-
-               if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
-               {
-                       switch(returntype)
-                       {
-                               case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break;
-                               case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break;
-                               case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break;
-                               case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break;
-
-                               default:
-                               case RETURN_TIMEOUT:
-                                       { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
-                       }
-                       _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
-                       ctf_EventLog("returned", flag.team, world);
-                       ctf_RespawnFlag(flag);
-               }
-       }
-}
-
-bool ctf_Stalemate_Customize()
-{SELFPARAM();
-       // make spectators see what the player would see
-       entity e, wp_owner;
-       e = WaypointSprite_getviewentity(other);
-       wp_owner = self.owner;
-
-       // team waypoints
-       if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
-       if(SAME_TEAM(wp_owner, e)) { return false; }
-       if(!IS_PLAYER(e)) { return false; }
-
-       return true;
-}
-
-void ctf_CheckStalemate(void)
-{
-       // declarations
-       int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
-       entity tmp_entity;
-
-       entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
-
-       // build list of stale flags
-       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
-       {
-               if(autocvar_g_ctf_stalemate)
-               if(tmp_entity.ctf_status != FLAG_BASE)
-               if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
-               {
-                       tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
-                       ctf_staleflaglist = tmp_entity;
-
-                       switch(tmp_entity.team)
-                       {
-                               case NUM_TEAM_1: ++stale_red_flags; break;
-                               case NUM_TEAM_2: ++stale_blue_flags; break;
-                               case NUM_TEAM_3: ++stale_yellow_flags; break;
-                               case NUM_TEAM_4: ++stale_pink_flags; break;
-                               default: ++stale_neutral_flags; break;
-                       }
-               }
-       }
-
-       if(ctf_oneflag)
-               stale_flags = (stale_neutral_flags >= 1);
-       else
-               stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
-
-       if(ctf_oneflag && stale_flags == 1)
-               ctf_stalemate = true;
-       else if(stale_flags >= 2)
-               ctf_stalemate = true;
-       else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
-               { ctf_stalemate = false; wpforenemy_announced = false; }
-       else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
-               { ctf_stalemate = false; wpforenemy_announced = false; }
-
-       // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
-       if(ctf_stalemate)
-       {
-               for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
-               {
-                       if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
-                       {
-                               entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
-                               wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
-                               tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
-                       }
-               }
-
-               if (!wpforenemy_announced)
-               {
-                       FOR_EACH_REALPLAYER(tmp_entity)
-                               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
-
-                       wpforenemy_announced = true;
-               }
-       }
-}
-
-void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{SELFPARAM();
-       if(ITEM_DAMAGE_NEEDKILL(deathtype))
-       {
-               if(autocvar_g_ctf_flag_return_damage_delay)
-               {
-                       self.ctf_flagdamaged = true;
-               }
-               else
-               {
-                       self.health = 0;
-                       ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
-               }
-               return;
-       }
-       if(autocvar_g_ctf_flag_return_damage)
-       {
-               // reduce health and check if it should be returned
-               self.health = self.health - damage;
-               ctf_CheckFlagReturn(self, RETURN_DAMAGE);
-               return;
-       }
-}
-
-void ctf_FlagThink()
-{SELFPARAM();
-       // declarations
-       entity tmp_entity;
-
-       self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
-
-       // captureshield
-       if(self == ctf_worldflaglist) // only for the first flag
-               FOR_EACH_CLIENT(tmp_entity)
-                       ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
-
-       // sanity checks
-       if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
-               LOG_TRACE("wtf the flag got squashed?\n");
-               tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
-               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
-       {
-               case FLAG_DROPPED:
-               {
-                       self.angles = '0 0 0';
-                       break;
-               }
-
-               default: break;
-       }
-
-       // main think method
-       switch(self.ctf_status)
-       {
-               case FLAG_BASE:
-               {
-                       if(autocvar_g_ctf_dropped_capture_radius)
-                       {
-                               for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
-                                       if(tmp_entity.ctf_status == FLAG_DROPPED)
-                                       if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
-                                       if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
-                                               ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
-                       }
-                       return;
-               }
-
-               case FLAG_DROPPED:
-               {
-                       if(autocvar_g_ctf_flag_dropped_floatinwater)
-                       {
-                               vector midpoint = ((self.absmin + self.absmax) * 0.5);
-                               if(pointcontents(midpoint) == CONTENT_WATER)
-                               {
-                                       self.velocity = self.velocity * 0.5;
-
-                                       if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
-                                               { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
-                                       else
-                                               { self.movetype = MOVETYPE_FLY; }
-                               }
-                               else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
-                       }
-                       if(autocvar_g_ctf_flag_return_dropped)
-                       {
-                               if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
-                               {
-                                       self.health = 0;
-                                       ctf_CheckFlagReturn(self, RETURN_DROPPED);
-                                       return;
-                               }
-                       }
-                       if(self.ctf_flagdamaged)
-                       {
-                               self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
-                               ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
-                               return;
-                       }
-                       else if(autocvar_g_ctf_flag_return_time)
-                       {
-                               self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
-                               ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
-                               return;
-                       }
-                       return;
-               }
-
-               case FLAG_CARRY:
-               {
-                       if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
-                       {
-                               self.health = 0;
-                               ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
-
-                               setself(self.owner);
-                               self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
-                               ImpulseCommands();
-                               setself(this);
-                       }
-                       if(autocvar_g_ctf_stalemate)
-                       {
-                               if(time >= wpforenemy_nextthink)
-                               {
-                                       ctf_CheckStalemate();
-                                       wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
-                               }
-                       }
-                       if(CTF_SAMETEAM(self, self.owner) && self.team)
-                       {
-                               if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
-                                       ctf_Handle_Throw(self.owner, world, DROP_THROW);
-                               else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
-                                       ctf_Handle_Return(self, self.owner);
-                       }
-                       return;
-               }
-
-               case FLAG_PASSING:
-               {
-                       vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
-                       targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
-                       WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
-
-                       if((self.pass_target == world)
-                               || (self.pass_target.deadflag != DEAD_NO)
-                               || (self.pass_target.flagcarried)
-                               || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
-                               || ((trace_fraction < 1) && (trace_ent != self.pass_target))
-                               || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
-                       {
-                               // give up, pass failed
-                               ctf_Handle_Drop(self, world, DROP_PASS);
-                       }
-                       else
-                       {
-                               // still a viable target, go for it
-                               ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
-                       }
-                       return;
-               }
-
-               default: // this should never happen
-               {
-                       LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n");
-                       return;
-               }
-       }
-}
-
-void ctf_FlagTouch()
-{SELFPARAM();
-       if(gameover) { return; }
-       if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
-
-       entity toucher = other, tmp_entity;
-       bool is_not_monster = (!IS_MONSTER(toucher)), num_perteam = 0;
-
-       // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
-       if(ITEM_TOUCH_NEEDKILL())
-       {
-               if(!autocvar_g_ctf_flag_return_damage_delay)
-               {
-                       self.health = 0;
-                       ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
-               }
-               if(!self.ctf_flagdamaged) { return; }
-       }
-
-       FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
-
-       // special touch behaviors
-       if(toucher.frozen) { return; }
-       else if(IS_VEHICLE(toucher))
-       {
-               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
-       }
-       else if(IS_MONSTER(toucher))
-       {
-               if(!autocvar_g_ctf_allow_monster_touch)
-                       return; // do nothing
-       }
-       else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
-       {
-               if(time > self.wait) // if we haven't in a while, play a sound/effect
-               {
-                       Send_Effect_(self.toucheffect, self.origin, '0 0 0', 1);
-                       _sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
-                       self.wait = time + FLAG_TOUCHRATE;
-               }
-               return;
-       }
-       else if(toucher.deadflag != DEAD_NO) { return; }
-
-       switch(self.ctf_status)
-       {
-               case FLAG_BASE:
-               {
-                       if(ctf_oneflag)
-                       {
-                               if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
-                                       ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
-                               else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
-                                       ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
-                       }
-                       else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
-                               ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
-                       else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
-                               ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
-                       break;
-               }
-
-               case FLAG_DROPPED:
-               {
-                       if(CTF_SAMETEAM(toucher, self) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && self.team) // automatically return if there's only 1 player on the team
-                               ctf_Handle_Return(self, toucher); // toucher just returned his own flag
-                       else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
-                               ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
-                       break;
-               }
-
-               case FLAG_CARRY:
-               {
-                       LOG_TRACE("Someone touched a flag even though it was being carried?\n");
-                       break;
-               }
-
-               case FLAG_PASSING:
-               {
-                       if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
-                       {
-                               if(DIFF_TEAM(toucher, self.pass_sender))
-                                       ctf_Handle_Return(self, toucher);
-                               else
-                                       ctf_Handle_Retrieve(self, toucher);
-                       }
-                       break;
-               }
-       }
-}
-
-.float last_respawn;
-void ctf_RespawnFlag(entity flag)
-{
-       // check for flag respawn being called twice in a row
-       if(flag.last_respawn > time - 0.5)
-               { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
-
-       flag.last_respawn = time;
-
-       // reset the player (if there is one)
-       if((flag.owner) && (flag.owner.flagcarried == flag))
-       {
-               WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
-               WaypointSprite_Kill(flag.wps_flagcarrier);
-
-               flag.owner.flagcarried = world;
-
-               if(flag.speedrunning)
-                       ctf_FakeTimeLimit(flag.owner, -1);
-       }
-
-       if((flag.owner) && (flag.owner.vehicle))
-               flag.scale = FLAG_SCALE;
-
-       if(flag.ctf_status == FLAG_DROPPED)
-               { WaypointSprite_Kill(flag.wps_flagdropped); }
-
-       // reset the flag
-       setattachment(flag, world, "");
-       setorigin(flag, flag.ctf_spawnorigin);
-
-       flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
-       flag.takedamage = DAMAGE_NO;
-       flag.health = flag.max_flag_health;
-       flag.solid = SOLID_TRIGGER;
-       flag.velocity = '0 0 0';
-       flag.angles = flag.mangle;
-       flag.flags = FL_ITEM | FL_NOTARGET;
-
-       flag.ctf_status = FLAG_BASE;
-       flag.owner = world;
-       flag.pass_distance = 0;
-       flag.pass_sender = world;
-       flag.pass_target = world;
-       flag.ctf_dropper = world;
-       flag.ctf_pickuptime = 0;
-       flag.ctf_droptime = 0;
-       flag.ctf_flagdamaged = 0;
-
-       ctf_CheckStalemate();
-}
-
-void ctf_Reset()
-{SELFPARAM();
-       if(self.owner)
-               if(IS_PLAYER(self.owner))
-                       ctf_Handle_Throw(self.owner, world, DROP_RESET);
-
-       ctf_RespawnFlag(self);
-}
-
-void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
-{SELFPARAM();
-       // bot waypoints
-       waypoint_spawnforitem_force(self, self.origin);
-       self.nearestwaypointtimeout = 0; // activate waypointing again
-       self.bot_basewaypoint = self.nearestwaypoint;
-
-       // waypointsprites
-       entity basename;
-       switch (self.team)
-       {
-               case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
-               case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
-               case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
-               case NUM_TEAM_4: basename = WP_FlagBasePink; break;
-               default: basename = WP_FlagBaseNeutral; break;
-       }
-
-       entity wp = WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG);
-       wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 1 1');
-       WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
-
-       // captureshield setup
-       ctf_CaptureShield_Spawn(self);
-}
-
-void set_flag_string(entity flag, .string field, string value, string teamname)
-{
-       if(flag.(field) == "")
-               flag.(field) = strzone(sprintf(value,teamname));
-}
-
-void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
-{SELFPARAM();
-       // declarations
-       setself(flag); // for later usage with droptofloor()
-
-       // main setup
-       flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
-       ctf_worldflaglist = flag;
-
-       setattachment(flag, world, "");
-
-       flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
-       flag.team = teamnumber;
-       flag.classname = "item_flag_team";
-       flag.target = "###item###"; // wut?
-       flag.flags = FL_ITEM | FL_NOTARGET;
-       flag.solid = SOLID_TRIGGER;
-       flag.takedamage = DAMAGE_NO;
-       flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
-       flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
-       flag.health = flag.max_flag_health;
-       flag.event_damage = ctf_FlagDamage;
-       flag.pushable = true;
-       flag.teleportable = TELEPORT_NORMAL;
-       flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
-       flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
-       flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
-       flag.velocity = '0 0 0';
-       flag.mangle = flag.angles;
-       flag.reset = ctf_Reset;
-       flag.touch = ctf_FlagTouch;
-       flag.think = ctf_FlagThink;
-       flag.nextthink = time + FLAG_THINKRATE;
-       flag.ctf_status = FLAG_BASE;
-
-       string teamname = Static_Team_ColorName_Lower(teamnumber);
-       // appearence
-       if(!flag.scale)                         { flag.scale = FLAG_SCALE; }
-       if(flag.skin == 0)                      { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
-       if(flag.model == "")            { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
-       set_flag_string(flag, toucheffect,      "%sflag_touch", teamname);
-       set_flag_string(flag, passeffect,       "%s_pass",              teamname);
-       set_flag_string(flag, capeffect,        "%s_cap",               teamname);
-
-       // sounds
-       flag.snd_flag_taken = SND(CTF_TAKEN(teamnumber));
-       flag.snd_flag_returned = SND(CTF_RETURNED(teamnumber));
-       flag.snd_flag_capture = SND(CTF_CAPTURE(teamnumber));
-       flag.snd_flag_dropped = SND(CTF_DROPPED(teamnumber));
-       if (flag.snd_flag_respawn == "") flag.snd_flag_respawn = SND(CTF_RESPAWN); // if there is ever a team-based sound for this, update the code to match.
-       precache_sound(flag.snd_flag_respawn);
-       if (flag.snd_flag_touch == "") flag.snd_flag_touch = SND(CTF_TOUCH); // again has no team-based sound
-       precache_sound(flag.snd_flag_touch);
-       if (flag.snd_flag_pass == "") flag.snd_flag_pass = SND(CTF_PASS); // same story here
-       precache_sound(flag.snd_flag_pass);
-
-       // precache
-       precache_model(flag.model);
-
-       // appearence
-       _setmodel(flag, flag.model); // precision set below
-       setsize(flag, FLAG_MIN, FLAG_MAX);
-       setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
-
-       if(autocvar_g_ctf_flag_glowtrails)
-       {
-               switch(teamnumber)
-               {
-                       case NUM_TEAM_1: flag.glow_color = 251; break;
-                       case NUM_TEAM_2: flag.glow_color = 210; break;
-                       case NUM_TEAM_3: flag.glow_color = 110; break;
-                       case NUM_TEAM_4: flag.glow_color = 145; break;
-                       default:                 flag.glow_color = 254; break;
-               }
-               flag.glow_size = 25;
-               flag.glow_trail = 1;
-       }
-
-       flag.effects |= EF_LOWPRECISION;
-       if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
-       if(autocvar_g_ctf_dynamiclights)
-       {
-               switch(teamnumber)
-               {
-                       case NUM_TEAM_1: flag.effects |= EF_RED; break;
-                       case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
-                       case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
-                       case NUM_TEAM_4: flag.effects |= EF_RED; break;
-                       default:                 flag.effects |= EF_DIMLIGHT; break;
-               }
-       }
-
-       // flag placement
-       if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
-       {
-               flag.dropped_origin = flag.origin;
-               flag.noalign = true;
-               flag.movetype = MOVETYPE_NONE;
-       }
-       else // drop to floor, automatically find a platform and set that as spawn origin
-       {
-               flag.noalign = false;
-               setself(flag);
-               droptofloor();
-               flag.movetype = MOVETYPE_TOSS;
-       }
-
-       InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-// NOTE: LEGACY CODE, needs to be re-written!
-
-void havocbot_calculate_middlepoint()
-{
-       entity f;
-       vector s = '0 0 0';
-       vector fo = '0 0 0';
-       float n = 0;
-
-       f = ctf_worldflaglist;
-       while (f)
-       {
-               fo = f.origin;
-               s = s + fo;
-               f = f.ctf_worldflagnext;
-       }
-       if(!n)
-               return;
-       havocbot_ctf_middlepoint = s * (1.0 / n);
-       havocbot_ctf_middlepoint_radius  = vlen(fo - havocbot_ctf_middlepoint);
-}
-
-
-entity havocbot_ctf_find_flag(entity bot)
-{
-       entity f;
-       f = ctf_worldflaglist;
-       while (f)
-       {
-               if (CTF_SAMETEAM(bot, f))
-                       return f;
-               f = f.ctf_worldflagnext;
-       }
-       return world;
-}
-
-entity havocbot_ctf_find_enemy_flag(entity bot)
-{
-       entity f;
-       f = ctf_worldflaglist;
-       while (f)
-       {
-               if(ctf_oneflag)
-               {
-                       if(CTF_DIFFTEAM(bot, f))
-                       {
-                               if(f.team)
-                               {
-                                       if(bot.flagcarried)
-                                               return f;
-                               }
-                               else if(!bot.flagcarried)
-                                       return f;
-                       }
-               }
-               else if (CTF_DIFFTEAM(bot, f))
-                       return f;
-               f = f.ctf_worldflagnext;
-       }
-       return world;
-}
-
-int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
-{
-       if (!teamplay)
-               return 0;
-
-       int c = 0;
-       entity head;
-
-       FOR_EACH_PLAYER(head)
-       {
-               if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
-                       continue;
-
-               if(vlen(head.origin - org) < tc_radius)
-                       ++c;
-       }
-
-       return c;
-}
-
-void havocbot_goalrating_ctf_ourflag(float ratingscale)
-{SELFPARAM();
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               if (CTF_SAMETEAM(self, head))
-                       break;
-               head = head.ctf_worldflagnext;
-       }
-       if (head)
-               navigation_routerating(head, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_ourbase(float ratingscale)
-{SELFPARAM();
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               if (CTF_SAMETEAM(self, head))
-                       break;
-               head = head.ctf_worldflagnext;
-       }
-       if (!head)
-               return;
-
-       navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_enemyflag(float ratingscale)
-{SELFPARAM();
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               if(ctf_oneflag)
-               {
-                       if(CTF_DIFFTEAM(self, head))
-                       {
-                               if(head.team)
-                               {
-                                       if(self.flagcarried)
-                                               break;
-                               }
-                               else if(!self.flagcarried)
-                                       break;
-                       }
-               }
-               else if(CTF_DIFFTEAM(self, head))
-                       break;
-               head = head.ctf_worldflagnext;
-       }
-       if (head)
-               navigation_routerating(head, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_enemybase(float ratingscale)
-{SELFPARAM();
-       if (!bot_waypoints_for_items)
-       {
-               havocbot_goalrating_ctf_enemyflag(ratingscale);
-               return;
-       }
-
-       entity head;
-
-       head = havocbot_ctf_find_enemy_flag(self);
-
-       if (!head)
-               return;
-
-       navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
-{SELFPARAM();
-       entity mf;
-
-       mf = havocbot_ctf_find_flag(self);
-
-       if(mf.ctf_status == FLAG_BASE)
-               return;
-
-       if(mf.tag_entity)
-               navigation_routerating(mf.tag_entity, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
-{
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               // flag is out in the field
-               if(head.ctf_status != FLAG_BASE)
-               if(head.tag_entity==world)      // dropped
-               {
-                       if(df_radius)
-                       {
-                               if(vlen(org-head.origin)<df_radius)
-                                       navigation_routerating(head, ratingscale, 10000);
-                       }
-                       else
-                               navigation_routerating(head, ratingscale, 10000);
-               }
-
-               head = head.ctf_worldflagnext;
-       }
-}
-
-void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
-{SELFPARAM();
-       entity head;
-       float t;
-       head = findchainfloat(bot_pickup, true);
-       while (head)
-       {
-               // gather health and armor only
-               if (head.solid)
-               if (head.health || head.armorvalue)
-               if (vlen(head.origin - org) < sradius)
-               {
-                       // get the value of the item
-                       t = head.bot_pickupevalfunc(self, head) * 0.0001;
-                       if (t > 0)
-                               navigation_routerating(head, t * ratingscale, 500);
-               }
-               head = head.chain;
-       }
-}
-
-void havocbot_ctf_reset_role(entity bot)
-{
-       float cdefense, cmiddle, coffense;
-       entity mf, ef, head;
-       float c;
-
-       if(bot.deadflag != DEAD_NO)
-               return;
-
-       if(vlen(havocbot_ctf_middlepoint)==0)
-               havocbot_calculate_middlepoint();
-
-       // Check ctf flags
-       if (bot.flagcarried)
-       {
-               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       mf = havocbot_ctf_find_flag(bot);
-       ef = havocbot_ctf_find_enemy_flag(bot);
-
-       // Retrieve stolen flag
-       if(mf.ctf_status!=FLAG_BASE)
-       {
-               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
-               return;
-       }
-
-       // If enemy flag is taken go to the middle to intercept pursuers
-       if(ef.ctf_status!=FLAG_BASE)
-       {
-               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
-               return;
-       }
-
-       // if there is only me on the team switch to offense
-       c = 0;
-       FOR_EACH_PLAYER(head)
-       if(SAME_TEAM(head, bot))
-               ++c;
-
-       if(c==1)
-       {
-               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
-               return;
-       }
-
-       // Evaluate best position to take
-       // Count mates on middle position
-       cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
-
-       // Count mates on defense position
-       cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
-
-       // Count mates on offense position
-       coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
-
-       if(cdefense<=coffense)
-               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
-       else if(coffense<=cmiddle)
-               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
-       else
-               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
-}
-
-void havocbot_role_ctf_carrier()
-{SELFPARAM();
-       if(self.deadflag != DEAD_NO)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.flagcarried == world)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.bot_strategytime < time)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
-               navigation_goalrating_start();
-               if(ctf_oneflag)
-                       havocbot_goalrating_ctf_enemybase(50000);
-               else
-                       havocbot_goalrating_ctf_ourbase(50000);
-
-               if(self.health<100)
-                       havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
-
-               navigation_goalrating_end();
-
-               if (self.navigation_hasgoals)
-                       self.havocbot_cantfindflag = time + 10;
-               else if (time > self.havocbot_cantfindflag)
-               {
-                       // Can't navigate to my own base, suicide!
-                       // TODO: drop it and wander around
-                       Damage(self, self, self, 100000, DEATH_KILL.m_id, self.origin, '0 0 0');
-                       return;
-               }
-       }
-}
-
-void havocbot_role_ctf_escort()
-{SELFPARAM();
-       entity mf, ef;
-
-       if(self.deadflag != DEAD_NO)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.flagcarried)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // If enemy flag is back on the base switch to previous role
-       ef = havocbot_ctf_find_enemy_flag(self);
-       if(ef.ctf_status==FLAG_BASE)
-       {
-               self.havocbot_role = self.havocbot_previous_role;
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       // If the flag carrier reached the base switch to defense
-       mf = havocbot_ctf_find_flag(self);
-       if(mf.ctf_status!=FLAG_BASE)
-       if(vlen(ef.origin - mf.dropped_origin) < 300)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!self.havocbot_role_timeout)
-       {
-               self.havocbot_role_timeout = time + random() * 30 + 60;
-       }
-
-       // If nothing happened just switch to previous role
-       if (time > self.havocbot_role_timeout)
-       {
-               self.havocbot_role = self.havocbot_previous_role;
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       // Chase the flag carrier
-       if (self.bot_strategytime < time)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-               havocbot_goalrating_ctf_enemyflag(30000);
-               havocbot_goalrating_ctf_ourstolenflag(40000);
-               havocbot_goalrating_items(10000, self.origin, 10000);
-               navigation_goalrating_end();
-       }
-}
-
-void havocbot_role_ctf_offense()
-{SELFPARAM();
-       entity mf, ef;
-       vector pos;
-
-       if(self.deadflag != DEAD_NO)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.flagcarried)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // Check flags
-       mf = havocbot_ctf_find_flag(self);
-       ef = havocbot_ctf_find_enemy_flag(self);
-
-       // Own flag stolen
-       if(mf.ctf_status!=FLAG_BASE)
-       {
-               if(mf.tag_entity)
-                       pos = mf.tag_entity.origin;
-               else
-                       pos = mf.origin;
-
-               // Try to get it if closer than the enemy base
-               if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
-               {
-                       havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
-                       return;
-               }
-       }
-
-       // Escort flag carrier
-       if(ef.ctf_status!=FLAG_BASE)
-       {
-               if(ef.tag_entity)
-                       pos = ef.tag_entity.origin;
-               else
-                       pos = ef.origin;
-
-               if(vlen(pos-mf.dropped_origin)>700)
-               {
-                       havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
-                       return;
-               }
-       }
-
-       // About to fail, switch to middlefield
-       if(self.health<50)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + 120;
-
-       if (time > self.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.bot_strategytime < time)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-               havocbot_goalrating_ctf_ourstolenflag(50000);
-               havocbot_goalrating_ctf_enemybase(20000);
-               havocbot_goalrating_items(5000, self.origin, 1000);
-               havocbot_goalrating_items(1000, self.origin, 10000);
-               navigation_goalrating_end();
-       }
-}
-
-// Retriever (temporary role):
-void havocbot_role_ctf_retriever()
-{SELFPARAM();
-       entity mf;
-
-       if(self.deadflag != DEAD_NO)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.flagcarried)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // If flag is back on the base switch to previous role
-       mf = havocbot_ctf_find_flag(self);
-       if(mf.ctf_status==FLAG_BASE)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + 20;
-
-       if (time > self.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.bot_strategytime < time)
-       {
-               float rt_radius;
-               rt_radius = 10000;
-
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-               havocbot_goalrating_ctf_ourstolenflag(50000);
-               havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
-               havocbot_goalrating_ctf_enemybase(30000);
-               havocbot_goalrating_items(500, self.origin, rt_radius);
-               navigation_goalrating_end();
-       }
-}
-
-void havocbot_role_ctf_middle()
-{SELFPARAM();
-       entity mf;
-
-       if(self.deadflag != DEAD_NO)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.flagcarried)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       mf = havocbot_ctf_find_flag(self);
-       if(mf.ctf_status!=FLAG_BASE)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
-               return;
-       }
-
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + 10;
-
-       if (time > self.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.bot_strategytime < time)
-       {
-               vector org;
-
-               org = havocbot_ctf_middlepoint;
-               org.z = self.origin.z;
-
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-               havocbot_goalrating_ctf_ourstolenflag(50000);
-               havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
-               havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
-               havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
-               havocbot_goalrating_items(2500, self.origin, 10000);
-               havocbot_goalrating_ctf_enemybase(2500);
-               navigation_goalrating_end();
-       }
-}
-
-void havocbot_role_ctf_defense()
-{SELFPARAM();
-       entity mf;
-
-       if(self.deadflag != DEAD_NO)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.flagcarried)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // If own flag was captured
-       mf = havocbot_ctf_find_flag(self);
-       if(mf.ctf_status!=FLAG_BASE)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
-               return;
-       }
-
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + 30;
-
-       if (time > self.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-       if (self.bot_strategytime < time)
-       {
-               float mp_radius;
-               vector org;
-
-               org = mf.dropped_origin;
-               mp_radius = havocbot_ctf_middlepoint_radius;
-
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-
-               // if enemies are closer to our base, go there
-               entity head, closestplayer = world;
-               float distance, bestdistance = 10000;
-               FOR_EACH_PLAYER(head)
-               {
-                       if(head.deadflag!=DEAD_NO)
-                               continue;
-
-                       distance = vlen(org - head.origin);
-                       if(distance<bestdistance)
-                       {
-                               closestplayer = head;
-                               bestdistance = distance;
-                       }
-               }
-
-               if(closestplayer)
-               if(DIFF_TEAM(closestplayer, self))
-               if(vlen(org - self.origin)>1000)
-               if(checkpvs(self.origin,closestplayer)||random()<0.5)
-                       havocbot_goalrating_ctf_ourbase(30000);
-
-               havocbot_goalrating_ctf_ourstolenflag(20000);
-               havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
-               havocbot_goalrating_enemyplayers(15000, org, mp_radius);
-               havocbot_goalrating_items(10000, org, mp_radius);
-               havocbot_goalrating_items(5000, self.origin, 10000);
-               navigation_goalrating_end();
-       }
-}
-
-void havocbot_role_ctf_setrole(entity bot, int role)
-{
-       LOG_TRACE(strcat(bot.netname," switched to "));
-       switch(role)
-       {
-               case HAVOCBOT_CTF_ROLE_CARRIER:
-                       LOG_TRACE("carrier");
-                       bot.havocbot_role = havocbot_role_ctf_carrier;
-                       bot.havocbot_role_timeout = 0;
-                       bot.havocbot_cantfindflag = time + 10;
-                       bot.bot_strategytime = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_DEFENSE:
-                       LOG_TRACE("defense");
-                       bot.havocbot_role = havocbot_role_ctf_defense;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_MIDDLE:
-                       LOG_TRACE("middle");
-                       bot.havocbot_role = havocbot_role_ctf_middle;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_OFFENSE:
-                       LOG_TRACE("offense");
-                       bot.havocbot_role = havocbot_role_ctf_offense;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_RETRIEVER:
-                       LOG_TRACE("retriever");
-                       bot.havocbot_previous_role = bot.havocbot_role;
-                       bot.havocbot_role = havocbot_role_ctf_retriever;
-                       bot.havocbot_role_timeout = time + 10;
-                       bot.bot_strategytime = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_ESCORT:
-                       LOG_TRACE("escort");
-                       bot.havocbot_previous_role = bot.havocbot_role;
-                       bot.havocbot_role = havocbot_role_ctf_escort;
-                       bot.havocbot_role_timeout = time + 30;
-                       bot.bot_strategytime = 0;
-                       break;
-       }
-       LOG_TRACE("\n");
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
-{SELFPARAM();
-       entity flag;
-       int t = 0, t2 = 0, t3 = 0;
-
-       // initially clear items so they can be set as necessary later.
-       self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING          | CTF_RED_FLAG_TAKEN            | CTF_RED_FLAG_LOST
-                                                  | CTF_BLUE_FLAG_CARRYING             | CTF_BLUE_FLAG_TAKEN           | CTF_BLUE_FLAG_LOST
-                                                  | CTF_YELLOW_FLAG_CARRYING   | CTF_YELLOW_FLAG_TAKEN         | CTF_YELLOW_FLAG_LOST
-                                                  | CTF_PINK_FLAG_CARRYING     | CTF_PINK_FLAG_TAKEN           | CTF_PINK_FLAG_LOST
-                                                  | CTF_NEUTRAL_FLAG_CARRYING  | CTF_NEUTRAL_FLAG_TAKEN        | CTF_NEUTRAL_FLAG_LOST
-                                                  | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
-
-       // scan through all the flags and notify the client about them
-       for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
-       {
-               if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING;                t2 = CTF_RED_FLAG_TAKEN;                t3 = CTF_RED_FLAG_LOST; }
-               if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING;               t2 = CTF_BLUE_FLAG_TAKEN;               t3 = CTF_BLUE_FLAG_LOST; }
-               if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING;     t2 = CTF_YELLOW_FLAG_TAKEN;             t3 = CTF_YELLOW_FLAG_LOST; }
-               if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING;               t2 = CTF_PINK_FLAG_TAKEN;               t3 = CTF_PINK_FLAG_LOST; }
-               if(flag.team == 0)                      { t = CTF_NEUTRAL_FLAG_CARRYING;        t2 = CTF_NEUTRAL_FLAG_TAKEN;    t3 = CTF_NEUTRAL_FLAG_LOST; self.ctf_flagstatus |= CTF_FLAG_NEUTRAL; }
-
-               switch(flag.ctf_status)
-               {
-                       case FLAG_PASSING:
-                       case FLAG_CARRY:
-                       {
-                               if((flag.owner == self) || (flag.pass_sender == self))
-                                       self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
-                               else
-                                       self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
-                               break;
-                       }
-                       case FLAG_DROPPED:
-                       {
-                               self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
-                               break;
-                       }
-               }
-       }
-
-       // item for stopping players from capturing the flag too often
-       if(self.ctf_captureshielded)
-               self.ctf_flagstatus |= CTF_SHIELDED;
-
-       // update the health of the flag carrier waypointsprite
-       if(self.wps_flagcarrier)
-               WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
-{
-       if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
-       {
-               if(frag_target == frag_attacker) // damage done to yourself
-               {
-                       frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
-                       frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
-               }
-               else // damage done to everyone else
-               {
-                       frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
-                       frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
-               }
-       }
-       else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
-       {
-               if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)))
-               if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
-               {
-                       frag_target.wps_helpme_time = time;
-                       WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
-               }
-               // todo: add notification for when flag carrier needs help?
-       }
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
-{
-       if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
-       {
-               PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
-               PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
-       }
-
-       if(frag_target.flagcarried)
-       {
-               entity tmp_entity = frag_target.flagcarried;
-               ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
-               tmp_entity.ctf_dropper = world;
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
-{
-       frag_score = 0;
-       return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
-}
-
-void ctf_RemovePlayer(entity player)
-{
-       if(player.flagcarried)
-               { ctf_Handle_Throw(player, world, DROP_NORMAL); }
-
-       for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
-       {
-               if(flag.pass_sender == player) { flag.pass_sender = world; }
-               if(flag.pass_target == player) { flag.pass_target = world; }
-               if(flag.ctf_dropper == player) { flag.ctf_dropper = world; }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
-{SELFPARAM();
-       ctf_RemovePlayer(self);
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
-{SELFPARAM();
-       ctf_RemovePlayer(self);
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
-{SELFPARAM();
-       if(self.flagcarried)
-       if(!autocvar_g_ctf_portalteleport)
-               { ctf_Handle_Throw(self, world, DROP_NORMAL); }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
-{SELFPARAM();
-       if(MUTATOR_RETURNVALUE || gameover) { return false; }
-
-       entity player = self;
-
-       if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
-       {
-               // pass the flag to a team mate
-               if(autocvar_g_ctf_pass)
-               {
-                       entity head, closest_target = world;
-                       head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
-
-                       while(head) // find the closest acceptable target to pass to
-                       {
-                               if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
-                               if(head != player && SAME_TEAM(head, player))
-                               if(!head.speedrunning && !head.vehicle)
-                               {
-                                       // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
-                                       vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
-                                       vector passer_center = CENTER_OR_VIEWOFS(player);
-
-                                       if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
-                                       {
-                                               if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
-                                               {
-                                                       if(IS_BOT_CLIENT(head))
-                                                       {
-                                                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
-                                                               ctf_Handle_Throw(head, player, DROP_PASS);
-                                                       }
-                                                       else
-                                                       {
-                                                               Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
-                                                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
-                                                       }
-                                                       player.throw_antispam = time + autocvar_g_ctf_pass_wait;
-                                                       return true;
-                                               }
-                                               else if(player.flagcarried)
-                                               {
-                                                       if(closest_target)
-                                                       {
-                                                               vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
-                                                               if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
-                                                                       { closest_target = head; }
-                                                       }
-                                                       else { closest_target = head; }
-                                               }
-                                       }
-                               }
-                               head = head.chain;
-                       }
-
-                       if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
-               }
-
-               // throw the flag in front of you
-               if(autocvar_g_ctf_throw && player.flagcarried)
-               {
-                       if(player.throw_count == -1)
-                       {
-                               if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
-                               {
-                                       player.throw_prevtime = time;
-                                       player.throw_count = 1;
-                                       ctf_Handle_Throw(player, world, DROP_THROW);
-                                       return true;
-                               }
-                               else
-                               {
-                                       Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
-                                       return false;
-                               }
-                       }
-                       else
-                       {
-                               if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
-                               else { player.throw_count += 1; }
-                               if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
-
-                               player.throw_prevtime = time;
-                               ctf_Handle_Throw(player, world, DROP_THROW);
-                               return true;
-                       }
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
-{SELFPARAM();
-       if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
-       {
-               self.wps_helpme_time = time;
-               WaypointSprite_HelpMePing(self.wps_flagcarrier);
-       }
-       else // create a normal help me waypointsprite
-       {
-               WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, false, RADARICON_HELPME);
-               WaypointSprite_Ping(self.wps_helpme);
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
-{
-       if(vh_player.flagcarried)
-       {
-               vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver
-
-               if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
-               {
-                       ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
-               }
-               else
-               {
-                       setattachment(vh_player.flagcarried, vh_vehicle, "");
-                       setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
-                       vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
-                       //vh_player.flagcarried.angles = '0 0 0';
-               }
-               return true;
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
-{
-       if(vh_player.flagcarried)
-       {
-               setattachment(vh_player.flagcarried, vh_player, "");
-               setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
-               vh_player.flagcarried.scale = FLAG_SCALE;
-               vh_player.flagcarried.angles = '0 0 0';
-               vh_player.flagcarried.nodrawtoclient = world;
-               return true;
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
-{SELFPARAM();
-       if(self.flagcarried)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, ((self.flagcarried.team) ? APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
-               ctf_RespawnFlag(self.flagcarried);
-               return true;
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
-{
-       entity flag; // temporary entity for the search method
-
-       for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
-       {
-               switch(flag.ctf_status)
-               {
-                       case FLAG_DROPPED:
-                       case FLAG_PASSING:
-                       {
-                               // lock the flag, game is over
-                               flag.movetype = MOVETYPE_NONE;
-                               flag.takedamage = DAMAGE_NO;
-                               flag.solid = SOLID_NOT;
-                               flag.nextthink = false; // stop thinking
-
-                               //dprint("stopping the ", flag.netname, " from moving.\n");
-                               break;
-                       }
-
-                       default:
-                       case FLAG_BASE:
-                       case FLAG_CARRY:
-                       {
-                               // do nothing for these flags
-                               break;
-                       }
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
-{SELFPARAM();
-       havocbot_ctf_reset_role(self);
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
-{
-       //ret_float = ctf_teams;
-       ret_string = "ctf_team";
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
-{SELFPARAM();
-       self.ctf_flagstatus = other.ctf_flagstatus;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GetRecords)
-{
-       for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
-       {
-               if (MapInfo_Get_ByID(i))
-               {
-                       float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
-
-                       if(!r)
-                               continue;
-
-                       // TODO: uid2name
-                       string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
-                       ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
-               }
-       }
-
-       return false;
-}
-
-bool superspec_Spectate(entity _player); // TODO
-void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
-MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
-{
-       if(IS_PLAYER(self) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
-
-       if(cmd_name == "followfc")
-       {
-               if(!g_ctf)
-                       return true;
-
-               entity _player;
-               int _team = 0;
-               bool found = false;
-
-               if(cmd_argc == 2)
-               {
-                       switch(argv(1))
-                       {
-                               case "red": _team = NUM_TEAM_1; break;
-                               case "blue": _team = NUM_TEAM_2; break;
-                               case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break;
-                               case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break;
-                       }
-               }
-
-               FOR_EACH_PLAYER(_player)
-               {
-                       if(_player.flagcarried && (_player.team == _team || _team == 0))
-                       {
-                               found = true;
-                               if(_team == 0 && IS_SPEC(self) && self.enemy == _player)
-                                       continue; // already spectating a fc, try to find the other fc
-                               return superspec_Spectate(_player);
-                       }
-               }
-
-               if(!found)
-                       superspec_msg("", "", self, "No active flag carrier\n", 1);
-               return true;
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
-{
-       if(frag_target.flagcarried)
-               ctf_Handle_Throw(frag_target, world, DROP_THROW);
-
-       return false;
-}
-
-
-// ==========
-// Spawnfuncs
-// ==========
-
-/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag for team one (Red).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red and blue as skins 0 and 1...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_team1)
-{
-       if(!g_ctf) { remove(self); return; }
-
-       ctf_FlagSetup(NUM_TEAM_1, self);
-}
-
-/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag for team two (Blue).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red and blue as skins 0 and 1...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_team2)
-{
-       if(!g_ctf) { remove(self); return; }
-
-       ctf_FlagSetup(NUM_TEAM_2, self);
-}
-
-/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag for team three (Yellow).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_team3)
-{
-       if(!g_ctf) { remove(self); return; }
-
-       ctf_FlagSetup(NUM_TEAM_3, self);
-}
-
-/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag for team four (Pink).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_team4)
-{
-       if(!g_ctf) { remove(self); return; }
-
-       ctf_FlagSetup(NUM_TEAM_4, self);
-}
-
-/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag (Neutral).
-Keys:
-"angle" Angle the flag will point (minus 90 degrees)...
-"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
-"noise" sound played when flag is picked up...
-"noise1" sound played when flag is returned by a teammate...
-"noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself...
-"noise4" sound played when flag is dropped by a player...
-"noise5" sound played when flag touches the ground... */
-spawnfunc(item_flag_neutral)
-{
-       if(!g_ctf) { remove(self); return; }
-       if(!cvar("g_ctf_oneflag")) { remove(self); return; }
-
-       ctf_FlagSetup(0, self);
-}
-
-/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
-Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
-Note: If you use spawnfunc_ctf_team entities you must define at least 2!  However, unlike domination, you don't need to make a blank one too.
-Keys:
-"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
-"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
-spawnfunc(ctf_team)
-{
-       if(!g_ctf) { remove(self); return; }
-
-       self.classname = "ctf_team";
-       self.team = self.cnt + 1;
-}
-
-// compatibility for quake maps
-spawnfunc(team_CTF_redflag)    { spawnfunc_item_flag_team1(this);    }
-spawnfunc(team_CTF_blueflag)   { spawnfunc_item_flag_team2(this);    }
-spawnfunc(info_player_team1);
-spawnfunc(team_CTF_redplayer)  { spawnfunc_info_player_team1(this);  }
-spawnfunc(team_CTF_redspawn)   { spawnfunc_info_player_team1(this);  }
-spawnfunc(info_player_team2);
-spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this);  }
-spawnfunc(team_CTF_bluespawn)  { spawnfunc_info_player_team2(this);  }
-
-void team_CTF_neutralflag()                     { SELFPARAM(); spawnfunc_item_flag_neutral(self);  }
-void team_neutralobelisk()                      { SELFPARAM(); spawnfunc_item_flag_neutral(self);  }
-
-
-// ==============
-// Initialization
-// ==============
-
-// scoreboard setup
-void ctf_ScoreRules(int teams)
-{
-       CheckAllowedTeams(world);
-       ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
-       ScoreInfo_SetLabel_TeamScore  (ST_CTF_CAPS,     "caps",      SFL_SORT_PRIO_PRIMARY);
-       ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
-       ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME,  "captime",   SFL_LOWER_IS_BETTER | SFL_TIME);
-       ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS,  "pickups",   0);
-       ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS,  "fckills",   0);
-       ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS,  "returns",   0);
-       ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS,    "drops",     SFL_LOWER_IS_BETTER);
-       ScoreRules_basics_end();
-}
-
-// code from here on is just to support maps that don't have flag and team entities
-void ctf_SpawnTeam (string teamname, int teamcolor)
-{
-       entity this = new(ctf_team);
-       this.netname = teamname;
-       this.cnt = teamcolor;
-       this.spawnfunc_checked = true;
-       WITH(entity, self, this, spawnfunc_ctf_team(this));
-}
-
-void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
-{
-       ctf_teams = 2;
-
-       entity tmp_entity;
-       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
-       {
-               if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
-               if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
-               if(tmp_entity.team == 0) { ctf_oneflag = true; }
-       }
-
-       ctf_teams = bound(2, ctf_teams, 4);
-
-       // if no teams are found, spawn defaults
-       if(find(world, classname, "ctf_team") == world)
-       {
-               LOG_INFO("No ""ctf_team"" entities found on this map, creating them anyway.\n");
-               ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
-               ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
-               if(ctf_teams >= 3)
-                       ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
-               if(ctf_teams >= 4)
-                       ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
-       }
-
-       ctf_ScoreRules(ctf_teams);
-}
-
-void ctf_Initialize()
-{
-       ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
-
-       ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
-       ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
-       ctf_captureshield_force = autocvar_g_ctf_shield_force;
-
-       addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
-
-       InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
-}
-
-REGISTER_MUTATOR(ctf, g_ctf)
-{
-       ActivateTeamplay();
-       SetLimits(autocvar_capturelimit_override, -1, autocvar_captureleadlimit_override, -1);
-       have_team_spawns = -1; // request team spawns
-
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This is a game type and it cannot be added at runtime.");
-               ctf_Initialize();
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // we actually cannot roll back ctf_Initialize here
-               // BUT: we don't need to! If this gets called, adding always
-               // succeeds.
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This is a game type and it cannot be removed at runtime.");
-               return -1;
-       }
-
-       return 0;
-}
diff --git a/qcsrc/server/mutators/gamemode_ctf.qh b/qcsrc/server/mutators/gamemode_ctf.qh
deleted file mode 100644 (file)
index 2a8b481..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-#ifndef GAMEMODE_CTF_H
-#define GAMEMODE_CTF_H
-// these are needed since mutators are compiled last
-
-#ifdef SVQC
-// used in cheats.qc
-void ctf_RespawnFlag(entity flag);
-
-// score rule declarations
-const int ST_CTF_CAPS = 1;
-const int SP_CTF_CAPS = 4;
-const int SP_CTF_CAPTIME = 5;
-const int SP_CTF_PICKUPS = 6;
-const int SP_CTF_DROPS = 7;
-const int SP_CTF_FCKILLS = 8;
-const int SP_CTF_RETURNS = 9;
-
-// flag constants // for most of these, there is just one question to be asked: WHYYYYY?
-#define FLAG_MIN (PL_MIN_CONST + '0 0 -13')
-#define FLAG_MAX (PL_MAX_CONST + '0 0 -13')
-
-const float FLAG_SCALE = 0.6;
-
-const float FLAG_THINKRATE = 0.2;
-const float FLAG_TOUCHRATE = 0.5;
-const float WPFE_THINKRATE = 0.5;
-
-const vector FLAG_DROP_OFFSET = ('0 0 32');
-const vector FLAG_CARRY_OFFSET = ('-16 0 8');
-#define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13))
-const vector FLAG_WAYPOINT_OFFSET = ('0 0 64');
-const vector FLAG_FLOAT_OFFSET = ('0 0 32');
-const vector FLAG_PASS_ARC_OFFSET = ('0 0 -10');
-
-const vector VEHICLE_FLAG_OFFSET = ('0 0 96');
-const float VEHICLE_FLAG_SCALE = 1.0;
-
-// waypoint colors
-#define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1')
-#define WPCOLOR_FLAGCARRIER(t) (WP_FlagCarrier.m_color)
-#define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, false)) * 0.5 : '1 1 1')
-
-// sounds
-#define snd_flag_taken noise
-#define snd_flag_returned noise1
-#define snd_flag_capture noise2
-#define snd_flag_respawn noise3
-.string snd_flag_dropped;
-.string snd_flag_touch;
-.string snd_flag_pass;
-
-// effects
-.string toucheffect;
-.string passeffect;
-.string capeffect;
-
-// list of flags on the map
-entity ctf_worldflaglist;
-.entity ctf_worldflagnext;
-.entity ctf_staleflagnext;
-
-// waypoint sprites
-.entity bot_basewaypoint; // flag waypointsprite
-.entity wps_helpme;
-.entity wps_flagbase;
-.entity wps_flagcarrier;
-.entity wps_flagdropped;
-.entity wps_enemyflagcarrier;
-.float wps_helpme_time;
-bool wpforenemy_announced;
-float wpforenemy_nextthink;
-
-// statuses
-const int FLAG_BASE = 1;
-const int FLAG_DROPPED = 2;
-const int FLAG_CARRY = 3;
-const int FLAG_PASSING = 4;
-
-const int DROP_NORMAL = 1;
-const int DROP_THROW = 2;
-const int DROP_PASS = 3;
-const int DROP_RESET = 4;
-
-const int PICKUP_BASE = 1;
-const int PICKUP_DROPPED = 2;
-
-const int CAPTURE_NORMAL = 1;
-const int CAPTURE_DROPPED = 2;
-
-const int RETURN_TIMEOUT = 1;
-const int RETURN_DROPPED = 2;
-const int RETURN_DAMAGE = 3;
-const int RETURN_SPEEDRUN = 4;
-const int RETURN_NEEDKILL = 5;
-
-// flag properties
-#define ctf_spawnorigin dropped_origin
-bool ctf_stalemate; // indicates that a stalemate is active
-float ctf_captimerecord; // record time for capturing the flag
-.float ctf_pickuptime;
-.float ctf_droptime;
-.int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
-.entity ctf_dropper; // don't allow spam of dropping the flag
-.int max_flag_health;
-.float next_take_time;
-.bool ctf_flagdamaged;
-int ctf_teams;
-
-// passing/throwing properties
-.float pass_distance;
-.entity pass_sender;
-.entity pass_target;
-.float throw_antispam;
-.float throw_prevtime;
-.int throw_count;
-
-// CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
-.bool ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
-float ctf_captureshield_min_negscore; // punish at -20 points
-float ctf_captureshield_max_ratio; // punish at most 30% of each team
-float ctf_captureshield_force; // push force of the shield
-
-// 1 flag ctf
-bool ctf_oneflag; // indicates whether or not a neutral flag has been found
-
-// bot player logic
-const int HAVOCBOT_CTF_ROLE_NONE = 0;
-const int HAVOCBOT_CTF_ROLE_DEFENSE = 2;
-const int HAVOCBOT_CTF_ROLE_MIDDLE = 4;
-const int HAVOCBOT_CTF_ROLE_OFFENSE = 8;
-const int HAVOCBOT_CTF_ROLE_CARRIER = 16;
-const int HAVOCBOT_CTF_ROLE_RETRIEVER = 32;
-const int HAVOCBOT_CTF_ROLE_ESCORT = 64;
-
-.bool havocbot_cantfindflag;
-
-vector havocbot_ctf_middlepoint;
-float havocbot_ctf_middlepoint_radius;
-
-void havocbot_role_ctf_setrole(entity bot, int role);
-
-// team checking
-#define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b))
-#define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b))
-
-// networked flag statuses
-.int ctf_flagstatus;
-#endif
-
-const int CTF_RED_FLAG_TAKEN                   = 1;
-const int CTF_RED_FLAG_LOST                            = 2;
-const int CTF_RED_FLAG_CARRYING                        = 3;
-const int CTF_BLUE_FLAG_TAKEN                  = 4;
-const int CTF_BLUE_FLAG_LOST                   = 8;
-const int CTF_BLUE_FLAG_CARRYING               = 12;
-const int CTF_YELLOW_FLAG_TAKEN                        = 16;
-const int CTF_YELLOW_FLAG_LOST                 = 32;
-const int CTF_YELLOW_FLAG_CARRYING             = 48;
-const int CTF_PINK_FLAG_TAKEN                  = 64;
-const int CTF_PINK_FLAG_LOST                   = 128;
-const int CTF_PINK_FLAG_CARRYING               = 192;
-const int CTF_NEUTRAL_FLAG_TAKEN               = 256;
-const int CTF_NEUTRAL_FLAG_LOST                        = 512;
-const int CTF_NEUTRAL_FLAG_CARRYING            = 768;
-const int CTF_FLAG_NEUTRAL                             = 2048;
-const int CTF_SHIELDED                                 = 4096;
-
-#endif
diff --git a/qcsrc/server/mutators/gamemode_cts.qc b/qcsrc/server/mutators/gamemode_cts.qc
deleted file mode 100644 (file)
index 679bbb0..0000000
+++ /dev/null
@@ -1,436 +0,0 @@
-#include "gamemode_cts.qh"
-
-#include "gamemode.qh"
-
-#include "../race.qh"
-
-float autocvar_g_cts_finish_kill_delay;
-bool autocvar_g_cts_selfdamage;
-
-// legacy bot roles
-.float race_checkpoint;
-void havocbot_role_cts()
-{SELFPARAM();
-       if(self.deadflag != DEAD_NO)
-               return;
-
-       entity e;
-       if (self.bot_strategytime < time)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-
-               for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; )
-               {
-                       if(e.cnt == self.race_checkpoint)
-                       {
-                               navigation_routerating(e, 1000000, 5000);
-                       }
-                       else if(self.race_checkpoint == -1)
-                       {
-                               navigation_routerating(e, 1000000, 5000);
-                       }
-               }
-
-               navigation_goalrating_end();
-       }
-}
-
-void cts_ScoreRules()
-{
-       ScoreRules_basics(0, 0, 0, false);
-       if(g_race_qualifying)
-       {
-               ScoreInfo_SetLabel_PlayerScore(SP_CTS_FASTEST, "fastest",   SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-       }
-       else
-       {
-               ScoreInfo_SetLabel_PlayerScore(SP_CTS_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
-               ScoreInfo_SetLabel_PlayerScore(SP_CTS_TIME,    "time",      SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-               ScoreInfo_SetLabel_PlayerScore(SP_CTS_FASTEST, "fastest",   SFL_LOWER_IS_BETTER | SFL_TIME);
-       }
-       ScoreRules_basics_end();
-}
-
-void cts_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
-{
-       if(autocvar_sv_eventlog)
-               GameLogEcho(strcat(":cts:", mode, ":", ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void CTS_ClientKill(entity e) // silent version of ClientKill, used when player finishes a CTS run. Useful to prevent cheating by running back to the start line and starting out with more speed
-{
-    e.killindicator = spawn();
-    e.killindicator.owner = e;
-    e.killindicator.think = KillIndicator_Think;
-    e.killindicator.nextthink = time + (e.lip) * 0.05;
-    e.killindicator.cnt = ceil(autocvar_g_cts_finish_kill_delay);
-    e.killindicator.health = 1; // this is used to indicate that it should be silent
-    e.lip = 0;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerPhysics)
-{SELFPARAM();
-       self.race_movetime_frac += PHYS_INPUT_TIMELENGTH;
-       float f = floor(self.race_movetime_frac);
-       self.race_movetime_frac -= f;
-       self.race_movetime_count += f;
-       self.race_movetime = self.race_movetime_frac + self.race_movetime_count;
-
-#ifdef SVQC
-       if(IS_PLAYER(self))
-       {
-               if (self.race_penalty)
-                       if (time > self.race_penalty)
-                               self.race_penalty = 0;
-               if(self.race_penalty)
-               {
-                       self.velocity = '0 0 0';
-                       self.movetype = MOVETYPE_NONE;
-                       self.disableclientprediction = 2;
-               }
-       }
-#endif
-
-       // force kbd movement for fairness
-       float wishspeed;
-       vector wishvel;
-
-       // if record times matter
-       // ensure nothing EVIL is being done (i.e. div0_evade)
-       // this hinders joystick users though
-       // but it still gives SOME analog control
-       wishvel.x = fabs(self.movement.x);
-       wishvel.y = fabs(self.movement.y);
-       if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
-       {
-               wishvel.z = 0;
-               wishspeed = vlen(wishvel);
-               if(wishvel.x >= 2 * wishvel.y)
-               {
-                       // pure X motion
-                       if(self.movement.x > 0)
-                               self.movement_x = wishspeed;
-                       else
-                               self.movement_x = -wishspeed;
-                       self.movement_y = 0;
-               }
-               else if(wishvel.y >= 2 * wishvel.x)
-               {
-                       // pure Y motion
-                       self.movement_x = 0;
-                       if(self.movement.y > 0)
-                               self.movement_y = wishspeed;
-                       else
-                               self.movement_y = -wishspeed;
-               }
-               else
-               {
-                       // diagonal
-                       if(self.movement.x > 0)
-                               self.movement_x = M_SQRT1_2 * wishspeed;
-                       else
-                               self.movement_x = -M_SQRT1_2 * wishspeed;
-                       if(self.movement.y > 0)
-                               self.movement_y = M_SQRT1_2 * wishspeed;
-                       else
-                               self.movement_y = -M_SQRT1_2 * wishspeed;
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, reset_map_global)
-{
-       float s;
-
-       Score_NicePrint(world);
-
-       race_ClearRecords();
-       PlayerScore_Sort(race_place, 0, 1, 0);
-
-       entity e;
-       FOR_EACH_CLIENT(e)
-       {
-               if(e.race_place)
-               {
-                       s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
-                       if(!s)
-                               e.race_place = 0;
-               }
-               cts_EventLog(ftos(e.race_place), e);
-       }
-
-       if(g_race_qualifying == 2)
-       {
-               g_race_qualifying = 0;
-               independent_players = 0;
-               cvar_set("fraglimit", ftos(race_fraglimit));
-               cvar_set("leadlimit", ftos(race_leadlimit));
-               cvar_set("timelimit", ftos(race_timelimit));
-               cts_ScoreRules();
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerPreThink)
-{SELFPARAM();
-       if(IS_SPEC(self) || IS_OBSERVER(self))
-       if(g_race_qualifying)
-       if(msg_entity.enemy.race_laptime)
-               race_SendNextCheckpoint(msg_entity.enemy, 1);
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, ClientConnect)
-{SELFPARAM();
-       race_PreparePlayer();
-       self.race_checkpoint = -1;
-
-       if(IS_REAL_CLIENT(self))
-       {
-               string rr = CTS_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);
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver)
-{SELFPARAM();
-       if(PlayerScore_Add(self, SP_RACE_FASTEST, 0))
-               self.frags = FRAGS_LMS_LOSER;
-       else
-               self.frags = FRAGS_SPECTATOR;
-
-       race_PreparePlayer();
-       self.race_checkpoint = -1;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerSpawn)
-{SELFPARAM();
-       if(spawn_spot.target == "")
-               // Emergency: this wasn't a real spawnpoint. Can this ever happen?
-               race_PreparePlayer();
-
-       // if we need to respawn, do it right
-       self.race_respawn_checkpoint = self.race_checkpoint;
-       self.race_respawn_spotref = spawn_spot;
-
-       self.race_place = 0;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PutClientInServer)
-{SELFPARAM();
-       if(IS_PLAYER(self))
-       if(!gameover)
-       {
-               if(self.killcount == -666 /* initial spawn */ || g_race_qualifying) // spawn
-                       race_PreparePlayer();
-               else // respawn
-                       race_RetractPlayer();
-
-               race_AbandonRaceCheck(self);
-       }
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerDies)
-{SELFPARAM();
-       self.respawn_flags |= RESPAWN_FORCE;
-       race_AbandonRaceCheck(self);
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole)
-{SELFPARAM();
-       self.havocbot_role = havocbot_role_cts;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(cts, GetPressedKeys)
-{SELFPARAM();
-       if(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 (!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 = CTS_RECORD;
-                       race_send_speedaward(MSG_ALL);
-                       speedaward_lastsent = speedaward_speed;
-                       if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
-                       {
-                               speedaward_alltimebest = speedaward_speed;
-                               speedaward_alltimebest_holder = speedaward_holder;
-                               speedaward_alltimebest_uid = speedaward_uid;
-                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
-                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
-                               race_send_speedaward_alltimebest(MSG_ALL);
-                       }
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon)
-{
-       // no weapon dropping in CTS
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(cts, FilterItem)
-{SELFPARAM();
-       if(self.classname == "droppedweapon")
-               return true;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerDamage_Calculate)
-{
-       if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id)
-       if(!autocvar_g_cts_selfdamage)
-               frag_damage = 0;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear)
-{
-       return true; // in CTS, you don't lose score by observing
-}
-
-MUTATOR_HOOKFUNCTION(cts, SetModname)
-{
-       g_cloaked = 1; // always enable cloak in CTS
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, GetRecords)
-{
-       for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
-       {
-               if(MapInfo_Get_ByID(i))
-               {
-                       float r = race_readTime(MapInfo_Map_bspname, 1);
-
-                       if(!r)
-                               continue;
-
-                       string h = race_readName(MapInfo_Map_bspname, 1);
-                       ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, ClientKill)
-{
-       ret_float = 0;
-
-       if(self.killindicator && self.killindicator.health == 1) // self.killindicator.health == 1 means that the kill indicator was spawned by CTS_ClientKill
-       {
-               remove(self.killindicator);
-               self.killindicator = world;
-
-               ClientKill_Now(); // allow instant kill in this case
-               return;
-       }
-
-}
-
-MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint)
-{
-       if(autocvar_g_cts_finish_kill_delay)
-               CTS_ClientKill(self);
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, FixClientCvars)
-{
-       stuffcmd(fix_client, "cl_cmd settemp cl_movecliptokeyboard 2\n");
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(cts, WantWeapon)
-{
-       ret_float = (want_weaponinfo.weapon == WEP_SHOTGUN.m_id);
-       want_mutatorblocked = true;
-       return true;
-}
-
-void cts_Initialize()
-{
-       cts_ScoreRules();
-}
-
-REGISTER_MUTATOR(cts, g_cts)
-{
-       g_race_qualifying = 1;
-       independent_players = 1;
-       SetLimits(0, 0, 0, -1);
-
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This is a game type and it cannot be added at runtime.");
-               cts_Initialize();
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // we actually cannot roll back cts_Initialize here
-               // BUT: we don't need to! If this gets called, adding always
-               // succeeds.
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This is a game type and it cannot be removed at runtime.");
-               return -1;
-       }
-
-       return 0;
-}
diff --git a/qcsrc/server/mutators/gamemode_cts.qh b/qcsrc/server/mutators/gamemode_cts.qh
deleted file mode 100644 (file)
index fa27fe4..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef GAMEMODE_CTS_H
-#define GAMEMODE_CTS_H
-//float g_race_qualifying;
-
-// scores
-const float ST_CTS_LAPS = 1;
-const float SP_CTS_LAPS = 4;
-const float SP_CTS_TIME = 5;
-const float SP_CTS_FASTEST = 6;
-#endif
diff --git a/qcsrc/server/mutators/gamemode_deathmatch.qc b/qcsrc/server/mutators/gamemode_deathmatch.qc
deleted file mode 100644 (file)
index d5f9cbd..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-#include "gamemode.qh"
-
-MUTATOR_HOOKFUNCTION(dm, Scores_CountFragsRemaining)
-{
-       // announce remaining frags
-       return true;
-}
-
-REGISTER_MUTATOR(dm, IS_GAMETYPE(DEATHMATCH))
-{
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This is a game type and it cannot be added at runtime.");
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // we actually cannot roll back dm_Initialize here
-               // BUT: we don't need to! If this gets called, adding always
-               // succeeds.
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               print("This is a game type and it cannot be removed at runtime.");
-               return -1;
-       }
-
-       return 0;
-}
diff --git a/qcsrc/server/mutators/gamemode_domination.qc b/qcsrc/server/mutators/gamemode_domination.qc
deleted file mode 100644 (file)
index 504f31d..0000000
+++ /dev/null
@@ -1,693 +0,0 @@
-#include "gamemode_domination.qh"
-
-#include "gamemode.qh"
-
-#include "../teamplay.qh"
-
-bool g_domination;
-
-int autocvar_g_domination_default_teams;
-bool autocvar_g_domination_disable_frags;
-int autocvar_g_domination_point_amt;
-bool autocvar_g_domination_point_fullbright;
-int autocvar_g_domination_point_leadlimit;
-bool autocvar_g_domination_roundbased;
-int autocvar_g_domination_roundbased_point_limit;
-float autocvar_g_domination_round_timelimit;
-float autocvar_g_domination_warmup;
-#define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
-float autocvar_g_domination_point_rate;
-int autocvar_g_domination_teams_override;
-
-void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
-{
-       if(autocvar_sv_eventlog)
-               GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void set_dom_state(entity e)
-{
-       e.dom_total_pps = total_pps;
-       e.dom_pps_red = pps_red;
-       e.dom_pps_blue = pps_blue;
-       if(domination_teams >= 3)
-               e.dom_pps_yellow = pps_yellow;
-       if(domination_teams >= 4)
-               e.dom_pps_pink = pps_pink;
-}
-
-void dompoint_captured ()
-{SELFPARAM();
-       entity head;
-       float old_delay, old_team, real_team;
-
-       // now that the delay has expired, switch to the latest team to lay claim to this point
-       head = self.owner;
-
-       real_team = self.cnt;
-       self.cnt = -1;
-
-       dom_EventLog("taken", self.team, self.dmg_inflictor);
-       self.dmg_inflictor = world;
-
-       self.goalentity = head;
-       self.model = head.mdl;
-       self.modelindex = head.dmg;
-       self.skin = head.skin;
-
-       float points, wait_time;
-       if (autocvar_g_domination_point_amt)
-               points = autocvar_g_domination_point_amt;
-       else
-               points = self.frags;
-       if (autocvar_g_domination_point_rate)
-               wait_time = autocvar_g_domination_point_rate;
-       else
-               wait_time = self.wait;
-
-       if(domination_roundbased)
-               bprint(sprintf("^3%s^3%s\n", head.netname, self.message));
-       else
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time);
-
-       if(self.enemy.playerid == self.enemy_playerid)
-               PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
-       else
-               self.enemy = world;
-
-       if (head.noise != "")
-               if(self.enemy)
-                       _sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
-               else
-                       _sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
-       if (head.noise1 != "")
-               play2all(head.noise1);
-
-       self.delay = time + wait_time;
-
-       // do trigger work
-       old_delay = self.delay;
-       old_team = self.team;
-       self.team = real_team;
-       self.delay = 0;
-       activator = self;
-       SUB_UseTargets ();
-       self.delay = old_delay;
-       self.team = old_team;
-
-       entity msg = WP_DomNeut;
-       switch(self.team)
-       {
-               case NUM_TEAM_1: msg = WP_DomRed; break;
-               case NUM_TEAM_2: msg = WP_DomBlue; break;
-               case NUM_TEAM_3: msg = WP_DomYellow; break;
-               case NUM_TEAM_4: msg = WP_DomPink; break;
-       }
-
-       WaypointSprite_UpdateSprites(self.sprite, msg, WP_Null, WP_Null);
-
-       total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
-       for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
-       {
-               if (autocvar_g_domination_point_amt)
-                       points = autocvar_g_domination_point_amt;
-               else
-                       points = head.frags;
-               if (autocvar_g_domination_point_rate)
-                       wait_time = autocvar_g_domination_point_rate;
-               else
-                       wait_time = head.wait;
-               switch(head.goalentity.team)
-               {
-                       case NUM_TEAM_1:
-                               pps_red += points/wait_time;
-                               break;
-                       case NUM_TEAM_2:
-                               pps_blue += points/wait_time;
-                               break;
-                       case NUM_TEAM_3:
-                               pps_yellow += points/wait_time;
-                               break;
-                       case NUM_TEAM_4:
-                               pps_pink += points/wait_time;
-                               break;
-               }
-               total_pps += points/wait_time;
-       }
-
-       WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
-       WaypointSprite_Ping(self.sprite);
-
-       self.captime = time;
-
-       FOR_EACH_REALCLIENT(head)
-               set_dom_state(head);
-}
-
-void AnimateDomPoint()
-{SELFPARAM();
-       if(self.pain_finished > time)
-               return;
-       self.pain_finished = time + self.t_width;
-       if(self.nextthink > self.pain_finished)
-               self.nextthink = self.pain_finished;
-
-       self.frame = self.frame + 1;
-       if(self.frame > self.t_length)
-               self.frame = 0;
-}
-
-void dompointthink()
-{SELFPARAM();
-       float fragamt;
-
-       self.nextthink = time + 0.1;
-
-       //self.frame = self.frame + 1;
-       //if(self.frame > 119)
-       //      self.frame = 0;
-       AnimateDomPoint();
-
-       // give points
-
-       if (gameover || self.delay > time || time < game_starttime)     // game has ended, don't keep giving points
-               return;
-
-       if(autocvar_g_domination_point_rate)
-               self.delay = time + autocvar_g_domination_point_rate;
-       else
-               self.delay = time + self.wait;
-
-       // give credit to the team
-       // NOTE: this defaults to 0
-       if (!domination_roundbased)
-       if (self.goalentity.netname != "")
-       {
-               if(autocvar_g_domination_point_amt)
-                       fragamt = autocvar_g_domination_point_amt;
-               else
-                       fragamt = self.frags;
-               TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
-               TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
-
-               // give credit to the individual player, if he is still there
-               if (self.enemy.playerid == self.enemy_playerid)
-               {
-                       PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
-                       PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
-               }
-               else
-                       self.enemy = world;
-       }
-}
-
-void dompointtouch()
-{SELFPARAM();
-       entity head;
-       if (!IS_PLAYER(other))
-               return;
-       if (other.health < 1)
-               return;
-
-       if(round_handler_IsActive() && !round_handler_IsRoundStarted())
-               return;
-
-       if(time < self.captime + 0.3)
-               return;
-
-       // only valid teams can claim it
-       head = find(world, classname, "dom_team");
-       while (head && head.team != other.team)
-               head = find(head, classname, "dom_team");
-       if (!head || head.netname == "" || head == self.goalentity)
-               return;
-
-       // delay capture
-
-       self.team = self.goalentity.team; // this stores the PREVIOUS team!
-
-       self.cnt = other.team;
-       self.owner = head; // team to switch to after the delay
-       self.dmg_inflictor = other;
-
-       // self.state = 1;
-       // self.delay = time + cvar("g_domination_point_capturetime");
-       //self.nextthink = time + cvar("g_domination_point_capturetime");
-       //self.think = dompoint_captured;
-
-       // go to neutral team in the mean time
-       head = find(world, classname, "dom_team");
-       while (head && head.netname != "")
-               head = find(head, classname, "dom_team");
-       if(head == world)
-               return;
-
-       WaypointSprite_UpdateSprites(self.sprite, WP_DomNeut, WP_Null, WP_Null);
-       WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
-       WaypointSprite_Ping(self.sprite);
-
-       self.goalentity = head;
-       self.model = head.mdl;
-       self.modelindex = head.dmg;
-       self.skin = head.skin;
-
-       self.enemy = other; // individual player scoring
-       self.enemy_playerid = other.playerid;
-       dompoint_captured();
-}
-
-void dom_controlpoint_setup()
-{SELFPARAM();
-       entity head;
-       // find the spawnfunc_dom_team representing unclaimed points
-       head = find(world, classname, "dom_team");
-       while(head && head.netname != "")
-               head = find(head, classname, "dom_team");
-       if (!head)
-               objerror("no spawnfunc_dom_team with netname \"\" found\n");
-
-       // copy important properties from spawnfunc_dom_team entity
-       self.goalentity = head;
-       _setmodel(self, head.mdl); // precision already set
-       self.skin = head.skin;
-
-       self.cnt = -1;
-
-       if(self.message == "")
-               self.message = " has captured a control point";
-
-       if(self.frags <= 0)
-               self.frags = 1;
-       if(self.wait <= 0)
-               self.wait = 5;
-
-       float points, waittime;
-       if (autocvar_g_domination_point_amt)
-               points = autocvar_g_domination_point_amt;
-       else
-               points = self.frags;
-       if (autocvar_g_domination_point_rate)
-               waittime = autocvar_g_domination_point_rate;
-       else
-               waittime = self.wait;
-
-       total_pps += points/waittime;
-
-       if(!self.t_width)
-               self.t_width = 0.02; // frame animation rate
-       if(!self.t_length)
-               self.t_length = 239; // maximum frame
-
-       self.think = dompointthink;
-       self.nextthink = time;
-       self.touch = dompointtouch;
-       self.solid = SOLID_TRIGGER;
-       self.flags = FL_ITEM;
-       setsize(self, '-32 -32 -32', '32 32 32');
-       setorigin(self, self.origin + '0 0 20');
-       droptofloor();
-
-       waypoint_spawnforitem(self);
-       WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT);
-}
-
-float total_controlpoints;
-void Domination_count_controlpoints()
-{
-       entity e;
-       total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
-       for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
-       {
-               ++total_controlpoints;
-               redowned += (e.goalentity.team == NUM_TEAM_1);
-               blueowned += (e.goalentity.team == NUM_TEAM_2);
-               yellowowned += (e.goalentity.team == NUM_TEAM_3);
-               pinkowned += (e.goalentity.team == NUM_TEAM_4);
-       }
-}
-
-float Domination_GetWinnerTeam()
-{
-       float winner_team = 0;
-       if(redowned == total_controlpoints)
-               winner_team = NUM_TEAM_1;
-       if(blueowned == total_controlpoints)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_2;
-       }
-       if(yellowowned == total_controlpoints)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_3;
-       }
-       if(pinkowned == total_controlpoints)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_4;
-       }
-       if(winner_team)
-               return winner_team;
-       return -1; // no control points left?
-}
-
-#define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
-#define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
-float Domination_CheckWinner()
-{
-       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
-               round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
-               return 1;
-       }
-
-       Domination_count_controlpoints();
-
-       float winner_team = Domination_GetWinnerTeam();
-
-       if(winner_team == -1)
-               return 0;
-
-       if(winner_team > 0)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
-               TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
-       }
-       else if(winner_team == -1)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
-       }
-
-       round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
-
-       return 1;
-}
-
-float Domination_CheckPlayers()
-{
-       return 1;
-}
-
-void Domination_RoundStart()
-{
-       entity e;
-       FOR_EACH_PLAYER(e)
-               e.player_blocked = 0;
-}
-
-//go to best items, or control points you don't own
-void havocbot_role_dom()
-{SELFPARAM();
-       if(self.deadflag != DEAD_NO)
-               return;
-
-       if (self.bot_strategytime < time)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-               havocbot_goalrating_controlpoints(10000, self.origin, 15000);
-               havocbot_goalrating_items(8000, self.origin, 8000);
-               //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
-               //havocbot_goalrating_waypoints(1, self.origin, 1000);
-               navigation_goalrating_end();
-       }
-}
-
-MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
-{
-       // fallback?
-       ret_float = domination_teams;
-       ret_string = "dom_team";
-
-       entity head = find(world, classname, ret_string);
-       while(head)
-       {
-               if(head.netname != "")
-               {
-                       switch(head.team)
-                       {
-                               case NUM_TEAM_1: c1 = 0; break;
-                               case NUM_TEAM_2: c2 = 0; break;
-                               case NUM_TEAM_3: c3 = 0; break;
-                               case NUM_TEAM_4: c4 = 0; break;
-                       }
-               }
-
-               head = find(head, classname, ret_string);
-       }
-
-       ret_string = string_null;
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(dom, reset_map_players)
-{SELFPARAM();
-       total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
-       entity e;
-       FOR_EACH_PLAYER(e)
-       {
-               setself(e);
-               PutClientInServer();
-               self.player_blocked = 1;
-               if(IS_REAL_CLIENT(self))
-                       set_dom_state(self);
-       }
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
-{SELFPARAM();
-       if(domination_roundbased)
-       if(!round_handler_IsRoundStarted())
-               self.player_blocked = 1;
-       else
-               self.player_blocked = 0;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(dom, ClientConnect)
-{SELFPARAM();
-       set_dom_state(self);
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
-{SELFPARAM();
-       self.havocbot_role = havocbot_role_dom;
-       return true;
-}
-
-/*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
-Control point for Domination gameplay.
-*/
-spawnfunc(dom_controlpoint)
-{
-       if(!g_domination)
-       {
-               remove(self);
-               return;
-       }
-       self.think = dom_controlpoint_setup;
-       self.nextthink = time + 0.1;
-       self.reset = dom_controlpoint_setup;
-
-       if(!self.scale)
-               self.scale = 0.6;
-
-       self.effects = self.effects | EF_LOWPRECISION;
-       if (autocvar_g_domination_point_fullbright)
-               self.effects |= EF_FULLBRIGHT;
-}
-
-/*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
-Team declaration for Domination gameplay, this allows you to decide what team
-names and control point models are used in your map.
-
-Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
-can have netname set!  The nameless team owns all control points at start.
-
-Keys:
-"netname"
- Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
-"cnt"
- Scoreboard color of the team (for example 4 is red and 13 is blue)
-"model"
- Model to use for control points owned by this team (for example
- "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
- keycard)
-"skin"
- Skin of the model to use (for team skins on a single model)
-"noise"
- Sound to play when this team captures a point.
- (this is a localized sound, like a small alarm or other effect)
-"noise1"
- Narrator speech to play when this team captures a point.
- (this is a global sound, like "Red team has captured a control point")
-*/
-
-spawnfunc(dom_team)
-{
-       if(!g_domination || autocvar_g_domination_teams_override >= 2)
-       {
-               remove(self);
-               return;
-       }
-       precache_model(self.model);
-       if (self.noise != "")
-               precache_sound(self.noise);
-       if (self.noise1 != "")
-               precache_sound(self.noise1);
-       self.classname = "dom_team";
-       _setmodel(self, self.model); // precision not needed
-       self.mdl = self.model;
-       self.dmg = self.modelindex;
-       self.model = "";
-       self.modelindex = 0;
-       // this would have to be changed if used in quakeworld
-       if(self.cnt)
-               self.team = self.cnt + 1; // WHY are these different anyway?
-}
-
-// scoreboard setup
-void ScoreRules_dom(float teams)
-{
-       if(domination_roundbased)
-       {
-               ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
-               ScoreInfo_SetLabel_TeamScore  (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
-               ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
-               ScoreRules_basics_end();
-       }
-       else
-       {
-               float sp_domticks, sp_score;
-               sp_score = sp_domticks = 0;
-               if(autocvar_g_domination_disable_frags)
-                       sp_domticks = SFL_SORT_PRIO_PRIMARY;
-               else
-                       sp_score = SFL_SORT_PRIO_PRIMARY;
-               ScoreRules_basics(teams, sp_score, sp_score, true);
-               ScoreInfo_SetLabel_TeamScore  (ST_DOM_TICKS,    "ticks",     sp_domticks);
-               ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS,    "ticks",     sp_domticks);
-               ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES,    "takes",     0);
-               ScoreRules_basics_end();
-       }
-}
-
-// code from here on is just to support maps that don't have control point and team entities
-void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
-{SELFPARAM();
-       setself(spawn());
-       self.classname = "dom_team";
-       self.netname = teamname;
-       self.cnt = teamcolor;
-       self.model = pointmodel;
-       self.skin = pointskin;
-       self.noise = capsound;
-       self.noise1 = capnarration;
-       self.message = capmessage;
-
-       // this code is identical to spawnfunc_dom_team
-       _setmodel(self, self.model); // precision not needed
-       self.mdl = self.model;
-       self.dmg = self.modelindex;
-       self.model = "";
-       self.modelindex = 0;
-       // this would have to be changed if used in quakeworld
-       self.team = self.cnt + 1;
-
-       //eprint(self);
-       setself(this);
-}
-
-void _spawnfunc_dom_controlpoint() { SELFPARAM(); spawnfunc_dom_controlpoint(self); }
-void dom_spawnpoint(vector org)
-{SELFPARAM();
-       setself(spawn());
-       self.classname = "dom_controlpoint";
-       self.think = _spawnfunc_dom_controlpoint;
-       self.nextthink = time;
-       setorigin(self, org);
-       spawnfunc_dom_controlpoint(this);
-       setself(this);
-}
-
-// spawn some default teams if the map is not set up for domination
-void dom_spawnteams(float teams)
-{
-       dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND(DOM_CLAIM), "", "Red team has captured a control point");
-       dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND(DOM_CLAIM), "", "Blue team has captured a control point");
-       if(teams >= 3)
-               dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND(DOM_CLAIM), "", "Yellow team has captured a control point");
-       if(teams >= 4)
-               dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND(DOM_CLAIM), "", "Pink team has captured a control point");
-       dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
-}
-
-void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
-{
-       // if no teams are found, spawn defaults
-       if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
-       {
-               LOG_INFO("No ""dom_team"" entities found on this map, creating them anyway.\n");
-               domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
-               dom_spawnteams(domination_teams);
-       }
-
-       CheckAllowedTeams(world);
-       domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
-
-       addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
-       addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
-       addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
-       if(domination_teams >= 3) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
-       if(domination_teams >= 4) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
-
-       domination_roundbased = autocvar_g_domination_roundbased;
-
-       ScoreRules_dom(domination_teams);
-
-       if(domination_roundbased)
-       {
-               round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
-               round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
-       }
-}
-
-void dom_Initialize()
-{
-       g_domination = true;
-       InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);
-}
-
-
-REGISTER_MUTATOR(dom, IS_GAMETYPE(DOMINATION))
-{
-       int fraglimit_override = autocvar_g_domination_point_limit;
-       if(autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
-               fraglimit_override = autocvar_g_domination_roundbased_point_limit;
-
-       ActivateTeamplay();
-       SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, -1, -1);
-       have_team_spawns = -1; // request team spawns
-
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This is a game type and it cannot be added at runtime.");
-               dom_Initialize();
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This is a game type and it cannot be removed at runtime.");
-               return -1;
-       }
-
-       return 0;
-}
diff --git a/qcsrc/server/mutators/gamemode_domination.qh b/qcsrc/server/mutators/gamemode_domination.qh
deleted file mode 100644 (file)
index d017b62..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-#ifndef GAMEMODE_DOMINATION_H
-#define GAMEMODE_DOMINATION_H
-// these are needed since mutators are compiled last
-
-// score rule declarations
-const float ST_DOM_TICKS = 1;
-const float SP_DOM_TICKS = 4;
-const float SP_DOM_TAKES = 5;
-const float ST_DOM_CAPS = 1;
-const float SP_DOM_CAPS = 4;
-
-// pps: points per second
-.float dom_total_pps;
-.float dom_pps_red;
-.float dom_pps_blue;
-.float dom_pps_yellow;
-.float dom_pps_pink;
-float total_pps;
-float pps_red;
-float pps_blue;
-float pps_yellow;
-float pps_pink;
-
-// capture declarations
-.float enemy_playerid;
-.entity sprite;
-.float captime;
-
-// misc globals
-float domination_roundbased;
-float domination_teams;
-#endif
diff --git a/qcsrc/server/mutators/gamemode_freezetag.qc b/qcsrc/server/mutators/gamemode_freezetag.qc
deleted file mode 100644 (file)
index d6c8d29..0000000
+++ /dev/null
@@ -1,643 +0,0 @@
-#include "gamemode_freezetag.qh"
-
-#include "gamemode.qh"
-
-float autocvar_g_freezetag_frozen_maxtime;
-bool autocvar_g_freezetag_revive_nade;
-float autocvar_g_freezetag_revive_nade_health;
-int autocvar_g_freezetag_point_leadlimit;
-int autocvar_g_freezetag_point_limit;
-float autocvar_g_freezetag_revive_extra_size;
-float autocvar_g_freezetag_revive_speed;
-float autocvar_g_freezetag_revive_clearspeed;
-float autocvar_g_freezetag_round_timelimit;
-int autocvar_g_freezetag_teams;
-int autocvar_g_freezetag_teams_override;
-bool autocvar_g_freezetag_team_spawns;
-float autocvar_g_freezetag_warmup;
-
-const float SP_FREEZETAG_REVIVALS = 4;
-void freezetag_ScoreRules(float teams)
-{
-       ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, true); // SFL_SORT_PRIO_PRIMARY
-       ScoreInfo_SetLabel_PlayerScore(SP_FREEZETAG_REVIVALS, "revivals", 0);
-       ScoreRules_basics_end();
-}
-
-void freezetag_count_alive_players()
-{
-       entity e;
-       total_players = redalive = bluealive = yellowalive = pinkalive = 0;
-       FOR_EACH_PLAYER(e)
-       {
-               switch(e.team)
-               {
-                       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)
-       {
-               e.redalive_stat = redalive;
-               e.bluealive_stat = bluealive;
-               e.yellowalive_stat = yellowalive;
-               e.pinkalive_stat = pinkalive;
-       }
-
-       eliminatedPlayers.SendFlags |= 1;
-}
-#define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
-#define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == freezetag_teams)
-
-float freezetag_CheckTeams()
-{
-       static float prev_missing_teams_mask;
-       if(FREEZETAG_ALIVE_TEAMS_OK())
-       {
-               if(prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               return 1;
-       }
-       if(total_players == 0)
-       {
-               if(prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               return 0;
-       }
-       float missing_teams_mask = (!redalive) + (!bluealive) * 2;
-       if(freezetag_teams >= 3) missing_teams_mask += (!yellowalive) * 4;
-       if(freezetag_teams >= 4) missing_teams_mask += (!pinkalive) * 8;
-       if(prev_missing_teams_mask != missing_teams_mask)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
-               prev_missing_teams_mask = missing_teams_mask;
-       }
-       return 0;
-}
-
-float freezetag_getWinnerTeam()
-{
-       float winner_team = 0;
-       if(redalive >= 1)
-               winner_team = NUM_TEAM_1;
-       if(bluealive >= 1)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_2;
-       }
-       if(yellowalive >= 1)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_3;
-       }
-       if(pinkalive >= 1)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_4;
-       }
-       if(winner_team)
-               return winner_team;
-       return -1; // no player left
-}
-
-float freezetag_CheckWinner()
-{
-       entity e;
-       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
-               FOR_EACH_PLAYER(e)
-               {
-                       e.freezetag_frozen_timeout = 0;
-                       nades_Clear(e);
-               }
-               round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
-               return 1;
-       }
-
-       if(FREEZETAG_ALIVE_TEAMS() > 1)
-               return 0;
-
-       float winner_team;
-       winner_team = freezetag_getWinnerTeam();
-       if(winner_team > 0)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
-               TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
-       }
-       else if(winner_team == -1)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
-       }
-
-       FOR_EACH_PLAYER(e)
-       {
-               e.freezetag_frozen_timeout = 0;
-               nades_Clear(e);
-       }
-       round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
-       return 1;
-}
-
-entity freezetag_LastPlayerForTeam()
-{SELFPARAM();
-       entity pl, last_pl = world;
-       FOR_EACH_PLAYER(pl)
-       {
-               if(pl.health >= 1)
-               if(!pl.frozen)
-               if(pl != self)
-               if(pl.team == self.team)
-               if(!last_pl)
-                       last_pl = pl;
-               else
-                       return world;
-       }
-       return last_pl;
-}
-
-void freezetag_LastPlayerForTeam_Notify()
-{
-       if(round_handler_IsActive())
-       if(round_handler_IsRoundStarted())
-       {
-               entity pl = freezetag_LastPlayerForTeam();
-               if(pl)
-                       Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
-       }
-}
-
-void freezetag_Add_Score(entity attacker)
-{SELFPARAM();
-       if(attacker == self)
-       {
-               // you froze your own dumb self
-               // counted as "suicide" already
-               PlayerScore_Add(self, SP_SCORE, -1);
-       }
-       else if(IS_PLAYER(attacker))
-       {
-               // got frozen by an enemy
-               // counted as "kill" and "death" already
-               PlayerScore_Add(self, SP_SCORE, -1);
-               PlayerScore_Add(attacker, SP_SCORE, +1);
-       }
-       // else nothing - got frozen by the game type rules themselves
-}
-
-void freezetag_Freeze(entity attacker)
-{SELFPARAM();
-       if(self.frozen)
-               return;
-
-       if(autocvar_g_freezetag_frozen_maxtime > 0)
-               self.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
-
-       Freeze(self, 0, 1, true);
-
-       freezetag_count_alive_players();
-
-       freezetag_Add_Score(attacker);
-}
-
-void freezetag_Unfreeze(entity attacker)
-{SELFPARAM();
-       self.freezetag_frozen_time = 0;
-       self.freezetag_frozen_timeout = 0;
-
-       Unfreeze(self);
-}
-
-float freezetag_isEliminated(entity e)
-{
-       if(IS_PLAYER(e) && (e.frozen == 1 || e.deadflag != DEAD_NO))
-               return true;
-       return false;
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-void() havocbot_role_ft_freeing;
-void() havocbot_role_ft_offense;
-
-void havocbot_goalrating_freeplayers(float ratingscale, vector org, float sradius)
-{SELFPARAM();
-       entity head;
-       float distance;
-
-       FOR_EACH_PLAYER(head)
-       {
-               if ((head != self) && (head.team == self.team))
-               {
-                       if (head.frozen == 1)
-                       {
-                               distance = vlen(head.origin - org);
-                               if (distance > sradius)
-                                       continue;
-                               navigation_routerating(head, ratingscale, 2000);
-                       }
-                       else
-                       {
-                               // If teamate is not frozen still seek them out as fight better
-                               // in a group.
-                               navigation_routerating(head, ratingscale/3, 2000);
-                       }
-               }
-       }
-}
-
-void havocbot_role_ft_offense()
-{SELFPARAM();
-       entity head;
-       float unfrozen;
-
-       if(self.deadflag != DEAD_NO)
-               return;
-
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + random() * 10 + 20;
-
-       // Count how many players on team are unfrozen.
-       unfrozen = 0;
-       FOR_EACH_PLAYER(head)
-       {
-               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.frozen)) || (time > self.havocbot_role_timeout))
-       {
-               LOG_TRACE("changing role to freeing\n");
-               self.havocbot_role = havocbot_role_ft_freeing;
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (time > self.bot_strategytime)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
-               navigation_goalrating_start();
-               havocbot_goalrating_items(10000, self.origin, 10000);
-               havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
-               havocbot_goalrating_freeplayers(9000, self.origin, 10000);
-               //havocbot_goalrating_waypoints(1, self.origin, 1000);
-               navigation_goalrating_end();
-       }
-}
-
-void havocbot_role_ft_freeing()
-{SELFPARAM();
-       if(self.deadflag != DEAD_NO)
-               return;
-
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + random() * 10 + 20;
-
-       if (time > self.havocbot_role_timeout)
-       {
-               LOG_TRACE("changing role to offense\n");
-               self.havocbot_role = havocbot_role_ft_offense;
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (time > self.bot_strategytime)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
-               navigation_goalrating_start();
-               havocbot_goalrating_items(8000, self.origin, 10000);
-               havocbot_goalrating_enemyplayers(10000, self.origin, 10000);
-               havocbot_goalrating_freeplayers(20000, self.origin, 10000);
-               //havocbot_goalrating_waypoints(1, self.origin, 1000);
-               navigation_goalrating_end();
-       }
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-void ft_RemovePlayer()
-{SELFPARAM();
-       self.health = 0; // neccessary to update correctly alive stats
-       if(!self.frozen)
-               freezetag_LastPlayerForTeam_Notify();
-       freezetag_Unfreeze(world);
-       freezetag_count_alive_players();
-}
-
-MUTATOR_HOOKFUNCTION(ft, ClientDisconnect)
-{SELFPARAM();
-       ft_RemovePlayer();
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver)
-{SELFPARAM();
-       ft_RemovePlayer();
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerDies)
-{SELFPARAM();
-       if(round_handler_IsActive())
-       if(round_handler_CountdownRunning())
-       {
-               if(self.frozen)
-                       freezetag_Unfreeze(world);
-               freezetag_count_alive_players();
-               return 1; // let the player die so that he can respawn whenever he wants
-       }
-
-       // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
-       // you succeed changing team through the menu: you both really die (gibbing) and get frozen
-       if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
-               || frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id)
-       {
-               // let the player die, he will be automatically frozen when he respawns
-               if(self.frozen != 1)
-               {
-                       freezetag_Add_Score(frag_attacker);
-                       freezetag_count_alive_players();
-                       freezetag_LastPlayerForTeam_Notify();
-               }
-               else
-                       freezetag_Unfreeze(world); // remove ice
-               self.health = 0; // Unfreeze resets health
-               self.freezetag_frozen_timeout = -2; // freeze on respawn
-               return 1;
-       }
-
-       if(self.frozen)
-               return 1;
-
-       freezetag_Freeze(frag_attacker);
-       freezetag_LastPlayerForTeam_Notify();
-
-       if(frag_attacker == frag_target || frag_attacker == world)
-       {
-               if(IS_PLAYER(frag_target))
-                       Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
-       }
-       else
-       {
-               if(IS_PLAYER(frag_target))
-                       Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_FROZEN, frag_attacker.netname);
-               if(IS_PLAYER(frag_attacker))
-                       Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_FREEZETAG_FREEZE, frag_target.netname);
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
-       }
-
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
-{SELFPARAM();
-       if(self.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
-               return 1; // do nothing, round is starting right now
-
-       if(self.freezetag_frozen_timeout == -2) // player was dead
-       {
-               freezetag_Freeze(world);
-               return 1;
-       }
-
-       freezetag_count_alive_players();
-
-       if(round_handler_IsActive())
-       if(round_handler_IsRoundStarted())
-       {
-               Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
-               freezetag_Freeze(world);
-       }
-
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ft, reset_map_players)
-{SELFPARAM();
-       entity e;
-       FOR_EACH_PLAYER(e)
-       {
-               e.killcount = 0;
-               e.freezetag_frozen_timeout = -1;
-               setself(e);
-               PutClientInServer();
-               e.freezetag_frozen_timeout = 0;
-       }
-       freezetag_count_alive_players();
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST)
-{
-       frag_score = 0; // no frags counted in Freeze Tag
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST)
-{SELFPARAM();
-       float n;
-
-       if(gameover)
-               return 1;
-
-       if(self.frozen == 1)
-       {
-               // keep health = 1
-               self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
-       }
-
-       if(round_handler_IsActive())
-       if(!round_handler_IsRoundStarted())
-               return 1;
-
-       entity o;
-       o = world;
-       //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;
-       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.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(!o)
-                               o = other;
-                       if(self.frozen == 1)
-                               other.reviving = true;
-                       ++n;
-               }
-       }
-
-       if(n && self.frozen == 1) // 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 * ((warmup_stage) ? warmup_start_health : start_health));
-
-               if(self.revive_progress >= 1)
-               {
-                       freezetag_Unfreeze(self);
-                       freezetag_count_alive_players();
-
-                       if(n == -1)
-                       {
-                               Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
-                               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, self.netname, autocvar_g_freezetag_frozen_maxtime);
-                               return 1;
-                       }
-
-                       // EVERY team mate nearby gets a point (even if multiple!)
-                       FOR_EACH_PLAYER(other)
-                       {
-                               if(other.reviving)
-                               {
-                                       PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1);
-                                       PlayerScore_Add(other, SP_SCORE, +1);
-
-                                       nades_GiveBonus(other,autocvar_g_nades_bonus_score_low);
-                               }
-                       }
-
-                       Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
-                       Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname);
-                       Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED, self.netname, o.netname);
-               }
-
-               FOR_EACH_PLAYER(other)
-               {
-                       if(other.reviving)
-                       {
-                               other.revive_progress = self.revive_progress;
-                               other.reviving = false;
-                       }
-               }
-       }
-       else if(!n && self.frozen == 1) // only if no teammate is nearby will we reset
-       {
-               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));
-       }
-       else if(!n && !self.frozen)
-       {
-               self.revive_progress = 0; // thawing nobody
-       }
-
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(ft, SetStartItems)
-{
-       start_items &= ~IT_UNLIMITED_AMMO;
-       //start_health       = warmup_start_health       = cvar("g_lms_start_health");
-       //start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
-       start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
-       start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
-       start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
-       start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
-       start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
-       start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
-
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
-{SELFPARAM();
-       if (!self.deadflag)
-       {
-               if (random() < 0.5)
-                       self.havocbot_role = havocbot_role_ft_freeing;
-               else
-                       self.havocbot_role = havocbot_role_ft_offense;
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, GetTeamCount, CBC_ORDER_EXCLUSIVE)
-{
-       ret_float = freezetag_teams;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
-{
-       // most weapons arena
-       if(ret_string == "0" || ret_string == "")
-               ret_string = "most";
-       return false;
-}
-
-void freezetag_Initialize()
-{
-       freezetag_teams = autocvar_g_freezetag_teams_override;
-       if(freezetag_teams < 2)
-               freezetag_teams = autocvar_g_freezetag_teams;
-       freezetag_teams = bound(2, freezetag_teams, 4);
-       freezetag_ScoreRules(freezetag_teams);
-
-       round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
-       round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
-
-       addstat(STAT_REDALIVE, AS_INT, redalive_stat);
-       addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
-       addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
-       addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
-
-       EliminatedPlayers_Init(freezetag_isEliminated);
-}
-
-REGISTER_MUTATOR(ft, g_freezetag)
-{
-       ActivateTeamplay();
-       SetLimits(autocvar_g_freezetag_point_limit, autocvar_g_freezetag_point_leadlimit, -1, -1);
-
-       if(autocvar_g_freezetag_team_spawns)
-               have_team_spawns = -1; // request team spawns
-
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This is a game type and it cannot be added at runtime.");
-               freezetag_Initialize();
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // we actually cannot roll back freezetag_Initialize here
-               // BUT: we don't need to! If this gets called, adding always
-               // succeeds.
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This is a game type and it cannot be removed at runtime.");
-               return -1;
-       }
-
-       return 0;
-}
diff --git a/qcsrc/server/mutators/gamemode_freezetag.qh b/qcsrc/server/mutators/gamemode_freezetag.qh
deleted file mode 100644 (file)
index 19489fc..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-#ifndef GAMEMODE_FREEZETAG_H
-#define GAMEMODE_FREEZETAG_H
-.float freezetag_frozen_time;
-.float freezetag_frozen_timeout;
-const float ICE_MAX_ALPHA = 1;
-const float ICE_MIN_ALPHA = 0.1;
-float freezetag_teams;
-
-.float reviving; // temp var
-
-#endif
diff --git a/qcsrc/server/mutators/gamemode_invasion.qc b/qcsrc/server/mutators/gamemode_invasion.qc
deleted file mode 100644 (file)
index e752da9..0000000
+++ /dev/null
@@ -1,507 +0,0 @@
-#include "gamemode_invasion.qh"
-
-#include "gamemode.qh"
-
-#include "../../common/monsters/spawn.qh"
-#include "../../common/monsters/sv_monsters.qh"
-
-#include "../teamplay.qh"
-
-bool g_invasion;
-
-float autocvar_g_invasion_round_timelimit;
-int autocvar_g_invasion_teams;
-bool 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;
-int autocvar_g_invasion_monster_count;
-bool autocvar_g_invasion_zombies_only;
-float autocvar_g_invasion_spawn_delay;
-
-spawnfunc(invasion_spawnpoint)
-{
-       if(!g_invasion) { remove(self); return; }
-
-       self.classname = "invasion_spawnpoint";
-
-       if(autocvar_g_invasion_zombies_only) // precache only if it hasn't been already
-       if(self.monsterid) {
-               Monster mon = get_monsterinfo(self.monsterid);
-               mon.mr_precache(mon);
-       }
-}
-
-float invasion_PickMonster(float supermonster_count)
-{
-       if(autocvar_g_invasion_zombies_only)
-               return MON_ZOMBIE.monsterid;
-
-       float i;
-       entity mon;
-
-       RandomSelection_Init();
-
-       for(i = MON_FIRST; i <= MON_LAST; ++i)
-       {
-               mon = get_monsterinfo(i);
-               if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM) || ((mon.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
-                       continue; // flying/swimming monsters not yet supported
-
-               RandomSelection_Add(world, i, string_null, 1, 1);
-       }
-
-       return RandomSelection_chosen_float;
-}
-
-entity invasion_PickSpawn()
-{
-       entity e;
-
-       RandomSelection_Init();
-
-       for(e = world;(e = find(e, classname, "invasion_spawnpoint")); )
-       {
-               RandomSelection_Add(e, 0, string_null, 1, ((time >= e.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
-               e.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
-       }
-
-       return RandomSelection_chosen_ent;
-}
-
-void invasion_SpawnChosenMonster(float mon)
-{
-       entity spawn_point, monster;
-
-       spawn_point = invasion_PickSpawn();
-
-       if(spawn_point == world)
-       {
-               LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations\n");
-               entity e = spawn();
-               setsize(e, (get_monsterinfo(mon)).mins, (get_monsterinfo(mon)).maxs);
-
-               if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
-                       monster = spawnmonster("", mon, world, world, e.origin, false, false, 2);
-               else return;
-
-               e.think = SUB_Remove;
-               e.nextthink = time + 0.1;
-       }
-       else
-               monster = spawnmonster("", ((spawn_point.monsterid) ? spawn_point.monsterid : mon), spawn_point, spawn_point, spawn_point.origin, false, false, 2);
-
-       if(spawn_point) monster.target2 = spawn_point.target2;
-       monster.spawnshieldtime = time;
-       if(spawn_point && spawn_point.target_range) monster.target_range = spawn_point.target_range;
-
-       if(teamplay)
-       if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0)
-               monster.team = spawn_point.team;
-       else
-       {
-               RandomSelection_Init();
-               if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_Add(world, NUM_TEAM_1, string_null, 1, 1);
-               if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_Add(world, NUM_TEAM_2, string_null, 1, 1);
-               if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_Add(world, NUM_TEAM_3, string_null, 1, 1); }
-               if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_Add(world, NUM_TEAM_4, string_null, 1, 1); }
-
-               monster.team = RandomSelection_chosen_float;
-       }
-
-       if(teamplay)
-       {
-               monster_setupcolors(monster);
-
-               if(monster.sprite)
-               {
-                       WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0'));
-
-                       monster.sprite.team = 0;
-                       monster.sprite.SendFlags |= 1;
-               }
-       }
-
-       monster.monster_attack = false; // it's the player's job to kill all the monsters
-
-       if(inv_roundcnt >= inv_maxrounds)
-               monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses
-}
-
-void invasion_SpawnMonsters(float supermonster_count)
-{
-       float chosen_monster = invasion_PickMonster(supermonster_count);
-
-       invasion_SpawnChosenMonster(chosen_monster);
-}
-
-float Invasion_CheckWinner()
-{
-       entity head;
-       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
-       {
-               FOR_EACH_MONSTER(head)
-                       Monster_Remove(head);
-
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
-               round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
-               return 1;
-       }
-
-       float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;
-
-       FOR_EACH_MONSTER(head) if(head.health > 0)
-       {
-               if((get_monsterinfo(head.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
-                       ++supermonster_count;
-               ++total_alive_monsters;
-
-               if(teamplay)
-               switch(head.team)
-               {
-                       case NUM_TEAM_1: ++red_alive; break;
-                       case NUM_TEAM_2: ++blue_alive; break;
-                       case NUM_TEAM_3: ++yellow_alive; break;
-                       case NUM_TEAM_4: ++pink_alive; break;
-               }
-       }
-
-       if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
-       {
-               if(time >= inv_lastcheck)
-               {
-                       invasion_SpawnMonsters(supermonster_count);
-                       inv_lastcheck = time + autocvar_g_invasion_spawn_delay;
-               }
-
-               return 0;
-       }
-
-       if(inv_numspawned < 1)
-               return 0; // nothing has spawned yet
-
-       if(teamplay)
-       {
-               if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1)
-                       return 0;
-       }
-       else if(inv_numkilled < inv_maxspawned)
-               return 0;
-
-       entity winner = world;
-       float winning_score = 0, winner_team = 0;
-
-
-       if(teamplay)
-       {
-               if(red_alive > 0) { winner_team = NUM_TEAM_1; }
-               if(blue_alive > 0)
-               if(winner_team) { winner_team = 0; }
-               else { winner_team = NUM_TEAM_2; }
-               if(yellow_alive > 0)
-               if(winner_team) { winner_team = 0; }
-               else { winner_team = NUM_TEAM_3; }
-               if(pink_alive > 0)
-               if(winner_team) { winner_team = 0; }
-               else { winner_team = NUM_TEAM_4; }
-       }
-       else
-       FOR_EACH_PLAYER(head)
-       {
-               float cs = PlayerScore_Add(head, SP_KILLS, 0);
-               if(cs > winning_score)
-               {
-                       winning_score = cs;
-                       winner = head;
-               }
-       }
-
-       FOR_EACH_MONSTER(head)
-               Monster_Remove(head);
-
-       if(teamplay)
-       {
-               if(winner_team)
-               {
-                       Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
-                       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
-               }
-       }
-       else if(winner)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
-       }
-
-       round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
-
-       return 1;
-}
-
-float Invasion_CheckPlayers()
-{
-       return true;
-}
-
-void Invasion_RoundStart()
-{
-       entity e;
-       float numplayers = 0;
-       FOR_EACH_PLAYER(e)
-       {
-               e.player_blocked = 0;
-               ++numplayers;
-       }
-
-       if(inv_roundcnt < inv_maxrounds)
-               inv_roundcnt += 1; // a limiter to stop crazy counts
-
-       inv_monsterskill = inv_roundcnt + max(1, numplayers * 0.3);
-
-       inv_maxcurrent = 0;
-       inv_numspawned = 0;
-       inv_numkilled = 0;
-
-       inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
-
-       if(teamplay)
-       {
-               DistributeEvenly_Init(inv_maxspawned, invasion_teams);
-               inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1);
-               inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1);
-               if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1);
-               if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(inv, MonsterDies)
-{SELFPARAM();
-       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
-       {
-               inv_numkilled += 1;
-               inv_maxcurrent -= 1;
-               if(teamplay) { inv_monsters_perteam[self.team] -= 1; }
-
-               if(IS_PLAYER(frag_attacker))
-               if(SAME_TEAM(frag_attacker, self)) // in non-teamplay modes, same team = same player, so this works
-                       PlayerScore_Add(frag_attacker, SP_KILLS, -1);
-               else
-               {
-                       PlayerScore_Add(frag_attacker, SP_KILLS, +1);
-                       if(teamplay)
-                               TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(inv, MonsterSpawn)
-{SELFPARAM();
-       if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
-               return true;
-
-       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
-       {
-               inv_numspawned += 1;
-               inv_maxcurrent += 1;
-       }
-
-       self.monster_skill = inv_monsterskill;
-
-       if((get_monsterinfo(self.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, self.monster_name);
-
-       self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(inv, OnEntityPreSpawn)
-{SELFPARAM();
-       if(startsWith(self.classname, "monster_"))
-       if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
-               return true;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(inv, SV_StartFrame)
-{
-       monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
-       monsters_killed = inv_numkilled;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(inv, PlayerRegen)
-{
-       // no regeneration in invasion
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, PlayerSpawn)
-{SELFPARAM();
-       self.bot_attack = false;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(inv, PlayerDamage_Calculate)
-{
-       if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target)
-       {
-               frag_damage = 0;
-               frag_force = '0 0 0';
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(inv, SV_ParseClientCommand)
-{SELFPARAM();
-       if(MUTATOR_RETURNVALUE) // command was already handled?
-               return false;
-
-       if(cmd_name == "debuginvasion")
-       {
-               sprint(self, strcat("inv_maxspawned = ", ftos(inv_maxspawned), "\n"));
-               sprint(self, strcat("inv_numspawned = ", ftos(inv_numspawned), "\n"));
-               sprint(self, strcat("inv_numkilled = ", ftos(inv_numkilled), "\n"));
-               sprint(self, strcat("inv_roundcnt = ", ftos(inv_roundcnt), "\n"));
-               sprint(self, strcat("monsters_total = ", ftos(monsters_total), "\n"));
-               sprint(self, strcat("monsters_killed = ", ftos(monsters_killed), "\n"));
-               sprint(self, strcat("inv_monsterskill = ", ftos(inv_monsterskill), "\n"));
-
-               return true;
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(inv, BotShouldAttack)
-{
-       if(!IS_MONSTER(checkentity))
-               return true;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(inv, SetStartItems)
-{
-       start_health = 200;
-       start_armorvalue = 200;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid)
-{
-       if(IS_MONSTER(frag_target))
-               return MUT_ACCADD_INVALID;
-       return MUT_ACCADD_INDIFFERENT;
-}
-
-MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning)
-{
-       // monster spawning disabled during an invasion
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, GetTeamCount, CBC_ORDER_EXCLUSIVE)
-{
-       ret_float = invasion_teams;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(inv, AllowMobButcher)
-{
-       ret_string = "This command does not work during an invasion!";
-       return true;
-}
-
-void invasion_ScoreRules(float inv_teams)
-{
-       if(inv_teams) { CheckAllowedTeams(world); }
-       ScoreRules_basics(inv_teams, 0, 0, false);
-       if(inv_teams) ScoreInfo_SetLabel_TeamScore(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
-       ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
-       ScoreRules_basics_end();
-}
-
-void invasion_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
-{
-       if(autocvar_g_invasion_teams)
-               invasion_teams = bound(2, autocvar_g_invasion_teams, 4);
-       else
-               invasion_teams = 0;
-
-       independent_players = 1; // to disable extra useless scores
-
-       invasion_ScoreRules(invasion_teams);
-
-       independent_players = 0;
-
-       round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart);
-       round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
-
-       inv_roundcnt = 0;
-       inv_maxrounds = 15; // 15?
-}
-
-void invasion_Initialize()
-{
-       if(autocvar_g_invasion_zombies_only) {
-               Monster mon = MON_ZOMBIE;
-               mon.mr_precache(mon);
-       } else
-       {
-               float i;
-               entity mon;
-               for(i = MON_FIRST; i <= MON_LAST; ++i)
-               {
-                       mon = get_monsterinfo(i);
-                       if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM))
-                               continue; // flying/swimming monsters not yet supported
-
-                       mon.mr_precache(mon);
-               }
-       }
-
-       InitializeEntity(world, invasion_DelayedInit, INITPRIO_GAMETYPE);
-}
-
-REGISTER_MUTATOR(inv, IS_GAMETYPE(INVASION))
-{
-       SetLimits(autocvar_g_invasion_point_limit, -1, -1, -1);
-       if(autocvar_g_invasion_teams >= 2)
-       {
-               ActivateTeamplay();
-               if(autocvar_g_invasion_team_spawns)
-                       have_team_spawns = -1; // request team spawns
-       }
-
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This is a game type and it cannot be added at runtime.");
-               g_invasion = true;
-               invasion_Initialize();
-
-               cvar_settemp("g_monsters", "1");
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // we actually cannot roll back invasion_Initialize here
-               // BUT: we don't need to! If this gets called, adding always
-               // succeeds.
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This is a game type and it cannot be removed at runtime.");
-               return -1;
-       }
-
-       return 0;
-}
diff --git a/qcsrc/server/mutators/gamemode_invasion.qh b/qcsrc/server/mutators/gamemode_invasion.qh
deleted file mode 100644 (file)
index 191aef9..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef GAMEMODE_INVASION_H
-#define GAMEMODE_INVASION_H
-
-float inv_numspawned;
-float inv_maxspawned;
-float inv_roundcnt;
-float inv_maxrounds;
-float inv_numkilled;
-float inv_lastcheck;
-float inv_maxcurrent;
-
-float invasion_teams;
-float inv_monsters_perteam[17];
-
-float inv_monsterskill;
-
-const float ST_INV_KILLS = 1;
-#endif
diff --git a/qcsrc/server/mutators/gamemode_keepaway.qc b/qcsrc/server/mutators/gamemode_keepaway.qc
deleted file mode 100644 (file)
index f96aada..0000000
+++ /dev/null
@@ -1,485 +0,0 @@
-#include "gamemode_keepaway.qh"
-
-#include "gamemode.qh"
-
-int autocvar_g_keepaway_ballcarrier_effects;
-float autocvar_g_keepaway_ballcarrier_damage;
-float autocvar_g_keepaway_ballcarrier_force;
-float autocvar_g_keepaway_ballcarrier_highspeed;
-float autocvar_g_keepaway_ballcarrier_selfdamage;
-float autocvar_g_keepaway_ballcarrier_selfforce;
-float autocvar_g_keepaway_noncarrier_damage;
-float autocvar_g_keepaway_noncarrier_force;
-float autocvar_g_keepaway_noncarrier_selfdamage;
-float autocvar_g_keepaway_noncarrier_selfforce;
-bool autocvar_g_keepaway_noncarrier_warn;
-int autocvar_g_keepaway_score_bckill;
-int autocvar_g_keepaway_score_killac;
-int autocvar_g_keepaway_score_timepoints;
-float autocvar_g_keepaway_score_timeinterval;
-float autocvar_g_keepawayball_damageforcescale;
-int autocvar_g_keepawayball_effects;
-float autocvar_g_keepawayball_respawntime;
-int autocvar_g_keepawayball_trail_color;
-
-float ka_ballcarrier_waypointsprite_visible_for_player(entity e) // runs on waypoints which are attached to ballcarriers, updates once per frame
-{
-       if(e.ballcarried)
-               if(IS_SPEC(other))
-                       return false; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen
-
-       // TODO: Make the ballcarrier lack a waypointsprite whenever they have the invisibility powerup
-
-       return true;
-}
-
-void ka_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
-{
-       if(autocvar_sv_eventlog)
-               GameLogEcho(strcat(":ka:", mode, ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void ka_TouchEvent();
-void ka_RespawnBall() // runs whenever the ball needs to be relocated
-{SELFPARAM();
-       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))
-       {
-               entity spot = SelectSpawnPoint(true);
-               setorigin(self, spot.origin);
-               self.angles = spot.angles;
-       }
-
-       makevectors(self.angles);
-       self.movetype = MOVETYPE_BOUNCE;
-       self.velocity = '0 0 200';
-       self.angles = '0 0 0';
-       self.effects = autocvar_g_keepawayball_effects;
-       self.touch = ka_TouchEvent;
-       self.think = ka_RespawnBall;
-       self.nextthink = time + autocvar_g_keepawayball_respawntime;
-
-       Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1);
-       Send_Effect(EFFECT_ELECTRO_COMBO, self.origin, '0 0 0', 1);
-
-       WaypointSprite_Spawn(WP_KaBall, 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
-       WaypointSprite_Ping(self.waypointsprite_attachedforcarrier);
-
-       sound(self, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-}
-
-void ka_TimeScoring()
-{SELFPARAM();
-       if(self.owner.ballcarried)
-       { // add points for holding the ball after a certain amount of time
-               if(autocvar_g_keepaway_score_timepoints)
-                       PlayerScore_Add(self.owner, SP_SCORE, autocvar_g_keepaway_score_timepoints);
-
-               PlayerScore_Add(self.owner, SP_KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds"
-               self.nextthink = time + autocvar_g_keepaway_score_timeinterval;
-       }
-}
-
-void ka_TouchEvent() // runs any time that the ball comes in contact with something
-{SELFPARAM();
-       if(gameover) { return; }
-       if(!self) { return; }
-       if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
-       { // The ball fell off the map, respawn it since players can't get to it
-               ka_RespawnBall();
-               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
-               Send_Effect(EFFECT_BALL_SPARKS, self.origin, '0 0 0', 1);
-               sound(self, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM);
-               return;
-       }
-       else if(self.wait > time) { return; }
-
-       // attach the ball to the player
-       self.owner = other;
-       other.ballcarried = self;
-       setattachment(self, other, "");
-       setorigin(self, '0 0 0');
-
-       // make the ball invisible/unable to do anything/set up time scoring
-       self.velocity = '0 0 0';
-       self.movetype = MOVETYPE_NONE;
-       self.effects |= EF_NODRAW;
-       self.touch = func_null;
-       self.think = ka_TimeScoring;
-       self.nextthink = time + autocvar_g_keepaway_score_timeinterval;
-       self.takedamage = DAMAGE_NO;
-
-       // apply effects to player
-       other.glow_color = autocvar_g_keepawayball_trail_color;
-       other.glow_trail = true;
-       other.effects |= autocvar_g_keepaway_ballcarrier_effects;
-
-       // messages and sounds
-       ka_EventLog("pickup", other);
-       Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_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, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-
-       // scoring
-       PlayerScore_Add(other, SP_KEEPAWAY_PICKUPS, 1);
-
-       // waypoints
-       WaypointSprite_AttachCarrier(WP_KaBallCarrier, other, RADARICON_FLAGCARRIER);
-       other.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
-       WaypointSprite_UpdateRule(other.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
-       WaypointSprite_Ping(other.waypointsprite_attachedforcarrier);
-       WaypointSprite_Kill(self.waypointsprite_attachedforcarrier);
-}
-
-void ka_DropEvent(entity plyr) // runs any time that a player is supposed to lose the ball
-{
-       entity ball;
-       ball = plyr.ballcarried;
-
-       if(!ball) { return; }
-
-       // reset the ball
-       setattachment(ball, world, "");
-       ball.movetype = MOVETYPE_BOUNCE;
-       ball.wait = time + 1;
-       ball.touch = ka_TouchEvent;
-       ball.think = ka_RespawnBall;
-       ball.nextthink = time + autocvar_g_keepawayball_respawntime;
-       ball.takedamage = DAMAGE_YES;
-       ball.effects &= ~EF_NODRAW;
-       setorigin(ball, plyr.origin + '0 0 10');
-       ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
-       ball.owner.ballcarried = world; // I hope nothing checks to see if the world has the ball in the rest of my code :P
-       ball.owner = world;
-
-       // reset the player effects
-       plyr.glow_trail = false;
-       plyr.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
-
-       // messages and sounds
-       ka_EventLog("dropped", plyr);
-       Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname);
-       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname);
-       sound(other, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-
-       // scoring
-       // PlayerScore_Add(plyr, SP_KEEPAWAY_DROPS, 1); Not anymore, this is 100% the same as pickups and is useless.
-
-       // waypoints
-       WaypointSprite_Spawn(WP_KaBall, 0, 0, ball, '0 0 64', world, ball.team, ball, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
-       WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
-       WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier);
-       WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier);
-}
-
-void ka_Reset() // used to clear the ballcarrier whenever the match switches from warmup to normal
-{SELFPARAM();
-       if((self.owner) && (IS_PLAYER(self.owner)))
-               ka_DropEvent(self.owner);
-
-       if(time < game_starttime)
-       {
-               self.think = ka_RespawnBall;
-               self.touch = func_null;
-               self.nextthink = game_starttime;
-       }
-       else
-               ka_RespawnBall();
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-void havocbot_goalrating_ball(float ratingscale, vector org)
-{SELFPARAM();
-       float t;
-       entity ball_owner;
-       ball_owner = ka_ball.owner;
-
-       if (ball_owner == self)
-               return;
-
-       // If ball is carried by player then hunt them down.
-       if (ball_owner)
-       {
-               t = (self.health + self.armorvalue) / (ball_owner.health + ball_owner.armorvalue);
-               navigation_routerating(ball_owner, t * ratingscale, 2000);
-       }
-       else // Ball has been dropped so collect.
-               navigation_routerating(ka_ball, ratingscale, 2000);
-}
-
-void havocbot_role_ka_carrier()
-{SELFPARAM();
-       if (self.deadflag != DEAD_NO)
-               return;
-
-       if (time > self.bot_strategytime)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
-               navigation_goalrating_start();
-               havocbot_goalrating_items(10000, self.origin, 10000);
-               havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
-               //havocbot_goalrating_waypoints(1, self.origin, 1000);
-               navigation_goalrating_end();
-       }
-
-       if (!self.ballcarried)
-       {
-               self.havocbot_role = havocbot_role_ka_collector;
-               self.bot_strategytime = 0;
-       }
-}
-
-void havocbot_role_ka_collector()
-{SELFPARAM();
-       if (self.deadflag != DEAD_NO)
-               return;
-
-       if (time > self.bot_strategytime)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
-               navigation_goalrating_start();
-               havocbot_goalrating_items(10000, self.origin, 10000);
-               havocbot_goalrating_enemyplayers(1000, self.origin, 10000);
-               havocbot_goalrating_ball(20000, self.origin);
-               navigation_goalrating_end();
-       }
-
-       if (self.ballcarried)
-       {
-               self.havocbot_role = havocbot_role_ka_carrier;
-               self.bot_strategytime = 0;
-       }
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-MUTATOR_HOOKFUNCTION(ka, PlayerDies)
-{SELFPARAM();
-       if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)))
-       {
-               if(frag_target.ballcarried) { // add to amount of times killing carrier
-                       PlayerScore_Add(frag_attacker, SP_KEEPAWAY_CARRIERKILLS, 1);
-                       if(autocvar_g_keepaway_score_bckill) // add bckills to the score
-                               PlayerScore_Add(frag_attacker, SP_SCORE, autocvar_g_keepaway_score_bckill);
-               }
-               else if(!frag_attacker.ballcarried)
-                       if(autocvar_g_keepaway_noncarrier_warn)
-                               Send_Notification(NOTIF_ONE_ONLY, frag_attacker, MSG_CENTER, CENTER_KEEPAWAY_WARN);
-
-               if(frag_attacker.ballcarried) // add to amount of kills while ballcarrier
-                       PlayerScore_Add(frag_attacker, SP_SCORE, autocvar_g_keepaway_score_killac);
-       }
-
-       if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has died, drop it
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(ka, GiveFragsForKill)
-{
-       frag_score = 0; // no frags counted in keepaway
-       return 1; // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPreThink)
-{SELFPARAM();
-       // clear the item used for the ball in keepaway
-       self.items &= ~IT_KEY1;
-
-       // if the player has the ball, make sure they have the item for it (Used for HUD primarily)
-       if(self.ballcarried)
-               self.items |= IT_KEY1;
-
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerUseKey)
-{SELFPARAM();
-       if(MUTATOR_RETURNVALUE == 0)
-       if(self.ballcarried)
-       {
-               ka_DropEvent(self);
-               return 1;
-       }
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
-{
-       if(frag_attacker.ballcarried) // if the attacker is a ballcarrier
-       {
-               if(frag_target == frag_attacker) // damage done to yourself
-               {
-                       frag_damage *= autocvar_g_keepaway_ballcarrier_selfdamage;
-                       frag_force *= autocvar_g_keepaway_ballcarrier_selfforce;
-               }
-               else // damage done to noncarriers
-               {
-                       frag_damage *= autocvar_g_keepaway_ballcarrier_damage;
-                       frag_force *= autocvar_g_keepaway_ballcarrier_force;
-               }
-       }
-       else if (!frag_target.ballcarried) // if the target is a noncarrier
-       {
-               if(frag_target == frag_attacker) // damage done to yourself
-               {
-                       frag_damage *= autocvar_g_keepaway_noncarrier_selfdamage;
-                       frag_force *= autocvar_g_keepaway_noncarrier_selfforce;
-               }
-               else // damage done to other noncarriers
-               {
-                       frag_damage *= autocvar_g_keepaway_noncarrier_damage;
-                       frag_force *= autocvar_g_keepaway_noncarrier_force;
-               }
-       }
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(ka, ClientDisconnect)
-{SELFPARAM();
-       if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has left the match, drop it
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(ka, MakePlayerObserver)
-{SELFPARAM();
-       if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has left the match, drop it
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPowerups)
-{SELFPARAM();
-       // In the future this hook is supposed to allow me to do some extra stuff with waypointsprites and invisibility powerup
-       // So bare with me until I can fix a certain bug with ka_ballcarrier_waypointsprite_visible_for_player()
-
-       self.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
-
-       if(self.ballcarried)
-               self.effects |= autocvar_g_keepaway_ballcarrier_effects;
-
-       return 0;
-}
-
-.float stat_sv_airspeedlimit_nonqw;
-.float stat_sv_maxspeed;
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPhysics)
-{SELFPARAM();
-       if(self.ballcarried)
-       {
-               self.stat_sv_airspeedlimit_nonqw *= autocvar_g_keepaway_ballcarrier_highspeed;
-               self.stat_sv_maxspeed *= autocvar_g_keepaway_ballcarrier_highspeed;
-       }
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ka, BotShouldAttack)
-{SELFPARAM();
-       // if neither player has ball then don't attack unless the ball is on the ground
-       if(!checkentity.ballcarried && !self.ballcarried && ka_ball.owner)
-               return true;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ka, HavocBot_ChooseRole)
-{SELFPARAM();
-       if (self.ballcarried)
-               self.havocbot_role = havocbot_role_ka_carrier;
-       else
-               self.havocbot_role = havocbot_role_ka_collector;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ka, DropSpecialItems)
-{
-       if(frag_target.ballcarried)
-               ka_DropEvent(frag_target);
-
-       return false;
-}
-
-
-// ==============
-// Initialization
-// ==============
-
-void ka_SpawnBall() // loads various values for the ball, runs only once at start of match
-{
-       entity e;
-       e = spawn();
-       e.model = "models/orbs/orbblue.md3";
-       precache_model(e.model);
-       _setmodel(e, e.model);
-       setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
-       e.classname = "keepawayball";
-       e.damageforcescale = autocvar_g_keepawayball_damageforcescale;
-       e.takedamage = DAMAGE_YES;
-       e.solid = SOLID_TRIGGER;
-       e.movetype = MOVETYPE_BOUNCE;
-       e.glow_color = autocvar_g_keepawayball_trail_color;
-       e.glow_trail = true;
-       e.flags = FL_ITEM;
-       e.reset = ka_Reset;
-       e.touch = ka_TouchEvent;
-       e.owner = world;
-       ka_ball = e;
-
-       InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So.
-}
-
-void ka_ScoreRules()
-{
-       ScoreRules_basics(0, SFL_SORT_PRIO_PRIMARY, 0, true); // SFL_SORT_PRIO_PRIMARY
-       ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_PICKUPS,                     "pickups",              0);
-       ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_CARRIERKILLS,        "bckills",              0);
-       ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_BCTIME,                      "bctime",               SFL_SORT_PRIO_SECONDARY);
-       ScoreRules_basics_end();
-}
-
-void ka_Initialize() // run at the start of a match, initiates game mode
-{
-       ka_ScoreRules();
-       ka_SpawnBall();
-}
-
-
-REGISTER_MUTATOR(ka, IS_GAMETYPE(KEEPAWAY))
-{
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This is a game type and it cannot be added at runtime.");
-               ka_Initialize();
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // we actually cannot roll back ka_Initialize here
-               // BUT: we don't need to! If this gets called, adding always
-               // succeeds.
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This is a game type and it cannot be removed at runtime.");
-               return -1;
-       }
-
-       return 0;
-}
diff --git a/qcsrc/server/mutators/gamemode_keepaway.qh b/qcsrc/server/mutators/gamemode_keepaway.qh
deleted file mode 100644 (file)
index 250f2fb..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-#ifndef GAMEMODE_KEEPAWAY_H
-#define GAMEMODE_KEEPAWAY_H
-
-// these are needed since mutators are compiled last
-
-entity ka_ball;
-
-const float SP_KEEPAWAY_PICKUPS = 4;
-const float SP_KEEPAWAY_CARRIERKILLS = 5;
-const float SP_KEEPAWAY_BCTIME = 6;
-
-void() havocbot_role_ka_carrier;
-void() havocbot_role_ka_collector;
-
-void ka_DropEvent(entity plyr);
-
-#endif
diff --git a/qcsrc/server/mutators/gamemode_keyhunt.qc b/qcsrc/server/mutators/gamemode_keyhunt.qc
deleted file mode 100644 (file)
index 8f680ae..0000000
+++ /dev/null
@@ -1,1380 +0,0 @@
-#include "gamemode_keyhunt.qh"
-
-#include "gamemode.qh"
-
-float autocvar_g_balance_keyhunt_damageforcescale;
-float autocvar_g_balance_keyhunt_delay_collect;
-float autocvar_g_balance_keyhunt_delay_return;
-float autocvar_g_balance_keyhunt_delay_round;
-float autocvar_g_balance_keyhunt_delay_tracking;
-float autocvar_g_balance_keyhunt_dropvelocity;
-float autocvar_g_balance_keyhunt_maxdist;
-float autocvar_g_balance_keyhunt_protecttime;
-
-int autocvar_g_balance_keyhunt_score_capture;
-int autocvar_g_balance_keyhunt_score_carrierfrag;
-int autocvar_g_balance_keyhunt_score_collect;
-int autocvar_g_balance_keyhunt_score_destroyed;
-int autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
-int autocvar_g_balance_keyhunt_score_push;
-float autocvar_g_balance_keyhunt_throwvelocity;
-
-int autocvar_g_keyhunt_point_leadlimit;
-bool autocvar_g_keyhunt_team_spawns;
-#define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit")
-int autocvar_g_keyhunt_teams;
-int autocvar_g_keyhunt_teams_override;
-
-// #define KH_PLAYER_USE_ATTACHMENT
-// #define KH_PLAYER_USE_CARRIEDMODEL
-
-#ifdef KH_PLAYER_USE_ATTACHMENT
-const vector KH_PLAYER_ATTACHMENT_DIST_ROTATED = '0 -4 0';
-const vector KH_PLAYER_ATTACHMENT_DIST = '4 0 0';
-const vector KH_PLAYER_ATTACHMENT = '0 0 0';
-const vector KH_PLAYER_ATTACHMENT_ANGLES = '0 0 0';
-const string KH_PLAYER_ATTACHMENT_BONE = "";
-#else
-const float KH_KEY_ZSHIFT = 22;
-const float KH_KEY_XYDIST = 24;
-const float KH_KEY_XYSPEED = 45;
-#endif
-const float KH_KEY_WP_ZSHIFT = 20;
-
-const vector KH_KEY_MIN = '-10 -10 -46';
-const vector KH_KEY_MAX = '10 10 3';
-const float KH_KEY_BRIGHTNESS = 2;
-
-float kh_no_radar_circles;
-
-// kh_state
-//     bits  0- 4: team of key 1, or 0 for no such key, or 30 for dropped, or 31 for self
-//     bits  5- 9: team of key 2, or 0 for no such key, or 30 for dropped, or 31 for self
-//     bits 10-14: team of key 3, or 0 for no such key, or 30 for dropped, or 31 for self
-//     bits 15-19: team of key 4, or 0 for no such key, or 30 for dropped, or 31 for self
-.float kh_state;
-.float siren_time;  //  time delay the siren
-//.float stuff_time;  //  time delay to stuffcmd a cvar
-
-float kh_keystatus[17];
-//kh_keystatus[0] = status of dropped keys, kh_keystatus[1 - 16] = player #
-//replace 17 with cvar("maxplayers") or similar !!!!!!!!!
-//for(i = 0; i < maxplayers; ++i)
-//     kh_keystatus[i] = "0";
-
-float kh_Team_ByID(float t)
-{
-       if(t == 0) return NUM_TEAM_1;
-       if(t == 1) return NUM_TEAM_2;
-       if(t == 2) return NUM_TEAM_3;
-       if(t == 3) return NUM_TEAM_4;
-       return 0;
-}
-
-//entity kh_worldkeylist;
-.entity kh_worldkeynext;
-entity kh_controller;
-//float kh_tracking_enabled;
-float kh_teams;
-float kh_interferemsg_time, kh_interferemsg_team;
-.entity kh_next, kh_prev; // linked list
-.float kh_droptime;
-.float kh_dropperteam;
-.entity kh_previous_owner;
-.float kh_previous_owner_playerid;
-.float kh_cp_duration;
-
-float kh_key_dropped, kh_key_carried;
-
-const float ST_KH_CAPS = 1;
-const float SP_KH_CAPS = 4;
-const float SP_KH_PUSHES = 5;
-const float SP_KH_DESTROYS = 6;
-const float SP_KH_PICKUPS = 7;
-const float SP_KH_KCKILLS = 8;
-const float SP_KH_LOSSES = 9;
-void kh_ScoreRules(float teams)
-{
-       ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, true);
-       ScoreInfo_SetLabel_TeamScore(  ST_KH_CAPS,      "caps",      SFL_SORT_PRIO_SECONDARY);
-       ScoreInfo_SetLabel_PlayerScore(SP_KH_CAPS,      "caps",      SFL_SORT_PRIO_SECONDARY);
-       ScoreInfo_SetLabel_PlayerScore(SP_KH_PUSHES,    "pushes",    0);
-       ScoreInfo_SetLabel_PlayerScore(SP_KH_DESTROYS,  "destroyed", SFL_LOWER_IS_BETTER);
-       ScoreInfo_SetLabel_PlayerScore(SP_KH_PICKUPS,   "pickups",   0);
-       ScoreInfo_SetLabel_PlayerScore(SP_KH_KCKILLS,   "kckills",   0);
-       ScoreInfo_SetLabel_PlayerScore(SP_KH_LOSSES,    "losses",    SFL_LOWER_IS_BETTER);
-       ScoreRules_basics_end();
-}
-
-float kh_KeyCarrier_waypointsprite_visible_for_player(entity e)  // runs all the time
-{SELFPARAM();
-       if(!IS_PLAYER(e) || self.team != e.team)
-               if(!kh_tracking_enabled)
-                       return false;
-
-       return true;
-}
-
-float kh_Key_waypointsprite_visible_for_player(entity e) // ??
-{SELFPARAM();
-       if(!kh_tracking_enabled)
-               return false;
-       if(!self.owner)
-               return true;
-       if(!self.owner.owner)
-               return true;
-       return false;  // draw only when key is not owned
-}
-
-void kh_update_state()
-{
-       entity player;
-       entity key;
-       float s;
-       float f;
-
-       s = 0;
-       FOR_EACH_KH_KEY(key)
-       {
-               if(key.owner)
-                       f = key.team;
-               else
-                       f = 30;
-               s |= pow(32, key.count) * f;
-       }
-
-       FOR_EACH_CLIENT(player)
-       {
-               player.kh_state = s;
-       }
-
-       FOR_EACH_KH_KEY(key)
-       {
-               if(key.owner)
-                       key.owner.kh_state |= pow(32, key.count) * 31;
-       }
-       //print(ftos((nextent(world)).kh_state), "\n");
-}
-
-
-
-
-var kh_Think_t kh_Controller_Thinkfunc;
-void kh_Controller_SetThink(float t, kh_Think_t func)  // runs occasionaly
-{
-       kh_Controller_Thinkfunc = func;
-       kh_controller.cnt = ceil(t);
-       if(t == 0)
-               kh_controller.nextthink = time; // force
-}
-void kh_WaitForPlayers();
-void kh_Controller_Think()  // called a lot
-{SELFPARAM();
-       if(intermission_running)
-               return;
-       if(self.cnt > 0)
-       { if(self.think != kh_WaitForPlayers) { self.cnt -= 1; } }
-       else if(self.cnt == 0)
-       {
-               self.cnt -= 1;
-               kh_Controller_Thinkfunc();
-       }
-       self.nextthink = time + 1;
-}
-
-// frags f: take from cvar * f
-// frags 0: no frags
-void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner)  // update the score when a key is captured
-{
-       string s;
-       if(intermission_running)
-               return;
-
-       if(frags_player)
-               UpdateFrags(player, frags_player);
-
-       if(key && key.owner && frags_owner)
-               UpdateFrags(key.owner, frags_owner);
-
-       if(!autocvar_sv_eventlog)  //output extra info to the console or text file
-               return;
-
-       s = strcat(":keyhunt:", what, ":", ftos(player.playerid), ":", ftos(frags_player));
-
-       if(key && key.owner)
-               s = strcat(s, ":", ftos(key.owner.playerid));
-       else
-               s = strcat(s, ":0");
-
-       s = strcat(s, ":", ftos(frags_owner), ":");
-
-       if(key)
-               s = strcat(s, key.netname);
-
-       GameLogEcho(s);
-}
-
-vector kh_AttachedOrigin(entity e)  // runs when a team captures the flag, it can run 2 or 3 times.
-{
-       if(e.tag_entity)
-       {
-               makevectors(e.tag_entity.angles);
-               return e.tag_entity.origin + e.origin.x * v_forward - e.origin.y * v_right + e.origin.z * v_up;
-       }
-       else
-               return e.origin;
-}
-
-void kh_Key_Attach(entity key)  // runs when a player picks up a key and several times when a key is assigned to a player at the start of a round
-{
-#ifdef KH_PLAYER_USE_ATTACHMENT
-       entity first;
-       first = key.owner.kh_next;
-       if(key == first)
-       {
-               setattachment(key, key.owner, KH_PLAYER_ATTACHMENT_BONE);
-               if(key.kh_next)
-               {
-                       setattachment(key.kh_next, key, "");
-                       setorigin(key, key.kh_next.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);
-                       setorigin(key.kh_next, KH_PLAYER_ATTACHMENT_DIST_ROTATED);
-                       key.kh_next.angles = '0 0 0';
-               }
-               else
-                       setorigin(key, KH_PLAYER_ATTACHMENT);
-               key.angles = KH_PLAYER_ATTACHMENT_ANGLES;
-       }
-       else
-       {
-               setattachment(key, key.kh_prev, "");
-               if(key.kh_next)
-                       setattachment(key.kh_next, key, "");
-               setorigin(key, KH_PLAYER_ATTACHMENT_DIST_ROTATED);
-               setorigin(first, first.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);
-               key.angles = '0 0 0';
-       }
-#else
-       setattachment(key, key.owner, "");
-       setorigin(key, '0 0 1' * KH_KEY_ZSHIFT);  // fixing x, y in think
-       key.angles_y -= key.owner.angles.y;
-#endif
-       key.flags = 0;
-       key.solid = SOLID_NOT;
-       key.movetype = MOVETYPE_NONE;
-       key.team = key.owner.team;
-       key.nextthink = time;
-       key.damageforcescale = 0;
-       key.takedamage = DAMAGE_NO;
-       key.modelindex = kh_key_carried;
-}
-
-void kh_Key_Detach(entity key) // runs every time a key is dropped or lost. Runs several times times when all the keys are captured
-{
-#ifdef KH_PLAYER_USE_ATTACHMENT
-       entity first;
-       first = key.owner.kh_next;
-       if(key == first)
-       {
-               if(key.kh_next)
-               {
-                       setattachment(key.kh_next, key.owner, KH_PLAYER_ATTACHMENT_BONE);
-                       setorigin(key.kh_next, key.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);
-                       key.kh_next.angles = KH_PLAYER_ATTACHMENT_ANGLES;
-               }
-       }
-       else
-       {
-               if(key.kh_next)
-                       setattachment(key.kh_next, key.kh_prev, "");
-               setorigin(first, first.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);
-       }
-       // in any case:
-       setattachment(key, world, "");
-       setorigin(key, key.owner.origin + '0 0 1' * (PL_MIN.z - KH_KEY_MIN_z));
-       key.angles = key.owner.angles;
-#else
-       setorigin(key, key.owner.origin + key.origin.z * '0 0 1');
-       setattachment(key, world, "");
-       key.angles_y += key.owner.angles.y;
-#endif
-       key.flags = FL_ITEM;
-       key.solid = SOLID_TRIGGER;
-       key.movetype = MOVETYPE_TOSS;
-       key.pain_finished = time + autocvar_g_balance_keyhunt_delay_return;
-       key.damageforcescale = autocvar_g_balance_keyhunt_damageforcescale;
-       key.takedamage = DAMAGE_YES;
-       // let key.team stay
-       key.modelindex = kh_key_dropped;
-       key.kh_previous_owner = key.owner;
-       key.kh_previous_owner_playerid = key.owner.playerid;
-}
-
-void kh_Key_AssignTo(entity key, entity player)  // runs every time a key is picked up or assigned. Runs prior to kh_key_attach
-{
-       entity k;
-       float ownerteam0, ownerteam;
-       if(key.owner == player)
-               return;
-
-       ownerteam0 = kh_Key_AllOwnedByWhichTeam();
-
-       if(key.owner)
-       {
-               kh_Key_Detach(key);
-
-               // remove from linked list
-               if(key.kh_next)
-                       key.kh_next.kh_prev = key.kh_prev;
-               key.kh_prev.kh_next = key.kh_next;
-               key.kh_next = world;
-               key.kh_prev = world;
-
-               if(key.owner.kh_next == world)
-               {
-                       // No longer a key carrier
-                       if(!kh_no_radar_circles)
-                               WaypointSprite_Ping(key.owner.waypointsprite_attachedforcarrier);
-                       WaypointSprite_DetachCarrier(key.owner);
-               }
-       }
-
-       key.owner = player;
-
-       if(player)
-       {
-               // insert into linked list
-               key.kh_next = player.kh_next;
-               key.kh_prev = player;
-               player.kh_next = key;
-               if(key.kh_next)
-                       key.kh_next.kh_prev = key;
-
-               float i;
-               i = kh_keystatus[key.owner.playerid];
-                       if(key.netname == "^1red key")
-                               i += 1;
-                       if(key.netname == "^4blue key")
-                               i += 2;
-                       if(key.netname == "^3yellow key")
-                               i += 4;
-                       if(key.netname == "^6pink key")
-                               i += 8;
-               kh_keystatus[key.owner.playerid] = i;
-
-               kh_Key_Attach(key);
-
-               if(key.kh_next == world)
-               {
-                       // player is now a key carrier
-                       entity wp = WaypointSprite_AttachCarrier(WP_Null, player, RADARICON_FLAGCARRIER);
-                       wp.colormod = colormapPaletteColor(player.team - 1, 0);
-                       player.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_KeyCarrier_waypointsprite_visible_for_player;
-                       WaypointSprite_UpdateRule(player.waypointsprite_attachedforcarrier, player.team, SPRITERULE_TEAMPLAY);
-                       if(player.team == NUM_TEAM_1)
-                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierRed, WP_KeyCarrierFriend, WP_KeyCarrierRed);
-                       else if(player.team == NUM_TEAM_2)
-                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierBlue, WP_KeyCarrierFriend, WP_KeyCarrierBlue);
-                       else if(player.team == NUM_TEAM_3)
-                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierYellow, WP_KeyCarrierFriend, WP_KeyCarrierYellow);
-                       else if(player.team == NUM_TEAM_4)
-                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierPink, WP_KeyCarrierFriend, WP_KeyCarrierPink);
-                       if(!kh_no_radar_circles)
-                               WaypointSprite_Ping(player.waypointsprite_attachedforcarrier);
-               }
-       }
-
-       // moved that here, also update if there's no player
-       kh_update_state();
-
-       key.pusher = world;
-
-       ownerteam = kh_Key_AllOwnedByWhichTeam();
-       if(ownerteam != ownerteam0)
-       {
-               if(ownerteam != -1)
-               {
-                       kh_interferemsg_time = time + 0.2;
-                       kh_interferemsg_team = player.team;
-
-                       // audit all key carrier sprites, update them to RUN HERE
-                       FOR_EACH_KH_KEY(k)
-                       {
-                               if (!k.owner) continue;
-                               entity first = WP_Null;
-                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, LAMBDA(first = it; break));
-                               entity third = WP_Null;
-                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, LAMBDA(third = it; break));
-                               WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFinish, third);
-                       }
-               }
-               else
-               {
-                       kh_interferemsg_time = 0;
-
-                       // audit all key carrier sprites, update them to RUN HERE
-                       FOR_EACH_KH_KEY(k)
-                       {
-                               if (!k.owner) continue;
-                               entity first = WP_Null;
-                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, LAMBDA(first = it; break));
-                               entity third = WP_Null;
-                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, LAMBDA(third = it; break));
-                               WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third);
-                       }
-               }
-       }
-}
-
-void kh_Key_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{SELFPARAM();
-       if(self.owner)
-               return;
-       if(ITEM_DAMAGE_NEEDKILL(deathtype))
-       {
-               // touching lava, or hurt trigger
-               // what shall we do?
-               // immediately return is bad
-               // maybe start a shorter countdown?
-       }
-       if(vlen(force) <= 0)
-               return;
-       if(time > self.pushltime)
-               if(IS_PLAYER(attacker))
-                       self.team = attacker.team;
-}
-
-void kh_Key_Collect(entity key, entity player)  //a player picks up a dropped key
-{
-       sound(player, CH_TRIGGER, SND_KH_COLLECT, VOL_BASE, ATTEN_NORM);
-
-       if(key.kh_dropperteam != player.team)
-       {
-               kh_Scores_Event(player, key, "collect", autocvar_g_balance_keyhunt_score_collect, 0);
-               PlayerScore_Add(player, SP_KH_PICKUPS, 1);
-       }
-       key.kh_dropperteam = 0;
-       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_PICKUP_), player.netname);
-
-       kh_Key_AssignTo(key, player); // this also updates .kh_state
-}
-
-void kh_Key_Touch()  // runs many, many times when a key has been dropped and can be picked up
-{SELFPARAM();
-       if(intermission_running)
-               return;
-
-       if(self.owner) // already carried
-               return;
-
-       if(ITEM_TOUCH_NEEDKILL())
-       {
-               // touching sky, or nodrop
-               // what shall we do?
-               // immediately return is bad
-               // maybe start a shorter countdown?
-       }
-
-       if (!IS_PLAYER(other))
-               return;
-       if(other.deadflag != DEAD_NO)
-               return;
-       if(other == self.enemy)
-               if(time < self.kh_droptime + autocvar_g_balance_keyhunt_delay_collect)
-                       return;  // you just dropped it!
-       kh_Key_Collect(self, other);
-}
-
-void kh_Key_Remove(entity key)  // runs after when all the keys have been collected or when a key has been dropped for more than X seconds
-{
-       entity o;
-       o = key.owner;
-       kh_Key_AssignTo(key, world);
-       if(o) // it was attached
-               WaypointSprite_Kill(key.waypointsprite_attachedforcarrier);
-       else // it was dropped
-               WaypointSprite_DetachCarrier(key);
-
-       // remove key from key list
-       if (kh_worldkeylist == key)
-               kh_worldkeylist = kh_worldkeylist.kh_worldkeynext;
-       else
-       {
-               o = kh_worldkeylist;
-               while (o)
-               {
-                       if (o.kh_worldkeynext == key)
-                       {
-                               o.kh_worldkeynext = o.kh_worldkeynext.kh_worldkeynext;
-                               break;
-                       }
-                       o = o.kh_worldkeynext;
-               }
-       }
-
-       remove(key);
-
-       kh_update_state();
-}
-
-void kh_FinishRound()  // runs when a team captures the keys
-{
-       // prepare next round
-       kh_interferemsg_time = 0;
-       entity key;
-
-       kh_no_radar_circles = true;
-       FOR_EACH_KH_KEY(key)
-               kh_Key_Remove(key);
-       kh_no_radar_circles = false;
-
-       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
-       kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
-}
-
-void kh_WinnerTeam(float teem)  // runs when a team wins // Samual: Teem?.... TEEM?!?! what the fuck is wrong with you people
-{
-       // all key carriers get some points
-       vector firstorigin, lastorigin, midpoint;
-       float first;
-       entity key;
-       float score;
-       score = (kh_teams - 1) * autocvar_g_balance_keyhunt_score_capture;
-       DistributeEvenly_Init(score, kh_teams);
-       // twice the score for 3 team games, three times the score for 4 team games!
-       // note: for a win by destroying the key, this should NOT be applied
-       FOR_EACH_KH_KEY(key)
-       {
-               float f;
-               f = DistributeEvenly_Get(1);
-               kh_Scores_Event(key.owner, key, "capture", f, 0);
-               PlayerTeamScore_Add(key.owner, SP_KH_CAPS, ST_KH_CAPS, 1);
-               nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high);
-       }
-
-       first = true;
-       string keyowner = "";
-       FOR_EACH_KH_KEY(key)
-               if(key.owner.kh_next == key)
-               {
-                       if(!first)
-                               keyowner = strcat(keyowner, ", ");
-                       keyowner = key.owner.netname;
-                       first = false;
-               }
-
-       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(teem, INFO_KEYHUNT_CAPTURE_), keyowner);
-
-       first = true;
-       midpoint = '0 0 0';
-       firstorigin = '0 0 0';
-       lastorigin = '0 0 0';
-       FOR_EACH_KH_KEY(key)
-       {
-               vector thisorigin;
-
-               thisorigin = kh_AttachedOrigin(key);
-               //dprint("Key origin: ", vtos(thisorigin), "\n");
-               midpoint += thisorigin;
-
-               if(!first)
-                       te_lightning2(world, lastorigin, thisorigin);
-               lastorigin = thisorigin;
-               if(first)
-                       firstorigin = thisorigin;
-               first = false;
-       }
-       if(kh_teams > 2)
-       {
-               te_lightning2(world, lastorigin, firstorigin);
-       }
-       midpoint = midpoint * (1 / kh_teams);
-       te_customflash(midpoint, 1000, 1, Team_ColorRGB(teem) * 0.5 + '0.5 0.5 0.5');  // make the color >=0.5 in each component
-
-       play2all(SND(KH_CAPTURE));
-       kh_FinishRound();
-}
-
-void kh_LoserTeam(float teem, entity lostkey)  // runs when a player pushes a flag carrier off the map
-{
-       entity player, key, attacker;
-       float players;
-       float keys;
-       float f;
-
-       attacker = world;
-       if(lostkey.pusher)
-               if(lostkey.pusher.team != teem)
-                       if(IS_PLAYER(lostkey.pusher))
-                               attacker = lostkey.pusher;
-
-       players = keys = 0;
-
-       if(attacker)
-       {
-               if(lostkey.kh_previous_owner)
-                       kh_Scores_Event(lostkey.kh_previous_owner, world, "pushed", 0, -autocvar_g_balance_keyhunt_score_push);
-                       // don't actually GIVE him the -nn points, just log
-               kh_Scores_Event(attacker, world, "push", autocvar_g_balance_keyhunt_score_push, 0);
-               PlayerScore_Add(attacker, SP_KH_PUSHES, 1);
-               //centerprint(attacker, "Your push is the best!"); // does this really need to exist?
-       }
-       else
-       {
-               float of, fragsleft, i, j, thisteam;
-               of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
-
-               FOR_EACH_PLAYER(player)
-                       if(player.team != teem)
-                               ++players;
-
-               FOR_EACH_KH_KEY(key)
-                       if(key.owner && key.team != teem)
-                               ++keys;
-
-               if(lostkey.kh_previous_owner)
-                       kh_Scores_Event(lostkey.kh_previous_owner, world, "destroyed", 0, -autocvar_g_balance_keyhunt_score_destroyed);
-                       // don't actually GIVE him the -nn points, just log
-
-               if(lostkey.kh_previous_owner.playerid == lostkey.kh_previous_owner_playerid)
-                       PlayerScore_Add(lostkey.kh_previous_owner, SP_KH_DESTROYS, 1);
-
-               DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players);
-
-               FOR_EACH_KH_KEY(key)
-                       if(key.owner && key.team != teem)
-                       {
-                               f = DistributeEvenly_Get(of);
-                               kh_Scores_Event(key.owner, world, "destroyed_holdingkey", f, 0);
-                       }
-
-               fragsleft = DistributeEvenly_Get(players);
-
-               // Now distribute these among all other teams...
-               j = kh_teams - 1;
-               for(i = 0; i < kh_teams; ++i)
-               {
-                       thisteam = kh_Team_ByID(i);
-                       if(thisteam == teem) // bad boy, no cookie - this WILL happen
-                               continue;
-
-                       players = 0;
-                       FOR_EACH_PLAYER(player)
-                               if(player.team == thisteam)
-                                       ++players;
-
-                       DistributeEvenly_Init(fragsleft, j);
-                       fragsleft = DistributeEvenly_Get(j - 1);
-                       DistributeEvenly_Init(DistributeEvenly_Get(1), players);
-
-                       FOR_EACH_PLAYER(player)
-                               if(player.team == thisteam)
-                               {
-                                       f = DistributeEvenly_Get(1);
-                                       kh_Scores_Event(player, world, "destroyed", f, 0);
-                               }
-
-                       --j;
-               }
-       }
-
-       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(lostkey, INFO_KEYHUNT_LOST_), lostkey.kh_previous_owner.netname);
-
-       play2all(SND(KH_DESTROY));
-       te_tarexplosion(lostkey.origin);
-
-       kh_FinishRound();
-}
-
-void kh_Key_Think()  // runs all the time
-{SELFPARAM();
-       entity head;
-       //entity player;  // needed by FOR_EACH_PLAYER
-
-       if(intermission_running)
-               return;
-
-       if(self.owner)
-       {
-#ifndef KH_PLAYER_USE_ATTACHMENT
-               makevectors('0 1 0' * (self.cnt + (time % 360) * KH_KEY_XYSPEED));
-               setorigin(self, v_forward * KH_KEY_XYDIST + '0 0 1' * self.origin.z);
-#endif
-       }
-
-       // if in nodrop or time over, end the round
-       if(!self.owner)
-               if(time > self.pain_finished)
-                       kh_LoserTeam(self.team, self);
-
-       if(self.owner)
-       if(kh_Key_AllOwnedByWhichTeam() != -1)
-       {
-               if(self.siren_time < time)
-               {
-                       sound(self.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM);  // play a simple alarm
-                       self.siren_time = time + 2.5;  // repeat every 2.5 seconds
-               }
-
-               entity key;
-               vector p;
-               p = self.owner.origin;
-               FOR_EACH_KH_KEY(key)
-                       if(vlen(key.owner.origin - p) > autocvar_g_balance_keyhunt_maxdist)
-                               goto not_winning;
-               kh_WinnerTeam(self.team);
-:not_winning
-       }
-
-       if(kh_interferemsg_time && time > kh_interferemsg_time)
-       {
-               kh_interferemsg_time = 0;
-               FOR_EACH_PLAYER(head)
-               {
-                       if(head.team == kh_interferemsg_team)
-                               if(head.kh_next)
-                                       Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_KEYHUNT_MEET);
-                               else
-                                       Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_KEYHUNT_HELP);
-                       else
-                               Send_Notification(NOTIF_ONE, head, MSG_CENTER, APP_TEAM_NUM_4(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE_));
-               }
-       }
-
-       self.nextthink = time + 0.05;
-}
-
-void key_reset()
-{SELFPARAM();
-       kh_Key_AssignTo(self, world);
-       kh_Key_Remove(self);
-}
-
-const string STR_ITEM_KH_KEY = "item_kh_key";
-void kh_Key_Spawn(entity initial_owner, float angle, float i)  // runs every time a new flag is created, ie after all the keys have been collected
-{
-       entity key;
-       key = spawn();
-       key.count = i;
-       key.classname = STR_ITEM_KH_KEY;
-       key.touch = kh_Key_Touch;
-       key.think = kh_Key_Think;
-       key.nextthink = time;
-       key.items = IT_KEY1 | IT_KEY2;
-       key.cnt = angle;
-       key.angles = '0 360 0' * random();
-       key.event_damage = kh_Key_Damage;
-       key.takedamage = DAMAGE_YES;
-       key.modelindex = kh_key_dropped;
-       key.model = "key";
-       key.kh_dropperteam = 0;
-       key.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
-       setsize(key, KH_KEY_MIN, KH_KEY_MAX);
-       key.colormod = Team_ColorRGB(initial_owner.team) * KH_KEY_BRIGHTNESS;
-       key.reset = key_reset;
-
-       switch(initial_owner.team)
-       {
-               case NUM_TEAM_1:
-                       key.netname = "^1red key";
-                       break;
-               case NUM_TEAM_2:
-                       key.netname = "^4blue key";
-                       break;
-               case NUM_TEAM_3:
-                       key.netname = "^3yellow key";
-                       break;
-               case NUM_TEAM_4:
-                       key.netname = "^6pink key";
-                       break;
-               default:
-                       key.netname = "NETGIER key";
-                       break;
-       }
-
-       // link into key list
-       key.kh_worldkeynext = kh_worldkeylist;
-       kh_worldkeylist = key;
-
-       Send_Notification(NOTIF_ONE, initial_owner, MSG_CENTER, APP_TEAM_NUM_4(initial_owner.team, CENTER_KEYHUNT_START_));
-
-       WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, world, key.team, key, waypointsprite_attachedforcarrier, false, RADARICON_FLAG);
-       key.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_Key_waypointsprite_visible_for_player;
-
-       kh_Key_AssignTo(key, initial_owner);
-}
-
-// -1 when no team completely owns all keys yet
-float kh_Key_AllOwnedByWhichTeam()  // constantly called. check to see if all the keys are owned by the same team
-{
-       entity key;
-       float teem;
-       float keys;
-
-       teem = -1;
-       keys = kh_teams;
-       FOR_EACH_KH_KEY(key)
-       {
-               if(!key.owner)
-                       return -1;
-               if(teem == -1)
-                       teem = key.team;
-               else if(teem != key.team)
-                       return -1;
-               --keys;
-       }
-       if(keys != 0)
-               return -1;
-       return teem;
-}
-
-void kh_Key_DropOne(entity key)
-{
-       // prevent collecting this one for some time
-       entity player;
-       player = key.owner;
-
-       key.kh_droptime = time;
-       key.enemy = player;
-
-       kh_Scores_Event(player, key, "dropkey", 0, 0);
-       PlayerScore_Add(player, SP_KH_LOSSES, 1);
-       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_DROP_), player.netname);
-
-       kh_Key_AssignTo(key, world);
-       makevectors(player.v_angle);
-       key.velocity = W_CalculateProjectileVelocity(player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false);
-       key.pusher = world;
-       key.pushltime = time + autocvar_g_balance_keyhunt_protecttime;
-       key.kh_dropperteam = key.team;
-
-       sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM);
-}
-
-void kh_Key_DropAll(entity player, float suicide) // runs whenever a player dies
-{
-       entity key;
-       entity mypusher;
-       if(player.kh_next)
-       {
-               mypusher = world;
-               if(player.pusher)
-                       if(time < player.pushltime)
-                               mypusher = player.pusher;
-               while((key = player.kh_next))
-               {
-                       kh_Scores_Event(player, key, "losekey", 0, 0);
-                       PlayerScore_Add(player, SP_KH_LOSSES, 1);
-                       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_LOST_), player.netname);
-                       kh_Key_AssignTo(key, world);
-                       makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random());
-                       key.velocity = W_CalculateProjectileVelocity(player.velocity, autocvar_g_balance_keyhunt_dropvelocity * v_forward, false);
-                       key.pusher = mypusher;
-                       key.pushltime = time + autocvar_g_balance_keyhunt_protecttime;
-                       if(suicide)
-                               key.kh_dropperteam = player.team;
-               }
-               sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM);
-       }
-}
-
-float kh_CheckPlayers(float num)
-{
-       if(num < kh_teams)
-       {
-               float t_team = kh_Team_ByID(num);
-               float players = 0;
-               entity tmp_player;
-               FOR_EACH_PLAYER(tmp_player)
-                       if(tmp_player.deadflag == DEAD_NO)
-                               if(!tmp_player.BUTTON_CHAT)
-                                       if(tmp_player.team == t_team)
-                                               ++players;
-
-               if (!players) { return t_team; }
-       }
-       return 0;
-}
-
-#define KH_READY_TEAMS() (!p1 + !p2 + ((kh_teams >= 3) ? !p3 : p3) + ((kh_teams >= 4) ? !p4 : p4))
-#define KH_READY_TEAMS_OK() (KH_READY_TEAMS() == kh_teams)
-void kh_WaitForPlayers()  // delay start of the round until enough players are present
-{
-       if(time < game_starttime)
-       {
-               kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
-               return;
-       }
-
-       static float prev_missing_teams_mask;
-       float p1 = kh_CheckPlayers(0), p2 = kh_CheckPlayers(1), p3 = kh_CheckPlayers(2), p4 = kh_CheckPlayers(3);
-       if(KH_READY_TEAMS_OK())
-       {
-               if(prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
-               kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
-       }
-       else
-       {
-               if(player_count == 0)
-               {
-                       if(prev_missing_teams_mask > 0)
-                               Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
-                       prev_missing_teams_mask = -1;
-               }
-               else
-               {
-                       float missing_teams_mask = (!!p1) + (!!p2) * 2;
-                       if(kh_teams >= 3) missing_teams_mask += (!!p3) * 4;
-                       if(kh_teams >= 4) missing_teams_mask += (!!p4) * 8;
-                       if(prev_missing_teams_mask != missing_teams_mask)
-                       {
-                               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
-                               prev_missing_teams_mask = missing_teams_mask;
-                       }
-               }
-               kh_Controller_SetThink(1, kh_WaitForPlayers);
-       }
-}
-
-void kh_EnableTrackingDevice()  // runs after each round
-{
-       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT);
-       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT_OTHER);
-
-       kh_tracking_enabled = true;
-}
-
-void kh_StartRound()  // runs at the start of each round
-{
-       float i, players, teem;
-       entity player;
-
-       if(time < game_starttime)
-       {
-               kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
-               return;
-       }
-
-       float p1 = kh_CheckPlayers(0), p2 = kh_CheckPlayers(1), p3 = kh_CheckPlayers(2), p4 = kh_CheckPlayers(3);
-       if(!KH_READY_TEAMS_OK())
-       {
-               kh_Controller_SetThink(1, kh_WaitForPlayers);
-               return;
-       }
-
-       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT);
-       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT_OTHER);
-
-       for(i = 0; i < kh_teams; ++i)
-       {
-               teem = kh_Team_ByID(i);
-               players = 0;
-               entity my_player = world;
-               FOR_EACH_PLAYER(player)
-                       if(player.deadflag == DEAD_NO)
-                               if(!player.BUTTON_CHAT)
-                                       if(player.team == teem)
-                                       {
-                                               ++players;
-                                               if(random() * players <= 1)
-                                                       my_player = player;
-                                       }
-               kh_Key_Spawn(my_player, 360 * i / kh_teams, i);
-       }
-
-       kh_tracking_enabled = false;
-       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_SCAN, autocvar_g_balance_keyhunt_delay_tracking);
-       kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_tracking, kh_EnableTrackingDevice);
-}
-
-float kh_HandleFrags(entity attacker, entity targ, float f)  // adds to the player score
-{
-       if(attacker == targ)
-               return f;
-
-       if(targ.kh_next)
-       {
-               if(attacker.team == targ.team)
-               {
-                       entity k;
-                       float nk;
-                       nk = 0;
-                       for(k = targ.kh_next; k != world; k = k.kh_next)
-                               ++nk;
-                       kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", -nk * autocvar_g_balance_keyhunt_score_collect, 0);
-               }
-               else
-               {
-                       kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", autocvar_g_balance_keyhunt_score_carrierfrag-1, 0);
-                       PlayerScore_Add(attacker, SP_KH_KCKILLS, 1);
-                       // the frag gets added later
-               }
-       }
-
-       return f;
-}
-
-void kh_Initialize()  // sets up th KH environment
-{
-       // setup variables
-       kh_teams = autocvar_g_keyhunt_teams_override;
-       if(kh_teams < 2)
-               kh_teams = autocvar_g_keyhunt_teams;
-       kh_teams = bound(2, kh_teams, 4);
-
-       // make a KH entity for controlling the game
-       kh_controller = spawn();
-       kh_controller.think = kh_Controller_Think;
-       kh_Controller_SetThink(0, kh_WaitForPlayers);
-
-       setmodel(kh_controller, MDL_KH_KEY);
-       kh_key_dropped = kh_controller.modelindex;
-       /*
-       dprint(vtos(kh_controller.mins));
-       dprint(vtos(kh_controller.maxs));
-       dprint("\n");
-       */
-#ifdef KH_PLAYER_USE_CARRIEDMODEL
-       setmodel(kh_controller, MDL_KH_KEY_CARRIED);
-       kh_key_carried = kh_controller.modelindex;
-#else
-       kh_key_carried = kh_key_dropped;
-#endif
-
-       kh_controller.model = "";
-       kh_controller.modelindex = 0;
-
-       addstat(STAT_KH_KEYS, AS_INT, kh_state);
-
-       kh_ScoreRules(kh_teams);
-}
-
-void kh_finalize()
-{
-       // to be called before intermission
-       kh_FinishRound();
-       remove(kh_controller);
-       kh_controller = world;
-}
-
-// legacy bot role
-
-void() havocbot_role_kh_carrier;
-void() havocbot_role_kh_defense;
-void() havocbot_role_kh_offense;
-void() havocbot_role_kh_freelancer;
-
-
-void havocbot_goalrating_kh(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
-{SELFPARAM();
-       entity head;
-       for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
-       {
-               if(head.owner == self)
-                       continue;
-               if(!kh_tracking_enabled)
-               {
-                       // if it's carried by our team we know about it
-                       // otherwise we have to see it to know about it
-                       if(!head.owner || head.team != self.team)
-                       {
-                               traceline(self.origin + self.view_ofs, head.origin, MOVE_NOMONSTERS, self);
-                               if (trace_fraction < 1 && trace_ent != head)
-                                       continue; // skip what I can't see
-                       }
-               }
-               if(!head.owner)
-                       navigation_routerating(head, ratingscale_dropped * BOT_PICKUP_RATING_HIGH, 100000);
-               else if(head.team == self.team)
-                       navigation_routerating(head.owner, ratingscale_team * BOT_PICKUP_RATING_HIGH, 100000);
-               else
-                       navigation_routerating(head.owner, ratingscale_enemy * BOT_PICKUP_RATING_HIGH, 100000);
-       }
-
-       havocbot_goalrating_items(1, self.origin, 10000);
-}
-
-void havocbot_role_kh_carrier()
-{SELFPARAM();
-       if(self.deadflag != DEAD_NO)
-               return;
-
-       if (!(self.kh_next))
-       {
-               LOG_TRACE("changing role to freelancer\n");
-               self.havocbot_role = havocbot_role_kh_freelancer;
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (self.bot_strategytime < time)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-
-               if(kh_Key_AllOwnedByWhichTeam() == self.team)
-                       havocbot_goalrating_kh(10, 0.1, 0.1); // bring home
-               else
-                       havocbot_goalrating_kh(4, 4, 1); // play defensively
-
-               navigation_goalrating_end();
-       }
-}
-
-void havocbot_role_kh_defense()
-{SELFPARAM();
-       if(self.deadflag != DEAD_NO)
-               return;
-
-       if (self.kh_next)
-       {
-               LOG_TRACE("changing role to carrier\n");
-               self.havocbot_role = havocbot_role_kh_carrier;
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + random() * 10 + 20;
-       if (time > self.havocbot_role_timeout)
-       {
-               LOG_TRACE("changing role to freelancer\n");
-               self.havocbot_role = havocbot_role_kh_freelancer;
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (self.bot_strategytime < time)
-       {
-               float key_owner_team;
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-
-               key_owner_team = kh_Key_AllOwnedByWhichTeam();
-               if(key_owner_team == self.team)
-                       havocbot_goalrating_kh(10, 0.1, 0.1); // defend key carriers
-               else if(key_owner_team == -1)
-                       havocbot_goalrating_kh(4, 1, 0.1); // play defensively
-               else
-                       havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY
-
-               navigation_goalrating_end();
-       }
-}
-
-void havocbot_role_kh_offense()
-{SELFPARAM();
-       if(self.deadflag != DEAD_NO)
-               return;
-
-       if (self.kh_next)
-       {
-               LOG_TRACE("changing role to carrier\n");
-               self.havocbot_role = havocbot_role_kh_carrier;
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + random() * 10 + 20;
-       if (time > self.havocbot_role_timeout)
-       {
-               LOG_TRACE("changing role to freelancer\n");
-               self.havocbot_role = havocbot_role_kh_freelancer;
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (self.bot_strategytime < time)
-       {
-               float key_owner_team;
-
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-
-               key_owner_team = kh_Key_AllOwnedByWhichTeam();
-               if(key_owner_team == self.team)
-                       havocbot_goalrating_kh(10, 0.1, 0.1); // defend anyway
-               else if(key_owner_team == -1)
-                       havocbot_goalrating_kh(0.1, 1, 4); // play offensively
-               else
-                       havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK! EMERGENCY!
-
-               navigation_goalrating_end();
-       }
-}
-
-void havocbot_role_kh_freelancer()
-{SELFPARAM();
-       if(self.deadflag != DEAD_NO)
-               return;
-
-       if (self.kh_next)
-       {
-               LOG_TRACE("changing role to carrier\n");
-               self.havocbot_role = havocbot_role_kh_carrier;
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + random() * 10 + 10;
-       if (time > self.havocbot_role_timeout)
-       {
-               if (random() < 0.5)
-               {
-                       LOG_TRACE("changing role to offense\n");
-                       self.havocbot_role = havocbot_role_kh_offense;
-               }
-               else
-               {
-                       LOG_TRACE("changing role to defense\n");
-                       self.havocbot_role = havocbot_role_kh_defense;
-               }
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (self.bot_strategytime < time)
-       {
-               float key_owner_team;
-
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-
-               key_owner_team = kh_Key_AllOwnedByWhichTeam();
-               if(key_owner_team == self.team)
-                       havocbot_goalrating_kh(10, 0.1, 0.1); // defend anyway
-               else if(key_owner_team == -1)
-                       havocbot_goalrating_kh(1, 10, 4); // prefer dropped keys
-               else
-                       havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY
-
-               navigation_goalrating_end();
-       }
-}
-
-
-// register this as a mutator
-
-MUTATOR_HOOKFUNCTION(kh, ClientDisconnect)
-{SELFPARAM();
-       kh_Key_DropAll(self, true);
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver)
-{SELFPARAM();
-       kh_Key_DropAll(self, true);
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(kh, PlayerDies)
-{SELFPARAM();
-       if(self == other)
-               kh_Key_DropAll(self, true);
-       else if(IS_PLAYER(other))
-               kh_Key_DropAll(self, false);
-       else
-               kh_Key_DropAll(self, true);
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST)
-{
-       frag_score = kh_HandleFrags(frag_attacker, frag_target, frag_score);
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(kh, MatchEnd)
-{
-       kh_finalize();
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(kh, GetTeamCount, CBC_ORDER_EXCLUSIVE)
-{
-       ret_float = kh_teams;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(kh, SpectateCopy)
-{SELFPARAM();
-       self.kh_state = other.kh_state;
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(kh, PlayerUseKey)
-{SELFPARAM();
-       if(MUTATOR_RETURNVALUE == 0)
-       {
-               entity k;
-               k = self.kh_next;
-               if(k)
-               {
-                       kh_Key_DropOne(k);
-                       return 1;
-               }
-       }
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole)
-{
-       if(self.deadflag != DEAD_NO)
-               return true;
-
-       float r = random() * 3;
-       if (r < 1)
-               self.havocbot_role = havocbot_role_kh_offense;
-       else if (r < 2)
-               self.havocbot_role = havocbot_role_kh_defense;
-       else
-               self.havocbot_role = havocbot_role_kh_freelancer;
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(kh, DropSpecialItems)
-{
-       kh_Key_DropAll(frag_target, false);
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(kh, reset_map_global)
-{
-       kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round + (game_starttime - time), kh_StartRound);
-       return false;
-}
-
-REGISTER_MUTATOR(kh, IS_GAMETYPE(KEYHUNT))
-{
-       ActivateTeamplay();
-       SetLimits(autocvar_g_keyhunt_point_limit, autocvar_g_keyhunt_point_leadlimit, -1, -1);
-       if(autocvar_g_keyhunt_team_spawns)
-               have_team_spawns = -1; // request team spawns
-
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This is a game type and it cannot be added at runtime.");
-               kh_Initialize();
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // we actually cannot roll back kh_Initialize here
-               // BUT: we don't need to! If this gets called, adding always
-               // succeeds.
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This is a game type and it cannot be removed at runtime.");
-               return -1;
-       }
-
-       return 0;
-}
diff --git a/qcsrc/server/mutators/gamemode_keyhunt.qh b/qcsrc/server/mutators/gamemode_keyhunt.qh
deleted file mode 100644 (file)
index 2d96b2f..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-#ifndef GAMEMODE_KEYHUNT_H
-#define GAMEMODE_KEYHUNT_H
-
-#define FOR_EACH_KH_KEY(v) for(v = kh_worldkeylist; v; v = v.kh_worldkeynext )
-
-// ALL OF THESE should be removed in the future, as other code should not have to care
-
-// used by bots:
-float kh_tracking_enabled;
-.entity kh_next;
-float kh_Key_AllOwnedByWhichTeam();
-
-typedef void(void) kh_Think_t;
-void kh_StartRound();
-void kh_Controller_SetThink(float t, kh_Think_t func);
-
-entity kh_worldkeylist;
-.entity kh_worldkeynext;
-
-#endif
diff --git a/qcsrc/server/mutators/gamemode_lms.qc b/qcsrc/server/mutators/gamemode_lms.qc
deleted file mode 100644 (file)
index 65c85f7..0000000
+++ /dev/null
@@ -1,300 +0,0 @@
-#include "gamemode_lms.qh"
-
-#include "gamemode.qh"
-
-#include "../campaign.qh"
-#include "../command/cmd.qh"
-
-int autocvar_g_lms_extra_lives;
-bool autocvar_g_lms_join_anytime;
-int autocvar_g_lms_last_join;
-#define autocvar_g_lms_lives_override cvar("g_lms_lives_override")
-bool autocvar_g_lms_regenerate;
-
-// main functions
-float LMS_NewPlayerLives()
-{
-       float fl;
-       fl = autocvar_fraglimit;
-       if(fl == 0)
-               fl = 999;
-
-       // first player has left the game for dying too much? Nobody else can get in.
-       if(lms_lowest_lives < 1)
-               return 0;
-
-       if(!autocvar_g_lms_join_anytime)
-               if(lms_lowest_lives < fl - autocvar_g_lms_last_join)
-                       return 0;
-
-       return bound(1, lms_lowest_lives, fl);
-}
-
-// mutator hooks
-MUTATOR_HOOKFUNCTION(lms, reset_map_global)
-{
-       lms_lowest_lives = 999;
-       lms_next_place = player_count;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(lms, reset_map_players)
-{SELFPARAM();
-       entity e;
-       if(restart_mapalreadyrestarted || (time < game_starttime))
-       FOR_EACH_CLIENT(e)
-       if(IS_PLAYER(e))
-       {
-               WITH(entity, self, e, PlayerScore_Add(e, SP_LMS_LIVES, LMS_NewPlayerLives()));
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
-{SELFPARAM();
-       // player is dead and becomes observer
-       // FIXME fix LMS scoring for new system
-       if(PlayerScore_Add(self, SP_LMS_RANK, 0) > 0)
-       {
-               self.classname = "observer";
-               Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_LMS_NOLIVES);
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerDies)
-{SELFPARAM();
-       self.respawn_flags |= RESPAWN_FORCE;
-
-       return false;
-}
-
-void lms_RemovePlayer(entity player)
-{
-       // Only if the player cannot play at all
-       if(PlayerScore_Add(player, SP_LMS_RANK, 0) == 666)
-               player.frags = FRAGS_SPECTATOR;
-       else
-               player.frags = FRAGS_LMS_LOSER;
-
-       if(player.killcount != -666)
-               if(PlayerScore_Add(player, SP_LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
-                       Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
-               else
-                       Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
-{SELFPARAM();
-       lms_RemovePlayer(self);
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
-{SELFPARAM();
-       lms_RemovePlayer(self);
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientConnect)
-{SELFPARAM();
-       self.classname = "player";
-       campaign_bots_may_start = 1;
-
-       if(PlayerScore_Add(self, SP_LMS_LIVES, LMS_NewPlayerLives()) <= 0)
-       {
-               PlayerScore_Add(self, SP_LMS_RANK, 666);
-               self.frags = FRAGS_SPECTATOR;
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
-{SELFPARAM();
-       if(self.deadflag == DEAD_DYING)
-               self.deadflag = DEAD_RESPAWNING;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
-{
-       if(autocvar_g_lms_regenerate)
-               return false;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
-{
-       // forbode!
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
-{
-       // remove a life
-       float tl;
-       tl = PlayerScore_Add(frag_target, SP_LMS_LIVES, -1);
-       if(tl < lms_lowest_lives)
-               lms_lowest_lives = tl;
-       if(tl <= 0)
-       {
-               if(!lms_next_place)
-                       lms_next_place = player_count;
-               else
-                       lms_next_place = min(lms_next_place, player_count);
-               PlayerScore_Add(frag_target, SP_LMS_RANK, lms_next_place); // won't ever spawn again
-               --lms_next_place;
-       }
-       frag_score = 0;
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, SetStartItems)
-{
-       start_items &= ~IT_UNLIMITED_AMMO;
-       start_health       = warmup_start_health       = cvar("g_lms_start_health");
-       start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
-       start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
-       start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
-       start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
-       start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
-       start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
-       start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
-{
-       // don't clear player score
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, FilterItem)
-{SELFPARAM();
-       if(autocvar_g_lms_extra_lives)
-       if(self.itemdef == ITEM_HealthMega)
-       {
-               self.max_health = 1;
-               return false;
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ItemTouch)
-{SELFPARAM();
-       // give extra lives for mega health
-       if (self.items & ITEM_HealthMega.m_itemid)
-       {
-               Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_EXTRALIVES);
-               PlayerScore_Add(other, SP_LMS_LIVES, autocvar_g_lms_extra_lives);
-       }
-
-       return MUT_ITEMTOUCH_CONTINUE;
-}
-
-MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
-{
-       entity head;
-       FOR_EACH_REALCLIENT(head)
-       {
-               ++bot_activerealplayers;
-               ++bot_realplayers;
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
-{
-       if(self.lms_spectate_warning)
-       {
-               // for the forfeit message...
-               self.lms_spectate_warning = 2;
-               // mark player as spectator
-               PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0));
-       }
-       else
-       {
-               self.lms_spectate_warning = 1;
-               sprint(self, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n");
-               return MUT_SPECCMD_RETURN;
-       }
-       return MUT_SPECCMD_CONTINUE;
-}
-
-MUTATOR_HOOKFUNCTION(lms, CheckRules_World)
-{
-       ret_float = WinningCondition_LMS();
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, WantWeapon)
-{
-       want_allguns = true;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
-{
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
-{
-       if(gameover)
-       if(score_field == SP_LMS_RANK)
-               return true; // allow writing to this field in intermission as it is needed for newly joining players
-       return false;
-}
-
-// scoreboard stuff
-void lms_ScoreRules()
-{
-       ScoreRules_basics(0, 0, 0, false);
-       ScoreInfo_SetLabel_PlayerScore(SP_LMS_LIVES,    "lives",     SFL_SORT_PRIO_SECONDARY);
-       ScoreInfo_SetLabel_PlayerScore(SP_LMS_RANK,     "rank",      SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
-       ScoreRules_basics_end();
-}
-
-void lms_Initialize()
-{
-       lms_lowest_lives = 9999;
-       lms_next_place = 0;
-
-       lms_ScoreRules();
-}
-
-REGISTER_MUTATOR(lms, IS_GAMETYPE(LMS))
-{
-       SetLimits(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override), 0, -1, -1);
-
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This is a game type and it cannot be added at runtime.");
-               lms_Initialize();
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // we actually cannot roll back lms_Initialize here
-               // BUT: we don't need to! If this gets called, adding always
-               // succeeds.
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This is a game type and it cannot be removed at runtime.");
-               return -1;
-       }
-
-       return 0;
-}
diff --git a/qcsrc/server/mutators/gamemode_lms.qh b/qcsrc/server/mutators/gamemode_lms.qh
deleted file mode 100644 (file)
index 474e3e1..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-#ifndef GAMEMODE_LMS_H
-#define GAMEMODE_LMS_H
-
-// scoreboard stuff
-const float SP_LMS_LIVES = 4;
-const float SP_LMS_RANK = 5;
-
-// lives related defs
-float lms_lowest_lives;
-float lms_next_place;
-float LMS_NewPlayerLives();
-#endif
diff --git a/qcsrc/server/mutators/gamemode_onslaught.qc b/qcsrc/server/mutators/gamemode_onslaught.qc
deleted file mode 100644 (file)
index 3ebcb7e..0000000
+++ /dev/null
@@ -1,2217 +0,0 @@
-#include "gamemode.qh"
-#include "../controlpoint.qh"
-#include "../generator.qh"
-
-bool g_onslaught;
-
-float autocvar_g_onslaught_debug;
-float autocvar_g_onslaught_teleport_wait;
-bool autocvar_g_onslaught_spawn_at_controlpoints;
-bool autocvar_g_onslaught_spawn_at_generator;
-float autocvar_g_onslaught_cp_proxydecap;
-float autocvar_g_onslaught_cp_proxydecap_distance = 512;
-float autocvar_g_onslaught_cp_proxydecap_dps = 100;
-float autocvar_g_onslaught_spawn_at_controlpoints_chance = 0.5;
-float autocvar_g_onslaught_spawn_at_controlpoints_random;
-float autocvar_g_onslaught_spawn_at_generator_chance;
-float autocvar_g_onslaught_spawn_at_generator_random;
-float autocvar_g_onslaught_cp_buildhealth;
-float autocvar_g_onslaught_cp_buildtime;
-float autocvar_g_onslaught_cp_health;
-float autocvar_g_onslaught_cp_regen;
-float autocvar_g_onslaught_gen_health;
-float autocvar_g_onslaught_shield_force = 100;
-float autocvar_g_onslaught_allow_vehicle_touch;
-float autocvar_g_onslaught_round_timelimit;
-float autocvar_g_onslaught_point_limit;
-float autocvar_g_onslaught_warmup;
-float autocvar_g_onslaught_teleport_radius;
-float autocvar_g_onslaught_spawn_choose;
-float autocvar_g_onslaught_click_radius;
-
-void FixSize(entity e);
-
-// =======================
-// CaptureShield Functions
-// =======================
-
-bool ons_CaptureShield_Customize()
-{SELFPARAM();
-       entity e = WaypointSprite_getviewentity(other);
-
-       if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, e.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return false; }
-       if(SAME_TEAM(self, e)) { return false; }
-
-       return true;
-}
-
-void ons_CaptureShield_Touch()
-{SELFPARAM();
-       if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, other.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return; }
-       if(!IS_PLAYER(other)) { return; }
-       if(SAME_TEAM(other, self)) { return; }
-
-       vector mymid = (self.absmin + self.absmax) * 0.5;
-       vector othermid = (other.absmin + other.absmax) * 0.5;
-
-       Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ons_captureshield_force);
-
-       if(IS_REAL_CLIENT(other))
-       {
-               play2(other, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
-
-               if(self.enemy.classname == "onslaught_generator")
-                       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_GENERATOR_SHIELDED);
-               else
-                       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_CONTROLPOINT_SHIELDED);
-       }
-}
-
-void ons_CaptureShield_Reset()
-{SELFPARAM();
-       self.colormap = self.enemy.colormap;
-       self.team = self.enemy.team;
-}
-
-void ons_CaptureShield_Spawn(entity generator, bool is_generator)
-{
-       entity shield = spawn();
-
-       shield.enemy = generator;
-       shield.team = generator.team;
-       shield.colormap = generator.colormap;
-       shield.reset = ons_CaptureShield_Reset;
-       shield.touch = ons_CaptureShield_Touch;
-       shield.customizeentityforclient = ons_CaptureShield_Customize;
-       shield.classname = "ons_captureshield";
-       shield.effects = EF_ADDITIVE;
-       shield.movetype = MOVETYPE_NOCLIP;
-       shield.solid = SOLID_TRIGGER;
-       shield.avelocity = '7 0 11';
-       shield.scale = 1;
-       shield.model = ((is_generator) ? "models/onslaught/generator_shield.md3" : "models/onslaught/controlpoint_shield.md3");
-
-       precache_model(shield.model);
-       setorigin(shield, generator.origin);
-       _setmodel(shield, shield.model);
-       setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
-}
-
-
-// ==========
-// Junk Pile
-// ==========
-
-void ons_debug(string input)
-{
-       switch(autocvar_g_onslaught_debug)
-       {
-               case 1: LOG_TRACE(input); break;
-               case 2: LOG_INFO(input); break;
-       }
-}
-
-void setmodel_fixsize(entity e, Model m)
-{
-       setmodel(e, m);
-       FixSize(e);
-}
-
-void onslaught_updatelinks()
-{
-       entity l;
-       // first check if the game has ended
-       ons_debug("--- updatelinks ---\n");
-       // mark generators as being shielded and networked
-       for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
-       {
-               if (l.iscaptured)
-                       ons_debug(strcat(etos(l), " (generator) belongs to team ", ftos(l.team), "\n"));
-               else
-                       ons_debug(strcat(etos(l), " (generator) is destroyed\n"));
-               l.islinked = l.iscaptured;
-               l.isshielded = l.iscaptured;
-               l.sprite.SendFlags |= 16;
-       }
-       // mark points as shielded and not networked
-       for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
-       {
-               l.islinked = false;
-               l.isshielded = true;
-               int i;
-               for(i = 0; i < 17; ++i) { l.isgenneighbor[i] = false; l.iscpneighbor[i] = false; }
-               ons_debug(strcat(etos(l), " (point) belongs to team ", ftos(l.team), "\n"));
-               l.sprite.SendFlags |= 16;
-       }
-       // flow power outward from the generators through the network
-       bool stop = false;
-       while (!stop)
-       {
-               stop = true;
-               for(l = ons_worldlinklist; l; l = l.ons_worldlinknext)
-               {
-                       // if both points are captured by the same team, and only one of
-                       // them is powered, mark the other one as powered as well
-                       if (l.enemy.iscaptured && l.goalentity.iscaptured)
-                               if (l.enemy.islinked != l.goalentity.islinked)
-                                       if(SAME_TEAM(l.enemy, l.goalentity))
-                                       {
-                                               if (!l.goalentity.islinked)
-                                               {
-                                                       stop = false;
-                                                       l.goalentity.islinked = true;
-                                                       ons_debug(strcat(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)\n"));
-                                               }
-                                               else if (!l.enemy.islinked)
-                                               {
-                                                       stop = false;
-                                                       l.enemy.islinked = true;
-                                                       ons_debug(strcat(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)\n"));
-                                               }
-                                       }
-               }
-       }
-       // now that we know which points are powered we can mark their neighbors
-       // as unshielded if team differs
-       for(l = ons_worldlinklist; l; l = l.ons_worldlinknext)
-       {
-               if (l.goalentity.islinked)
-               {
-                       if(DIFF_TEAM(l.goalentity, l.enemy))
-                       {
-                               ons_debug(strcat(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)\n"));
-                               l.enemy.isshielded = false;
-                       }
-                       if(l.goalentity.classname == "onslaught_generator")
-                               l.enemy.isgenneighbor[l.goalentity.team] = true;
-                       else
-                               l.enemy.iscpneighbor[l.goalentity.team] = true;
-               }
-               if (l.enemy.islinked)
-               {
-                       if(DIFF_TEAM(l.goalentity, l.enemy))
-                       {
-                               ons_debug(strcat(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)\n"));
-                               l.goalentity.isshielded = false;
-                       }
-                       if(l.enemy.classname == "onslaught_generator")
-                               l.goalentity.isgenneighbor[l.enemy.team] = true;
-                       else
-                               l.goalentity.iscpneighbor[l.enemy.team] = true;
-               }
-       }
-       // now update the generators
-       for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
-       {
-               if (l.isshielded)
-               {
-                       ons_debug(strcat(etos(l), " (generator) is shielded\n"));
-                       l.takedamage = DAMAGE_NO;
-                       l.bot_attack = false;
-               }
-               else
-               {
-                       ons_debug(strcat(etos(l), " (generator) is not shielded\n"));
-                       l.takedamage = DAMAGE_AIM;
-                       l.bot_attack = true;
-               }
-
-               ons_Generator_UpdateSprite(l);
-       }
-       // now update the takedamage and alpha variables on control point icons
-       for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
-       {
-               if (l.isshielded)
-               {
-                       ons_debug(strcat(etos(l), " (point) is shielded\n"));
-                       if (l.goalentity)
-                       {
-                               l.goalentity.takedamage = DAMAGE_NO;
-                               l.goalentity.bot_attack = false;
-                       }
-               }
-               else
-               {
-                       ons_debug(strcat(etos(l), " (point) is not shielded\n"));
-                       if (l.goalentity)
-                       {
-                               l.goalentity.takedamage = DAMAGE_AIM;
-                               l.goalentity.bot_attack = true;
-                       }
-               }
-               ons_ControlPoint_UpdateSprite(l);
-       }
-       l = findchain(classname, "ons_captureshield");
-       while(l)
-       {
-               l.team = l.enemy.team;
-               l.colormap = l.enemy.colormap;
-               l = l.chain;
-       }
-}
-
-
-// ===================
-// Main Link Functions
-// ===================
-
-bool ons_Link_Send(entity this, entity to, int sendflags)
-{
-       WriteByte(MSG_ENTITY, ENT_CLIENT_RADARLINK);
-       WriteByte(MSG_ENTITY, sendflags);
-       if(sendflags & 1)
-       {
-               WriteCoord(MSG_ENTITY, self.goalentity.origin_x);
-               WriteCoord(MSG_ENTITY, self.goalentity.origin_y);
-               WriteCoord(MSG_ENTITY, self.goalentity.origin_z);
-       }
-       if(sendflags & 2)
-       {
-               WriteCoord(MSG_ENTITY, self.enemy.origin_x);
-               WriteCoord(MSG_ENTITY, self.enemy.origin_y);
-               WriteCoord(MSG_ENTITY, self.enemy.origin_z);
-       }
-       if(sendflags & 4)
-       {
-               WriteByte(MSG_ENTITY, self.clientcolors); // which is goalentity's color + enemy's color * 16
-       }
-       return true;
-}
-
-void ons_Link_CheckUpdate()
-{SELFPARAM();
-       // TODO check if the two sides have moved (currently they won't move anyway)
-       float cc = 0, cc1 = 0, cc2 = 0;
-
-       if(self.goalentity.islinked || self.goalentity.iscaptured) { cc1 = (self.goalentity.team - 1) * 0x01; }
-       if(self.enemy.islinked || self.enemy.iscaptured) { cc2 = (self.enemy.team - 1) * 0x10; }
-
-       cc = cc1 + cc2;
-
-       if(cc != self.clientcolors)
-       {
-               self.clientcolors = cc;
-               self.SendFlags |= 4;
-       }
-
-       self.nextthink = time;
-}
-
-void ons_DelayedLinkSetup()
-{SELFPARAM();
-       self.goalentity = find(world, targetname, self.target);
-       self.enemy = find(world, targetname, self.target2);
-       if(!self.goalentity) { objerror("can not find target\n"); }
-       if(!self.enemy) { objerror("can not find target2\n"); }
-
-       ons_debug(strcat(etos(self.goalentity), " linked with ", etos(self.enemy), "\n"));
-       self.SendFlags |= 3;
-       self.think = ons_Link_CheckUpdate;
-       self.nextthink = time;
-}
-
-
-// =============================
-// Main Control Point Functions
-// =============================
-
-int ons_ControlPoint_CanBeLinked(entity cp, int teamnumber)
-{
-       if(cp.isgenneighbor[teamnumber]) { return 2; }
-       if(cp.iscpneighbor[teamnumber]) { return 1; }
-
-       return 0;
-}
-
-int ons_ControlPoint_Attackable(entity cp, int teamnumber)
-       // -2: SAME TEAM, attackable by enemy!
-       // -1: SAME TEAM!
-       // 0: off limits
-       // 1: attack it
-       // 2: touch it
-       // 3: attack it (HIGH PRIO)
-       // 4: touch it (HIGH PRIO)
-{
-       int a;
-
-       if(cp.isshielded)
-       {
-               return 0;
-       }
-       else if(cp.goalentity)
-       {
-               // if there's already an icon built, nothing happens
-               if(cp.team == teamnumber)
-               {
-                       a = ons_ControlPoint_CanBeLinked(cp, teamnumber);
-                       if(a) // attackable by enemy?
-                               return -2; // EMERGENCY!
-                       return -1;
-               }
-               // we know it can be linked, so no need to check
-               // but...
-               a = ons_ControlPoint_CanBeLinked(cp, teamnumber);
-               if(a == 2) // near our generator?
-                       return 3; // EMERGENCY!
-               return 1;
-       }
-       else
-       {
-               // free point
-               if(ons_ControlPoint_CanBeLinked(cp, teamnumber))
-               {
-                       a = ons_ControlPoint_CanBeLinked(cp, teamnumber); // why was this here NUM_TEAM_1 + NUM_TEAM_2 - t
-                       if(a == 2)
-                               return 4; // GET THIS ONE NOW!
-                       else
-                               return 2; // TOUCH ME
-               }
-       }
-       return 0;
-}
-
-void ons_ControlPoint_Icon_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{SELFPARAM();
-       if(damage <= 0) { return; }
-
-       if (self.owner.isshielded)
-       {
-               // this is protected by a shield, so ignore the damage
-               if (time > self.pain_finished)
-                       if (IS_PLAYER(attacker))
-                       {
-                               play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
-                               self.pain_finished = time + 1;
-                               attacker.typehitsound += 1; // play both sounds (shield is way too quiet)
-                       }
-
-               return;
-       }
-
-       if(IS_PLAYER(attacker))
-       if(time - ons_notification_time[self.team] > 10)
-       {
-               play2team(self.team, SND(ONS_CONTROLPOINT_UNDERATTACK));
-               ons_notification_time[self.team] = time;
-       }
-
-       self.health = self.health - damage;
-       if(self.owner.iscaptured)
-               WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
-       else
-               WaypointSprite_UpdateBuildFinished(self.owner.sprite, time + (self.max_health - self.health) / (self.count / ONS_CP_THINKRATE));
-       self.pain_finished = time + 1;
-       // particles on every hit
-       pointparticles(particleeffectnum(EFFECT_SPARKS), hitloc, force*-1, 1);
-       //sound on every hit
-       if (random() < 0.5)
-               sound(self, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE+0.3, ATTEN_NORM);
-       else
-               sound(self, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE+0.3, ATTEN_NORM);
-
-       if (self.health < 0)
-       {
-               sound(self, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM);
-               pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), self.origin, '0 0 0', 1);
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_CPDESTROYED_), self.owner.message, attacker.netname);
-
-               PlayerScore_Add(attacker, SP_ONS_TAKES, 1);
-               PlayerScore_Add(attacker, SP_SCORE, 10);
-
-               self.owner.goalentity = world;
-               self.owner.islinked = false;
-               self.owner.iscaptured = false;
-               self.owner.team = 0;
-               self.owner.colormap = 1024;
-
-               WaypointSprite_UpdateMaxHealth(self.owner.sprite, 0);
-
-               onslaught_updatelinks();
-
-               // Use targets now (somebody make sure this is in the right place..)
-               setself(self.owner);
-               activator = self;
-               SUB_UseTargets ();
-               setself(this);
-
-               self.owner.waslinked = self.owner.islinked;
-               if(self.owner.model != "models/onslaught/controlpoint_pad.md3")
-                       setmodel_fixsize(self.owner, MDL_ONS_CP_PAD1);
-               //setsize(self, '-32 -32 0', '32 32 8');
-
-               remove(self);
-       }
-
-       self.SendFlags |= CPSF_STATUS;
-}
-
-void ons_ControlPoint_Icon_Think()
-{SELFPARAM();
-       self.nextthink = time + ONS_CP_THINKRATE;
-
-       if(autocvar_g_onslaught_cp_proxydecap)
-       {
-        int _enemy_count = 0;
-        int _friendly_count = 0;
-        float _dist;
-        entity _player;
-
-        FOR_EACH_PLAYER(_player)
-        {
-            if(!_player.deadflag)
-            {
-                _dist = vlen(_player.origin - self.origin);
-                if(_dist < autocvar_g_onslaught_cp_proxydecap_distance)
-                {
-                                       if(SAME_TEAM(_player, self))
-                        ++_friendly_count;
-                    else
-                        ++_enemy_count;
-                }
-            }
-        }
-
-        _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE);
-        _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE);
-
-        self.health = bound(0, self.health + (_friendly_count - _enemy_count), self.max_health);
-               self.SendFlags |= CPSF_STATUS;
-        if(self.health <= 0)
-        {
-            ons_ControlPoint_Icon_Damage(self, self, 1, 0, self.origin, '0 0 0');
-            return;
-        }
-    }
-
-       if (time > self.pain_finished + 5)
-       {
-               if(self.health < self.max_health)
-               {
-                       self.health = self.health + self.count;
-                       if (self.health >= self.max_health)
-                               self.health = self.max_health;
-                       WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
-               }
-       }
-
-       if(self.owner.islinked != self.owner.waslinked)
-       {
-               // unteam the spawnpoint if needed
-               int t = self.owner.team;
-               if(!self.owner.islinked)
-                       self.owner.team = 0;
-
-               setself(self.owner);
-               activator = self;
-               SUB_UseTargets ();
-               setself(this);
-
-               self.owner.team = t;
-
-               self.owner.waslinked = self.owner.islinked;
-       }
-
-       // damaged fx
-       if(random() < 0.6 - self.health / self.max_health)
-       {
-               Send_Effect(EFFECT_ELECTRIC_SPARKS, self.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1);
-
-               if(random() > 0.8)
-                       sound(self, CH_PAIN, SND_ONS_SPARK1, VOL_BASE, ATTEN_NORM);
-               else if (random() > 0.5)
-                       sound(self, CH_PAIN, SND_ONS_SPARK2, VOL_BASE, ATTEN_NORM);
-       }
-}
-
-void ons_ControlPoint_Icon_BuildThink()
-{SELFPARAM();
-       int a;
-
-       self.nextthink = time + ONS_CP_THINKRATE;
-
-       // only do this if there is power
-       a = ons_ControlPoint_CanBeLinked(self.owner, self.owner.team);
-       if(!a)
-               return;
-
-       self.health = self.health + self.count;
-
-       self.SendFlags |= CPSF_STATUS;
-
-       if (self.health >= self.max_health)
-       {
-               self.health = self.max_health;
-               self.count = autocvar_g_onslaught_cp_regen * ONS_CP_THINKRATE; // slow repair rate from now on
-               self.think = ons_ControlPoint_Icon_Think;
-               sound(self, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILT, VOL_BASE, ATTEN_NORM);
-               self.owner.iscaptured = true;
-               self.solid = SOLID_BBOX;
-
-               Send_Effect(EFFECT_CAP(self.owner.team), self.owner.origin, '0 0 0', 1);
-
-               WaypointSprite_UpdateMaxHealth(self.owner.sprite, self.max_health);
-               WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
-
-               if(IS_PLAYER(self.owner.ons_toucher))
-               {
-                       Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ONSLAUGHT_CAPTURE, self.owner.ons_toucher.netname, self.owner.message);
-                       Send_Notification(NOTIF_ALL_EXCEPT, self.owner.ons_toucher, MSG_CENTER, APP_TEAM_ENT_4(self.owner.ons_toucher, CENTER_ONS_CAPTURE_), self.owner.message);
-                       Send_Notification(NOTIF_ONE, self.owner.ons_toucher, MSG_CENTER, CENTER_ONS_CAPTURE, self.owner.message);
-                       PlayerScore_Add(self.owner.ons_toucher, SP_ONS_CAPS, 1);
-                       PlayerTeamScore_AddScore(self.owner.ons_toucher, 10);
-               }
-
-               self.owner.ons_toucher = world;
-
-               onslaught_updatelinks();
-
-               // Use targets now (somebody make sure this is in the right place..)
-               setself(self.owner);
-               activator = self;
-               SUB_UseTargets ();
-               setself(this);
-
-               self.SendFlags |= CPSF_SETUP;
-       }
-       if(self.owner.model != MDL_ONS_CP_PAD2.model_str())
-               setmodel_fixsize(self.owner, MDL_ONS_CP_PAD2);
-
-       if(random() < 0.9 - self.health / self.max_health)
-               Send_Effect(EFFECT_RAGE, self.origin + 10 * randomvec(), '0 0 -1', 1);
-}
-
-void onslaught_controlpoint_icon_link(entity e, void() spawnproc);
-
-void ons_ControlPoint_Icon_Spawn(entity cp, entity player)
-{
-       entity e = spawn();
-
-       setsize(e, CPICON_MIN, CPICON_MAX);
-       setorigin(e, cp.origin + CPICON_OFFSET);
-
-       e.classname = "onslaught_controlpoint_icon";
-       e.owner = cp;
-       e.max_health = autocvar_g_onslaught_cp_health;
-       e.health = autocvar_g_onslaught_cp_buildhealth;
-       e.solid = SOLID_NOT;
-       e.takedamage = DAMAGE_AIM;
-       e.bot_attack = true;
-       e.event_damage = ons_ControlPoint_Icon_Damage;
-       e.team = player.team;
-       e.colormap = 1024 + (e.team - 1) * 17;
-       e.count = (e.max_health - e.health) * ONS_CP_THINKRATE / autocvar_g_onslaught_cp_buildtime; // how long it takes to build
-
-       sound(e, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILD, VOL_BASE, ATTEN_NORM);
-
-       cp.goalentity = e;
-       cp.team = e.team;
-       cp.colormap = e.colormap;
-
-       Send_Effect(EFFECT_FLAG_TOUCH(player.team), e.origin, '0 0 0', 1);
-
-       WaypointSprite_UpdateBuildFinished(cp.sprite, time + (e.max_health - e.health) / (e.count / ONS_CP_THINKRATE));
-       WaypointSprite_UpdateRule(cp.sprite,cp.team,SPRITERULE_TEAMPLAY);
-       cp.sprite.SendFlags |= 16;
-
-       onslaught_controlpoint_icon_link(e, ons_ControlPoint_Icon_BuildThink);
-}
-
-entity ons_ControlPoint_Waypoint(entity e)
-{
-       if(e.team)
-       {
-               int a = ons_ControlPoint_Attackable(e, e.team);
-
-               if(a == -2) { return WP_OnsCPDefend; } // defend now
-               if(a == -1 || a == 1 || a == 2) { return WP_OnsCP; } // touch
-               if(a == 3 || a == 4) { return WP_OnsCPAttack; } // attack
-       }
-       else
-               return WP_OnsCP;
-
-       return WP_Null;
-}
-
-void ons_ControlPoint_UpdateSprite(entity e)
-{
-       entity s1 = ons_ControlPoint_Waypoint(e);
-       WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1);
-
-       bool sh;
-       sh = !(ons_ControlPoint_CanBeLinked(e, NUM_TEAM_1) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_2) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_3) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_4));
-
-       if(e.lastteam != e.team + 2 || e.lastshielded != sh || e.iscaptured != e.lastcaptured)
-       {
-               if(e.iscaptured) // don't mess up build bars!
-               {
-                       if(sh)
-                       {
-                               WaypointSprite_UpdateMaxHealth(e.sprite, 0);
-                       }
-                       else
-                       {
-                               WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health);
-                               WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health);
-                       }
-               }
-               if(e.lastshielded)
-               {
-                       if(e.team)
-                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, false));
-                       else
-                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5');
-               }
-               else
-               {
-                       if(e.team)
-                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, false));
-                       else
-                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75');
-               }
-               WaypointSprite_Ping(e.sprite);
-
-               e.lastteam = e.team + 2;
-               e.lastshielded = sh;
-               e.lastcaptured = e.iscaptured;
-       }
-}
-
-void ons_ControlPoint_Touch()
-{SELFPARAM();
-       entity toucher = other;
-       int attackable;
-
-       if(IS_VEHICLE(toucher) && toucher.owner)
-       if(autocvar_g_onslaught_allow_vehicle_touch)
-               toucher = toucher.owner;
-       else
-               return;
-
-       if(!IS_PLAYER(toucher)) { return; }
-       if(toucher.frozen) { return; }
-       if(toucher.deadflag != DEAD_NO) { return; }
-
-       if ( SAME_TEAM(self,toucher) )
-       if ( self.iscaptured )
-       {
-               if(time <= toucher.teleport_antispam)
-                       Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT_ANTISPAM, rint(toucher.teleport_antispam - time));
-               else
-                       Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT);
-       }
-
-       attackable = ons_ControlPoint_Attackable(self, toucher.team);
-       if(attackable != 2 && attackable != 4)
-               return;
-       // we've verified that this player has a legitimate claim to this point,
-       // so start building the captured point icon (which only captures this
-       // point if it successfully builds without being destroyed first)
-       ons_ControlPoint_Icon_Spawn(self, toucher);
-
-       self.ons_toucher = toucher;
-
-       onslaught_updatelinks();
-}
-
-void ons_ControlPoint_Think()
-{SELFPARAM();
-       self.nextthink = time + ONS_CP_THINKRATE;
-       CSQCMODEL_AUTOUPDATE(self);
-}
-
-void ons_ControlPoint_Reset()
-{SELFPARAM();
-       if(self.goalentity)
-               remove(self.goalentity);
-
-       self.goalentity = world;
-       self.team = 0;
-       self.colormap = 1024;
-       self.iscaptured = false;
-       self.islinked = false;
-       self.isshielded = true;
-       self.think = ons_ControlPoint_Think;
-       self.ons_toucher = world;
-       self.nextthink = time + ONS_CP_THINKRATE;
-       setmodel_fixsize(self, MDL_ONS_CP_PAD1);
-
-       WaypointSprite_UpdateMaxHealth(self.sprite, 0);
-       WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY);
-
-       onslaught_updatelinks();
-
-       activator = self;
-       SUB_UseTargets(); // to reset the structures, playerspawns etc.
-
-       CSQCMODEL_AUTOUPDATE(self);
-}
-
-void ons_DelayedControlPoint_Setup(void)
-{SELFPARAM();
-       onslaught_updatelinks();
-
-       // captureshield setup
-       ons_CaptureShield_Spawn(self, false);
-
-       CSQCMODEL_AUTOINIT(self);
-}
-
-void ons_ControlPoint_Setup(entity cp)
-{SELFPARAM();
-       // declarations
-       setself(cp); // for later usage with droptofloor()
-
-       // main setup
-       cp.ons_worldcpnext = ons_worldcplist; // link control point into ons_worldcplist
-       ons_worldcplist = cp;
-
-       cp.netname = "Control point";
-       cp.team = 0;
-       cp.solid = SOLID_BBOX;
-       cp.movetype = MOVETYPE_NONE;
-       cp.touch = ons_ControlPoint_Touch;
-       cp.think = ons_ControlPoint_Think;
-       cp.nextthink = time + ONS_CP_THINKRATE;
-       cp.reset = ons_ControlPoint_Reset;
-       cp.colormap = 1024;
-       cp.iscaptured = false;
-       cp.islinked = false;
-       cp.isshielded = true;
-
-       if(cp.message == "") { cp.message = "a"; }
-
-       // appearence
-       setmodel_fixsize(cp, MDL_ONS_CP_PAD1);
-
-       // control point placement
-       if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location
-       {
-               cp.noalign = true;
-               cp.movetype = MOVETYPE_NONE;
-       }
-       else // drop to floor, automatically find a platform and set that as spawn origin
-       {
-               setorigin(cp, cp.origin + '0 0 20');
-               cp.noalign = false;
-               setself(cp);
-               droptofloor();
-               cp.movetype = MOVETYPE_TOSS;
-       }
-
-       // waypointsprites
-       WaypointSprite_SpawnFixed(WP_Null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE);
-       WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY);
-
-       InitializeEntity(cp, ons_DelayedControlPoint_Setup, INITPRIO_SETLOCATION);
-}
-
-
-// =========================
-// Main Generator Functions
-// =========================
-
-entity ons_Generator_Waypoint(entity e)
-{
-       if (e.isshielded)
-               return WP_OnsGenShielded;
-       return WP_OnsGen;
-}
-
-void ons_Generator_UpdateSprite(entity e)
-{
-       entity s1 = ons_Generator_Waypoint(e);
-       WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1);
-
-       if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded)
-       {
-               e.lastteam = e.team + 2;
-               e.lastshielded = e.isshielded;
-               if(e.lastshielded)
-               {
-                       if(e.team)
-                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, false));
-                       else
-                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5');
-               }
-               else
-               {
-                       if(e.team)
-                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, false));
-                       else
-                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75');
-               }
-               WaypointSprite_Ping(e.sprite);
-       }
-}
-
-void ons_GeneratorDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{SELFPARAM();
-       if(damage <= 0) { return; }
-       if(warmup_stage || gameover) { return; }
-       if(!round_handler_IsRoundStarted()) { return; }
-
-       if (attacker != self)
-       {
-               if (self.isshielded)
-               {
-                       // this is protected by a shield, so ignore the damage
-                       if (time > self.pain_finished)
-                               if (IS_PLAYER(attacker))
-                               {
-                                       play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
-                                       attacker.typehitsound += 1;
-                                       self.pain_finished = time + 1;
-                               }
-                       return;
-               }
-               if (time > self.pain_finished)
-               {
-                       self.pain_finished = time + 10;
-                       entity head;
-                       FOR_EACH_REALPLAYER(head) if(SAME_TEAM(head, self)) { Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_GENERATOR_UNDERATTACK); }
-                       play2team(self.team, SND(ONS_GENERATOR_UNDERATTACK));
-               }
-       }
-       self.health = self.health - damage;
-       WaypointSprite_UpdateHealth(self.sprite, self.health);
-       // choose an animation frame based on health
-       self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1);
-       // see if the generator is still functional, or dying
-       if (self.health > 0)
-       {
-               self.lasthealth = self.health;
-       }
-       else
-       {
-               if (attacker == self)
-                       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_OVERTIME_));
-               else
-               {
-                       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_));
-                       PlayerScore_Add(attacker, SP_SCORE, 100);
-               }
-               self.iscaptured = false;
-               self.islinked = false;
-               self.isshielded = false;
-               self.takedamage = DAMAGE_NO; // can't be hurt anymore
-               self.event_damage = func_null; // won't do anything if hurt
-               self.count = 0; // reset counter
-               self.think = func_null;
-               self.nextthink = 0;
-               //self.think(); // do the first explosion now
-
-               WaypointSprite_UpdateMaxHealth(self.sprite, 0);
-               WaypointSprite_Ping(self.sprite);
-               //WaypointSprite_Kill(self.sprite); // can't do this yet, code too poor
-
-               onslaught_updatelinks();
-       }
-
-       // Throw some flaming gibs on damage, more damage = more chance for gib
-       if(random() < damage/220)
-       {
-               sound(self, CH_TRIGGER, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
-       }
-       else
-       {
-               // particles on every hit
-               Send_Effect(EFFECT_SPARKS, hitloc, force * -1, 1);
-
-               //sound on every hit
-               if (random() < 0.5)
-                       sound(self, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE, ATTEN_NORM);
-               else
-                       sound(self, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE, ATTEN_NORM);
-       }
-
-       self.SendFlags |= GSF_STATUS;
-}
-
-void ons_GeneratorThink()
-{SELFPARAM();
-       entity e;
-       self.nextthink = time + GEN_THINKRATE;
-       if (!gameover)
-       {
-        if(!self.isshielded && self.wait < time)
-        {
-            self.wait = time + 5;
-            FOR_EACH_REALPLAYER(e)
-            {
-                               if(SAME_TEAM(e, self))
-                               {
-                                       Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM);
-                    soundto(MSG_ONE, e, CHAN_AUTO, SND(KH_ALARM), VOL_BASE, ATTEN_NONE);    // FIXME: unique sound?
-                }
-                               else
-                                       Send_Notification(NOTIF_ONE, e, MSG_CENTER, APP_TEAM_NUM_4(self.team, CENTER_ONS_NOTSHIELDED_));
-            }
-        }
-       }
-}
-
-void ons_GeneratorReset()
-{SELFPARAM();
-       self.team = self.team_saved;
-       self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health;
-       self.takedamage = DAMAGE_AIM;
-       self.bot_attack = true;
-       self.iscaptured = true;
-       self.islinked = true;
-       self.isshielded = true;
-       self.event_damage = ons_GeneratorDamage;
-       self.think = ons_GeneratorThink;
-       self.nextthink = time + GEN_THINKRATE;
-
-       Net_LinkEntity(self, false, 0, generator_send);
-
-       self.SendFlags = GSF_SETUP; // just incase
-       self.SendFlags |= GSF_STATUS;
-
-       WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
-       WaypointSprite_UpdateHealth(self.sprite, self.health);
-       WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY);
-
-       onslaught_updatelinks();
-}
-
-void ons_DelayedGeneratorSetup()
-{SELFPARAM();
-       // bot waypoints
-       waypoint_spawnforitem_force(self, self.origin);
-       self.nearestwaypointtimeout = 0; // activate waypointing again
-       self.bot_basewaypoint = self.nearestwaypoint;
-
-       // captureshield setup
-       ons_CaptureShield_Spawn(self, true);
-
-       onslaught_updatelinks();
-
-       Net_LinkEntity(self, false, 0, generator_send);
-}
-
-
-void onslaught_generator_touch()
-{SELFPARAM();
-       if ( IS_PLAYER(other) )
-       if ( SAME_TEAM(self,other) )
-       if ( self.iscaptured )
-       {
-               Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_TELEPORT);
-       }
-}
-
-void ons_GeneratorSetup(entity gen) // called when spawning a generator entity on the map as a spawnfunc
-{SELFPARAM();
-       // declarations
-       int teamnumber = gen.team;
-       setself(gen); // for later usage with droptofloor()
-
-       // main setup
-       gen.ons_worldgeneratornext = ons_worldgeneratorlist; // link generator into ons_worldgeneratorlist
-       ons_worldgeneratorlist = gen;
-
-       gen.netname = sprintf("%s generator", Team_ColoredFullName(teamnumber));
-       gen.classname = "onslaught_generator";
-       gen.solid = SOLID_BBOX;
-       gen.team_saved = teamnumber;
-       gen.movetype = MOVETYPE_NONE;
-       gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health;
-       gen.takedamage = DAMAGE_AIM;
-       gen.bot_attack = true;
-       gen.event_damage = ons_GeneratorDamage;
-       gen.reset = ons_GeneratorReset;
-       gen.think = ons_GeneratorThink;
-       gen.nextthink = time + GEN_THINKRATE;
-       gen.iscaptured = true;
-       gen.islinked = true;
-       gen.isshielded = true;
-       gen.touch = onslaught_generator_touch;
-
-       // appearence
-       // model handled by CSQC
-       setsize(gen, GENERATOR_MIN, GENERATOR_MAX);
-       setorigin(gen, (gen.origin + CPGEN_SPAWN_OFFSET));
-       gen.colormap = 1024 + (teamnumber - 1) * 17;
-
-       // generator placement
-       setself(gen);
-       droptofloor();
-
-       // waypointsprites
-       WaypointSprite_SpawnFixed(WP_Null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE);
-       WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY);
-       WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
-       WaypointSprite_UpdateHealth(self.sprite, self.health);
-
-       InitializeEntity(gen, ons_DelayedGeneratorSetup, INITPRIO_SETLOCATION);
-}
-
-
-// ===============
-//  Round Handler
-// ===============
-
-int total_generators;
-void Onslaught_count_generators()
-{
-       entity e;
-       total_generators = redowned = blueowned = yellowowned = pinkowned = 0;
-       for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext)
-       {
-               ++total_generators;
-               redowned += (e.team == NUM_TEAM_1 && e.health > 0);
-               blueowned += (e.team == NUM_TEAM_2 && e.health > 0);
-               yellowowned += (e.team == NUM_TEAM_3 && e.health > 0);
-               pinkowned += (e.team == NUM_TEAM_4 && e.health > 0);
-       }
-}
-
-int Onslaught_GetWinnerTeam()
-{
-       int winner_team = 0;
-       if(redowned > 0)
-               winner_team = NUM_TEAM_1;
-       if(blueowned > 0)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_2;
-       }
-       if(yellowowned > 0)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_3;
-       }
-       if(pinkowned > 0)
-       {
-               if(winner_team) return 0;
-               winner_team = NUM_TEAM_4;
-       }
-       if(winner_team)
-               return winner_team;
-       return -1; // no generators left?
-}
-
-#define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
-#define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1)
-bool Onslaught_CheckWinner()
-{
-       entity e;
-
-       if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0))
-       {
-               ons_stalemate = true;
-
-               if (!wpforenemy_announced)
-               {
-                       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT);
-                       sound(world, CH_INFO, SND_ONS_GENERATOR_DECAY, VOL_BASE, ATTEN_NONE);
-
-                       wpforenemy_announced = true;
-               }
-
-               entity tmp_entity; // temporary entity
-               float d;
-               for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) if(time >= tmp_entity.ons_overtime_damagedelay)
-               {
-                       // tmp_entity.max_health / 300 gives 5 minutes of overtime.
-                       // control points reduce the overtime duration.
-                       d = 1;
-                       for(e = ons_worldcplist; e; e = e.ons_worldcpnext)
-                       {
-                               if(DIFF_TEAM(e, tmp_entity))
-                               if(e.islinked)
-                                       d = d + 1;
-                       }
-
-                       if(autocvar_g_campaign && autocvar__campaign_testrun)
-                               d = d * tmp_entity.max_health;
-                       else
-                               d = d * tmp_entity.max_health / max(30, 60 * autocvar_timelimit_suddendeath);
-
-                       Damage(tmp_entity, tmp_entity, tmp_entity, d, DEATH_HURTTRIGGER.m_id, tmp_entity.origin, '0 0 0');
-
-                       tmp_entity.sprite.SendFlags |= 16;
-
-                       tmp_entity.ons_overtime_damagedelay = time + 1;
-               }
-       }
-       else { wpforenemy_announced = false; ons_stalemate = false; }
-
-       Onslaught_count_generators();
-
-       if(ONS_OWNED_GENERATORS_OK())
-               return 0;
-
-       int winner_team = Onslaught_GetWinnerTeam();
-
-       if(winner_team > 0)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
-               TeamScore_AddToTeam(winner_team, ST_ONS_CAPS, +1);
-       }
-       else if(winner_team == -1)
-       {
-               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
-       }
-
-       ons_stalemate = false;
-
-       play2all(SND(CTF_CAPTURE(winner_team)));
-
-       round_handler_Init(7, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit);
-
-       FOR_EACH_PLAYER(e)
-       {
-               e.ons_roundlost = true;
-               e.player_blocked = true;
-
-               nades_Clear(e);
-       }
-
-       return 1;
-}
-
-bool Onslaught_CheckPlayers()
-{
-       return 1;
-}
-
-void Onslaught_RoundStart()
-{
-       entity tmp_entity;
-       FOR_EACH_PLAYER(tmp_entity) { tmp_entity.player_blocked = false; }
-
-       for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext)
-               tmp_entity.sprite.SendFlags |= 16;
-
-       for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
-               tmp_entity.sprite.SendFlags |= 16;
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-// NOTE: LEGACY CODE, needs to be re-written!
-
-void havocbot_goalrating_ons_offenseitems(float ratingscale, vector org, float sradius)
-{SELFPARAM();
-       entity head;
-       float t, c;
-       int i;
-       bool needarmor = false, needweapons = false;
-
-       // Needs armor/health?
-       if(self.health<100)
-               needarmor = true;
-
-       // Needs weapons?
-       c = 0;
-       for(i = WEP_FIRST; i <= WEP_LAST ; ++i)
-       {
-               // Find weapon
-               if(self.weapons & WepSet_FromWeapon(i))
-               if(++c>=4)
-                       break;
-       }
-
-       if(c<4)
-               needweapons = true;
-
-       if(!needweapons && !needarmor)
-               return;
-
-       ons_debug(strcat(self.netname, " needs weapons ", ftos(needweapons) , "\n"));
-       ons_debug(strcat(self.netname, " needs armor ", ftos(needarmor) , "\n"));
-
-       // See what is around
-       head = findchainfloat(bot_pickup, true);
-       while (head)
-       {
-               // gather health and armor only
-               if (head.solid)
-               if ( ((head.health || head.armorvalue) && needarmor) || (head.weapons && needweapons ) )
-               if (vlen(head.origin - org) < sradius)
-               {
-                       t = head.bot_pickupevalfunc(self, head);
-                       if (t > 0)
-                               navigation_routerating(head, t * ratingscale, 500);
-               }
-               head = head.chain;
-       }
-}
-
-void havocbot_role_ons_setrole(entity bot, int role)
-{
-       ons_debug(strcat(bot.netname," switched to "));
-       switch(role)
-       {
-               case HAVOCBOT_ONS_ROLE_DEFENSE:
-                       ons_debug("defense");
-                       bot.havocbot_role = havocbot_role_ons_defense;
-                       bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_ONS_ROLE_ASSISTANT:
-                       ons_debug("assistant");
-                       bot.havocbot_role = havocbot_role_ons_assistant;
-                       bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_ONS_ROLE_OFFENSE:
-                       ons_debug("offense");
-                       bot.havocbot_role = havocbot_role_ons_offense;
-                       bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-       }
-       ons_debug("\n");
-}
-
-int havocbot_ons_teamcount(entity bot, int role)
-{SELFPARAM();
-       int c = 0;
-       entity head;
-
-       FOR_EACH_PLAYER(head)
-       if(SAME_TEAM(head, self))
-       if(head.havocbot_role_flags & role)
-               ++c;
-
-       return c;
-}
-
-void havocbot_goalrating_ons_controlpoints_attack(float ratingscale)
-{SELFPARAM();
-       entity cp, cp1, cp2, best, pl, wp;
-       float radius, bestvalue;
-       int c;
-       bool found;
-
-       // Filter control points
-       for(cp2 = ons_worldcplist; cp2; cp2 = cp2.ons_worldcpnext)
-       {
-               cp2.wpcost = c = 0;
-               cp2.wpconsidered = false;
-
-               if(cp2.isshielded)
-                       continue;
-
-               // Ignore owned controlpoints
-               if(!(cp2.isgenneighbor[self.team] || cp2.iscpneighbor[self.team]))
-                       continue;
-
-               // Count team mates interested in this control point
-               // (easier and cleaner than keeping counters per cp and teams)
-               FOR_EACH_PLAYER(pl)
-               if(SAME_TEAM(pl, self))
-               if(pl.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE)
-               if(pl.havocbot_ons_target==cp2)
-                       ++c;
-
-               // NOTE: probably decrease the cost of attackable control points
-               cp2.wpcost = c;
-               cp2.wpconsidered = true;
-       }
-
-       // We'll consider only the best case
-       bestvalue = 99999999999;
-       cp = world;
-       for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext)
-       {
-               if (!cp1.wpconsidered)
-                       continue;
-
-               if(cp1.wpcost<bestvalue)
-               {
-                       bestvalue = cp1.wpcost;
-                       cp = cp1;
-                       self.havocbot_ons_target = cp1;
-               }
-       }
-
-       if (!cp)
-               return;
-
-       ons_debug(strcat(self.netname, " chose cp ranked ", ftos(bestvalue), "\n"));
-
-       if(cp.goalentity)
-       {
-               // Should be attacked
-               // Rate waypoints near it
-               found = false;
-               best = world;
-               bestvalue = 99999999999;
-               for(radius=0; radius<1000 && !found; radius+=500)
-               {
-                       for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
-                       {
-                               if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
-                               if(wp.classname=="waypoint")
-                               if(checkpvs(wp.origin,cp))
-                               {
-                                       found = true;
-                                       if(wp.cnt<bestvalue)
-                                       {
-                                               best = wp;
-                                               bestvalue = wp.cnt;
-                                       }
-                               }
-                       }
-               }
-
-               if(best)
-               {
-                       navigation_routerating(best, ratingscale, 10000);
-                       best.cnt += 1;
-
-                       self.havocbot_attack_time = 0;
-                       if(checkpvs(self.view_ofs,cp))
-                       if(checkpvs(self.view_ofs,best))
-                               self.havocbot_attack_time = time + 2;
-               }
-               else
-               {
-                       navigation_routerating(cp, ratingscale, 10000);
-               }
-               ons_debug(strcat(self.netname, " found an attackable controlpoint at ", vtos(cp.origin) ,"\n"));
-       }
-       else
-       {
-               // Should be touched
-               ons_debug(strcat(self.netname, " found a touchable controlpoint at ", vtos(cp.origin) ,"\n"));
-               found = false;
-
-               // Look for auto generated waypoint
-               if (!bot_waypoints_for_items)
-               for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
-               {
-                       if(wp.classname=="waypoint")
-                       {
-                               navigation_routerating(wp, ratingscale, 10000);
-                               found = true;
-                       }
-               }
-
-               // Nothing found, rate the controlpoint itself
-               if (!found)
-                       navigation_routerating(cp, ratingscale, 10000);
-       }
-}
-
-bool havocbot_goalrating_ons_generator_attack(float ratingscale)
-{SELFPARAM();
-       entity g, wp, bestwp;
-       bool found;
-       int best;
-
-       for(g = ons_worldgeneratorlist; g; g = g.ons_worldgeneratornext)
-       {
-               if(SAME_TEAM(g, self) || g.isshielded)
-                       continue;
-
-               // Should be attacked
-               // Rate waypoints near it
-               found = false;
-               bestwp = world;
-               best = 99999999999;
-
-               for(wp=findradius(g.origin,400); wp; wp=wp.chain)
-               {
-                       if(wp.classname=="waypoint")
-                       if(checkpvs(wp.origin,g))
-                       {
-                               found = true;
-                               if(wp.cnt<best)
-                               {
-                                       bestwp = wp;
-                                       best = wp.cnt;
-                               }
-                       }
-               }
-
-               if(bestwp)
-               {
-                       ons_debug("waypoints found around generator\n");
-                       navigation_routerating(bestwp, ratingscale, 10000);
-                       bestwp.cnt += 1;
-
-                       self.havocbot_attack_time = 0;
-                       if(checkpvs(self.view_ofs,g))
-                       if(checkpvs(self.view_ofs,bestwp))
-                               self.havocbot_attack_time = time + 5;
-
-                       return true;
-               }
-               else
-               {
-                       ons_debug("generator found without waypoints around\n");
-                       // if there aren't waypoints near the generator go straight to it
-                       navigation_routerating(g, ratingscale, 10000);
-                       self.havocbot_attack_time = 0;
-                       return true;
-               }
-       }
-       return false;
-}
-
-void havocbot_role_ons_offense()
-{SELFPARAM();
-       if(self.deadflag != DEAD_NO)
-       {
-               self.havocbot_attack_time = 0;
-               havocbot_ons_reset_role(self);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + 120;
-
-       if (time > self.havocbot_role_timeout)
-       {
-               havocbot_ons_reset_role(self);
-               return;
-       }
-
-       if(self.havocbot_attack_time>time)
-               return;
-
-       if (self.bot_strategytime < time)
-       {
-               navigation_goalrating_start();
-               havocbot_goalrating_enemyplayers(20000, self.origin, 650);
-               if(!havocbot_goalrating_ons_generator_attack(20000))
-                       havocbot_goalrating_ons_controlpoints_attack(20000);
-               havocbot_goalrating_ons_offenseitems(10000, self.origin, 10000);
-               navigation_goalrating_end();
-
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-       }
-}
-
-void havocbot_role_ons_assistant()
-{SELFPARAM();
-       havocbot_ons_reset_role(self);
-}
-
-void havocbot_role_ons_defense()
-{SELFPARAM();
-       havocbot_ons_reset_role(self);
-}
-
-void havocbot_ons_reset_role(entity bot)
-{SELFPARAM();
-       entity head;
-       int c = 0;
-
-       if(self.deadflag != DEAD_NO)
-               return;
-
-       bot.havocbot_ons_target = world;
-
-       // TODO: Defend control points or generator if necessary
-
-       // if there is only me on the team switch to offense
-       c = 0;
-       FOR_EACH_PLAYER(head)
-       if(SAME_TEAM(head, self))
-               ++c;
-
-       if(c==1)
-       {
-               havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
-               return;
-       }
-
-       havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
-}
-
-
-/*
- * Find control point or generator owned by the same team self which is nearest to pos
- * if max_dist is positive, only control points within this range will be considered
- */
-entity ons_Nearest_ControlPoint(vector pos, float max_dist)
-{SELFPARAM();
-       entity tmp_entity, closest_target = world;
-       tmp_entity = findchain(classname, "onslaught_controlpoint");
-       while(tmp_entity)
-       {
-               if(SAME_TEAM(tmp_entity, self))
-               if(tmp_entity.iscaptured)
-               if(max_dist <= 0 || vlen(tmp_entity.origin - pos) <= max_dist)
-               if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world)
-                       closest_target = tmp_entity;
-               tmp_entity = tmp_entity.chain;
-       }
-       tmp_entity = findchain(classname, "onslaught_generator");
-       while(tmp_entity)
-       {
-               if(SAME_TEAM(tmp_entity, self))
-               if(max_dist <= 0 || vlen(tmp_entity.origin - pos) < max_dist)
-               if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world)
-                       closest_target = tmp_entity;
-               tmp_entity = tmp_entity.chain;
-       }
-
-       return closest_target;
-}
-
-/*
- * Find control point or generator owned by the same team self which is nearest to pos
- * if max_dist is positive, only control points within this range will be considered
- * This function only check distances on the XY plane, disregarding Z
- */
-entity ons_Nearest_ControlPoint_2D(vector pos, float max_dist)
-{SELFPARAM();
-       entity tmp_entity, closest_target = world;
-       vector delta;
-       float smallest_distance = 0, distance;
-
-       tmp_entity = findchain(classname, "onslaught_controlpoint");
-       while(tmp_entity)
-       {
-               delta = tmp_entity.origin - pos;
-               delta_z = 0;
-               distance = vlen(delta);
-
-               if(SAME_TEAM(tmp_entity, self))
-               if(tmp_entity.iscaptured)
-               if(max_dist <= 0 || distance <= max_dist)
-               if(closest_target == world || distance <= smallest_distance )
-               {
-                       closest_target = tmp_entity;
-                       smallest_distance = distance;
-               }
-
-               tmp_entity = tmp_entity.chain;
-       }
-       tmp_entity = findchain(classname, "onslaught_generator");
-       while(tmp_entity)
-       {
-               delta = tmp_entity.origin - pos;
-               delta_z = 0;
-               distance = vlen(delta);
-
-               if(SAME_TEAM(tmp_entity, self))
-               if(max_dist <= 0 || distance <= max_dist)
-               if(closest_target == world || distance <= smallest_distance )
-               {
-                       closest_target = tmp_entity;
-                       smallest_distance = distance;
-               }
-
-               tmp_entity = tmp_entity.chain;
-       }
-
-       return closest_target;
-}
-/**
- * find the number of control points and generators in the same team as self
- */
-int ons_Count_SelfControlPoints()
-{SELFPARAM();
-       entity tmp_entity;
-       tmp_entity = findchain(classname, "onslaught_controlpoint");
-       int n = 0;
-       while(tmp_entity)
-       {
-               if(SAME_TEAM(tmp_entity, self))
-               if(tmp_entity.iscaptured)
-                       n++;
-               tmp_entity = tmp_entity.chain;
-       }
-       tmp_entity = findchain(classname, "onslaught_generator");
-       while(tmp_entity)
-       {
-               if(SAME_TEAM(tmp_entity, self))
-                       n++;
-               tmp_entity = tmp_entity.chain;
-       }
-       return n;
-}
-
-/**
- * Teleport player to a random position near tele_target
- * if tele_effects is true, teleport sound+particles are created
- * return false on failure
- */
-bool ons_Teleport(entity player, entity tele_target, float range, bool tele_effects)
-{
-       if ( !tele_target )
-               return false;
-
-       int i;
-       vector loc;
-       float theta;
-       for(i = 0; i < 16; ++i)
-       {
-               theta = random() * 2 * M_PI;
-               loc_y = sin(theta);
-               loc_x = cos(theta);
-               loc_z = 0;
-               loc *= random() * range;
-
-               loc += tele_target.origin + '0 0 128';
-
-               tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, player);
-               if(trace_fraction == 1.0 && !trace_startsolid)
-               {
-                       traceline(tele_target.origin, loc, MOVE_NOMONSTERS, tele_target); // double check to make sure we're not spawning outside the world
-                       if(trace_fraction == 1.0 && !trace_startsolid)
-                       {
-                               if ( tele_effects )
-                               {
-                                       Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1);
-                                       sound (player, CH_TRIGGER, SND_TELEPORT, VOL_BASE, ATTEN_NORM);
-                               }
-                               setorigin(player, loc);
-                               player.angles = '0 1 0' * ( theta * RAD2DEG + 180 );
-                               makevectors(player.angles);
-                               player.fixangle = true;
-                               player.teleport_antispam = time + autocvar_g_onslaught_teleport_wait;
-
-                               if ( tele_effects )
-                                       Send_Effect(EFFECT_TELEPORT, player.origin + v_forward * 32, '0 0 0', 1);
-                               return true;
-                       }
-               }
-       }
-
-       return false;
-}
-
-// ==============
-// Hook Functions
-// ==============
-
-MUTATOR_HOOKFUNCTION(ons, reset_map_global)
-{SELFPARAM();
-       entity e;
-       FOR_EACH_PLAYER(e)
-       {
-               e.ons_roundlost = false;
-               e.ons_deathloc = '0 0 0';
-               WITH(entity, self, e, PutClientInServer());
-       }
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ons, ClientDisconnect)
-{SELFPARAM();
-       self.ons_deathloc = '0 0 0';
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ons, MakePlayerObserver)
-{SELFPARAM();
-       self.ons_deathloc = '0 0 0';
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ons, PlayerSpawn)
-{SELFPARAM();
-       if(!round_handler_IsRoundStarted())
-       {
-               self.player_blocked = true;
-               return false;
-       }
-
-       entity l;
-       for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
-       {
-               l.sprite.SendFlags |= 16;
-       }
-       for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
-       {
-               l.sprite.SendFlags |= 16;
-       }
-
-       if(ons_stalemate) { Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); }
-
-       if ( autocvar_g_onslaught_spawn_choose )
-       if ( self.ons_spawn_by )
-       if ( ons_Teleport(self,self.ons_spawn_by,autocvar_g_onslaught_teleport_radius,false) )
-       {
-               self.ons_spawn_by = world;
-               return false;
-       }
-
-       if(autocvar_g_onslaught_spawn_at_controlpoints)
-       if(random() <= autocvar_g_onslaught_spawn_at_controlpoints_chance)
-       {
-               float random_target = autocvar_g_onslaught_spawn_at_controlpoints_random;
-               entity tmp_entity, closest_target = world;
-               vector spawn_loc = self.ons_deathloc;
-
-               // new joining player or round reset, don't bother checking
-               if(spawn_loc == '0 0 0') { return false; }
-
-               if(random_target) { RandomSelection_Init(); }
-
-               for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext)
-               {
-                       if(SAME_TEAM(tmp_entity, self))
-                       if(random_target)
-                               RandomSelection_Add(tmp_entity, 0, string_null, 1, 1);
-                       else if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world)
-                               closest_target = tmp_entity;
-               }
-
-               if(random_target) { closest_target = RandomSelection_chosen_ent; }
-
-               if(closest_target)
-               {
-                       float i;
-                       vector loc;
-                       for(i = 0; i < 10; ++i)
-                       {
-                               loc = closest_target.origin + '0 0 96';
-                               loc += ('0 1 0' * random()) * 128;
-                               tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self);
-                               if(trace_fraction == 1.0 && !trace_startsolid)
-                               {
-                                       traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world
-                                       if(trace_fraction == 1.0 && !trace_startsolid)
-                                       {
-                                               setorigin(self, loc);
-                                               self.angles = normalize(loc - closest_target.origin) * RAD2DEG;
-                                               return false;
-                                       }
-                               }
-                       }
-               }
-       }
-
-       if(autocvar_g_onslaught_spawn_at_generator)
-       if(random() <= autocvar_g_onslaught_spawn_at_generator_chance)
-       {
-               float random_target = autocvar_g_onslaught_spawn_at_generator_random;
-               entity tmp_entity, closest_target = world;
-               vector spawn_loc = self.ons_deathloc;
-
-               // new joining player or round reset, don't bother checking
-               if(spawn_loc == '0 0 0') { return false; }
-
-               if(random_target) { RandomSelection_Init(); }
-
-               for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
-               {
-                       if(random_target)
-                               RandomSelection_Add(tmp_entity, 0, string_null, 1, 1);
-                       else
-                       {
-                               if(SAME_TEAM(tmp_entity, self))
-                               if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world)
-                                       closest_target = tmp_entity;
-                       }
-               }
-
-               if(random_target) { closest_target = RandomSelection_chosen_ent; }
-
-               if(closest_target)
-               {
-                       float i;
-                       vector loc;
-                       for(i = 0; i < 10; ++i)
-                       {
-                               loc = closest_target.origin + '0 0 128';
-                               loc += ('0 1 0' * random()) * 256;
-                               tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self);
-                               if(trace_fraction == 1.0 && !trace_startsolid)
-                               {
-                                       traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world
-                                       if(trace_fraction == 1.0 && !trace_startsolid)
-                                       {
-                                               setorigin(self, loc);
-                                               self.angles = normalize(loc - closest_target.origin) * RAD2DEG;
-                                               return false;
-                                       }
-                               }
-                       }
-               }
-       }
-
-    return false;
-}
-
-MUTATOR_HOOKFUNCTION(ons, PlayerDies)
-{SELFPARAM();
-       frag_target.ons_deathloc = frag_target.origin;
-       entity l;
-       for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
-       {
-               l.sprite.SendFlags |= 16;
-       }
-       for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
-       {
-               l.sprite.SendFlags |= 16;
-       }
-
-       if ( autocvar_g_onslaught_spawn_choose )
-       if ( ons_Count_SelfControlPoints() > 1 )
-               stuffcmd(self, "qc_cmd_cl hud clickradar\n");
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ons, MonsterMove)
-{SELFPARAM();
-       entity e = find(world, targetname, self.target);
-       if (e != world)
-               self.team = e.team;
-
-       return false;
-}
-
-void ons_MonsterSpawn_Delayed()
-{SELFPARAM();
-       entity e, own = self.owner;
-
-       if(!own) { remove(self); return; }
-
-       if(own.targetname)
-       {
-               e = find(world, target, own.targetname);
-               if(e != world)
-               {
-                       own.team = e.team;
-
-                       activator = e;
-                       own.use();
-               }
-       }
-
-       remove(self);
-}
-
-MUTATOR_HOOKFUNCTION(ons, MonsterSpawn)
-{SELFPARAM();
-       entity e = spawn();
-       e.owner = self;
-       InitializeEntity(e, ons_MonsterSpawn_Delayed, INITPRIO_FINDTARGET);
-
-       return false;
-}
-
-void ons_TurretSpawn_Delayed()
-{SELFPARAM();
-       entity e, own = self.owner;
-
-       if(!own) { remove(self); return; }
-
-       if(own.targetname)
-       {
-               e = find(world, target, own.targetname);
-               if(e != world)
-               {
-                       own.team = e.team;
-                       own.active = ACTIVE_NOT;
-
-                       activator = e;
-                       own.use();
-               }
-       }
-
-       remove(self);
-}
-
-MUTATOR_HOOKFUNCTION(ons, TurretSpawn)
-{SELFPARAM();
-       entity e = spawn();
-       e.owner = self;
-       InitializeEntity(e, ons_TurretSpawn_Delayed, INITPRIO_FINDTARGET);
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ons, HavocBot_ChooseRole)
-{SELFPARAM();
-       havocbot_ons_reset_role(self);
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ons, GetTeamCount)
-{
-       // onslaught is special
-       for(entity tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
-       {
-               switch(tmp_entity.team)
-               {
-                       case NUM_TEAM_1: c1 = 0; break;
-                       case NUM_TEAM_2: c2 = 0; break;
-                       case NUM_TEAM_3: c3 = 0; break;
-                       case NUM_TEAM_4: c4 = 0; break;
-               }
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ons, SpectateCopy)
-{SELFPARAM();
-       self.ons_roundlost = other.ons_roundlost; // make spectators see it too
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ons, SV_ParseClientCommand)
-{SELFPARAM();
-       if(MUTATOR_RETURNVALUE) // command was already handled?
-               return false;
-
-       if ( cmd_name == "ons_spawn" )
-       {
-               vector pos = self.origin;
-               if(cmd_argc > 1)
-                       pos_x = stof(argv(1));
-               if(cmd_argc > 2)
-                       pos_y = stof(argv(2));
-               if(cmd_argc > 3)
-                       pos_z = stof(argv(3));
-
-               if ( IS_PLAYER(self) )
-               {
-                       if ( !self.frozen )
-                       {
-                               entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius);
-
-                               if ( !source_point && self.health > 0 )
-                               {
-                                       sprint(self, "\nYou need to be next to a control point\n");
-                                       return 1;
-                               }
-
-
-                               entity closest_target = ons_Nearest_ControlPoint_2D(pos, autocvar_g_onslaught_click_radius);
-
-                               if ( closest_target == world )
-                               {
-                                       sprint(self, "\nNo control point found\n");
-                                       return 1;
-                               }
-
-                               if ( self.health <= 0 )
-                               {
-                                       self.ons_spawn_by = closest_target;
-                                       self.respawn_flags = self.respawn_flags | RESPAWN_FORCE;
-                               }
-                               else
-                               {
-                                       if ( source_point == closest_target )
-                                       {
-                                               sprint(self, "\nTeleporting to the same point\n");
-                                               return 1;
-                                       }
-
-                                       if ( !ons_Teleport(self,closest_target,autocvar_g_onslaught_teleport_radius,true) )
-                                               sprint(self, "\nUnable to teleport there\n");
-                               }
-
-                               return 1;
-                       }
-
-                       sprint(self, "\nNo teleportation for you\n");
-               }
-
-               return 1;
-       }
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(ons, PlayerUseKey)
-{SELFPARAM();
-       if(MUTATOR_RETURNVALUE || gameover) { return false; }
-
-       if((time > self.teleport_antispam) && (self.deadflag == DEAD_NO) && !self.vehicle)
-       {
-               entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius);
-               if ( source_point )
-               {
-                       stuffcmd(self, "qc_cmd_cl hud clickradar\n");
-                       return true;
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ons, PlayHitsound)
-{
-       return (frag_victim.classname == "onslaught_generator" && !frag_victim.isshielded)
-               || (frag_victim.classname == "onslaught_controlpoint_icon" && !frag_victim.owner.isshielded);
-}
-
-MUTATOR_HOOKFUNCTION(ons, SendWaypoint)
-{
-       if(wp_sendflags & 16)
-       {
-               if(self.owner.classname == "onslaught_controlpoint")
-               {
-                       entity wp_owner = self.owner;
-                       entity e = WaypointSprite_getviewentity(wp_sendto);
-                       if(SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { wp_flag |= 2; }
-                       if(!ons_ControlPoint_Attackable(wp_owner, e.team)) { wp_flag |= 2; }
-               }
-               if(self.owner.classname == "onslaught_generator")
-               {
-                       entity wp_owner = self.owner;
-                       if(wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { wp_flag |= 2; }
-                       if(wp_owner.health <= 0) { wp_flag |= 2; }
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ons, TurretValidateTarget)
-{
-       if(substring(turret_target.classname, 0, 10) == "onslaught_") // don't attack onslaught targets, that's the player's job!
-       {
-               ret_float = -3;
-               return true;
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ons, TurretThink)
-{
-       // ONS uses somewhat backwards linking.
-       if(self.target)
-       {
-               entity e = find(world, targetname, self.target);
-               if (e != world)
-                       self.team = e.team;
-       }
-
-       if(self.team != self.tur_head.team)
-               turret_respawn();
-
-       return false;
-}
-
-
-// ==========
-// Spawnfuncs
-// ==========
-
-/*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16)
-  Link between control points.
-
-  This entity targets two different spawnfunc_onslaught_controlpoint or spawnfunc_onslaught_generator entities, and suppresses shielding on both if they are owned by different teams.
-
-keys:
-"target" - first control point.
-"target2" - second control point.
- */
-spawnfunc(onslaught_link)
-{
-       if(!g_onslaught) { remove(self); return; }
-
-       if (self.target == "" || self.target2 == "")
-               objerror("target and target2 must be set\n");
-
-       self.ons_worldlinknext = ons_worldlinklist; // link into ons_worldlinklist
-       ons_worldlinklist = self;
-
-       InitializeEntity(self, ons_DelayedLinkSetup, INITPRIO_FINDTARGET);
-       Net_LinkEntity(self, false, 0, ons_Link_Send);
-}
-
-/*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128)
-  Control point. Be sure to give this enough clearance so that the shootable part has room to exist
-
-  This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity.
-
-keys:
-"targetname" - name that spawnfunc_onslaught_link entities will use to target this.
-"target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities.
-"message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc)
- */
-
-spawnfunc(onslaught_controlpoint)
-{
-       if(!g_onslaught) { remove(self); return; }
-
-       ons_ControlPoint_Setup(self);
-}
-
-/*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64)
-  Base generator.
-
-  spawnfunc_onslaught_link entities can target this.
-
-keys:
-"team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET.
-"targetname" - name that spawnfunc_onslaught_link entities will use to target this.
- */
-spawnfunc(onslaught_generator)
-{
-       if(!g_onslaught) { remove(self); return; }
-       if(!self.team) { objerror("team must be set"); }
-
-       ons_GeneratorSetup(self);
-}
-
-// scoreboard setup
-void ons_ScoreRules()
-{
-       CheckAllowedTeams(world);
-       ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), SFL_SORT_PRIO_PRIMARY, 0, true);
-       ScoreInfo_SetLabel_TeamScore  (ST_ONS_CAPS,     "destroyed", SFL_SORT_PRIO_PRIMARY);
-       ScoreInfo_SetLabel_PlayerScore(SP_ONS_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
-       ScoreInfo_SetLabel_PlayerScore(SP_ONS_TAKES,    "takes",     0);
-       ScoreRules_basics_end();
-}
-
-void ons_DelayedInit() // Do this check with a delay so we can wait for teams to be set up
-{
-       ons_ScoreRules();
-
-       round_handler_Spawn(Onslaught_CheckPlayers, Onslaught_CheckWinner, Onslaught_RoundStart);
-       round_handler_Init(5, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit);
-}
-
-void ons_Initialize()
-{
-       g_onslaught = true;
-       ons_captureshield_force = autocvar_g_onslaught_shield_force;
-
-       addstat(STAT_ROUNDLOST, AS_INT, ons_roundlost);
-
-       InitializeEntity(world, ons_DelayedInit, INITPRIO_GAMETYPE);
-}
-
-REGISTER_MUTATOR(ons, IS_GAMETYPE(ONSLAUGHT))
-{
-       ActivateTeamplay();
-       SetLimits(autocvar_g_onslaught_point_limit, -1, -1, -1);
-       have_team_spawns = -1; // request team spawns
-
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This is a game type and it cannot be added at runtime.");
-               ons_Initialize();
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // we actually cannot roll back ons_Initialize here
-               // BUT: we don't need to! If this gets called, adding always
-               // succeeds.
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This is a game type and it cannot be removed at runtime.");
-               return -1;
-       }
-
-       return false;
-}
diff --git a/qcsrc/server/mutators/gamemode_onslaught.qh b/qcsrc/server/mutators/gamemode_onslaught.qh
deleted file mode 100644 (file)
index c6c3d18..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-// these are needed since mutators are compiled last
-
-#ifdef SVQC
-
-.entity ons_toucher; // player who touched the control point
-
-// control point / generator constants
-const float ONS_CP_THINKRATE = 0.2;
-const float GEN_THINKRATE = 1;
-#define CPGEN_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13))
-const vector CPGEN_WAYPOINT_OFFSET = ('0 0 128');
-const vector CPICON_OFFSET = ('0 0 96');
-
-// list of generators on the map
-entity ons_worldgeneratorlist;
-.entity ons_worldgeneratornext;
-.entity ons_stalegeneratornext;
-
-// list of control points on the map
-entity ons_worldcplist;
-.entity ons_worldcpnext;
-.entity ons_stalecpnext;
-
-// list of links on the map
-entity ons_worldlinklist;
-.entity ons_worldlinknext;
-.entity ons_stalelinknext;
-
-// definitions
-.entity sprite;
-.string target2;
-.int iscaptured;
-.int islinked;
-.int isshielded;
-.float lasthealth;
-.int lastteam;
-.int lastshielded;
-.int lastcaptured;
-
-.bool waslinked;
-
-bool ons_stalemate;
-
-.float teleport_antispam;
-
-.bool ons_roundlost;
-
-// waypoint sprites
-.entity bot_basewaypoint; // generator waypointsprite
-
-.bool isgenneighbor[17];
-.bool iscpneighbor[17];
-float ons_notification_time[17];
-
-.float ons_overtime_damagedelay;
-
-.vector ons_deathloc;
-
-.entity ons_spawn_by;
-
-// declarations for functions used outside gamemode_onslaught.qc
-void ons_Generator_UpdateSprite(entity e);
-void ons_ControlPoint_UpdateSprite(entity e);
-bool ons_ControlPoint_Attackable(entity cp, int teamnumber);
-
-// CaptureShield: Prevent capturing or destroying control point/generator if it is not available yet
-float ons_captureshield_force; // push force of the shield
-
-// bot player logic
-const int HAVOCBOT_ONS_ROLE_NONE               = 0;
-const int HAVOCBOT_ONS_ROLE_DEFENSE    = 2;
-const int HAVOCBOT_ONS_ROLE_ASSISTANT  = 4;
-const int HAVOCBOT_ONS_ROLE_OFFENSE    = 8;
-
-.entity havocbot_ons_target;
-
-.int havocbot_role_flags;
-.float havocbot_attack_time;
-
-void havocbot_role_ons_defense();
-void havocbot_role_ons_offense();
-void havocbot_role_ons_assistant();
-
-void havocbot_ons_reset_role(entity bot);
-void havocbot_goalrating_items(float ratingscale, vector org, float sradius);
-void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradius);
-
-// score rule declarations
-const int ST_ONS_CAPS = 1;
-const int SP_ONS_CAPS = 4;
-const int SP_ONS_TAKES = 6;
-
-#endif
diff --git a/qcsrc/server/mutators/gamemode_race.qc b/qcsrc/server/mutators/gamemode_race.qc
deleted file mode 100644 (file)
index 7a56d7f..0000000
+++ /dev/null
@@ -1,454 +0,0 @@
-#include "gamemode_race.qh"
-
-#include "gamemode.qh"
-
-#include "../race.qh"
-
-#define autocvar_g_race_laps_limit cvar("g_race_laps_limit")
-float autocvar_g_race_qualifying_timelimit;
-float autocvar_g_race_qualifying_timelimit_override;
-int autocvar_g_race_teams;
-
-// legacy bot roles
-.float race_checkpoint;
-void havocbot_role_race()
-{SELFPARAM();
-       if(self.deadflag != DEAD_NO)
-               return;
-
-       entity e;
-       if (self.bot_strategytime < time)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-
-               for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; )
-               {
-                       if(e.cnt == self.race_checkpoint)
-                       {
-                               navigation_routerating(e, 1000000, 5000);
-                       }
-                       else if(self.race_checkpoint == -1)
-                       {
-                               navigation_routerating(e, 1000000, 5000);
-                       }
-               }
-
-               navigation_goalrating_end();
-       }
-}
-
-void race_ScoreRules()
-{
-       ScoreRules_basics(race_teams, 0, 0, false);
-       if(race_teams)
-       {
-               ScoreInfo_SetLabel_TeamScore(  ST_RACE_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
-               ScoreInfo_SetLabel_PlayerScore(SP_RACE_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
-               ScoreInfo_SetLabel_PlayerScore(SP_RACE_TIME,    "time",      SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-               ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest",   SFL_LOWER_IS_BETTER | SFL_TIME);
-       }
-       else if(g_race_qualifying)
-       {
-               ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest",   SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-       }
-       else
-       {
-               ScoreInfo_SetLabel_PlayerScore(SP_RACE_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
-               ScoreInfo_SetLabel_PlayerScore(SP_RACE_TIME,    "time",      SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-               ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest",   SFL_LOWER_IS_BETTER | SFL_TIME);
-       }
-       ScoreRules_basics_end();
-}
-
-void race_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
-{
-       if(autocvar_sv_eventlog)
-               GameLogEcho(strcat(":race:", mode, ":", ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerPhysics)
-{SELFPARAM();
-       self.race_movetime_frac += PHYS_INPUT_TIMELENGTH;
-       float f = floor(self.race_movetime_frac);
-       self.race_movetime_frac -= f;
-       self.race_movetime_count += f;
-       self.race_movetime = self.race_movetime_frac + self.race_movetime_count;
-
-#ifdef SVQC
-       if(IS_PLAYER(self))
-       {
-               if (self.race_penalty)
-                       if (time > self.race_penalty)
-                               self.race_penalty = 0;
-               if(self.race_penalty)
-               {
-                       self.velocity = '0 0 0';
-                       self.movetype = MOVETYPE_NONE;
-                       self.disableclientprediction = 2;
-               }
-       }
-#endif
-
-       // force kbd movement for fairness
-       float wishspeed;
-       vector wishvel;
-
-       // if record times matter
-       // ensure nothing EVIL is being done (i.e. div0_evade)
-       // this hinders joystick users though
-       // but it still gives SOME analog control
-       wishvel.x = fabs(self.movement.x);
-       wishvel.y = fabs(self.movement.y);
-       if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
-       {
-               wishvel.z = 0;
-               wishspeed = vlen(wishvel);
-               if(wishvel.x >= 2 * wishvel.y)
-               {
-                       // pure X motion
-                       if(self.movement.x > 0)
-                               self.movement_x = wishspeed;
-                       else
-                               self.movement_x = -wishspeed;
-                       self.movement_y = 0;
-               }
-               else if(wishvel.y >= 2 * wishvel.x)
-               {
-                       // pure Y motion
-                       self.movement_x = 0;
-                       if(self.movement.y > 0)
-                               self.movement_y = wishspeed;
-                       else
-                               self.movement_y = -wishspeed;
-               }
-               else
-               {
-                       // diagonal
-                       if(self.movement.x > 0)
-                               self.movement_x = M_SQRT1_2 * wishspeed;
-                       else
-                               self.movement_x = -M_SQRT1_2 * wishspeed;
-                       if(self.movement.y > 0)
-                               self.movement_y = M_SQRT1_2 * wishspeed;
-                       else
-                               self.movement_y = -M_SQRT1_2 * wishspeed;
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, reset_map_global)
-{
-       float s;
-
-       Score_NicePrint(world);
-
-       race_ClearRecords();
-       PlayerScore_Sort(race_place, 0, 1, 0);
-
-       entity e;
-       FOR_EACH_CLIENT(e)
-       {
-               if(e.race_place)
-               {
-                       s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
-                       if(!s)
-                               e.race_place = 0;
-               }
-               race_EventLog(ftos(e.race_place), e);
-       }
-
-       if(g_race_qualifying == 2)
-       {
-               g_race_qualifying = 0;
-               independent_players = 0;
-               cvar_set("fraglimit", ftos(race_fraglimit));
-               cvar_set("leadlimit", ftos(race_leadlimit));
-               cvar_set("timelimit", ftos(race_timelimit));
-               race_ScoreRules();
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerPreThink)
-{SELFPARAM();
-       if(IS_SPEC(self) || IS_OBSERVER(self))
-       if(g_race_qualifying)
-       if(msg_entity.enemy.race_laptime)
-               race_SendNextCheckpoint(msg_entity.enemy, 1);
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, ClientConnect)
-{SELFPARAM();
-       race_PreparePlayer();
-       self.race_checkpoint = -1;
-
-       string rr = RACE_RECORD;
-
-       if(IS_REAL_CLIENT(self))
-       {
-               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);
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver)
-{SELFPARAM();
-       if(g_race_qualifying)
-       if(PlayerScore_Add(self, SP_RACE_FASTEST, 0))
-               self.frags = FRAGS_LMS_LOSER;
-       else
-               self.frags = FRAGS_SPECTATOR;
-
-       race_PreparePlayer();
-       self.race_checkpoint = -1;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerSpawn)
-{SELFPARAM();
-       if(spawn_spot.target == "")
-               // Emergency: this wasn't a real spawnpoint. Can this ever happen?
-               race_PreparePlayer();
-
-       // if we need to respawn, do it right
-       self.race_respawn_checkpoint = self.race_checkpoint;
-       self.race_respawn_spotref = spawn_spot;
-
-       self.race_place = 0;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, PutClientInServer)
-{SELFPARAM();
-       if(IS_PLAYER(self))
-       if(!gameover)
-       {
-               if(self.killcount == -666 /* initial spawn */ || g_race_qualifying) // spawn
-                       race_PreparePlayer();
-               else // respawn
-                       race_RetractPlayer();
-
-               race_AbandonRaceCheck(self);
-       }
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerDies)
-{SELFPARAM();
-       self.respawn_flags |= RESPAWN_FORCE;
-       race_AbandonRaceCheck(self);
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole)
-{SELFPARAM();
-       self.havocbot_role = havocbot_role_race;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(rc, GetPressedKeys)
-{SELFPARAM();
-       if(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 (!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 = RACE_RECORD;
-                       race_send_speedaward(MSG_ALL);
-                       speedaward_lastsent = speedaward_speed;
-                       if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
-                       {
-                               speedaward_alltimebest = speedaward_speed;
-                               speedaward_alltimebest_holder = speedaward_holder;
-                               speedaward_alltimebest_uid = speedaward_uid;
-                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
-                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
-                               race_send_speedaward_alltimebest(MSG_ALL);
-                       }
-               }
-       }
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear)
-{
-       if(g_race_qualifying)
-               return true; // in qualifying, you don't lose score by observing
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, GetTeamCount, CBC_ORDER_EXCLUSIVE)
-{
-       ret_float = race_teams;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
-{
-       // announce remaining frags if not in qualifying mode
-       if(!g_race_qualifying)
-               return true;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, GetRecords)
-{
-       for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
-       {
-               if(MapInfo_Get_ByID(i))
-               {
-                       float r = race_readTime(MapInfo_Map_bspname, 1);
-
-                       if(!r)
-                               continue;
-
-                       string h = race_readName(MapInfo_Map_bspname, 1);
-                       ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, FixClientCvars)
-{
-       stuffcmd(fix_client, "cl_cmd settemp cl_movecliptokeyboard 2\n");
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, CheckRules_World)
-{
-       if(g_race_qualifying == 2 && checkrules_timelimit >= 0)
-       {
-               ret_float = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
-               return true;
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
-{
-       if(g_race_qualifying == 2)
-               warmup_stage = 0;
-       return false;
-}
-
-void race_Initialize()
-{
-       race_ScoreRules();
-       if(g_race_qualifying == 2)
-               warmup_stage = 0;
-}
-
-void rc_SetLimits()
-{
-       int fraglimit_override, leadlimit_override;
-       float timelimit_override, qualifying_override;
-
-       if(autocvar_g_race_teams)
-       {
-               ActivateTeamplay();
-               race_teams = bound(2, autocvar_g_race_teams, 4);
-               have_team_spawns = -1; // request team spawns
-       }
-       else
-               race_teams = 0;
-
-       qualifying_override = autocvar_g_race_qualifying_timelimit_override;
-       fraglimit_override = autocvar_g_race_laps_limit;
-       leadlimit_override = 0; // currently not supported by race
-       timelimit_override = -1; // use default if we don't set it below
-
-       // we need to find out the correct value for g_race_qualifying
-       float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0;
-
-       if(autocvar_g_campaign)
-       {
-               g_race_qualifying = 1;
-               independent_players = 1;
-       }
-       else if(!autocvar_g_campaign && want_qualifying)
-       {
-               g_race_qualifying = 2;
-               independent_players = 1;
-               race_fraglimit = (race_fraglimit >= 0) ? fraglimit_override : autocvar_fraglimit;
-               race_leadlimit = (race_leadlimit >= 0) ? leadlimit_override : autocvar_leadlimit;
-               race_timelimit = (race_timelimit >= 0) ? timelimit_override : autocvar_timelimit;
-               fraglimit_override = 0;
-               leadlimit_override = 0;
-               timelimit_override = autocvar_g_race_qualifying_timelimit;
-       }
-       else
-               g_race_qualifying = 0;
-
-       SetLimits(fraglimit_override, leadlimit_override, timelimit_override, qualifying_override);
-}
-
-REGISTER_MUTATOR(rc, g_race)
-{
-       rc_SetLimits();
-
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This is a game type and it cannot be added at runtime.");
-               race_Initialize();
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // we actually cannot roll back race_Initialize here
-               // BUT: we don't need to! If this gets called, adding always
-               // succeeds.
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This is a game type and it cannot be removed at runtime.");
-               return -1;
-       }
-
-       return 0;
-}
diff --git a/qcsrc/server/mutators/gamemode_race.qh b/qcsrc/server/mutators/gamemode_race.qh
deleted file mode 100644 (file)
index 0e20b9b..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-#ifndef GAMEMODE_RACE_H
-#define GAMEMODE_RACE_H
-
-float g_race_qualifying;
-float race_teams;
-
-// scores
-const float ST_RACE_LAPS = 1;
-const float SP_RACE_LAPS = 4;
-const float SP_RACE_TIME = 5;
-const float SP_RACE_FASTEST = 6;
-#endif
diff --git a/qcsrc/server/mutators/gamemode_tdm.qc b/qcsrc/server/mutators/gamemode_tdm.qc
deleted file mode 100644 (file)
index 20270b5..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-#include "gamemode.qh"
-
-bool autocvar_g_tdm_team_spawns;
-int autocvar_g_tdm_point_limit;
-int autocvar_g_tdm_point_leadlimit;
-int autocvar_g_tdm_teams;
-int autocvar_g_tdm_teams_override;
-
-/*QUAKED spawnfunc_tdm_team (0 .5 .8) (-16 -16 -24) (16 16 32)
-Team declaration for TDM gameplay, this allows you to decide what team names and control point models are used in your map.
-Note: If you use spawnfunc_tdm_team entities you must define at least 2!  However, unlike domination, you don't need to make a blank one too.
-Keys:
-"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
-"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
-spawnfunc(tdm_team)
-{
-       if(!g_tdm || !self.cnt) { remove(self); return; }
-
-       self.classname = "tdm_team";
-       self.team = self.cnt + 1;
-}
-
-// code from here on is just to support maps that don't have team entities
-void tdm_SpawnTeam (string teamname, float teamcolor)
-{
-       entity this = new(tdm_team);
-       this.netname = teamname;
-       this.cnt = teamcolor;
-       this.spawnfunc_checked = true;
-       WITH(entity, self, this, spawnfunc_tdm_team(this));
-}
-
-void tdm_DelayedInit()
-{
-       // if no teams are found, spawn defaults
-       if(find(world, classname, "tdm_team") == world)
-       {
-               LOG_INFO("No ""tdm_team"" entities found on this map, creating them anyway.\n");
-
-               int numteams = min(4, autocvar_g_tdm_teams_override);
-
-               if(numteams < 2) { numteams = autocvar_g_tdm_teams; }
-               numteams = bound(2, numteams, 4);
-
-               float i;
-               for(i = 1; i <= numteams; ++i)
-                       tdm_SpawnTeam(Team_ColorName(Team_NumberToTeam(i)), Team_NumberToTeam(i) - 1);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(tdm, GetTeamCount, CBC_ORDER_EXCLUSIVE)
-{
-       ret_string = "tdm_team";
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining)
-{
-       // announce remaining frags
-       return true;
-}
-
-REGISTER_MUTATOR(tdm, g_tdm)
-{
-       ActivateTeamplay();
-       SetLimits(autocvar_g_tdm_point_limit, autocvar_g_tdm_point_leadlimit, -1, -1);
-       if(autocvar_g_tdm_team_spawns)
-               have_team_spawns = -1; // request team spawns
-
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This is a game type and it cannot be added at runtime.");
-               InitializeEntity(world, tdm_DelayedInit, INITPRIO_GAMETYPE);
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // we actually cannot roll back tdm_Initialize here
-               // BUT: we don't need to! If this gets called, adding always
-               // succeeds.
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This is a game type and it cannot be removed at runtime.");
-               return -1;
-       }
-
-       return 0;
-}
index 8dde34af3d8dc657f361ad85da1754821f0ee928..f883b84b4baa0a93cfb699f9e1b3a20396b6659b 100644 (file)
@@ -2,7 +2,6 @@
 #define MUTATOR_H
 
 #include "../../common/mutators/base.qh"
-#include "mutator_nades.qh"
 
 #include "../cl_client.qh"
 #include "../cl_player.qh"
diff --git a/qcsrc/server/mutators/mutator/gamemode_assault.qc b/qcsrc/server/mutators/mutator/gamemode_assault.qc
new file mode 100644 (file)
index 0000000..52fb40f
--- /dev/null
@@ -0,0 +1,671 @@
+#ifndef GAMEMODE_ASSAULT_H
+#define GAMEMODE_ASSAULT_H
+
+// sprites
+.entity assault_decreaser;
+.entity assault_sprite;
+
+// legacy bot defs
+const int HAVOCBOT_AST_ROLE_NONE = 0;
+const int HAVOCBOT_AST_ROLE_DEFENSE = 2;
+const int HAVOCBOT_AST_ROLE_OFFENSE = 4;
+
+.int havocbot_role_flags;
+.float havocbot_attack_time;
+
+.void() havocbot_role;
+.void() havocbot_previous_role;
+
+void() havocbot_role_ast_defense;
+void() havocbot_role_ast_offense;
+.entity havocbot_ast_target;
+
+void(entity bot) havocbot_ast_reset_role;
+
+void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
+void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
+
+// scoreboard stuff
+const float ST_ASSAULT_OBJECTIVES = 1;
+const float SP_ASSAULT_OBJECTIVES = 4;
+
+// predefined spawnfuncs
+void target_objective_decrease_activate();
+#endif
+
+#ifdef IMPLEMENTATION
+.entity sprite;
+
+// random functions
+void assault_objective_use()
+{SELFPARAM();
+       // activate objective
+       self.health = 100;
+       //print("^2Activated objective ", self.targetname, "=", etos(self), "\n");
+       //print("Activator is ", activator.classname, "\n");
+
+       for (entity e = world; (e = find(e, target, this.targetname)); )
+       {
+               if (e.classname == "target_objective_decrease")
+               {
+                       WITH(entity, self, e, target_objective_decrease_activate());
+               }
+       }
+
+       setself(this);
+}
+
+vector target_objective_spawn_evalfunc(entity player, entity spot, vector current)
+{SELFPARAM();
+       if(self.health < 0 || self.health >= ASSAULT_VALUE_INACTIVE)
+               return '-1 0 0';
+       return current;
+}
+
+// reset this objective. Used when spawning an objective
+// and when a new round starts
+void assault_objective_reset()
+{SELFPARAM();
+       self.health = ASSAULT_VALUE_INACTIVE;
+}
+
+// decrease the health of targeted objectives
+void assault_objective_decrease_use()
+{SELFPARAM();
+       if(activator.team != assault_attacker_team)
+       {
+               // wrong team triggered decrease
+               return;
+       }
+
+       if(other.assault_sprite)
+       {
+               WaypointSprite_Disown(other.assault_sprite, waypointsprite_deadlifetime);
+               if(other.classname == "func_assault_destructible")
+                       other.sprite = world;
+       }
+       else
+               return; // already activated! cannot activate again!
+
+       if(self.enemy.health < ASSAULT_VALUE_INACTIVE)
+       {
+               if(self.enemy.health - self.dmg > 0.5)
+               {
+                       PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.dmg);
+                       self.enemy.health = self.enemy.health - self.dmg;
+               }
+               else
+               {
+                       PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.enemy.health);
+                       PlayerTeamScore_Add(activator, SP_ASSAULT_OBJECTIVES, ST_ASSAULT_OBJECTIVES, 1);
+                       self.enemy.health = -1;
+
+                       entity oldactivator, head;
+
+                       setself(this.enemy);
+                       if(self.message)
+                       FOR_EACH_PLAYER(head)
+                               centerprint(head, self.message);
+
+                       oldactivator = activator;
+                       activator = this;
+                       SUB_UseTargets();
+                       activator = oldactivator;
+                       setself(this);
+               }
+       }
+}
+
+void assault_setenemytoobjective()
+{SELFPARAM();
+       entity objective;
+       for(objective = world; (objective = find(objective, targetname, self.target)); )
+       {
+               if(objective.classname == "target_objective")
+               {
+                       if(self.enemy == world)
+                               self.enemy = objective;
+                       else
+                               objerror("more than one objective as target - fix the map!");
+                       break;
+               }
+       }
+
+       if(self.enemy == world)
+               objerror("no objective as target - fix the map!");
+}
+
+float assault_decreaser_sprite_visible(entity e)
+{SELFPARAM();
+       entity decreaser;
+
+       decreaser = self.assault_decreaser;
+
+       if(decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE)
+               return false;
+
+       return true;
+}
+
+void target_objective_decrease_activate()
+{SELFPARAM();
+       entity ent, spr;
+       self.owner = world;
+       for(ent = world; (ent = find(ent, target, self.targetname)); )
+       {
+               if(ent.assault_sprite != world)
+               {
+                       WaypointSprite_Disown(ent.assault_sprite, waypointsprite_deadlifetime);
+                       if(ent.classname == "func_assault_destructible")
+                               ent.sprite = world;
+               }
+
+               spr = WaypointSprite_SpawnFixed(WP_Assault, 0.5 * (ent.absmin + ent.absmax), ent, assault_sprite, RADARICON_OBJECTIVE);
+               spr.assault_decreaser = self;
+               spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
+               spr.classname = "sprite_waypoint";
+               WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
+               if(ent.classname == "func_assault_destructible")
+               {
+                       WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
+                       WaypointSprite_UpdateMaxHealth(spr, ent.max_health);
+                       WaypointSprite_UpdateHealth(spr, ent.health);
+                       ent.sprite = spr;
+               }
+               else
+                       WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
+       }
+}
+
+void target_objective_decrease_findtarget()
+{
+       assault_setenemytoobjective();
+}
+
+void target_assault_roundend_reset()
+{SELFPARAM();
+       //print("round end reset\n");
+       self.cnt = self.cnt + 1; // up round counter
+       self.winning = 0; // up round
+}
+
+void target_assault_roundend_use()
+{SELFPARAM();
+       self.winning = 1; // round has been won by attackers
+}
+
+void assault_roundstart_use()
+{SELFPARAM();
+       activator = self;
+       SUB_UseTargets();
+
+       //(Re)spawn all turrets
+       for(entity ent = NULL; (ent = find(ent, classname, "turret_main")); ) {
+               // Swap turret teams
+               if(ent.team == NUM_TEAM_1)
+                       ent.team = NUM_TEAM_2;
+               else
+                       ent.team = NUM_TEAM_1;
+
+               // Dubbles as teamchange
+               WITH(entity, self, ent, turret_respawn());
+       }
+}
+
+void assault_wall_think()
+{SELFPARAM();
+       if(self.enemy.health < 0)
+       {
+               self.model = "";
+               self.solid = SOLID_NOT;
+       }
+       else
+       {
+               self.model = self.mdl;
+               self.solid = SOLID_BSP;
+       }
+
+       self.nextthink = time + 0.2;
+}
+
+// trigger new round
+// reset objectives, toggle spawnpoints, reset triggers, ...
+void vehicles_clearreturn(entity veh);
+void vehicles_spawn();
+void assault_new_round()
+{SELFPARAM();
+       //bprint("ASSAULT: new round\n");
+
+       // Eject players from vehicles
+       entity e;
+    FOR_EACH_PLAYER(e)
+    {
+        if(e.vehicle)
+        {
+               WITH(entity, self, e, vehicles_exit(VHEF_RELEASE));
+        }
+    }
+
+    for (entity e_ = findchainflags(vehicle_flags, VHF_ISVEHICLE); e_; e_ = e_.chain)
+    {
+       setself(e_);
+        vehicles_clearreturn(self);
+        vehicles_spawn();
+    }
+
+    setself(this);
+
+       // up round counter
+       self.winning = self.winning + 1;
+
+       // swap attacker/defender roles
+       if(assault_attacker_team == NUM_TEAM_1)
+               assault_attacker_team = NUM_TEAM_2;
+       else
+               assault_attacker_team = NUM_TEAM_1;
+
+       entity ent;
+       for(ent = world; (ent = nextent(ent)); )
+       {
+               if(clienttype(ent) == CLIENTTYPE_NOTACLIENT)
+               {
+                       if(ent.team_saved == NUM_TEAM_1)
+                               ent.team_saved = NUM_TEAM_2;
+                       else if(ent.team_saved == NUM_TEAM_2)
+                               ent.team_saved = NUM_TEAM_1;
+               }
+       }
+
+       // reset the level with a countdown
+       cvar_set("timelimit", ftos(ceil(time - game_starttime) / 60));
+       ReadyRestart_force(); // sets game_starttime
+}
+
+// spawnfuncs
+spawnfunc(info_player_attacker)
+{
+       if (!g_assault) { remove(self); return; }
+
+       self.team = NUM_TEAM_1; // red, gets swapped every round
+       spawnfunc_info_player_deathmatch(this);
+}
+
+spawnfunc(info_player_defender)
+{
+       if (!g_assault) { remove(self); return; }
+
+       self.team = NUM_TEAM_2; // blue, gets swapped every round
+       spawnfunc_info_player_deathmatch(this);
+}
+
+spawnfunc(target_objective)
+{
+       if (!g_assault) { remove(self); return; }
+
+       self.classname = "target_objective";
+       self.use = assault_objective_use;
+       assault_objective_reset();
+       self.reset = assault_objective_reset;
+       self.spawn_evalfunc = target_objective_spawn_evalfunc;
+}
+
+spawnfunc(target_objective_decrease)
+{
+       if (!g_assault) { remove(self); return; }
+
+       self.classname = "target_objective_decrease";
+
+       if(!self.dmg)
+               self.dmg = 101;
+
+       self.use = assault_objective_decrease_use;
+       self.health = ASSAULT_VALUE_INACTIVE;
+       self.max_health = ASSAULT_VALUE_INACTIVE;
+       self.enemy = world;
+
+       InitializeEntity(self, target_objective_decrease_findtarget, INITPRIO_FINDTARGET);
+}
+
+// destructible walls that can be used to trigger target_objective_decrease
+spawnfunc(func_breakable);
+spawnfunc(func_assault_destructible)
+{
+       if (!g_assault) { remove(self); return; }
+
+       self.spawnflags = 3;
+       self.classname = "func_assault_destructible";
+
+       if(assault_attacker_team == NUM_TEAM_1)
+               self.team = NUM_TEAM_2;
+       else
+               self.team = NUM_TEAM_1;
+
+       spawnfunc_func_breakable(this);
+}
+
+spawnfunc(func_assault_wall)
+{
+       if (!g_assault) { remove(self); return; }
+
+       self.classname = "func_assault_wall";
+       self.mdl = self.model;
+       _setmodel(self, self.mdl);
+       self.solid = SOLID_BSP;
+       self.think = assault_wall_think;
+       self.nextthink = time;
+       InitializeEntity(self, assault_setenemytoobjective, INITPRIO_FINDTARGET);
+}
+
+spawnfunc(target_assault_roundend)
+{
+       if (!g_assault) { remove(self); return; }
+
+       self.winning = 0; // round not yet won by attackers
+       self.classname = "target_assault_roundend";
+       self.use = target_assault_roundend_use;
+       self.cnt = 0; // first round
+       self.reset = target_assault_roundend_reset;
+}
+
+spawnfunc(target_assault_roundstart)
+{
+       if (!g_assault) { remove(self); return; }
+
+       assault_attacker_team = NUM_TEAM_1;
+       self.classname = "target_assault_roundstart";
+       self.use = assault_roundstart_use;
+       self.reset2 = assault_roundstart_use;
+       InitializeEntity(self, assault_roundstart_use, INITPRIO_FINDTARGET);
+}
+
+// legacy bot code
+void havocbot_goalrating_ast_targets(float ratingscale)
+{SELFPARAM();
+       entity ad, best, wp, tod;
+       float radius, found, bestvalue;
+       vector p;
+
+       ad = findchain(classname, "func_assault_destructible");
+
+       for (; ad; ad = ad.chain)
+       {
+               if (ad.target == "")
+                       continue;
+
+               if (!ad.bot_attack)
+                       continue;
+
+               found = false;
+               for(tod = world; (tod = find(tod, targetname, ad.target)); )
+               {
+                       if(tod.classname == "target_objective_decrease")
+                       {
+                               if(tod.enemy.health > 0 && tod.enemy.health < ASSAULT_VALUE_INACTIVE)
+                               {
+                               //      dprint(etos(ad),"\n");
+                                       found = true;
+                                       break;
+                               }
+                       }
+               }
+
+               if(!found)
+               {
+               ///     dprint("target not found\n");
+                       continue;
+               }
+               /// dprint("target #", etos(ad), " found\n");
+
+
+               p = 0.5 * (ad.absmin + ad.absmax);
+       //      dprint(vtos(ad.origin), " ", vtos(ad.absmin), " ", vtos(ad.absmax),"\n");
+       //      te_knightspike(p);
+       //      te_lightning2(world, '0 0 0', p);
+
+               // Find and rate waypoints around it
+               found = false;
+               best = world;
+               bestvalue = 99999999999;
+               for(radius=0; radius<1500 && !found; radius+=500)
+               {
+                       for(wp=findradius(p, radius); wp; wp=wp.chain)
+                       {
+                               if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
+                               if(wp.classname=="waypoint")
+                               if(checkpvs(wp.origin, ad))
+                               {
+                                       found = true;
+                                       if(wp.cnt<bestvalue)
+                                       {
+                                               best = wp;
+                                               bestvalue = wp.cnt;
+                                       }
+                               }
+                       }
+               }
+
+               if(best)
+               {
+               ///     dprint("waypoints around target were found\n");
+               //      te_lightning2(world, '0 0 0', best.origin);
+               //      te_knightspike(best.origin);
+
+                       navigation_routerating(best, ratingscale, 4000);
+                       best.cnt += 1;
+
+                       self.havocbot_attack_time = 0;
+
+                       if(checkpvs(self.view_ofs,ad))
+                       if(checkpvs(self.view_ofs,best))
+                       {
+                       //      dprint("increasing attack time for this target\n");
+                               self.havocbot_attack_time = time + 2;
+                       }
+               }
+       }
+}
+
+void havocbot_role_ast_offense()
+{SELFPARAM();
+       if(self.deadflag != DEAD_NO)
+       {
+               self.havocbot_attack_time = 0;
+               havocbot_ast_reset_role(self);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 120;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ast_reset_role(self);
+               return;
+       }
+
+       if(self.havocbot_attack_time>time)
+               return;
+
+       if (self.bot_strategytime < time)
+       {
+               navigation_goalrating_start();
+               havocbot_goalrating_enemyplayers(20000, self.origin, 650);
+               havocbot_goalrating_ast_targets(20000);
+               havocbot_goalrating_items(15000, self.origin, 10000);
+               navigation_goalrating_end();
+
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+       }
+}
+
+void havocbot_role_ast_defense()
+{SELFPARAM();
+       if(self.deadflag != DEAD_NO)
+       {
+               self.havocbot_attack_time = 0;
+               havocbot_ast_reset_role(self);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 120;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ast_reset_role(self);
+               return;
+       }
+
+       if(self.havocbot_attack_time>time)
+               return;
+
+       if (self.bot_strategytime < time)
+       {
+               navigation_goalrating_start();
+               havocbot_goalrating_enemyplayers(20000, self.origin, 3000);
+               havocbot_goalrating_ast_targets(20000);
+               havocbot_goalrating_items(15000, self.origin, 10000);
+               navigation_goalrating_end();
+
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+       }
+}
+
+void havocbot_role_ast_setrole(entity bot, float role)
+{
+       switch(role)
+       {
+               case HAVOCBOT_AST_ROLE_DEFENSE:
+                       bot.havocbot_role = havocbot_role_ast_defense;
+                       bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_AST_ROLE_OFFENSE:
+                       bot.havocbot_role = havocbot_role_ast_offense;
+                       bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+       }
+}
+
+void havocbot_ast_reset_role(entity bot)
+{SELFPARAM();
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       if(bot.team == assault_attacker_team)
+               havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_OFFENSE);
+       else
+               havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_DEFENSE);
+}
+
+// mutator hooks
+MUTATOR_HOOKFUNCTION(as, PlayerSpawn)
+{SELFPARAM();
+       if(self.team == assault_attacker_team)
+               Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
+       else
+               Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(as, TurretSpawn)
+{SELFPARAM();
+       if(!self.team || self.team == MAX_SHOT_DISTANCE)
+               self.team = 5; // this gets reversed when match starts?
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(as, VehicleSpawn)
+{SELFPARAM();
+       self.nextthink = time + 0.5;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole)
+{SELFPARAM();
+       havocbot_ast_reset_role(self);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, PlayHitsound)
+{
+       return (frag_victim.classname == "func_assault_destructible");
+}
+
+MUTATOR_HOOKFUNCTION(as, GetTeamCount)
+{
+       // assault always has 2 teams
+       c1 = c2 = 0;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, CheckRules_World)
+{
+       ret_float = WinningCondition_Assault();
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, ReadLevelCvars)
+{
+       // no assault warmups
+       warmup_stage = 0;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn)
+{
+       switch(self.classname)
+       {
+               case "info_player_team1":
+               case "info_player_team2":
+               case "info_player_team3":
+               case "info_player_team4":
+                       return true;
+       }
+
+       return false;
+}
+
+// scoreboard setup
+void assault_ScoreRules()
+{
+       ScoreRules_basics(2, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, true);
+       ScoreInfo_SetLabel_TeamScore(  ST_ASSAULT_OBJECTIVES,    "objectives",      SFL_SORT_PRIO_PRIMARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_ASSAULT_OBJECTIVES,    "objectives",      SFL_SORT_PRIO_PRIMARY);
+       ScoreRules_basics_end();
+}
+
+REGISTER_MUTATOR(as, g_assault)
+{
+       ActivateTeamplay();
+       have_team_spawns = -1; // request team spawns
+
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+               assault_ScoreRules();
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back assault_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/gamemode_ca.qc b/qcsrc/server/mutators/mutator/gamemode_ca.qc
new file mode 100644 (file)
index 0000000..20330b7
--- /dev/null
@@ -0,0 +1,529 @@
+#ifndef GAMEMODE_CA_H
+#define GAMEMODE_CA_H
+
+// should be removed in the future, as other code should not have to care
+.float caplayer; // 0.5 if scheduled to join the next round
+#endif
+
+#ifdef IMPLEMENTATION
+float autocvar_g_ca_damage2score_multiplier;
+int autocvar_g_ca_point_leadlimit;
+int autocvar_g_ca_point_limit;
+float autocvar_g_ca_round_timelimit;
+bool autocvar_g_ca_spectate_enemies;
+int autocvar_g_ca_teams;
+int autocvar_g_ca_teams_override;
+bool autocvar_g_ca_team_spawns;
+float autocvar_g_ca_warmup;
+
+float ca_teams;
+float allowed_to_spawn;
+
+const float ST_CA_ROUNDS = 1;
+void ca_ScoreRules(float teams)
+{
+       ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
+       ScoreInfo_SetLabel_TeamScore(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY);
+       ScoreRules_basics_end();
+}
+
+void CA_count_alive_players()
+{
+       entity e;
+       total_players = redalive = bluealive = yellowalive = pinkalive = 0;
+       FOR_EACH_PLAYER(e) {
+               if(e.team == NUM_TEAM_1)
+               {
+                       ++total_players;
+                       if (e.health >= 1) ++redalive;
+               }
+               else if(e.team == NUM_TEAM_2)
+               {
+                       ++total_players;
+                       if (e.health >= 1) ++bluealive;
+               }
+               else if(e.team == NUM_TEAM_3)
+               {
+                       ++total_players;
+                       if (e.health >= 1) ++yellowalive;
+               }
+               else if(e.team == NUM_TEAM_4)
+               {
+                       ++total_players;
+                       if (e.health >= 1) ++pinkalive;
+               }
+       }
+       FOR_EACH_REALCLIENT(e) {
+               e.redalive_stat = redalive;
+               e.bluealive_stat = bluealive;
+               e.yellowalive_stat = yellowalive;
+               e.pinkalive_stat = pinkalive;
+       }
+}
+
+float CA_GetWinnerTeam()
+{
+       float winner_team = 0;
+       if(redalive >= 1)
+               winner_team = NUM_TEAM_1;
+       if(bluealive >= 1)
+       {
+               if(winner_team) return 0;
+               winner_team = NUM_TEAM_2;
+       }
+       if(yellowalive >= 1)
+       {
+               if(winner_team) return 0;
+               winner_team = NUM_TEAM_3;
+       }
+       if(pinkalive >= 1)
+       {
+               if(winner_team) return 0;
+               winner_team = NUM_TEAM_4;
+       }
+       if(winner_team)
+               return winner_team;
+       return -1; // no player left
+}
+
+#define CA_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
+#define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == ca_teams)
+float CA_CheckWinner()
+{
+       entity e;
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
+               allowed_to_spawn = false;
+               round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+               FOR_EACH_PLAYER(e)
+                       nades_Clear(e);
+               return 1;
+       }
+
+       CA_count_alive_players();
+       if(CA_ALIVE_TEAMS() > 1)
+               return 0;
+
+       float winner_team = CA_GetWinnerTeam();
+       if(winner_team > 0)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
+               TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1);
+       }
+       else if(winner_team == -1)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
+       }
+
+       allowed_to_spawn = false;
+       round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+
+       FOR_EACH_PLAYER(e)
+               nades_Clear(e);
+
+       return 1;
+}
+
+void CA_RoundStart()
+{
+       if(warmup_stage)
+               allowed_to_spawn = true;
+       else
+               allowed_to_spawn = false;
+}
+
+float CA_CheckTeams()
+{
+       static float prev_missing_teams_mask;
+       allowed_to_spawn = true;
+       CA_count_alive_players();
+       if(CA_ALIVE_TEAMS_OK())
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               return 1;
+       }
+       if(total_players == 0)
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               return 0;
+       }
+       float missing_teams_mask = (!redalive) + (!bluealive) * 2;
+       if(ca_teams >= 3) missing_teams_mask += (!yellowalive) * 4;
+       if(ca_teams >= 4) missing_teams_mask += (!pinkalive) * 8;
+       if(prev_missing_teams_mask != missing_teams_mask)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
+               prev_missing_teams_mask = missing_teams_mask;
+       }
+       return 0;
+}
+
+float ca_isEliminated(entity e)
+{
+       if(e.caplayer == 1 && (e.deadflag != DEAD_NO || e.frags == FRAGS_LMS_LOSER))
+               return true;
+       if(e.caplayer == 0.5)
+               return true;
+       return false;
+}
+
+// Returns next available player to spectate if g_ca_spectate_enemies == 0
+entity CA_SpectateNext(entity player, entity start)
+{
+       if(SAME_TEAM(start, player))
+               return start;
+
+       entity spec_other = start;
+       // continue from current player
+       while(spec_other && DIFF_TEAM(spec_other, player))
+               spec_other = find(spec_other, classname, "player");
+
+       if (!spec_other)
+       {
+               // restart from begining
+               spec_other = find(spec_other, classname, "player");
+               while(spec_other && DIFF_TEAM(spec_other, player))
+                       spec_other = find(spec_other, classname, "player");
+       }
+
+       return spec_other;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerSpawn)
+{SELFPARAM();
+       self.caplayer = 1;
+       if(!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
+{SELFPARAM();
+       if(!allowed_to_spawn)
+       if(IS_PLAYER(self)) // this is true even when player is trying to join
+       {
+               self.classname = "observer";
+               if(self.jointime != time) //not when connecting
+               if(!self.caplayer)
+               {
+                       self.caplayer = 0.5;
+                       if(IS_REAL_CLIENT(self))
+                               Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CA_JOIN_LATE);
+               }
+       }
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, reset_map_players)
+{SELFPARAM();
+       entity e;
+       FOR_EACH_CLIENT(e)
+       {
+               setself(e);
+               self.killcount = 0;
+               if(!self.caplayer && IS_BOT_CLIENT(self))
+               {
+                       self.team = -1;
+                       self.caplayer = 1;
+               }
+               if(self.caplayer)
+               {
+                       self.classname = "player";
+                       self.caplayer = 1;
+                       PutClientInServer();
+               }
+       }
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientConnect)
+{SELFPARAM();
+       self.classname = "observer";
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, reset_map_global)
+{
+       allowed_to_spawn = true;
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, GetTeamCount, CBC_ORDER_EXCLUSIVE)
+{
+       ret_float = ca_teams;
+       return false;
+}
+
+entity ca_LastPlayerForTeam()
+{SELFPARAM();
+       entity pl, last_pl = world;
+       FOR_EACH_PLAYER(pl)
+       {
+               if(pl.health >= 1)
+               if(pl != self)
+               if(pl.team == self.team)
+               if(!last_pl)
+                       last_pl = pl;
+               else
+                       return world;
+       }
+       return last_pl;
+}
+
+void ca_LastPlayerForTeam_Notify()
+{
+       if(round_handler_IsActive())
+       if(round_handler_IsRoundStarted())
+       {
+               entity pl = ca_LastPlayerForTeam();
+               if(pl)
+                       Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerDies)
+{SELFPARAM();
+       ca_LastPlayerForTeam_Notify();
+       if(!allowed_to_spawn)
+               self.respawn_flags =  RESPAWN_SILENT;
+       if(!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
+{SELFPARAM();
+       if(self.caplayer == 1)
+               ca_LastPlayerForTeam_Notify();
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ForbidPlayerScore_Clear)
+{
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
+{SELFPARAM();
+       if(self.caplayer == 1)
+               ca_LastPlayerForTeam_Notify();
+       if(self.killindicator_teamchange == -2)
+               self.caplayer = 0;
+       if(self.caplayer)
+               self.frags = FRAGS_LMS_LOSER;
+       if(!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon)
+{
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+       frag_score = 0; // score will be given to the winner team when the round ends
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SetStartItems)
+{
+       start_items &= ~IT_UNLIMITED_AMMO;
+       start_health       = warmup_start_health       = cvar("g_lms_start_health");
+       start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
+       start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
+       start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
+       start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
+       start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
+       start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
+       start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
+
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerDamage_Calculate)
+{
+       if(IS_PLAYER(frag_target))
+       if(frag_target.deadflag == DEAD_NO)
+       if(frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id)
+               frag_damage = 0;
+
+       frag_mirrordamage = 0;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ca, FilterItem)
+{SELFPARAM();
+       if(autocvar_g_powerups <= 0)
+       if(self.flags & FL_POWERUP)
+               return true;
+
+       if(autocvar_g_pickup_items <= 0)
+               return true;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor)
+{
+       float excess = max(0, frag_damage - damage_take - damage_save);
+
+       if(frag_target != frag_attacker && IS_PLAYER(frag_attacker))
+               PlayerTeamScore_Add(frag_attacker, SP_SCORE, ST_SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier);
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerRegen)
+{
+       // no regeneration in CA
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
+{
+       // announce remaining frags
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectateSet)
+{
+       if(!autocvar_g_ca_spectate_enemies && self.caplayer)
+       if(DIFF_TEAM(spec_player, self))
+               return true;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectateNext)
+{SELFPARAM();
+       if(!autocvar_g_ca_spectate_enemies && self.caplayer)
+       {
+               spec_player = CA_SpectateNext(self, spec_player);
+               return true;
+       }
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectatePrev)
+{SELFPARAM();
+       if(!autocvar_g_ca_spectate_enemies && self.caplayer)
+       {
+               do { spec_player = spec_player.chain; }
+               while(spec_player && DIFF_TEAM(spec_player, self));
+
+               if (!spec_player)
+               {
+                       spec_player = spec_first;
+                       while(spec_player && DIFF_TEAM(spec_player, self))
+                               spec_player = spec_player.chain;
+                       if(spec_player == self.enemy)
+                               return MUT_SPECPREV_RETURN;
+               }
+       }
+
+       return MUT_SPECPREV_FOUND;
+}
+
+MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
+{
+       entity head;
+       FOR_EACH_REALCLIENT(head)
+       {
+               if(IS_PLAYER(head) || head.caplayer == 1)
+                       ++bot_activerealplayers;
+               ++bot_realplayers;
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate)
+{
+       if(self.caplayer)
+       {
+               // they're going to spec, we can do other checks
+               if(autocvar_sv_spectate && (IS_SPEC(self) || IS_OBSERVER(self)))
+                       Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CA_LEAVE);
+               return MUT_SPECCMD_FORCE;
+       }
+
+       return MUT_SPECCMD_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(ca, WantWeapon)
+{
+       want_allguns = true;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus)
+{
+       if(set_player.caplayer == 1)
+               return true;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SetWeaponArena)
+{
+       // most weapons arena
+       if(ret_string == "0" || ret_string == "")
+               ret_string = "most";
+       return false;
+}
+
+void ca_Initialize()
+{
+       allowed_to_spawn = true;
+
+       ca_teams = autocvar_g_ca_teams_override;
+       if(ca_teams < 2)
+               ca_teams = autocvar_g_ca_teams;
+       ca_teams = bound(2, ca_teams, 4);
+       ret_float = ca_teams;
+       ca_ScoreRules(ca_teams);
+
+       round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart);
+       round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+
+       addstat(STAT_REDALIVE, AS_INT, redalive_stat);
+       addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
+       addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
+       addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
+
+       EliminatedPlayers_Init(ca_isEliminated);
+}
+
+REGISTER_MUTATOR(ca, g_ca)
+{
+       ActivateTeamplay();
+       SetLimits(autocvar_g_ca_point_limit, autocvar_g_ca_point_leadlimit, -1, -1);
+
+       if(autocvar_g_ca_team_spawns)
+               have_team_spawns = -1; // request team spawns
+
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+               ca_Initialize();
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/gamemode_ctf.qc b/qcsrc/server/mutators/mutator/gamemode_ctf.qc
new file mode 100644 (file)
index 0000000..e2eb898
--- /dev/null
@@ -0,0 +1,2772 @@
+#ifndef GAMEMODE_CTF_H
+#define GAMEMODE_CTF_H
+
+#ifdef SVQC
+// used in cheats.qc
+void ctf_RespawnFlag(entity flag);
+
+// score rule declarations
+const int ST_CTF_CAPS = 1;
+const int SP_CTF_CAPS = 4;
+const int SP_CTF_CAPTIME = 5;
+const int SP_CTF_PICKUPS = 6;
+const int SP_CTF_DROPS = 7;
+const int SP_CTF_FCKILLS = 8;
+const int SP_CTF_RETURNS = 9;
+
+// flag constants // for most of these, there is just one question to be asked: WHYYYYY?
+#define FLAG_MIN (PL_MIN_CONST + '0 0 -13')
+#define FLAG_MAX (PL_MAX_CONST + '0 0 -13')
+
+const float FLAG_SCALE = 0.6;
+
+const float FLAG_THINKRATE = 0.2;
+const float FLAG_TOUCHRATE = 0.5;
+const float WPFE_THINKRATE = 0.5;
+
+const vector FLAG_DROP_OFFSET = ('0 0 32');
+const vector FLAG_CARRY_OFFSET = ('-16 0 8');
+#define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13))
+const vector FLAG_WAYPOINT_OFFSET = ('0 0 64');
+const vector FLAG_FLOAT_OFFSET = ('0 0 32');
+const vector FLAG_PASS_ARC_OFFSET = ('0 0 -10');
+
+const vector VEHICLE_FLAG_OFFSET = ('0 0 96');
+const float VEHICLE_FLAG_SCALE = 1.0;
+
+// waypoint colors
+#define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1')
+#define WPCOLOR_FLAGCARRIER(t) (WP_FlagCarrier.m_color)
+#define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, false)) * 0.5 : '1 1 1')
+
+// sounds
+#define snd_flag_taken noise
+#define snd_flag_returned noise1
+#define snd_flag_capture noise2
+#define snd_flag_respawn noise3
+.string snd_flag_dropped;
+.string snd_flag_touch;
+.string snd_flag_pass;
+
+// effects
+.string toucheffect;
+.string passeffect;
+.string capeffect;
+
+// list of flags on the map
+entity ctf_worldflaglist;
+.entity ctf_worldflagnext;
+.entity ctf_staleflagnext;
+
+// waypoint sprites
+.entity bot_basewaypoint; // flag waypointsprite
+.entity wps_helpme;
+.entity wps_flagbase;
+.entity wps_flagcarrier;
+.entity wps_flagdropped;
+.entity wps_enemyflagcarrier;
+.float wps_helpme_time;
+bool wpforenemy_announced;
+float wpforenemy_nextthink;
+
+// statuses
+const int FLAG_BASE = 1;
+const int FLAG_DROPPED = 2;
+const int FLAG_CARRY = 3;
+const int FLAG_PASSING = 4;
+
+const int DROP_NORMAL = 1;
+const int DROP_THROW = 2;
+const int DROP_PASS = 3;
+const int DROP_RESET = 4;
+
+const int PICKUP_BASE = 1;
+const int PICKUP_DROPPED = 2;
+
+const int CAPTURE_NORMAL = 1;
+const int CAPTURE_DROPPED = 2;
+
+const int RETURN_TIMEOUT = 1;
+const int RETURN_DROPPED = 2;
+const int RETURN_DAMAGE = 3;
+const int RETURN_SPEEDRUN = 4;
+const int RETURN_NEEDKILL = 5;
+
+// flag properties
+#define ctf_spawnorigin dropped_origin
+bool ctf_stalemate; // indicates that a stalemate is active
+float ctf_captimerecord; // record time for capturing the flag
+.float ctf_pickuptime;
+.float ctf_droptime;
+.int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
+.entity ctf_dropper; // don't allow spam of dropping the flag
+.int max_flag_health;
+.float next_take_time;
+.bool ctf_flagdamaged;
+int ctf_teams;
+
+// passing/throwing properties
+.float pass_distance;
+.entity pass_sender;
+.entity pass_target;
+.float throw_antispam;
+.float throw_prevtime;
+.int throw_count;
+
+// CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
+.bool ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
+float ctf_captureshield_min_negscore; // punish at -20 points
+float ctf_captureshield_max_ratio; // punish at most 30% of each team
+float ctf_captureshield_force; // push force of the shield
+
+// 1 flag ctf
+bool ctf_oneflag; // indicates whether or not a neutral flag has been found
+
+// bot player logic
+const int HAVOCBOT_CTF_ROLE_NONE = 0;
+const int HAVOCBOT_CTF_ROLE_DEFENSE = 2;
+const int HAVOCBOT_CTF_ROLE_MIDDLE = 4;
+const int HAVOCBOT_CTF_ROLE_OFFENSE = 8;
+const int HAVOCBOT_CTF_ROLE_CARRIER = 16;
+const int HAVOCBOT_CTF_ROLE_RETRIEVER = 32;
+const int HAVOCBOT_CTF_ROLE_ESCORT = 64;
+
+.bool havocbot_cantfindflag;
+
+vector havocbot_ctf_middlepoint;
+float havocbot_ctf_middlepoint_radius;
+
+void havocbot_role_ctf_setrole(entity bot, int role);
+
+// team checking
+#define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b))
+#define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b))
+
+// networked flag statuses
+.int ctf_flagstatus;
+#endif
+
+const int CTF_RED_FLAG_TAKEN                   = 1;
+const int CTF_RED_FLAG_LOST                            = 2;
+const int CTF_RED_FLAG_CARRYING                        = 3;
+const int CTF_BLUE_FLAG_TAKEN                  = 4;
+const int CTF_BLUE_FLAG_LOST                   = 8;
+const int CTF_BLUE_FLAG_CARRYING               = 12;
+const int CTF_YELLOW_FLAG_TAKEN                        = 16;
+const int CTF_YELLOW_FLAG_LOST                 = 32;
+const int CTF_YELLOW_FLAG_CARRYING             = 48;
+const int CTF_PINK_FLAG_TAKEN                  = 64;
+const int CTF_PINK_FLAG_LOST                   = 128;
+const int CTF_PINK_FLAG_CARRYING               = 192;
+const int CTF_NEUTRAL_FLAG_TAKEN               = 256;
+const int CTF_NEUTRAL_FLAG_LOST                        = 512;
+const int CTF_NEUTRAL_FLAG_CARRYING            = 768;
+const int CTF_FLAG_NEUTRAL                             = 2048;
+const int CTF_SHIELDED                                 = 4096;
+#endif
+
+#ifdef IMPLEMENTATION
+
+#ifdef SVQC
+#include "../../../common/vehicles/all.qh"
+#include "../../teamplay.qh"
+#endif
+
+#include "../../../lib/warpzone/common.qh"
+
+bool autocvar_g_ctf_allow_vehicle_carry;
+bool autocvar_g_ctf_allow_vehicle_touch;
+bool autocvar_g_ctf_allow_monster_touch;
+bool autocvar_g_ctf_throw;
+float autocvar_g_ctf_throw_angle_max;
+float autocvar_g_ctf_throw_angle_min;
+int autocvar_g_ctf_throw_punish_count;
+float autocvar_g_ctf_throw_punish_delay;
+float autocvar_g_ctf_throw_punish_time;
+float autocvar_g_ctf_throw_strengthmultiplier;
+float autocvar_g_ctf_throw_velocity_forward;
+float autocvar_g_ctf_throw_velocity_up;
+float autocvar_g_ctf_drop_velocity_up;
+float autocvar_g_ctf_drop_velocity_side;
+bool autocvar_g_ctf_oneflag_reverse;
+bool autocvar_g_ctf_portalteleport;
+bool autocvar_g_ctf_pass;
+float autocvar_g_ctf_pass_arc;
+float autocvar_g_ctf_pass_arc_max;
+float autocvar_g_ctf_pass_directional_max;
+float autocvar_g_ctf_pass_directional_min;
+float autocvar_g_ctf_pass_radius;
+float autocvar_g_ctf_pass_wait;
+bool autocvar_g_ctf_pass_request;
+float autocvar_g_ctf_pass_turnrate;
+float autocvar_g_ctf_pass_timelimit;
+float autocvar_g_ctf_pass_velocity;
+bool autocvar_g_ctf_dynamiclights;
+float autocvar_g_ctf_flag_collect_delay;
+float autocvar_g_ctf_flag_damageforcescale;
+bool autocvar_g_ctf_flag_dropped_waypoint;
+bool autocvar_g_ctf_flag_dropped_floatinwater;
+bool autocvar_g_ctf_flag_glowtrails;
+int autocvar_g_ctf_flag_health;
+bool autocvar_g_ctf_flag_return;
+float autocvar_g_ctf_flag_return_carried_radius;
+float autocvar_g_ctf_flag_return_time;
+bool autocvar_g_ctf_flag_return_when_unreachable;
+float autocvar_g_ctf_flag_return_damage;
+float autocvar_g_ctf_flag_return_damage_delay;
+float autocvar_g_ctf_flag_return_dropped;
+float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
+float autocvar_g_ctf_flagcarrier_auto_helpme_time;
+float autocvar_g_ctf_flagcarrier_selfdamagefactor;
+float autocvar_g_ctf_flagcarrier_selfforcefactor;
+float autocvar_g_ctf_flagcarrier_damagefactor;
+float autocvar_g_ctf_flagcarrier_forcefactor;
+//float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
+bool autocvar_g_ctf_fullbrightflags;
+bool autocvar_g_ctf_ignore_frags;
+int autocvar_g_ctf_score_capture;
+int autocvar_g_ctf_score_capture_assist;
+int autocvar_g_ctf_score_kill;
+int autocvar_g_ctf_score_penalty_drop;
+int autocvar_g_ctf_score_penalty_returned;
+int autocvar_g_ctf_score_pickup_base;
+int autocvar_g_ctf_score_pickup_dropped_early;
+int autocvar_g_ctf_score_pickup_dropped_late;
+int autocvar_g_ctf_score_return;
+float autocvar_g_ctf_shield_force;
+float autocvar_g_ctf_shield_max_ratio;
+int autocvar_g_ctf_shield_min_negscore;
+bool autocvar_g_ctf_stalemate;
+int autocvar_g_ctf_stalemate_endcondition;
+float autocvar_g_ctf_stalemate_time;
+bool autocvar_g_ctf_reverse;
+float autocvar_g_ctf_dropped_capture_delay;
+float autocvar_g_ctf_dropped_capture_radius;
+
+void ctf_FakeTimeLimit(entity e, float t)
+{
+       msg_entity = e;
+       WriteByte(MSG_ONE, 3); // svc_updatestat
+       WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
+       if(t < 0)
+               WriteCoord(MSG_ONE, autocvar_timelimit);
+       else
+               WriteCoord(MSG_ONE, (t + 1) / 60);
+}
+
+void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
+{
+       if(autocvar_sv_eventlog)
+               GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
+               //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void ctf_CaptureRecord(entity flag, entity player)
+{
+       float cap_record = ctf_captimerecord;
+       float cap_time = (time - flag.ctf_pickuptime);
+       string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
+
+       // notify about shit
+       if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
+       else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
+       else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
+       else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
+
+       // write that shit in the database
+       if(!ctf_oneflag) // but not in 1-flag mode
+       if((!ctf_captimerecord) || (cap_time < cap_record))
+       {
+               ctf_captimerecord = cap_time;
+               db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
+               db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
+               write_recordmarker(player, (time - cap_time), cap_time);
+       }
+}
+
+void ctf_FlagcarrierWaypoints(entity player)
+{
+       WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
+       WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
+       WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
+       WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
+}
+
+void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
+{
+       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 = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
+       float current_height = (initial_height * min(1, (current_distance / flag.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(flag.origin, targpos, MOVE_NOMONSTERS, flag);
+               if(trace_fraction < 1)
+               {
+                       //print("normal arc line failed, trying to find new pos...");
+                       WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
+                       targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
+                       WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
+                       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; }
+
+       //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
+
+       vector desired_direction = normalize(targpos - from);
+       if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
+       else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
+}
+
+bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
+{
+       if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
+       {
+               // directional tracing only
+               float spreadlimit;
+               makevectors(passer_angle);
+
+               // find the closest point on the enemy to the center of the attack
+               float h; // hypotenuse, which is the distance between attacker to head
+               float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
+
+               h = vlen(head_center - passer_center);
+               a = h * (normalize(head_center - passer_center) * v_forward);
+
+               vector nearest_on_line = (passer_center + a * v_forward);
+               float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
+
+               spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
+               spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
+
+               if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
+                       { return true; }
+               else
+                       { return false; }
+       }
+       else { return true; }
+}
+
+
+// =======================
+// CaptureShield Functions
+// =======================
+
+bool ctf_CaptureShield_CheckStatus(entity p)
+{
+       int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
+       entity e;
+       int players_worseeq, players_total;
+
+       if(ctf_captureshield_max_ratio <= 0)
+               return false;
+
+       s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
+       s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
+       s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
+       s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
+
+       sr = ((s - s2) + (s3 + s4));
+
+       if(sr >= -ctf_captureshield_min_negscore)
+               return false;
+
+       players_total = players_worseeq = 0;
+       FOR_EACH_PLAYER(e)
+       {
+               if(DIFF_TEAM(e, p))
+                       continue;
+               se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
+               se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
+               se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
+               se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
+
+               ser = ((se - se2) + (se3 + se4));
+
+               if(ser <= sr)
+                       ++players_worseeq;
+               ++players_total;
+       }
+
+       // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
+       // use this rule here
+
+       if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
+               return false;
+
+       return true;
+}
+
+void ctf_CaptureShield_Update(entity player, bool wanted_status)
+{
+       bool updated_status = ctf_CaptureShield_CheckStatus(player);
+       if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
+       {
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
+               player.ctf_captureshielded = updated_status;
+       }
+}
+
+bool ctf_CaptureShield_Customize()
+{SELFPARAM();
+       if(!other.ctf_captureshielded) { return false; }
+       if(CTF_SAMETEAM(self, other)) { return false; }
+
+       return true;
+}
+
+void ctf_CaptureShield_Touch()
+{SELFPARAM();
+       if(!other.ctf_captureshielded) { return; }
+       if(CTF_SAMETEAM(self, other)) { return; }
+
+       vector mymid = (self.absmin + self.absmax) * 0.5;
+       vector othermid = (other.absmin + other.absmax) * 0.5;
+
+       Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
+       if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
+}
+
+void ctf_CaptureShield_Spawn(entity flag)
+{SELFPARAM();
+       entity shield = spawn();
+
+       shield.enemy = self;
+       shield.team = self.team;
+       shield.touch = ctf_CaptureShield_Touch;
+       shield.customizeentityforclient = ctf_CaptureShield_Customize;
+       shield.classname = "ctf_captureshield";
+       shield.effects = EF_ADDITIVE;
+       shield.movetype = MOVETYPE_NOCLIP;
+       shield.solid = SOLID_TRIGGER;
+       shield.avelocity = '7 0 11';
+       shield.scale = 0.5;
+
+       setorigin(shield, self.origin);
+       setmodel(shield, MDL_CTF_SHIELD);
+       setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
+}
+
+
+// ====================
+// Drop/Pass/Throw Code
+// ====================
+
+void ctf_Handle_Drop(entity flag, entity player, int droptype)
+{
+       // declarations
+       player = (player ? player : flag.pass_sender);
+
+       // main
+       flag.movetype = MOVETYPE_TOSS;
+       flag.takedamage = DAMAGE_YES;
+       flag.angles = '0 0 0';
+       flag.health = flag.max_flag_health;
+       flag.ctf_droptime = time;
+       flag.ctf_dropper = player;
+       flag.ctf_status = FLAG_DROPPED;
+
+       // messages and sounds
+       Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
+       _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
+       ctf_EventLog("dropped", player.team, player);
+
+       // scoring
+       PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
+       PlayerScore_Add(player, SP_CTF_DROPS, 1);
+
+       // waypoints
+       if(autocvar_g_ctf_flag_dropped_waypoint) {
+               entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG);
+               wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
+       }
+
+       if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
+       {
+               WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
+               WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
+       }
+
+       player.throw_antispam = time + autocvar_g_ctf_pass_wait;
+
+       if(droptype == DROP_PASS)
+       {
+               flag.pass_distance = 0;
+               flag.pass_sender = world;
+               flag.pass_target = world;
+       }
+}
+
+void ctf_Handle_Retrieve(entity flag, entity player)
+{
+       entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
+       entity sender = flag.pass_sender;
+
+       // transfer flag to player
+       flag.owner = player;
+       flag.owner.flagcarried = flag;
+
+       // reset flag
+       if(player.vehicle)
+       {
+               setattachment(flag, player.vehicle, "");
+               setorigin(flag, VEHICLE_FLAG_OFFSET);
+               flag.scale = VEHICLE_FLAG_SCALE;
+       }
+       else
+       {
+               setattachment(flag, player, "");
+               setorigin(flag, FLAG_CARRY_OFFSET);
+       }
+       flag.movetype = MOVETYPE_NONE;
+       flag.takedamage = DAMAGE_NO;
+       flag.solid = SOLID_NOT;
+       flag.angles = '0 0 0';
+       flag.ctf_status = FLAG_CARRY;
+
+       // messages and sounds
+       _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
+       ctf_EventLog("receive", flag.team, player);
+
+       FOR_EACH_REALPLAYER(tmp_player)
+       {
+               if(tmp_player == sender)
+                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname);
+               else if(tmp_player == player)
+                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname);
+               else if(SAME_TEAM(tmp_player, sender))
+                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname);
+       }
+
+       // create new waypoint
+       ctf_FlagcarrierWaypoints(player);
+
+       sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
+       player.throw_antispam = sender.throw_antispam;
+
+       flag.pass_distance = 0;
+       flag.pass_sender = world;
+       flag.pass_target = world;
+}
+
+void ctf_Handle_Throw(entity player, entity receiver, int droptype)
+{
+       entity flag = player.flagcarried;
+       vector targ_origin, flag_velocity;
+
+       if(!flag) { return; }
+       if((droptype == DROP_PASS) && !receiver) { return; }
+
+       if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
+
+       // reset the flag
+       setattachment(flag, world, "");
+       setorigin(flag, player.origin + FLAG_DROP_OFFSET);
+       flag.owner.flagcarried = world;
+       flag.owner = world;
+       flag.solid = SOLID_TRIGGER;
+       flag.ctf_dropper = player;
+       flag.ctf_droptime = time;
+
+       flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
+
+       switch(droptype)
+       {
+               case DROP_PASS:
+               {
+                       // warpzone support:
+                       // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
+                       // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
+                       WarpZone_RefSys_Copy(flag, receiver);
+                       WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
+                       targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
+
+                       flag.pass_distance = vlen((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' *  player.origin.x) + ('0 1 0' *  player.origin.y))); // for the sake of this check, exclude Z axis
+                       ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
+
+                       // main
+                       flag.movetype = MOVETYPE_FLY;
+                       flag.takedamage = DAMAGE_NO;
+                       flag.pass_sender = player;
+                       flag.pass_target = receiver;
+                       flag.ctf_status = FLAG_PASSING;
+
+                       // other
+                       _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
+                       WarpZone_TrailParticles(world, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
+                       ctf_EventLog("pass", flag.team, player);
+                       break;
+               }
+
+               case DROP_THROW:
+               {
+                       makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
+
+                       flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
+                       flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
+                       ctf_Handle_Drop(flag, player, droptype);
+                       break;
+               }
+
+               case DROP_RESET:
+               {
+                       flag.velocity = '0 0 0'; // do nothing
+                       break;
+               }
+
+               default:
+               case DROP_NORMAL:
+               {
+                       flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
+                       ctf_Handle_Drop(flag, player, droptype);
+                       break;
+               }
+       }
+
+       // kill old waypointsprite
+       WaypointSprite_Ping(player.wps_flagcarrier);
+       WaypointSprite_Kill(player.wps_flagcarrier);
+
+       if(player.wps_enemyflagcarrier)
+               WaypointSprite_Kill(player.wps_enemyflagcarrier);
+
+       // captureshield
+       ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
+}
+
+
+// ==============
+// Event Handlers
+// ==============
+
+void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
+{
+       entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
+       entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
+       entity player_team_flag = world, tmp_entity;
+       float old_time, new_time;
+
+       if(!player) { return; } // without someone to give the reward to, we can't possibly cap
+       if(CTF_DIFFTEAM(player, flag)) { return; }
+
+       if(ctf_oneflag)
+       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+       if(SAME_TEAM(tmp_entity, player))
+       {
+               player_team_flag = tmp_entity;
+               break;
+       }
+
+       nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
+
+       player.throw_prevtime = time;
+       player.throw_count = 0;
+
+       // messages and sounds
+       Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
+       ctf_CaptureRecord(enemy_flag, player);
+       _sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
+
+       switch(capturetype)
+       {
+               case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
+               case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
+               default: break;
+       }
+
+       // scoring
+       PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
+       PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
+
+       old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
+       new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
+       if(!old_time || new_time < old_time)
+               PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
+
+       // effects
+       Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
+       //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
+
+       // other
+       if(capturetype == CAPTURE_NORMAL)
+       {
+               WaypointSprite_Kill(player.wps_flagcarrier);
+               if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
+
+               if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
+                       { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
+       }
+
+       // reset the flag
+       player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
+       ctf_RespawnFlag(enemy_flag);
+}
+
+void ctf_Handle_Return(entity flag, entity player)
+{
+       // messages and sounds
+       if(IS_MONSTER(player))
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
+       }
+       else if(flag.team)
+       {
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
+       }
+       _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
+       ctf_EventLog("return", flag.team, player);
+
+       // scoring
+       if(IS_PLAYER(player))
+       {
+               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
+
+       if(flag.ctf_dropper)
+       {
+               PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
+               ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
+               flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
+       }
+
+       // other
+       if(player.flagcarried == flag)
+               WaypointSprite_Kill(player.wps_flagcarrier);
+
+       // reset the flag
+       ctf_RespawnFlag(flag);
+}
+
+void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
+{
+       // declarations
+       float pickup_dropped_score; // used to calculate dropped pickup score
+       entity tmp_entity; // temporary entity
+
+       // attach the flag to the player
+       flag.owner = player;
+       player.flagcarried = flag;
+       if(player.vehicle)
+       {
+               setattachment(flag, player.vehicle, "");
+               setorigin(flag, VEHICLE_FLAG_OFFSET);
+               flag.scale = VEHICLE_FLAG_SCALE;
+       }
+       else
+       {
+               setattachment(flag, player, "");
+               setorigin(flag, FLAG_CARRY_OFFSET);
+       }
+
+       // flag setup
+       flag.movetype = MOVETYPE_NONE;
+       flag.takedamage = DAMAGE_NO;
+       flag.solid = SOLID_NOT;
+       flag.angles = '0 0 0';
+       flag.ctf_status = FLAG_CARRY;
+
+       switch(pickuptype)
+       {
+               case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
+               case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
+               default: break;
+       }
+
+       // messages and sounds
+       Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
+       if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
+       if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
+       else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
+       else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team)); }
+
+       Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname);
+
+       if(!flag.team)
+       FOR_EACH_PLAYER(tmp_entity)
+       if(tmp_entity != player)
+       if(DIFF_TEAM(player, tmp_entity))
+               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
+
+       if(flag.team)
+       FOR_EACH_PLAYER(tmp_entity)
+       if(tmp_entity != player)
+       if(CTF_SAMETEAM(flag, tmp_entity))
+       if(SAME_TEAM(player, tmp_entity))
+               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
+       else
+               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
+
+       _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
+
+       // scoring
+       PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
+       nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
+       switch(pickuptype)
+       {
+               case PICKUP_BASE:
+               {
+                       PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
+                       ctf_EventLog("steal", flag.team, player);
+                       break;
+               }
+
+               case PICKUP_DROPPED:
+               {
+                       pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
+                       pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
+                       LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
+                       PlayerTeamScore_AddScore(player, pickup_dropped_score);
+                       ctf_EventLog("pickup", flag.team, player);
+                       break;
+               }
+
+               default: break;
+       }
+
+       // speedrunning
+       if(pickuptype == PICKUP_BASE)
+       {
+               flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
+               if((player.speedrunning) && (ctf_captimerecord))
+                       ctf_FakeTimeLimit(player, time + ctf_captimerecord);
+       }
+
+       // effects
+       Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
+
+       // waypoints
+       if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
+       ctf_FlagcarrierWaypoints(player);
+       WaypointSprite_Ping(player.wps_flagcarrier);
+}
+
+
+// ===================
+// Main Flag Functions
+// ===================
+
+void ctf_CheckFlagReturn(entity flag, int returntype)
+{
+       if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
+       {
+               if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
+
+               if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
+               {
+                       switch(returntype)
+                       {
+                               case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break;
+                               case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break;
+                               case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break;
+                               case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break;
+
+                               default:
+                               case RETURN_TIMEOUT:
+                                       { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
+                       }
+                       _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
+                       ctf_EventLog("returned", flag.team, world);
+                       ctf_RespawnFlag(flag);
+               }
+       }
+}
+
+bool ctf_Stalemate_Customize()
+{SELFPARAM();
+       // make spectators see what the player would see
+       entity e, wp_owner;
+       e = WaypointSprite_getviewentity(other);
+       wp_owner = self.owner;
+
+       // team waypoints
+       if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
+       if(SAME_TEAM(wp_owner, e)) { return false; }
+       if(!IS_PLAYER(e)) { return false; }
+
+       return true;
+}
+
+void ctf_CheckStalemate(void)
+{
+       // declarations
+       int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
+       entity tmp_entity;
+
+       entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
+
+       // build list of stale flags
+       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+       {
+               if(autocvar_g_ctf_stalemate)
+               if(tmp_entity.ctf_status != FLAG_BASE)
+               if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
+               {
+                       tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
+                       ctf_staleflaglist = tmp_entity;
+
+                       switch(tmp_entity.team)
+                       {
+                               case NUM_TEAM_1: ++stale_red_flags; break;
+                               case NUM_TEAM_2: ++stale_blue_flags; break;
+                               case NUM_TEAM_3: ++stale_yellow_flags; break;
+                               case NUM_TEAM_4: ++stale_pink_flags; break;
+                               default: ++stale_neutral_flags; break;
+                       }
+               }
+       }
+
+       if(ctf_oneflag)
+               stale_flags = (stale_neutral_flags >= 1);
+       else
+               stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
+
+       if(ctf_oneflag && stale_flags == 1)
+               ctf_stalemate = true;
+       else if(stale_flags >= 2)
+               ctf_stalemate = true;
+       else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
+               { ctf_stalemate = false; wpforenemy_announced = false; }
+       else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
+               { ctf_stalemate = false; wpforenemy_announced = false; }
+
+       // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
+       if(ctf_stalemate)
+       {
+               for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
+               {
+                       if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
+                       {
+                               entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
+                               wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
+                               tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
+                       }
+               }
+
+               if (!wpforenemy_announced)
+               {
+                       FOR_EACH_REALPLAYER(tmp_entity)
+                               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
+
+                       wpforenemy_announced = true;
+               }
+       }
+}
+
+void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{SELFPARAM();
+       if(ITEM_DAMAGE_NEEDKILL(deathtype))
+       {
+               if(autocvar_g_ctf_flag_return_damage_delay)
+               {
+                       self.ctf_flagdamaged = true;
+               }
+               else
+               {
+                       self.health = 0;
+                       ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
+               }
+               return;
+       }
+       if(autocvar_g_ctf_flag_return_damage)
+       {
+               // reduce health and check if it should be returned
+               self.health = self.health - damage;
+               ctf_CheckFlagReturn(self, RETURN_DAMAGE);
+               return;
+       }
+}
+
+void ctf_FlagThink()
+{SELFPARAM();
+       // declarations
+       entity tmp_entity;
+
+       self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
+
+       // captureshield
+       if(self == ctf_worldflaglist) // only for the first flag
+               FOR_EACH_CLIENT(tmp_entity)
+                       ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
+
+       // sanity checks
+       if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
+               LOG_TRACE("wtf the flag got squashed?\n");
+               tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
+               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
+       {
+               case FLAG_DROPPED:
+               {
+                       self.angles = '0 0 0';
+                       break;
+               }
+
+               default: break;
+       }
+
+       // main think method
+       switch(self.ctf_status)
+       {
+               case FLAG_BASE:
+               {
+                       if(autocvar_g_ctf_dropped_capture_radius)
+                       {
+                               for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+                                       if(tmp_entity.ctf_status == FLAG_DROPPED)
+                                       if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
+                                       if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
+                                               ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
+                       }
+                       return;
+               }
+
+               case FLAG_DROPPED:
+               {
+                       if(autocvar_g_ctf_flag_dropped_floatinwater)
+                       {
+                               vector midpoint = ((self.absmin + self.absmax) * 0.5);
+                               if(pointcontents(midpoint) == CONTENT_WATER)
+                               {
+                                       self.velocity = self.velocity * 0.5;
+
+                                       if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
+                                               { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
+                                       else
+                                               { self.movetype = MOVETYPE_FLY; }
+                               }
+                               else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
+                       }
+                       if(autocvar_g_ctf_flag_return_dropped)
+                       {
+                               if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
+                               {
+                                       self.health = 0;
+                                       ctf_CheckFlagReturn(self, RETURN_DROPPED);
+                                       return;
+                               }
+                       }
+                       if(self.ctf_flagdamaged)
+                       {
+                               self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
+                               ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
+                               return;
+                       }
+                       else if(autocvar_g_ctf_flag_return_time)
+                       {
+                               self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
+                               ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
+                               return;
+                       }
+                       return;
+               }
+
+               case FLAG_CARRY:
+               {
+                       if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
+                       {
+                               self.health = 0;
+                               ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
+
+                               setself(self.owner);
+                               self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
+                               ImpulseCommands();
+                               setself(this);
+                       }
+                       if(autocvar_g_ctf_stalemate)
+                       {
+                               if(time >= wpforenemy_nextthink)
+                               {
+                                       ctf_CheckStalemate();
+                                       wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
+                               }
+                       }
+                       if(CTF_SAMETEAM(self, self.owner) && self.team)
+                       {
+                               if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
+                                       ctf_Handle_Throw(self.owner, world, DROP_THROW);
+                               else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
+                                       ctf_Handle_Return(self, self.owner);
+                       }
+                       return;
+               }
+
+               case FLAG_PASSING:
+               {
+                       vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
+                       targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
+                       WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
+
+                       if((self.pass_target == world)
+                               || (self.pass_target.deadflag != DEAD_NO)
+                               || (self.pass_target.flagcarried)
+                               || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
+                               || ((trace_fraction < 1) && (trace_ent != self.pass_target))
+                               || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
+                       {
+                               // give up, pass failed
+                               ctf_Handle_Drop(self, world, DROP_PASS);
+                       }
+                       else
+                       {
+                               // still a viable target, go for it
+                               ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
+                       }
+                       return;
+               }
+
+               default: // this should never happen
+               {
+                       LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n");
+                       return;
+               }
+       }
+}
+
+void ctf_FlagTouch()
+{SELFPARAM();
+       if(gameover) { return; }
+       if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
+
+       entity toucher = other, tmp_entity;
+       bool is_not_monster = (!IS_MONSTER(toucher)), num_perteam = 0;
+
+       // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
+       if(ITEM_TOUCH_NEEDKILL())
+       {
+               if(!autocvar_g_ctf_flag_return_damage_delay)
+               {
+                       self.health = 0;
+                       ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
+               }
+               if(!self.ctf_flagdamaged) { return; }
+       }
+
+       FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
+
+       // special touch behaviors
+       if(toucher.frozen) { return; }
+       else if(IS_VEHICLE(toucher))
+       {
+               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
+       }
+       else if(IS_MONSTER(toucher))
+       {
+               if(!autocvar_g_ctf_allow_monster_touch)
+                       return; // do nothing
+       }
+       else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
+       {
+               if(time > self.wait) // if we haven't in a while, play a sound/effect
+               {
+                       Send_Effect_(self.toucheffect, self.origin, '0 0 0', 1);
+                       _sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
+                       self.wait = time + FLAG_TOUCHRATE;
+               }
+               return;
+       }
+       else if(toucher.deadflag != DEAD_NO) { return; }
+
+       switch(self.ctf_status)
+       {
+               case FLAG_BASE:
+               {
+                       if(ctf_oneflag)
+                       {
+                               if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
+                                       ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
+                               else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
+                                       ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
+                       }
+                       else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
+                               ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
+                       else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
+                               ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
+                       break;
+               }
+
+               case FLAG_DROPPED:
+               {
+                       if(CTF_SAMETEAM(toucher, self) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && self.team) // automatically return if there's only 1 player on the team
+                               ctf_Handle_Return(self, toucher); // toucher just returned his own flag
+                       else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
+                               ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
+                       break;
+               }
+
+               case FLAG_CARRY:
+               {
+                       LOG_TRACE("Someone touched a flag even though it was being carried?\n");
+                       break;
+               }
+
+               case FLAG_PASSING:
+               {
+                       if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
+                       {
+                               if(DIFF_TEAM(toucher, self.pass_sender))
+                                       ctf_Handle_Return(self, toucher);
+                               else
+                                       ctf_Handle_Retrieve(self, toucher);
+                       }
+                       break;
+               }
+       }
+}
+
+.float last_respawn;
+void ctf_RespawnFlag(entity flag)
+{
+       // check for flag respawn being called twice in a row
+       if(flag.last_respawn > time - 0.5)
+               { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
+
+       flag.last_respawn = time;
+
+       // reset the player (if there is one)
+       if((flag.owner) && (flag.owner.flagcarried == flag))
+       {
+               WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
+               WaypointSprite_Kill(flag.wps_flagcarrier);
+
+               flag.owner.flagcarried = world;
+
+               if(flag.speedrunning)
+                       ctf_FakeTimeLimit(flag.owner, -1);
+       }
+
+       if((flag.owner) && (flag.owner.vehicle))
+               flag.scale = FLAG_SCALE;
+
+       if(flag.ctf_status == FLAG_DROPPED)
+               { WaypointSprite_Kill(flag.wps_flagdropped); }
+
+       // reset the flag
+       setattachment(flag, world, "");
+       setorigin(flag, flag.ctf_spawnorigin);
+
+       flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
+       flag.takedamage = DAMAGE_NO;
+       flag.health = flag.max_flag_health;
+       flag.solid = SOLID_TRIGGER;
+       flag.velocity = '0 0 0';
+       flag.angles = flag.mangle;
+       flag.flags = FL_ITEM | FL_NOTARGET;
+
+       flag.ctf_status = FLAG_BASE;
+       flag.owner = world;
+       flag.pass_distance = 0;
+       flag.pass_sender = world;
+       flag.pass_target = world;
+       flag.ctf_dropper = world;
+       flag.ctf_pickuptime = 0;
+       flag.ctf_droptime = 0;
+       flag.ctf_flagdamaged = 0;
+
+       ctf_CheckStalemate();
+}
+
+void ctf_Reset()
+{SELFPARAM();
+       if(self.owner)
+               if(IS_PLAYER(self.owner))
+                       ctf_Handle_Throw(self.owner, world, DROP_RESET);
+
+       ctf_RespawnFlag(self);
+}
+
+void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
+{SELFPARAM();
+       // bot waypoints
+       waypoint_spawnforitem_force(self, self.origin);
+       self.nearestwaypointtimeout = 0; // activate waypointing again
+       self.bot_basewaypoint = self.nearestwaypoint;
+
+       // waypointsprites
+       entity basename;
+       switch (self.team)
+       {
+               case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
+               case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
+               case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
+               case NUM_TEAM_4: basename = WP_FlagBasePink; break;
+               default: basename = WP_FlagBaseNeutral; break;
+       }
+
+       entity wp = WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG);
+       wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 1 1');
+       WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
+
+       // captureshield setup
+       ctf_CaptureShield_Spawn(self);
+}
+
+void set_flag_string(entity flag, .string field, string value, string teamname)
+{
+       if(flag.(field) == "")
+               flag.(field) = strzone(sprintf(value,teamname));
+}
+
+void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
+{SELFPARAM();
+       // declarations
+       setself(flag); // for later usage with droptofloor()
+
+       // main setup
+       flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
+       ctf_worldflaglist = flag;
+
+       setattachment(flag, world, "");
+
+       flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
+       flag.team = teamnumber;
+       flag.classname = "item_flag_team";
+       flag.target = "###item###"; // wut?
+       flag.flags = FL_ITEM | FL_NOTARGET;
+       flag.solid = SOLID_TRIGGER;
+       flag.takedamage = DAMAGE_NO;
+       flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
+       flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
+       flag.health = flag.max_flag_health;
+       flag.event_damage = ctf_FlagDamage;
+       flag.pushable = true;
+       flag.teleportable = TELEPORT_NORMAL;
+       flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+       flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
+       flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
+       flag.velocity = '0 0 0';
+       flag.mangle = flag.angles;
+       flag.reset = ctf_Reset;
+       flag.touch = ctf_FlagTouch;
+       flag.think = ctf_FlagThink;
+       flag.nextthink = time + FLAG_THINKRATE;
+       flag.ctf_status = FLAG_BASE;
+
+       string teamname = Static_Team_ColorName_Lower(teamnumber);
+       // appearence
+       if(!flag.scale)                         { flag.scale = FLAG_SCALE; }
+       if(flag.skin == 0)                      { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
+       if(flag.model == "")            { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
+       set_flag_string(flag, toucheffect,      "%sflag_touch", teamname);
+       set_flag_string(flag, passeffect,       "%s_pass",              teamname);
+       set_flag_string(flag, capeffect,        "%s_cap",               teamname);
+
+       // sounds
+       flag.snd_flag_taken = SND(CTF_TAKEN(teamnumber));
+       flag.snd_flag_returned = SND(CTF_RETURNED(teamnumber));
+       flag.snd_flag_capture = SND(CTF_CAPTURE(teamnumber));
+       flag.snd_flag_dropped = SND(CTF_DROPPED(teamnumber));
+       if (flag.snd_flag_respawn == "") flag.snd_flag_respawn = SND(CTF_RESPAWN); // if there is ever a team-based sound for this, update the code to match.
+       precache_sound(flag.snd_flag_respawn);
+       if (flag.snd_flag_touch == "") flag.snd_flag_touch = SND(CTF_TOUCH); // again has no team-based sound
+       precache_sound(flag.snd_flag_touch);
+       if (flag.snd_flag_pass == "") flag.snd_flag_pass = SND(CTF_PASS); // same story here
+       precache_sound(flag.snd_flag_pass);
+
+       // precache
+       precache_model(flag.model);
+
+       // appearence
+       _setmodel(flag, flag.model); // precision set below
+       setsize(flag, FLAG_MIN, FLAG_MAX);
+       setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
+
+       if(autocvar_g_ctf_flag_glowtrails)
+       {
+               switch(teamnumber)
+               {
+                       case NUM_TEAM_1: flag.glow_color = 251; break;
+                       case NUM_TEAM_2: flag.glow_color = 210; break;
+                       case NUM_TEAM_3: flag.glow_color = 110; break;
+                       case NUM_TEAM_4: flag.glow_color = 145; break;
+                       default:                 flag.glow_color = 254; break;
+               }
+               flag.glow_size = 25;
+               flag.glow_trail = 1;
+       }
+
+       flag.effects |= EF_LOWPRECISION;
+       if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
+       if(autocvar_g_ctf_dynamiclights)
+       {
+               switch(teamnumber)
+               {
+                       case NUM_TEAM_1: flag.effects |= EF_RED; break;
+                       case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
+                       case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
+                       case NUM_TEAM_4: flag.effects |= EF_RED; break;
+                       default:                 flag.effects |= EF_DIMLIGHT; break;
+               }
+       }
+
+       // flag placement
+       if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
+       {
+               flag.dropped_origin = flag.origin;
+               flag.noalign = true;
+               flag.movetype = MOVETYPE_NONE;
+       }
+       else // drop to floor, automatically find a platform and set that as spawn origin
+       {
+               flag.noalign = false;
+               setself(flag);
+               droptofloor();
+               flag.movetype = MOVETYPE_TOSS;
+       }
+
+       InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+// NOTE: LEGACY CODE, needs to be re-written!
+
+void havocbot_calculate_middlepoint()
+{
+       entity f;
+       vector s = '0 0 0';
+       vector fo = '0 0 0';
+       float n = 0;
+
+       f = ctf_worldflaglist;
+       while (f)
+       {
+               fo = f.origin;
+               s = s + fo;
+               f = f.ctf_worldflagnext;
+       }
+       if(!n)
+               return;
+       havocbot_ctf_middlepoint = s * (1.0 / n);
+       havocbot_ctf_middlepoint_radius  = vlen(fo - havocbot_ctf_middlepoint);
+}
+
+
+entity havocbot_ctf_find_flag(entity bot)
+{
+       entity f;
+       f = ctf_worldflaglist;
+       while (f)
+       {
+               if (CTF_SAMETEAM(bot, f))
+                       return f;
+               f = f.ctf_worldflagnext;
+       }
+       return world;
+}
+
+entity havocbot_ctf_find_enemy_flag(entity bot)
+{
+       entity f;
+       f = ctf_worldflaglist;
+       while (f)
+       {
+               if(ctf_oneflag)
+               {
+                       if(CTF_DIFFTEAM(bot, f))
+                       {
+                               if(f.team)
+                               {
+                                       if(bot.flagcarried)
+                                               return f;
+                               }
+                               else if(!bot.flagcarried)
+                                       return f;
+                       }
+               }
+               else if (CTF_DIFFTEAM(bot, f))
+                       return f;
+               f = f.ctf_worldflagnext;
+       }
+       return world;
+}
+
+int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
+{
+       if (!teamplay)
+               return 0;
+
+       int c = 0;
+       entity head;
+
+       FOR_EACH_PLAYER(head)
+       {
+               if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
+                       continue;
+
+               if(vlen(head.origin - org) < tc_radius)
+                       ++c;
+       }
+
+       return c;
+}
+
+void havocbot_goalrating_ctf_ourflag(float ratingscale)
+{SELFPARAM();
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if (CTF_SAMETEAM(self, head))
+                       break;
+               head = head.ctf_worldflagnext;
+       }
+       if (head)
+               navigation_routerating(head, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_ourbase(float ratingscale)
+{SELFPARAM();
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if (CTF_SAMETEAM(self, head))
+                       break;
+               head = head.ctf_worldflagnext;
+       }
+       if (!head)
+               return;
+
+       navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_enemyflag(float ratingscale)
+{SELFPARAM();
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if(ctf_oneflag)
+               {
+                       if(CTF_DIFFTEAM(self, head))
+                       {
+                               if(head.team)
+                               {
+                                       if(self.flagcarried)
+                                               break;
+                               }
+                               else if(!self.flagcarried)
+                                       break;
+                       }
+               }
+               else if(CTF_DIFFTEAM(self, head))
+                       break;
+               head = head.ctf_worldflagnext;
+       }
+       if (head)
+               navigation_routerating(head, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_enemybase(float ratingscale)
+{SELFPARAM();
+       if (!bot_waypoints_for_items)
+       {
+               havocbot_goalrating_ctf_enemyflag(ratingscale);
+               return;
+       }
+
+       entity head;
+
+       head = havocbot_ctf_find_enemy_flag(self);
+
+       if (!head)
+               return;
+
+       navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
+{SELFPARAM();
+       entity mf;
+
+       mf = havocbot_ctf_find_flag(self);
+
+       if(mf.ctf_status == FLAG_BASE)
+               return;
+
+       if(mf.tag_entity)
+               navigation_routerating(mf.tag_entity, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               // flag is out in the field
+               if(head.ctf_status != FLAG_BASE)
+               if(head.tag_entity==world)      // dropped
+               {
+                       if(df_radius)
+                       {
+                               if(vlen(org-head.origin)<df_radius)
+                                       navigation_routerating(head, ratingscale, 10000);
+                       }
+                       else
+                               navigation_routerating(head, ratingscale, 10000);
+               }
+
+               head = head.ctf_worldflagnext;
+       }
+}
+
+void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
+{SELFPARAM();
+       entity head;
+       float t;
+       head = findchainfloat(bot_pickup, true);
+       while (head)
+       {
+               // gather health and armor only
+               if (head.solid)
+               if (head.health || head.armorvalue)
+               if (vlen(head.origin - org) < sradius)
+               {
+                       // get the value of the item
+                       t = head.bot_pickupevalfunc(self, head) * 0.0001;
+                       if (t > 0)
+                               navigation_routerating(head, t * ratingscale, 500);
+               }
+               head = head.chain;
+       }
+}
+
+void havocbot_ctf_reset_role(entity bot)
+{
+       float cdefense, cmiddle, coffense;
+       entity mf, ef, head;
+       float c;
+
+       if(bot.deadflag != DEAD_NO)
+               return;
+
+       if(vlen(havocbot_ctf_middlepoint)==0)
+               havocbot_calculate_middlepoint();
+
+       // Check ctf flags
+       if (bot.flagcarried)
+       {
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       mf = havocbot_ctf_find_flag(bot);
+       ef = havocbot_ctf_find_enemy_flag(bot);
+
+       // Retrieve stolen flag
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
+               return;
+       }
+
+       // If enemy flag is taken go to the middle to intercept pursuers
+       if(ef.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
+               return;
+       }
+
+       // if there is only me on the team switch to offense
+       c = 0;
+       FOR_EACH_PLAYER(head)
+       if(SAME_TEAM(head, bot))
+               ++c;
+
+       if(c==1)
+       {
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
+               return;
+       }
+
+       // Evaluate best position to take
+       // Count mates on middle position
+       cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
+
+       // Count mates on defense position
+       cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
+
+       // Count mates on offense position
+       coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
+
+       if(cdefense<=coffense)
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
+       else if(coffense<=cmiddle)
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
+       else
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
+}
+
+void havocbot_role_ctf_carrier()
+{SELFPARAM();
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried == world)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               if(ctf_oneflag)
+                       havocbot_goalrating_ctf_enemybase(50000);
+               else
+                       havocbot_goalrating_ctf_ourbase(50000);
+
+               if(self.health<100)
+                       havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
+
+               navigation_goalrating_end();
+
+               if (self.navigation_hasgoals)
+                       self.havocbot_cantfindflag = time + 10;
+               else if (time > self.havocbot_cantfindflag)
+               {
+                       // Can't navigate to my own base, suicide!
+                       // TODO: drop it and wander around
+                       Damage(self, self, self, 100000, DEATH_KILL.m_id, self.origin, '0 0 0');
+                       return;
+               }
+       }
+}
+
+void havocbot_role_ctf_escort()
+{SELFPARAM();
+       entity mf, ef;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If enemy flag is back on the base switch to previous role
+       ef = havocbot_ctf_find_enemy_flag(self);
+       if(ef.ctf_status==FLAG_BASE)
+       {
+               self.havocbot_role = self.havocbot_previous_role;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       // If the flag carrier reached the base switch to defense
+       mf = havocbot_ctf_find_flag(self);
+       if(mf.ctf_status!=FLAG_BASE)
+       if(vlen(ef.origin - mf.dropped_origin) < 300)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!self.havocbot_role_timeout)
+       {
+               self.havocbot_role_timeout = time + random() * 30 + 60;
+       }
+
+       // If nothing happened just switch to previous role
+       if (time > self.havocbot_role_timeout)
+       {
+               self.havocbot_role = self.havocbot_previous_role;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       // Chase the flag carrier
+       if (self.bot_strategytime < time)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+               havocbot_goalrating_ctf_enemyflag(30000);
+               havocbot_goalrating_ctf_ourstolenflag(40000);
+               havocbot_goalrating_items(10000, self.origin, 10000);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_ctf_offense()
+{SELFPARAM();
+       entity mf, ef;
+       vector pos;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // Check flags
+       mf = havocbot_ctf_find_flag(self);
+       ef = havocbot_ctf_find_enemy_flag(self);
+
+       // Own flag stolen
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               if(mf.tag_entity)
+                       pos = mf.tag_entity.origin;
+               else
+                       pos = mf.origin;
+
+               // Try to get it if closer than the enemy base
+               if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
+               {
+                       havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
+                       return;
+               }
+       }
+
+       // Escort flag carrier
+       if(ef.ctf_status!=FLAG_BASE)
+       {
+               if(ef.tag_entity)
+                       pos = ef.tag_entity.origin;
+               else
+                       pos = ef.origin;
+
+               if(vlen(pos-mf.dropped_origin)>700)
+               {
+                       havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
+                       return;
+               }
+       }
+
+       // About to fail, switch to middlefield
+       if(self.health<50)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 120;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+               havocbot_goalrating_ctf_ourstolenflag(50000);
+               havocbot_goalrating_ctf_enemybase(20000);
+               havocbot_goalrating_items(5000, self.origin, 1000);
+               havocbot_goalrating_items(1000, self.origin, 10000);
+               navigation_goalrating_end();
+       }
+}
+
+// Retriever (temporary role):
+void havocbot_role_ctf_retriever()
+{SELFPARAM();
+       entity mf;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If flag is back on the base switch to previous role
+       mf = havocbot_ctf_find_flag(self);
+       if(mf.ctf_status==FLAG_BASE)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 20;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               float rt_radius;
+               rt_radius = 10000;
+
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+               havocbot_goalrating_ctf_ourstolenflag(50000);
+               havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
+               havocbot_goalrating_ctf_enemybase(30000);
+               havocbot_goalrating_items(500, self.origin, rt_radius);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_ctf_middle()
+{SELFPARAM();
+       entity mf;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       mf = havocbot_ctf_find_flag(self);
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
+               return;
+       }
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 10;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               vector org;
+
+               org = havocbot_ctf_middlepoint;
+               org.z = self.origin.z;
+
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+               havocbot_goalrating_ctf_ourstolenflag(50000);
+               havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(2500, self.origin, 10000);
+               havocbot_goalrating_ctf_enemybase(2500);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_ctf_defense()
+{SELFPARAM();
+       entity mf;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If own flag was captured
+       mf = havocbot_ctf_find_flag(self);
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
+               return;
+       }
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 30;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+       if (self.bot_strategytime < time)
+       {
+               float mp_radius;
+               vector org;
+
+               org = mf.dropped_origin;
+               mp_radius = havocbot_ctf_middlepoint_radius;
+
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+
+               // if enemies are closer to our base, go there
+               entity head, closestplayer = world;
+               float distance, bestdistance = 10000;
+               FOR_EACH_PLAYER(head)
+               {
+                       if(head.deadflag!=DEAD_NO)
+                               continue;
+
+                       distance = vlen(org - head.origin);
+                       if(distance<bestdistance)
+                       {
+                               closestplayer = head;
+                               bestdistance = distance;
+                       }
+               }
+
+               if(closestplayer)
+               if(DIFF_TEAM(closestplayer, self))
+               if(vlen(org - self.origin)>1000)
+               if(checkpvs(self.origin,closestplayer)||random()<0.5)
+                       havocbot_goalrating_ctf_ourbase(30000);
+
+               havocbot_goalrating_ctf_ourstolenflag(20000);
+               havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
+               havocbot_goalrating_enemyplayers(15000, org, mp_radius);
+               havocbot_goalrating_items(10000, org, mp_radius);
+               havocbot_goalrating_items(5000, self.origin, 10000);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_ctf_setrole(entity bot, int role)
+{
+       LOG_TRACE(strcat(bot.netname," switched to "));
+       switch(role)
+       {
+               case HAVOCBOT_CTF_ROLE_CARRIER:
+                       LOG_TRACE("carrier");
+                       bot.havocbot_role = havocbot_role_ctf_carrier;
+                       bot.havocbot_role_timeout = 0;
+                       bot.havocbot_cantfindflag = time + 10;
+                       bot.bot_strategytime = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_DEFENSE:
+                       LOG_TRACE("defense");
+                       bot.havocbot_role = havocbot_role_ctf_defense;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_MIDDLE:
+                       LOG_TRACE("middle");
+                       bot.havocbot_role = havocbot_role_ctf_middle;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_OFFENSE:
+                       LOG_TRACE("offense");
+                       bot.havocbot_role = havocbot_role_ctf_offense;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_RETRIEVER:
+                       LOG_TRACE("retriever");
+                       bot.havocbot_previous_role = bot.havocbot_role;
+                       bot.havocbot_role = havocbot_role_ctf_retriever;
+                       bot.havocbot_role_timeout = time + 10;
+                       bot.bot_strategytime = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_ESCORT:
+                       LOG_TRACE("escort");
+                       bot.havocbot_previous_role = bot.havocbot_role;
+                       bot.havocbot_role = havocbot_role_ctf_escort;
+                       bot.havocbot_role_timeout = time + 30;
+                       bot.bot_strategytime = 0;
+                       break;
+       }
+       LOG_TRACE("\n");
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
+{SELFPARAM();
+       entity flag;
+       int t = 0, t2 = 0, t3 = 0;
+
+       // initially clear items so they can be set as necessary later.
+       self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING          | CTF_RED_FLAG_TAKEN            | CTF_RED_FLAG_LOST
+                                                  | CTF_BLUE_FLAG_CARRYING             | CTF_BLUE_FLAG_TAKEN           | CTF_BLUE_FLAG_LOST
+                                                  | CTF_YELLOW_FLAG_CARRYING   | CTF_YELLOW_FLAG_TAKEN         | CTF_YELLOW_FLAG_LOST
+                                                  | CTF_PINK_FLAG_CARRYING     | CTF_PINK_FLAG_TAKEN           | CTF_PINK_FLAG_LOST
+                                                  | CTF_NEUTRAL_FLAG_CARRYING  | CTF_NEUTRAL_FLAG_TAKEN        | CTF_NEUTRAL_FLAG_LOST
+                                                  | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
+
+       // scan through all the flags and notify the client about them
+       for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+       {
+               if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING;                t2 = CTF_RED_FLAG_TAKEN;                t3 = CTF_RED_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING;               t2 = CTF_BLUE_FLAG_TAKEN;               t3 = CTF_BLUE_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING;     t2 = CTF_YELLOW_FLAG_TAKEN;             t3 = CTF_YELLOW_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING;               t2 = CTF_PINK_FLAG_TAKEN;               t3 = CTF_PINK_FLAG_LOST; }
+               if(flag.team == 0)                      { t = CTF_NEUTRAL_FLAG_CARRYING;        t2 = CTF_NEUTRAL_FLAG_TAKEN;    t3 = CTF_NEUTRAL_FLAG_LOST; self.ctf_flagstatus |= CTF_FLAG_NEUTRAL; }
+
+               switch(flag.ctf_status)
+               {
+                       case FLAG_PASSING:
+                       case FLAG_CARRY:
+                       {
+                               if((flag.owner == self) || (flag.pass_sender == self))
+                                       self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
+                               else
+                                       self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
+                               break;
+                       }
+                       case FLAG_DROPPED:
+                       {
+                               self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
+                               break;
+                       }
+               }
+       }
+
+       // item for stopping players from capturing the flag too often
+       if(self.ctf_captureshielded)
+               self.ctf_flagstatus |= CTF_SHIELDED;
+
+       // update the health of the flag carrier waypointsprite
+       if(self.wps_flagcarrier)
+               WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
+{
+       if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
+       {
+               if(frag_target == frag_attacker) // damage done to yourself
+               {
+                       frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
+                       frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
+               }
+               else // damage done to everyone else
+               {
+                       frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
+                       frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
+               }
+       }
+       else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
+       {
+               if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)))
+               if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
+               {
+                       frag_target.wps_helpme_time = time;
+                       WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
+               }
+               // todo: add notification for when flag carrier needs help?
+       }
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
+{
+       if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
+       {
+               PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
+               PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
+       }
+
+       if(frag_target.flagcarried)
+       {
+               entity tmp_entity = frag_target.flagcarried;
+               ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
+               tmp_entity.ctf_dropper = world;
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
+{
+       frag_score = 0;
+       return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
+}
+
+void ctf_RemovePlayer(entity player)
+{
+       if(player.flagcarried)
+               { ctf_Handle_Throw(player, world, DROP_NORMAL); }
+
+       for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+       {
+               if(flag.pass_sender == player) { flag.pass_sender = world; }
+               if(flag.pass_target == player) { flag.pass_target = world; }
+               if(flag.ctf_dropper == player) { flag.ctf_dropper = world; }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
+{SELFPARAM();
+       ctf_RemovePlayer(self);
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
+{SELFPARAM();
+       ctf_RemovePlayer(self);
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
+{SELFPARAM();
+       if(self.flagcarried)
+       if(!autocvar_g_ctf_portalteleport)
+               { ctf_Handle_Throw(self, world, DROP_NORMAL); }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
+{SELFPARAM();
+       if(MUTATOR_RETURNVALUE || gameover) { return false; }
+
+       entity player = self;
+
+       if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
+       {
+               // pass the flag to a team mate
+               if(autocvar_g_ctf_pass)
+               {
+                       entity head, closest_target = world;
+                       head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
+
+                       while(head) // find the closest acceptable target to pass to
+                       {
+                               if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
+                               if(head != player && SAME_TEAM(head, player))
+                               if(!head.speedrunning && !head.vehicle)
+                               {
+                                       // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
+                                       vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
+                                       vector passer_center = CENTER_OR_VIEWOFS(player);
+
+                                       if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
+                                       {
+                                               if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
+                                               {
+                                                       if(IS_BOT_CLIENT(head))
+                                                       {
+                                                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
+                                                               ctf_Handle_Throw(head, player, DROP_PASS);
+                                                       }
+                                                       else
+                                                       {
+                                                               Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
+                                                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
+                                                       }
+                                                       player.throw_antispam = time + autocvar_g_ctf_pass_wait;
+                                                       return true;
+                                               }
+                                               else if(player.flagcarried)
+                                               {
+                                                       if(closest_target)
+                                                       {
+                                                               vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
+                                                               if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
+                                                                       { closest_target = head; }
+                                                       }
+                                                       else { closest_target = head; }
+                                               }
+                                       }
+                               }
+                               head = head.chain;
+                       }
+
+                       if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
+               }
+
+               // throw the flag in front of you
+               if(autocvar_g_ctf_throw && player.flagcarried)
+               {
+                       if(player.throw_count == -1)
+                       {
+                               if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
+                               {
+                                       player.throw_prevtime = time;
+                                       player.throw_count = 1;
+                                       ctf_Handle_Throw(player, world, DROP_THROW);
+                                       return true;
+                               }
+                               else
+                               {
+                                       Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
+                                       return false;
+                               }
+                       }
+                       else
+                       {
+                               if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
+                               else { player.throw_count += 1; }
+                               if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
+
+                               player.throw_prevtime = time;
+                               ctf_Handle_Throw(player, world, DROP_THROW);
+                               return true;
+                       }
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
+{SELFPARAM();
+       if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
+       {
+               self.wps_helpme_time = time;
+               WaypointSprite_HelpMePing(self.wps_flagcarrier);
+       }
+       else // create a normal help me waypointsprite
+       {
+               WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, false, RADARICON_HELPME);
+               WaypointSprite_Ping(self.wps_helpme);
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
+{
+       if(vh_player.flagcarried)
+       {
+               vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver
+
+               if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
+               {
+                       ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
+               }
+               else
+               {
+                       setattachment(vh_player.flagcarried, vh_vehicle, "");
+                       setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
+                       vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
+                       //vh_player.flagcarried.angles = '0 0 0';
+               }
+               return true;
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
+{
+       if(vh_player.flagcarried)
+       {
+               setattachment(vh_player.flagcarried, vh_player, "");
+               setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
+               vh_player.flagcarried.scale = FLAG_SCALE;
+               vh_player.flagcarried.angles = '0 0 0';
+               vh_player.flagcarried.nodrawtoclient = world;
+               return true;
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
+{SELFPARAM();
+       if(self.flagcarried)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, ((self.flagcarried.team) ? APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
+               ctf_RespawnFlag(self.flagcarried);
+               return true;
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
+{
+       entity flag; // temporary entity for the search method
+
+       for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+       {
+               switch(flag.ctf_status)
+               {
+                       case FLAG_DROPPED:
+                       case FLAG_PASSING:
+                       {
+                               // lock the flag, game is over
+                               flag.movetype = MOVETYPE_NONE;
+                               flag.takedamage = DAMAGE_NO;
+                               flag.solid = SOLID_NOT;
+                               flag.nextthink = false; // stop thinking
+
+                               //dprint("stopping the ", flag.netname, " from moving.\n");
+                               break;
+                       }
+
+                       default:
+                       case FLAG_BASE:
+                       case FLAG_CARRY:
+                       {
+                               // do nothing for these flags
+                               break;
+                       }
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
+{SELFPARAM();
+       havocbot_ctf_reset_role(self);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
+{
+       //ret_float = ctf_teams;
+       ret_string = "ctf_team";
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
+{SELFPARAM();
+       self.ctf_flagstatus = other.ctf_flagstatus;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GetRecords)
+{
+       for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
+       {
+               if (MapInfo_Get_ByID(i))
+               {
+                       float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
+
+                       if(!r)
+                               continue;
+
+                       // TODO: uid2name
+                       string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
+                       ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
+               }
+       }
+
+       return false;
+}
+
+bool superspec_Spectate(entity _player); // TODO
+void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
+MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
+{
+       if(IS_PLAYER(self) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
+
+       if(cmd_name == "followfc")
+       {
+               if(!g_ctf)
+                       return true;
+
+               entity _player;
+               int _team = 0;
+               bool found = false;
+
+               if(cmd_argc == 2)
+               {
+                       switch(argv(1))
+                       {
+                               case "red": _team = NUM_TEAM_1; break;
+                               case "blue": _team = NUM_TEAM_2; break;
+                               case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break;
+                               case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break;
+                       }
+               }
+
+               FOR_EACH_PLAYER(_player)
+               {
+                       if(_player.flagcarried && (_player.team == _team || _team == 0))
+                       {
+                               found = true;
+                               if(_team == 0 && IS_SPEC(self) && self.enemy == _player)
+                                       continue; // already spectating a fc, try to find the other fc
+                               return superspec_Spectate(_player);
+                       }
+               }
+
+               if(!found)
+                       superspec_msg("", "", self, "No active flag carrier\n", 1);
+               return true;
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
+{
+       if(frag_target.flagcarried)
+               ctf_Handle_Throw(frag_target, world, DROP_THROW);
+
+       return false;
+}
+
+
+// ==========
+// Spawnfuncs
+// ==========
+
+/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team one (Red).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red and blue as skins 0 and 1...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_team1)
+{
+       if(!g_ctf) { remove(self); return; }
+
+       ctf_FlagSetup(NUM_TEAM_1, self);
+}
+
+/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team two (Blue).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red and blue as skins 0 and 1...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_team2)
+{
+       if(!g_ctf) { remove(self); return; }
+
+       ctf_FlagSetup(NUM_TEAM_2, self);
+}
+
+/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team three (Yellow).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_team3)
+{
+       if(!g_ctf) { remove(self); return; }
+
+       ctf_FlagSetup(NUM_TEAM_3, self);
+}
+
+/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team four (Pink).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_team4)
+{
+       if(!g_ctf) { remove(self); return; }
+
+       ctf_FlagSetup(NUM_TEAM_4, self);
+}
+
+/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag (Neutral).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+spawnfunc(item_flag_neutral)
+{
+       if(!g_ctf) { remove(self); return; }
+       if(!cvar("g_ctf_oneflag")) { remove(self); return; }
+
+       ctf_FlagSetup(0, self);
+}
+
+/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
+Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
+Note: If you use spawnfunc_ctf_team entities you must define at least 2!  However, unlike domination, you don't need to make a blank one too.
+Keys:
+"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
+"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
+spawnfunc(ctf_team)
+{
+       if(!g_ctf) { remove(self); return; }
+
+       self.classname = "ctf_team";
+       self.team = self.cnt + 1;
+}
+
+// compatibility for quake maps
+spawnfunc(team_CTF_redflag)    { spawnfunc_item_flag_team1(this);    }
+spawnfunc(team_CTF_blueflag)   { spawnfunc_item_flag_team2(this);    }
+spawnfunc(info_player_team1);
+spawnfunc(team_CTF_redplayer)  { spawnfunc_info_player_team1(this);  }
+spawnfunc(team_CTF_redspawn)   { spawnfunc_info_player_team1(this);  }
+spawnfunc(info_player_team2);
+spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this);  }
+spawnfunc(team_CTF_bluespawn)  { spawnfunc_info_player_team2(this);  }
+
+void team_CTF_neutralflag()                     { SELFPARAM(); spawnfunc_item_flag_neutral(self);  }
+void team_neutralobelisk()                      { SELFPARAM(); spawnfunc_item_flag_neutral(self);  }
+
+
+// ==============
+// Initialization
+// ==============
+
+// scoreboard setup
+void ctf_ScoreRules(int teams)
+{
+       CheckAllowedTeams(world);
+       ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
+       ScoreInfo_SetLabel_TeamScore  (ST_CTF_CAPS,     "caps",      SFL_SORT_PRIO_PRIMARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME,  "captime",   SFL_LOWER_IS_BETTER | SFL_TIME);
+       ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS,  "pickups",   0);
+       ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS,  "fckills",   0);
+       ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS,  "returns",   0);
+       ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS,    "drops",     SFL_LOWER_IS_BETTER);
+       ScoreRules_basics_end();
+}
+
+// code from here on is just to support maps that don't have flag and team entities
+void ctf_SpawnTeam (string teamname, int teamcolor)
+{
+       entity this = new(ctf_team);
+       this.netname = teamname;
+       this.cnt = teamcolor;
+       this.spawnfunc_checked = true;
+       WITH(entity, self, this, spawnfunc_ctf_team(this));
+}
+
+void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
+{
+       ctf_teams = 2;
+
+       entity tmp_entity;
+       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+       {
+               if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
+               if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
+               if(tmp_entity.team == 0) { ctf_oneflag = true; }
+       }
+
+       ctf_teams = bound(2, ctf_teams, 4);
+
+       // if no teams are found, spawn defaults
+       if(find(world, classname, "ctf_team") == world)
+       {
+               LOG_INFO("No ""ctf_team"" entities found on this map, creating them anyway.\n");
+               ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
+               ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
+               if(ctf_teams >= 3)
+                       ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
+               if(ctf_teams >= 4)
+                       ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
+       }
+
+       ctf_ScoreRules(ctf_teams);
+}
+
+void ctf_Initialize()
+{
+       ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
+
+       ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
+       ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
+       ctf_captureshield_force = autocvar_g_ctf_shield_force;
+
+       addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
+
+       InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
+}
+
+REGISTER_MUTATOR(ctf, g_ctf)
+{
+       ActivateTeamplay();
+       SetLimits(autocvar_capturelimit_override, -1, autocvar_captureleadlimit_override, -1);
+       have_team_spawns = -1; // request team spawns
+
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+               ctf_Initialize();
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back ctf_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/gamemode_cts.qc b/qcsrc/server/mutators/mutator/gamemode_cts.qc
new file mode 100644 (file)
index 0000000..1414fe0
--- /dev/null
@@ -0,0 +1,445 @@
+#ifndef GAMEMODE_CTS_H
+#define GAMEMODE_CTS_H
+
+// scores
+const float ST_CTS_LAPS = 1;
+const float SP_CTS_LAPS = 4;
+const float SP_CTS_TIME = 5;
+const float SP_CTS_FASTEST = 6;
+#endif
+
+#ifdef IMPLEMENTATION
+
+#include "../../race.qh"
+
+float autocvar_g_cts_finish_kill_delay;
+bool autocvar_g_cts_selfdamage;
+
+// legacy bot roles
+.float race_checkpoint;
+void havocbot_role_cts()
+{SELFPARAM();
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       entity e;
+       if (self.bot_strategytime < time)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+
+               for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; )
+               {
+                       if(e.cnt == self.race_checkpoint)
+                       {
+                               navigation_routerating(e, 1000000, 5000);
+                       }
+                       else if(self.race_checkpoint == -1)
+                       {
+                               navigation_routerating(e, 1000000, 5000);
+                       }
+               }
+
+               navigation_goalrating_end();
+       }
+}
+
+void cts_ScoreRules()
+{
+       ScoreRules_basics(0, 0, 0, false);
+       if(g_race_qualifying)
+       {
+               ScoreInfo_SetLabel_PlayerScore(SP_CTS_FASTEST, "fastest",   SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+       }
+       else
+       {
+               ScoreInfo_SetLabel_PlayerScore(SP_CTS_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
+               ScoreInfo_SetLabel_PlayerScore(SP_CTS_TIME,    "time",      SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+               ScoreInfo_SetLabel_PlayerScore(SP_CTS_FASTEST, "fastest",   SFL_LOWER_IS_BETTER | SFL_TIME);
+       }
+       ScoreRules_basics_end();
+}
+
+void cts_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
+{
+       if(autocvar_sv_eventlog)
+               GameLogEcho(strcat(":cts:", mode, ":", ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void CTS_ClientKill(entity e) // silent version of ClientKill, used when player finishes a CTS run. Useful to prevent cheating by running back to the start line and starting out with more speed
+{
+    e.killindicator = spawn();
+    e.killindicator.owner = e;
+    e.killindicator.think = KillIndicator_Think;
+    e.killindicator.nextthink = time + (e.lip) * 0.05;
+    e.killindicator.cnt = ceil(autocvar_g_cts_finish_kill_delay);
+    e.killindicator.health = 1; // this is used to indicate that it should be silent
+    e.lip = 0;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerPhysics)
+{SELFPARAM();
+       self.race_movetime_frac += PHYS_INPUT_TIMELENGTH;
+       float f = floor(self.race_movetime_frac);
+       self.race_movetime_frac -= f;
+       self.race_movetime_count += f;
+       self.race_movetime = self.race_movetime_frac + self.race_movetime_count;
+
+#ifdef SVQC
+       if(IS_PLAYER(self))
+       {
+               if (self.race_penalty)
+                       if (time > self.race_penalty)
+                               self.race_penalty = 0;
+               if(self.race_penalty)
+               {
+                       self.velocity = '0 0 0';
+                       self.movetype = MOVETYPE_NONE;
+                       self.disableclientprediction = 2;
+               }
+       }
+#endif
+
+       // force kbd movement for fairness
+       float wishspeed;
+       vector wishvel;
+
+       // if record times matter
+       // ensure nothing EVIL is being done (i.e. div0_evade)
+       // this hinders joystick users though
+       // but it still gives SOME analog control
+       wishvel.x = fabs(self.movement.x);
+       wishvel.y = fabs(self.movement.y);
+       if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
+       {
+               wishvel.z = 0;
+               wishspeed = vlen(wishvel);
+               if(wishvel.x >= 2 * wishvel.y)
+               {
+                       // pure X motion
+                       if(self.movement.x > 0)
+                               self.movement_x = wishspeed;
+                       else
+                               self.movement_x = -wishspeed;
+                       self.movement_y = 0;
+               }
+               else if(wishvel.y >= 2 * wishvel.x)
+               {
+                       // pure Y motion
+                       self.movement_x = 0;
+                       if(self.movement.y > 0)
+                               self.movement_y = wishspeed;
+                       else
+                               self.movement_y = -wishspeed;
+               }
+               else
+               {
+                       // diagonal
+                       if(self.movement.x > 0)
+                               self.movement_x = M_SQRT1_2 * wishspeed;
+                       else
+                               self.movement_x = -M_SQRT1_2 * wishspeed;
+                       if(self.movement.y > 0)
+                               self.movement_y = M_SQRT1_2 * wishspeed;
+                       else
+                               self.movement_y = -M_SQRT1_2 * wishspeed;
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, reset_map_global)
+{
+       float s;
+
+       Score_NicePrint(world);
+
+       race_ClearRecords();
+       PlayerScore_Sort(race_place, 0, 1, 0);
+
+       entity e;
+       FOR_EACH_CLIENT(e)
+       {
+               if(e.race_place)
+               {
+                       s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
+                       if(!s)
+                               e.race_place = 0;
+               }
+               cts_EventLog(ftos(e.race_place), e);
+       }
+
+       if(g_race_qualifying == 2)
+       {
+               g_race_qualifying = 0;
+               independent_players = 0;
+               cvar_set("fraglimit", ftos(race_fraglimit));
+               cvar_set("leadlimit", ftos(race_leadlimit));
+               cvar_set("timelimit", ftos(race_timelimit));
+               cts_ScoreRules();
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerPreThink)
+{SELFPARAM();
+       if(IS_SPEC(self) || IS_OBSERVER(self))
+       if(g_race_qualifying)
+       if(msg_entity.enemy.race_laptime)
+               race_SendNextCheckpoint(msg_entity.enemy, 1);
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, ClientConnect)
+{SELFPARAM();
+       race_PreparePlayer();
+       self.race_checkpoint = -1;
+
+       if(IS_REAL_CLIENT(self))
+       {
+               string rr = CTS_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);
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver)
+{SELFPARAM();
+       if(PlayerScore_Add(self, SP_RACE_FASTEST, 0))
+               self.frags = FRAGS_LMS_LOSER;
+       else
+               self.frags = FRAGS_SPECTATOR;
+
+       race_PreparePlayer();
+       self.race_checkpoint = -1;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerSpawn)
+{SELFPARAM();
+       if(spawn_spot.target == "")
+               // Emergency: this wasn't a real spawnpoint. Can this ever happen?
+               race_PreparePlayer();
+
+       // if we need to respawn, do it right
+       self.race_respawn_checkpoint = self.race_checkpoint;
+       self.race_respawn_spotref = spawn_spot;
+
+       self.race_place = 0;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PutClientInServer)
+{SELFPARAM();
+       if(IS_PLAYER(self))
+       if(!gameover)
+       {
+               if(self.killcount == -666 /* initial spawn */ || g_race_qualifying) // spawn
+                       race_PreparePlayer();
+               else // respawn
+                       race_RetractPlayer();
+
+               race_AbandonRaceCheck(self);
+       }
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerDies)
+{SELFPARAM();
+       self.respawn_flags |= RESPAWN_FORCE;
+       race_AbandonRaceCheck(self);
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole)
+{SELFPARAM();
+       self.havocbot_role = havocbot_role_cts;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(cts, GetPressedKeys)
+{SELFPARAM();
+       if(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 (!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 = CTS_RECORD;
+                       race_send_speedaward(MSG_ALL);
+                       speedaward_lastsent = speedaward_speed;
+                       if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
+                       {
+                               speedaward_alltimebest = speedaward_speed;
+                               speedaward_alltimebest_holder = speedaward_holder;
+                               speedaward_alltimebest_uid = speedaward_uid;
+                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
+                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
+                               race_send_speedaward_alltimebest(MSG_ALL);
+                       }
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon)
+{
+       // no weapon dropping in CTS
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(cts, FilterItem)
+{SELFPARAM();
+       if(self.classname == "droppedweapon")
+               return true;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerDamage_Calculate)
+{
+       if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id)
+       if(!autocvar_g_cts_selfdamage)
+               frag_damage = 0;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear)
+{
+       return true; // in CTS, you don't lose score by observing
+}
+
+MUTATOR_HOOKFUNCTION(cts, SetModname)
+{
+       g_cloaked = 1; // always enable cloak in CTS
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, GetRecords)
+{
+       for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
+       {
+               if(MapInfo_Get_ByID(i))
+               {
+                       float r = race_readTime(MapInfo_Map_bspname, 1);
+
+                       if(!r)
+                               continue;
+
+                       string h = race_readName(MapInfo_Map_bspname, 1);
+                       ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, ClientKill)
+{
+       ret_float = 0;
+
+       if(self.killindicator && self.killindicator.health == 1) // self.killindicator.health == 1 means that the kill indicator was spawned by CTS_ClientKill
+       {
+               remove(self.killindicator);
+               self.killindicator = world;
+
+               ClientKill_Now(); // allow instant kill in this case
+               return;
+       }
+
+}
+
+MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint)
+{
+       if(autocvar_g_cts_finish_kill_delay)
+               CTS_ClientKill(self);
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, FixClientCvars)
+{
+       stuffcmd(fix_client, "cl_cmd settemp cl_movecliptokeyboard 2\n");
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(cts, WantWeapon)
+{
+       ret_float = (want_weaponinfo.weapon == WEP_SHOTGUN.m_id);
+       want_mutatorblocked = true;
+       return true;
+}
+
+void cts_Initialize()
+{
+       cts_ScoreRules();
+}
+
+REGISTER_MUTATOR(cts, g_cts)
+{
+       g_race_qualifying = 1;
+       independent_players = 1;
+       SetLimits(0, 0, 0, -1);
+
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+               cts_Initialize();
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back cts_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/gamemode_deathmatch.qc b/qcsrc/server/mutators/mutator/gamemode_deathmatch.qc
new file mode 100644 (file)
index 0000000..ac7ea21
--- /dev/null
@@ -0,0 +1,31 @@
+#ifdef IMPLEMENTATION
+MUTATOR_HOOKFUNCTION(dm, Scores_CountFragsRemaining)
+{
+       // announce remaining frags
+       return true;
+}
+
+REGISTER_MUTATOR(dm, IS_GAMETYPE(DEATHMATCH))
+{
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back dm_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               print("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/gamemode_domination.qc b/qcsrc/server/mutators/mutator/gamemode_domination.qc
new file mode 100644 (file)
index 0000000..75d0d7d
--- /dev/null
@@ -0,0 +1,724 @@
+#ifndef GAMEMODE_DOMINATION_H
+#define GAMEMODE_DOMINATION_H
+
+// score rule declarations
+const float ST_DOM_TICKS = 1;
+const float SP_DOM_TICKS = 4;
+const float SP_DOM_TAKES = 5;
+const float ST_DOM_CAPS = 1;
+const float SP_DOM_CAPS = 4;
+
+// pps: points per second
+.float dom_total_pps;
+.float dom_pps_red;
+.float dom_pps_blue;
+.float dom_pps_yellow;
+.float dom_pps_pink;
+float total_pps;
+float pps_red;
+float pps_blue;
+float pps_yellow;
+float pps_pink;
+
+// capture declarations
+.float enemy_playerid;
+.entity sprite;
+.float captime;
+
+// misc globals
+float domination_roundbased;
+float domination_teams;
+#endif
+
+#ifdef IMPLEMENTATION
+
+#include "../../teamplay.qh"
+
+bool g_domination;
+
+int autocvar_g_domination_default_teams;
+bool autocvar_g_domination_disable_frags;
+int autocvar_g_domination_point_amt;
+bool autocvar_g_domination_point_fullbright;
+int autocvar_g_domination_point_leadlimit;
+bool autocvar_g_domination_roundbased;
+int autocvar_g_domination_roundbased_point_limit;
+float autocvar_g_domination_round_timelimit;
+float autocvar_g_domination_warmup;
+#define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
+float autocvar_g_domination_point_rate;
+int autocvar_g_domination_teams_override;
+
+void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
+{
+       if(autocvar_sv_eventlog)
+               GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void set_dom_state(entity e)
+{
+       e.dom_total_pps = total_pps;
+       e.dom_pps_red = pps_red;
+       e.dom_pps_blue = pps_blue;
+       if(domination_teams >= 3)
+               e.dom_pps_yellow = pps_yellow;
+       if(domination_teams >= 4)
+               e.dom_pps_pink = pps_pink;
+}
+
+void dompoint_captured ()
+{SELFPARAM();
+       entity head;
+       float old_delay, old_team, real_team;
+
+       // now that the delay has expired, switch to the latest team to lay claim to this point
+       head = self.owner;
+
+       real_team = self.cnt;
+       self.cnt = -1;
+
+       dom_EventLog("taken", self.team, self.dmg_inflictor);
+       self.dmg_inflictor = world;
+
+       self.goalentity = head;
+       self.model = head.mdl;
+       self.modelindex = head.dmg;
+       self.skin = head.skin;
+
+       float points, wait_time;
+       if (autocvar_g_domination_point_amt)
+               points = autocvar_g_domination_point_amt;
+       else
+               points = self.frags;
+       if (autocvar_g_domination_point_rate)
+               wait_time = autocvar_g_domination_point_rate;
+       else
+               wait_time = self.wait;
+
+       if(domination_roundbased)
+               bprint(sprintf("^3%s^3%s\n", head.netname, self.message));
+       else
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time);
+
+       if(self.enemy.playerid == self.enemy_playerid)
+               PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
+       else
+               self.enemy = world;
+
+       if (head.noise != "")
+               if(self.enemy)
+                       _sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
+               else
+                       _sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
+       if (head.noise1 != "")
+               play2all(head.noise1);
+
+       self.delay = time + wait_time;
+
+       // do trigger work
+       old_delay = self.delay;
+       old_team = self.team;
+       self.team = real_team;
+       self.delay = 0;
+       activator = self;
+       SUB_UseTargets ();
+       self.delay = old_delay;
+       self.team = old_team;
+
+       entity msg = WP_DomNeut;
+       switch(self.team)
+       {
+               case NUM_TEAM_1: msg = WP_DomRed; break;
+               case NUM_TEAM_2: msg = WP_DomBlue; break;
+               case NUM_TEAM_3: msg = WP_DomYellow; break;
+               case NUM_TEAM_4: msg = WP_DomPink; break;
+       }
+
+       WaypointSprite_UpdateSprites(self.sprite, msg, WP_Null, WP_Null);
+
+       total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
+       for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
+       {
+               if (autocvar_g_domination_point_amt)
+                       points = autocvar_g_domination_point_amt;
+               else
+                       points = head.frags;
+               if (autocvar_g_domination_point_rate)
+                       wait_time = autocvar_g_domination_point_rate;
+               else
+                       wait_time = head.wait;
+               switch(head.goalentity.team)
+               {
+                       case NUM_TEAM_1:
+                               pps_red += points/wait_time;
+                               break;
+                       case NUM_TEAM_2:
+                               pps_blue += points/wait_time;
+                               break;
+                       case NUM_TEAM_3:
+                               pps_yellow += points/wait_time;
+                               break;
+                       case NUM_TEAM_4:
+                               pps_pink += points/wait_time;
+                               break;
+               }
+               total_pps += points/wait_time;
+       }
+
+       WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
+       WaypointSprite_Ping(self.sprite);
+
+       self.captime = time;
+
+       FOR_EACH_REALCLIENT(head)
+               set_dom_state(head);
+}
+
+void AnimateDomPoint()
+{SELFPARAM();
+       if(self.pain_finished > time)
+               return;
+       self.pain_finished = time + self.t_width;
+       if(self.nextthink > self.pain_finished)
+               self.nextthink = self.pain_finished;
+
+       self.frame = self.frame + 1;
+       if(self.frame > self.t_length)
+               self.frame = 0;
+}
+
+void dompointthink()
+{SELFPARAM();
+       float fragamt;
+
+       self.nextthink = time + 0.1;
+
+       //self.frame = self.frame + 1;
+       //if(self.frame > 119)
+       //      self.frame = 0;
+       AnimateDomPoint();
+
+       // give points
+
+       if (gameover || self.delay > time || time < game_starttime)     // game has ended, don't keep giving points
+               return;
+
+       if(autocvar_g_domination_point_rate)
+               self.delay = time + autocvar_g_domination_point_rate;
+       else
+               self.delay = time + self.wait;
+
+       // give credit to the team
+       // NOTE: this defaults to 0
+       if (!domination_roundbased)
+       if (self.goalentity.netname != "")
+       {
+               if(autocvar_g_domination_point_amt)
+                       fragamt = autocvar_g_domination_point_amt;
+               else
+                       fragamt = self.frags;
+               TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
+               TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
+
+               // give credit to the individual player, if he is still there
+               if (self.enemy.playerid == self.enemy_playerid)
+               {
+                       PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
+                       PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
+               }
+               else
+                       self.enemy = world;
+       }
+}
+
+void dompointtouch()
+{SELFPARAM();
+       entity head;
+       if (!IS_PLAYER(other))
+               return;
+       if (other.health < 1)
+               return;
+
+       if(round_handler_IsActive() && !round_handler_IsRoundStarted())
+               return;
+
+       if(time < self.captime + 0.3)
+               return;
+
+       // only valid teams can claim it
+       head = find(world, classname, "dom_team");
+       while (head && head.team != other.team)
+               head = find(head, classname, "dom_team");
+       if (!head || head.netname == "" || head == self.goalentity)
+               return;
+
+       // delay capture
+
+       self.team = self.goalentity.team; // this stores the PREVIOUS team!
+
+       self.cnt = other.team;
+       self.owner = head; // team to switch to after the delay
+       self.dmg_inflictor = other;
+
+       // self.state = 1;
+       // self.delay = time + cvar("g_domination_point_capturetime");
+       //self.nextthink = time + cvar("g_domination_point_capturetime");
+       //self.think = dompoint_captured;
+
+       // go to neutral team in the mean time
+       head = find(world, classname, "dom_team");
+       while (head && head.netname != "")
+               head = find(head, classname, "dom_team");
+       if(head == world)
+               return;
+
+       WaypointSprite_UpdateSprites(self.sprite, WP_DomNeut, WP_Null, WP_Null);
+       WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
+       WaypointSprite_Ping(self.sprite);
+
+       self.goalentity = head;
+       self.model = head.mdl;
+       self.modelindex = head.dmg;
+       self.skin = head.skin;
+
+       self.enemy = other; // individual player scoring
+       self.enemy_playerid = other.playerid;
+       dompoint_captured();
+}
+
+void dom_controlpoint_setup()
+{SELFPARAM();
+       entity head;
+       // find the spawnfunc_dom_team representing unclaimed points
+       head = find(world, classname, "dom_team");
+       while(head && head.netname != "")
+               head = find(head, classname, "dom_team");
+       if (!head)
+               objerror("no spawnfunc_dom_team with netname \"\" found\n");
+
+       // copy important properties from spawnfunc_dom_team entity
+       self.goalentity = head;
+       _setmodel(self, head.mdl); // precision already set
+       self.skin = head.skin;
+
+       self.cnt = -1;
+
+       if(self.message == "")
+               self.message = " has captured a control point";
+
+       if(self.frags <= 0)
+               self.frags = 1;
+       if(self.wait <= 0)
+               self.wait = 5;
+
+       float points, waittime;
+       if (autocvar_g_domination_point_amt)
+               points = autocvar_g_domination_point_amt;
+       else
+               points = self.frags;
+       if (autocvar_g_domination_point_rate)
+               waittime = autocvar_g_domination_point_rate;
+       else
+               waittime = self.wait;
+
+       total_pps += points/waittime;
+
+       if(!self.t_width)
+               self.t_width = 0.02; // frame animation rate
+       if(!self.t_length)
+               self.t_length = 239; // maximum frame
+
+       self.think = dompointthink;
+       self.nextthink = time;
+       self.touch = dompointtouch;
+       self.solid = SOLID_TRIGGER;
+       self.flags = FL_ITEM;
+       setsize(self, '-32 -32 -32', '32 32 32');
+       setorigin(self, self.origin + '0 0 20');
+       droptofloor();
+
+       waypoint_spawnforitem(self);
+       WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT);
+}
+
+float total_controlpoints;
+void Domination_count_controlpoints()
+{
+       entity e;
+       total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
+       for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
+       {
+               ++total_controlpoints;
+               redowned += (e.goalentity.team == NUM_TEAM_1);
+               blueowned += (e.goalentity.team == NUM_TEAM_2);
+               yellowowned += (e.goalentity.team == NUM_TEAM_3);
+               pinkowned += (e.goalentity.team == NUM_TEAM_4);
+       }
+}
+
+float Domination_GetWinnerTeam()
+{
+       float winner_team = 0;
+       if(redowned == total_controlpoints)
+               winner_team = NUM_TEAM_1;
+       if(blueowned == total_controlpoints)
+       {
+               if(winner_team) return 0;
+               winner_team = NUM_TEAM_2;
+       }
+       if(yellowowned == total_controlpoints)
+       {
+               if(winner_team) return 0;
+               winner_team = NUM_TEAM_3;
+       }
+       if(pinkowned == total_controlpoints)
+       {
+               if(winner_team) return 0;
+               winner_team = NUM_TEAM_4;
+       }
+       if(winner_team)
+               return winner_team;
+       return -1; // no control points left?
+}
+
+#define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
+#define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
+float Domination_CheckWinner()
+{
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
+               round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
+               return 1;
+       }
+
+       Domination_count_controlpoints();
+
+       float winner_team = Domination_GetWinnerTeam();
+
+       if(winner_team == -1)
+               return 0;
+
+       if(winner_team > 0)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
+               TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
+       }
+       else if(winner_team == -1)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
+       }
+
+       round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
+
+       return 1;
+}
+
+float Domination_CheckPlayers()
+{
+       return 1;
+}
+
+void Domination_RoundStart()
+{
+       entity e;
+       FOR_EACH_PLAYER(e)
+               e.player_blocked = 0;
+}
+
+//go to best items, or control points you don't own
+void havocbot_role_dom()
+{SELFPARAM();
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       if (self.bot_strategytime < time)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+               havocbot_goalrating_controlpoints(10000, self.origin, 15000);
+               havocbot_goalrating_items(8000, self.origin, 8000);
+               //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
+               //havocbot_goalrating_waypoints(1, self.origin, 1000);
+               navigation_goalrating_end();
+       }
+}
+
+MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
+{
+       // fallback?
+       ret_float = domination_teams;
+       ret_string = "dom_team";
+
+       entity head = find(world, classname, ret_string);
+       while(head)
+       {
+               if(head.netname != "")
+               {
+                       switch(head.team)
+                       {
+                               case NUM_TEAM_1: c1 = 0; break;
+                               case NUM_TEAM_2: c2 = 0; break;
+                               case NUM_TEAM_3: c3 = 0; break;
+                               case NUM_TEAM_4: c4 = 0; break;
+                       }
+               }
+
+               head = find(head, classname, ret_string);
+       }
+
+       ret_string = string_null;
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(dom, reset_map_players)
+{SELFPARAM();
+       total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
+       entity e;
+       FOR_EACH_PLAYER(e)
+       {
+               setself(e);
+               PutClientInServer();
+               self.player_blocked = 1;
+               if(IS_REAL_CLIENT(self))
+                       set_dom_state(self);
+       }
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
+{SELFPARAM();
+       if(domination_roundbased)
+       if(!round_handler_IsRoundStarted())
+               self.player_blocked = 1;
+       else
+               self.player_blocked = 0;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(dom, ClientConnect)
+{SELFPARAM();
+       set_dom_state(self);
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
+{SELFPARAM();
+       self.havocbot_role = havocbot_role_dom;
+       return true;
+}
+
+/*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
+Control point for Domination gameplay.
+*/
+spawnfunc(dom_controlpoint)
+{
+       if(!g_domination)
+       {
+               remove(self);
+               return;
+       }
+       self.think = dom_controlpoint_setup;
+       self.nextthink = time + 0.1;
+       self.reset = dom_controlpoint_setup;
+
+       if(!self.scale)
+               self.scale = 0.6;
+
+       self.effects = self.effects | EF_LOWPRECISION;
+       if (autocvar_g_domination_point_fullbright)
+               self.effects |= EF_FULLBRIGHT;
+}
+
+/*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
+Team declaration for Domination gameplay, this allows you to decide what team
+names and control point models are used in your map.
+
+Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
+can have netname set!  The nameless team owns all control points at start.
+
+Keys:
+"netname"
+ Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
+"cnt"
+ Scoreboard color of the team (for example 4 is red and 13 is blue)
+"model"
+ Model to use for control points owned by this team (for example
+ "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
+ keycard)
+"skin"
+ Skin of the model to use (for team skins on a single model)
+"noise"
+ Sound to play when this team captures a point.
+ (this is a localized sound, like a small alarm or other effect)
+"noise1"
+ Narrator speech to play when this team captures a point.
+ (this is a global sound, like "Red team has captured a control point")
+*/
+
+spawnfunc(dom_team)
+{
+       if(!g_domination || autocvar_g_domination_teams_override >= 2)
+       {
+               remove(self);
+               return;
+       }
+       precache_model(self.model);
+       if (self.noise != "")
+               precache_sound(self.noise);
+       if (self.noise1 != "")
+               precache_sound(self.noise1);
+       self.classname = "dom_team";
+       _setmodel(self, self.model); // precision not needed
+       self.mdl = self.model;
+       self.dmg = self.modelindex;
+       self.model = "";
+       self.modelindex = 0;
+       // this would have to be changed if used in quakeworld
+       if(self.cnt)
+               self.team = self.cnt + 1; // WHY are these different anyway?
+}
+
+// scoreboard setup
+void ScoreRules_dom(float teams)
+{
+       if(domination_roundbased)
+       {
+               ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
+               ScoreInfo_SetLabel_TeamScore  (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
+               ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
+               ScoreRules_basics_end();
+       }
+       else
+       {
+               float sp_domticks, sp_score;
+               sp_score = sp_domticks = 0;
+               if(autocvar_g_domination_disable_frags)
+                       sp_domticks = SFL_SORT_PRIO_PRIMARY;
+               else
+                       sp_score = SFL_SORT_PRIO_PRIMARY;
+               ScoreRules_basics(teams, sp_score, sp_score, true);
+               ScoreInfo_SetLabel_TeamScore  (ST_DOM_TICKS,    "ticks",     sp_domticks);
+               ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS,    "ticks",     sp_domticks);
+               ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES,    "takes",     0);
+               ScoreRules_basics_end();
+       }
+}
+
+// code from here on is just to support maps that don't have control point and team entities
+void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
+{SELFPARAM();
+       setself(spawn());
+       self.classname = "dom_team";
+       self.netname = teamname;
+       self.cnt = teamcolor;
+       self.model = pointmodel;
+       self.skin = pointskin;
+       self.noise = capsound;
+       self.noise1 = capnarration;
+       self.message = capmessage;
+
+       // this code is identical to spawnfunc_dom_team
+       _setmodel(self, self.model); // precision not needed
+       self.mdl = self.model;
+       self.dmg = self.modelindex;
+       self.model = "";
+       self.modelindex = 0;
+       // this would have to be changed if used in quakeworld
+       self.team = self.cnt + 1;
+
+       //eprint(self);
+       setself(this);
+}
+
+void _spawnfunc_dom_controlpoint() { SELFPARAM(); spawnfunc_dom_controlpoint(self); }
+void dom_spawnpoint(vector org)
+{SELFPARAM();
+       setself(spawn());
+       self.classname = "dom_controlpoint";
+       self.think = _spawnfunc_dom_controlpoint;
+       self.nextthink = time;
+       setorigin(self, org);
+       spawnfunc_dom_controlpoint(this);
+       setself(this);
+}
+
+// spawn some default teams if the map is not set up for domination
+void dom_spawnteams(float teams)
+{
+       dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND(DOM_CLAIM), "", "Red team has captured a control point");
+       dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND(DOM_CLAIM), "", "Blue team has captured a control point");
+       if(teams >= 3)
+               dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND(DOM_CLAIM), "", "Yellow team has captured a control point");
+       if(teams >= 4)
+               dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND(DOM_CLAIM), "", "Pink team has captured a control point");
+       dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
+}
+
+void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
+{
+       // if no teams are found, spawn defaults
+       if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
+       {
+               LOG_INFO("No ""dom_team"" entities found on this map, creating them anyway.\n");
+               domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
+               dom_spawnteams(domination_teams);
+       }
+
+       CheckAllowedTeams(world);
+       domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
+
+       addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
+       addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
+       addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
+       if(domination_teams >= 3) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
+       if(domination_teams >= 4) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
+
+       domination_roundbased = autocvar_g_domination_roundbased;
+
+       ScoreRules_dom(domination_teams);
+
+       if(domination_roundbased)
+       {
+               round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
+               round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
+       }
+}
+
+void dom_Initialize()
+{
+       g_domination = true;
+       InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);
+}
+
+
+REGISTER_MUTATOR(dom, IS_GAMETYPE(DOMINATION))
+{
+       int fraglimit_override = autocvar_g_domination_point_limit;
+       if(autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
+               fraglimit_override = autocvar_g_domination_roundbased_point_limit;
+
+       ActivateTeamplay();
+       SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, -1, -1);
+       have_team_spawns = -1; // request team spawns
+
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+               dom_Initialize();
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/gamemode_freezetag.qc b/qcsrc/server/mutators/mutator/gamemode_freezetag.qc
new file mode 100644 (file)
index 0000000..912df15
--- /dev/null
@@ -0,0 +1,654 @@
+#ifndef GAMEMODE_FREEZETAG_H
+#define GAMEMODE_FREEZETAG_H
+
+.float freezetag_frozen_time;
+.float freezetag_frozen_timeout;
+const float ICE_MAX_ALPHA = 1;
+const float ICE_MIN_ALPHA = 0.1;
+float freezetag_teams;
+
+.float reviving; // temp var
+#endif
+
+#ifdef IMPLEMENTATION
+
+float autocvar_g_freezetag_frozen_maxtime;
+bool autocvar_g_freezetag_revive_nade;
+float autocvar_g_freezetag_revive_nade_health;
+int autocvar_g_freezetag_point_leadlimit;
+int autocvar_g_freezetag_point_limit;
+float autocvar_g_freezetag_revive_extra_size;
+float autocvar_g_freezetag_revive_speed;
+float autocvar_g_freezetag_revive_clearspeed;
+float autocvar_g_freezetag_round_timelimit;
+int autocvar_g_freezetag_teams;
+int autocvar_g_freezetag_teams_override;
+bool autocvar_g_freezetag_team_spawns;
+float autocvar_g_freezetag_warmup;
+
+const float SP_FREEZETAG_REVIVALS = 4;
+void freezetag_ScoreRules(float teams)
+{
+       ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, true); // SFL_SORT_PRIO_PRIMARY
+       ScoreInfo_SetLabel_PlayerScore(SP_FREEZETAG_REVIVALS, "revivals", 0);
+       ScoreRules_basics_end();
+}
+
+void freezetag_count_alive_players()
+{
+       entity e;
+       total_players = redalive = bluealive = yellowalive = pinkalive = 0;
+       FOR_EACH_PLAYER(e)
+       {
+               switch(e.team)
+               {
+                       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)
+       {
+               e.redalive_stat = redalive;
+               e.bluealive_stat = bluealive;
+               e.yellowalive_stat = yellowalive;
+               e.pinkalive_stat = pinkalive;
+       }
+
+       eliminatedPlayers.SendFlags |= 1;
+}
+#define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
+#define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == freezetag_teams)
+
+float freezetag_CheckTeams()
+{
+       static float prev_missing_teams_mask;
+       if(FREEZETAG_ALIVE_TEAMS_OK())
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               return 1;
+       }
+       if(total_players == 0)
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               return 0;
+       }
+       float missing_teams_mask = (!redalive) + (!bluealive) * 2;
+       if(freezetag_teams >= 3) missing_teams_mask += (!yellowalive) * 4;
+       if(freezetag_teams >= 4) missing_teams_mask += (!pinkalive) * 8;
+       if(prev_missing_teams_mask != missing_teams_mask)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
+               prev_missing_teams_mask = missing_teams_mask;
+       }
+       return 0;
+}
+
+float freezetag_getWinnerTeam()
+{
+       float winner_team = 0;
+       if(redalive >= 1)
+               winner_team = NUM_TEAM_1;
+       if(bluealive >= 1)
+       {
+               if(winner_team) return 0;
+               winner_team = NUM_TEAM_2;
+       }
+       if(yellowalive >= 1)
+       {
+               if(winner_team) return 0;
+               winner_team = NUM_TEAM_3;
+       }
+       if(pinkalive >= 1)
+       {
+               if(winner_team) return 0;
+               winner_team = NUM_TEAM_4;
+       }
+       if(winner_team)
+               return winner_team;
+       return -1; // no player left
+}
+
+float freezetag_CheckWinner()
+{
+       entity e;
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
+               FOR_EACH_PLAYER(e)
+               {
+                       e.freezetag_frozen_timeout = 0;
+                       nades_Clear(e);
+               }
+               round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+               return 1;
+       }
+
+       if(FREEZETAG_ALIVE_TEAMS() > 1)
+               return 0;
+
+       float winner_team;
+       winner_team = freezetag_getWinnerTeam();
+       if(winner_team > 0)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
+               TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
+       }
+       else if(winner_team == -1)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
+       }
+
+       FOR_EACH_PLAYER(e)
+       {
+               e.freezetag_frozen_timeout = 0;
+               nades_Clear(e);
+       }
+       round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+       return 1;
+}
+
+entity freezetag_LastPlayerForTeam()
+{SELFPARAM();
+       entity pl, last_pl = world;
+       FOR_EACH_PLAYER(pl)
+       {
+               if(pl.health >= 1)
+               if(!pl.frozen)
+               if(pl != self)
+               if(pl.team == self.team)
+               if(!last_pl)
+                       last_pl = pl;
+               else
+                       return world;
+       }
+       return last_pl;
+}
+
+void freezetag_LastPlayerForTeam_Notify()
+{
+       if(round_handler_IsActive())
+       if(round_handler_IsRoundStarted())
+       {
+               entity pl = freezetag_LastPlayerForTeam();
+               if(pl)
+                       Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
+       }
+}
+
+void freezetag_Add_Score(entity attacker)
+{SELFPARAM();
+       if(attacker == self)
+       {
+               // you froze your own dumb self
+               // counted as "suicide" already
+               PlayerScore_Add(self, SP_SCORE, -1);
+       }
+       else if(IS_PLAYER(attacker))
+       {
+               // got frozen by an enemy
+               // counted as "kill" and "death" already
+               PlayerScore_Add(self, SP_SCORE, -1);
+               PlayerScore_Add(attacker, SP_SCORE, +1);
+       }
+       // else nothing - got frozen by the game type rules themselves
+}
+
+void freezetag_Freeze(entity attacker)
+{SELFPARAM();
+       if(self.frozen)
+               return;
+
+       if(autocvar_g_freezetag_frozen_maxtime > 0)
+               self.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
+
+       Freeze(self, 0, 1, true);
+
+       freezetag_count_alive_players();
+
+       freezetag_Add_Score(attacker);
+}
+
+void freezetag_Unfreeze(entity attacker)
+{SELFPARAM();
+       self.freezetag_frozen_time = 0;
+       self.freezetag_frozen_timeout = 0;
+
+       Unfreeze(self);
+}
+
+float freezetag_isEliminated(entity e)
+{
+       if(IS_PLAYER(e) && (e.frozen == 1 || e.deadflag != DEAD_NO))
+               return true;
+       return false;
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+void() havocbot_role_ft_freeing;
+void() havocbot_role_ft_offense;
+
+void havocbot_goalrating_freeplayers(float ratingscale, vector org, float sradius)
+{SELFPARAM();
+       entity head;
+       float distance;
+
+       FOR_EACH_PLAYER(head)
+       {
+               if ((head != self) && (head.team == self.team))
+               {
+                       if (head.frozen == 1)
+                       {
+                               distance = vlen(head.origin - org);
+                               if (distance > sradius)
+                                       continue;
+                               navigation_routerating(head, ratingscale, 2000);
+                       }
+                       else
+                       {
+                               // If teamate is not frozen still seek them out as fight better
+                               // in a group.
+                               navigation_routerating(head, ratingscale/3, 2000);
+                       }
+               }
+       }
+}
+
+void havocbot_role_ft_offense()
+{SELFPARAM();
+       entity head;
+       float unfrozen;
+
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + random() * 10 + 20;
+
+       // Count how many players on team are unfrozen.
+       unfrozen = 0;
+       FOR_EACH_PLAYER(head)
+       {
+               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.frozen)) || (time > self.havocbot_role_timeout))
+       {
+               LOG_TRACE("changing role to freeing\n");
+               self.havocbot_role = havocbot_role_ft_freeing;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (time > self.bot_strategytime)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               havocbot_goalrating_items(10000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
+               havocbot_goalrating_freeplayers(9000, self.origin, 10000);
+               //havocbot_goalrating_waypoints(1, self.origin, 1000);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_ft_freeing()
+{SELFPARAM();
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + random() * 10 + 20;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               LOG_TRACE("changing role to offense\n");
+               self.havocbot_role = havocbot_role_ft_offense;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (time > self.bot_strategytime)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               havocbot_goalrating_items(8000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(10000, self.origin, 10000);
+               havocbot_goalrating_freeplayers(20000, self.origin, 10000);
+               //havocbot_goalrating_waypoints(1, self.origin, 1000);
+               navigation_goalrating_end();
+       }
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+void ft_RemovePlayer()
+{SELFPARAM();
+       self.health = 0; // neccessary to update correctly alive stats
+       if(!self.frozen)
+               freezetag_LastPlayerForTeam_Notify();
+       freezetag_Unfreeze(world);
+       freezetag_count_alive_players();
+}
+
+MUTATOR_HOOKFUNCTION(ft, ClientDisconnect)
+{SELFPARAM();
+       ft_RemovePlayer();
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver)
+{SELFPARAM();
+       ft_RemovePlayer();
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ft, PlayerDies)
+{SELFPARAM();
+       if(round_handler_IsActive())
+       if(round_handler_CountdownRunning())
+       {
+               if(self.frozen)
+                       freezetag_Unfreeze(world);
+               freezetag_count_alive_players();
+               return 1; // let the player die so that he can respawn whenever he wants
+       }
+
+       // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
+       // you succeed changing team through the menu: you both really die (gibbing) and get frozen
+       if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
+               || frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id)
+       {
+               // let the player die, he will be automatically frozen when he respawns
+               if(self.frozen != 1)
+               {
+                       freezetag_Add_Score(frag_attacker);
+                       freezetag_count_alive_players();
+                       freezetag_LastPlayerForTeam_Notify();
+               }
+               else
+                       freezetag_Unfreeze(world); // remove ice
+               self.health = 0; // Unfreeze resets health
+               self.freezetag_frozen_timeout = -2; // freeze on respawn
+               return 1;
+       }
+
+       if(self.frozen)
+               return 1;
+
+       freezetag_Freeze(frag_attacker);
+       freezetag_LastPlayerForTeam_Notify();
+
+       if(frag_attacker == frag_target || frag_attacker == world)
+       {
+               if(IS_PLAYER(frag_target))
+                       Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
+       }
+       else
+       {
+               if(IS_PLAYER(frag_target))
+                       Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_FROZEN, frag_attacker.netname);
+               if(IS_PLAYER(frag_attacker))
+                       Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_FREEZETAG_FREEZE, frag_target.netname);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
+       }
+
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
+{SELFPARAM();
+       if(self.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
+               return 1; // do nothing, round is starting right now
+
+       if(self.freezetag_frozen_timeout == -2) // player was dead
+       {
+               freezetag_Freeze(world);
+               return 1;
+       }
+
+       freezetag_count_alive_players();
+
+       if(round_handler_IsActive())
+       if(round_handler_IsRoundStarted())
+       {
+               Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
+               freezetag_Freeze(world);
+       }
+
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ft, reset_map_players)
+{SELFPARAM();
+       entity e;
+       FOR_EACH_PLAYER(e)
+       {
+               e.killcount = 0;
+               e.freezetag_frozen_timeout = -1;
+               setself(e);
+               PutClientInServer();
+               e.freezetag_frozen_timeout = 0;
+       }
+       freezetag_count_alive_players();
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+       frag_score = 0; // no frags counted in Freeze Tag
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST)
+{SELFPARAM();
+       float n;
+
+       if(gameover)
+               return 1;
+
+       if(self.frozen == 1)
+       {
+               // keep health = 1
+               self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
+       }
+
+       if(round_handler_IsActive())
+       if(!round_handler_IsRoundStarted())
+               return 1;
+
+       entity o;
+       o = world;
+       //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;
+       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.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(!o)
+                               o = other;
+                       if(self.frozen == 1)
+                               other.reviving = true;
+                       ++n;
+               }
+       }
+
+       if(n && self.frozen == 1) // 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 * ((warmup_stage) ? warmup_start_health : start_health));
+
+               if(self.revive_progress >= 1)
+               {
+                       freezetag_Unfreeze(self);
+                       freezetag_count_alive_players();
+
+                       if(n == -1)
+                       {
+                               Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
+                               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, self.netname, autocvar_g_freezetag_frozen_maxtime);
+                               return 1;
+                       }
+
+                       // EVERY team mate nearby gets a point (even if multiple!)
+                       FOR_EACH_PLAYER(other)
+                       {
+                               if(other.reviving)
+                               {
+                                       PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1);
+                                       PlayerScore_Add(other, SP_SCORE, +1);
+
+                                       nades_GiveBonus(other,autocvar_g_nades_bonus_score_low);
+                               }
+                       }
+
+                       Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
+                       Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname);
+                       Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED, self.netname, o.netname);
+               }
+
+               FOR_EACH_PLAYER(other)
+               {
+                       if(other.reviving)
+                       {
+                               other.revive_progress = self.revive_progress;
+                               other.reviving = false;
+                       }
+               }
+       }
+       else if(!n && self.frozen == 1) // only if no teammate is nearby will we reset
+       {
+               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));
+       }
+       else if(!n && !self.frozen)
+       {
+               self.revive_progress = 0; // thawing nobody
+       }
+
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(ft, SetStartItems)
+{
+       start_items &= ~IT_UNLIMITED_AMMO;
+       //start_health       = warmup_start_health       = cvar("g_lms_start_health");
+       //start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
+       start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
+       start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
+       start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
+       start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
+       start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
+       start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
+
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
+{SELFPARAM();
+       if (!self.deadflag)
+       {
+               if (random() < 0.5)
+                       self.havocbot_role = havocbot_role_ft_freeing;
+               else
+                       self.havocbot_role = havocbot_role_ft_offense;
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, GetTeamCount, CBC_ORDER_EXCLUSIVE)
+{
+       ret_float = freezetag_teams;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
+{
+       // most weapons arena
+       if(ret_string == "0" || ret_string == "")
+               ret_string = "most";
+       return false;
+}
+
+void freezetag_Initialize()
+{
+       freezetag_teams = autocvar_g_freezetag_teams_override;
+       if(freezetag_teams < 2)
+               freezetag_teams = autocvar_g_freezetag_teams;
+       freezetag_teams = bound(2, freezetag_teams, 4);
+       freezetag_ScoreRules(freezetag_teams);
+
+       round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
+       round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+
+       addstat(STAT_REDALIVE, AS_INT, redalive_stat);
+       addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
+       addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
+       addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
+
+       EliminatedPlayers_Init(freezetag_isEliminated);
+}
+
+REGISTER_MUTATOR(ft, g_freezetag)
+{
+       ActivateTeamplay();
+       SetLimits(autocvar_g_freezetag_point_limit, autocvar_g_freezetag_point_leadlimit, -1, -1);
+
+       if(autocvar_g_freezetag_team_spawns)
+               have_team_spawns = -1; // request team spawns
+
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+               freezetag_Initialize();
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back freezetag_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/gamemode_invasion.qc b/qcsrc/server/mutators/mutator/gamemode_invasion.qc
new file mode 100644 (file)
index 0000000..4dc5ed6
--- /dev/null
@@ -0,0 +1,525 @@
+#ifndef GAMEMODE_INVASION_H
+#define GAMEMODE_INVASION_H
+
+float inv_numspawned;
+float inv_maxspawned;
+float inv_roundcnt;
+float inv_maxrounds;
+float inv_numkilled;
+float inv_lastcheck;
+float inv_maxcurrent;
+
+float invasion_teams;
+float inv_monsters_perteam[17];
+
+float inv_monsterskill;
+
+const float ST_INV_KILLS = 1;
+#endif
+
+#ifdef IMPLEMENTATION
+
+#include "../../../common/monsters/spawn.qh"
+#include "../../../common/monsters/sv_monsters.qh"
+
+#include "../../teamplay.qh"
+
+bool g_invasion;
+
+float autocvar_g_invasion_round_timelimit;
+int autocvar_g_invasion_teams;
+bool 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;
+int autocvar_g_invasion_monster_count;
+bool autocvar_g_invasion_zombies_only;
+float autocvar_g_invasion_spawn_delay;
+
+spawnfunc(invasion_spawnpoint)
+{
+       if(!g_invasion) { remove(self); return; }
+
+       self.classname = "invasion_spawnpoint";
+
+       if(autocvar_g_invasion_zombies_only) // precache only if it hasn't been already
+       if(self.monsterid) {
+               Monster mon = get_monsterinfo(self.monsterid);
+               mon.mr_precache(mon);
+       }
+}
+
+float invasion_PickMonster(float supermonster_count)
+{
+       if(autocvar_g_invasion_zombies_only)
+               return MON_ZOMBIE.monsterid;
+
+       float i;
+       entity mon;
+
+       RandomSelection_Init();
+
+       for(i = MON_FIRST; i <= MON_LAST; ++i)
+       {
+               mon = get_monsterinfo(i);
+               if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM) || ((mon.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
+                       continue; // flying/swimming monsters not yet supported
+
+               RandomSelection_Add(world, i, string_null, 1, 1);
+       }
+
+       return RandomSelection_chosen_float;
+}
+
+entity invasion_PickSpawn()
+{
+       entity e;
+
+       RandomSelection_Init();
+
+       for(e = world;(e = find(e, classname, "invasion_spawnpoint")); )
+       {
+               RandomSelection_Add(e, 0, string_null, 1, ((time >= e.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
+               e.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
+       }
+
+       return RandomSelection_chosen_ent;
+}
+
+void invasion_SpawnChosenMonster(float mon)
+{
+       entity spawn_point, monster;
+
+       spawn_point = invasion_PickSpawn();
+
+       if(spawn_point == world)
+       {
+               LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations\n");
+               entity e = spawn();
+               setsize(e, (get_monsterinfo(mon)).mins, (get_monsterinfo(mon)).maxs);
+
+               if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+                       monster = spawnmonster("", mon, world, world, e.origin, false, false, 2);
+               else return;
+
+               e.think = SUB_Remove;
+               e.nextthink = time + 0.1;
+       }
+       else
+               monster = spawnmonster("", ((spawn_point.monsterid) ? spawn_point.monsterid : mon), spawn_point, spawn_point, spawn_point.origin, false, false, 2);
+
+       if(spawn_point) monster.target2 = spawn_point.target2;
+       monster.spawnshieldtime = time;
+       if(spawn_point && spawn_point.target_range) monster.target_range = spawn_point.target_range;
+
+       if(teamplay)
+       if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0)
+               monster.team = spawn_point.team;
+       else
+       {
+               RandomSelection_Init();
+               if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_Add(world, NUM_TEAM_1, string_null, 1, 1);
+               if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_Add(world, NUM_TEAM_2, string_null, 1, 1);
+               if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_Add(world, NUM_TEAM_3, string_null, 1, 1); }
+               if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_Add(world, NUM_TEAM_4, string_null, 1, 1); }
+
+               monster.team = RandomSelection_chosen_float;
+       }
+
+       if(teamplay)
+       {
+               monster_setupcolors(monster);
+
+               if(monster.sprite)
+               {
+                       WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0'));
+
+                       monster.sprite.team = 0;
+                       monster.sprite.SendFlags |= 1;
+               }
+       }
+
+       monster.monster_attack = false; // it's the player's job to kill all the monsters
+
+       if(inv_roundcnt >= inv_maxrounds)
+               monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses
+}
+
+void invasion_SpawnMonsters(float supermonster_count)
+{
+       float chosen_monster = invasion_PickMonster(supermonster_count);
+
+       invasion_SpawnChosenMonster(chosen_monster);
+}
+
+float Invasion_CheckWinner()
+{
+       entity head;
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               FOR_EACH_MONSTER(head)
+                       Monster_Remove(head);
+
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
+               round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+               return 1;
+       }
+
+       float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;
+
+       FOR_EACH_MONSTER(head) if(head.health > 0)
+       {
+               if((get_monsterinfo(head.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
+                       ++supermonster_count;
+               ++total_alive_monsters;
+
+               if(teamplay)
+               switch(head.team)
+               {
+                       case NUM_TEAM_1: ++red_alive; break;
+                       case NUM_TEAM_2: ++blue_alive; break;
+                       case NUM_TEAM_3: ++yellow_alive; break;
+                       case NUM_TEAM_4: ++pink_alive; break;
+               }
+       }
+
+       if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
+       {
+               if(time >= inv_lastcheck)
+               {
+                       invasion_SpawnMonsters(supermonster_count);
+                       inv_lastcheck = time + autocvar_g_invasion_spawn_delay;
+               }
+
+               return 0;
+       }
+
+       if(inv_numspawned < 1)
+               return 0; // nothing has spawned yet
+
+       if(teamplay)
+       {
+               if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1)
+                       return 0;
+       }
+       else if(inv_numkilled < inv_maxspawned)
+               return 0;
+
+       entity winner = world;
+       float winning_score = 0, winner_team = 0;
+
+
+       if(teamplay)
+       {
+               if(red_alive > 0) { winner_team = NUM_TEAM_1; }
+               if(blue_alive > 0)
+               if(winner_team) { winner_team = 0; }
+               else { winner_team = NUM_TEAM_2; }
+               if(yellow_alive > 0)
+               if(winner_team) { winner_team = 0; }
+               else { winner_team = NUM_TEAM_3; }
+               if(pink_alive > 0)
+               if(winner_team) { winner_team = 0; }
+               else { winner_team = NUM_TEAM_4; }
+       }
+       else
+       FOR_EACH_PLAYER(head)
+       {
+               float cs = PlayerScore_Add(head, SP_KILLS, 0);
+               if(cs > winning_score)
+               {
+                       winning_score = cs;
+                       winner = head;
+               }
+       }
+
+       FOR_EACH_MONSTER(head)
+               Monster_Remove(head);
+
+       if(teamplay)
+       {
+               if(winner_team)
+               {
+                       Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
+                       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
+               }
+       }
+       else if(winner)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
+       }
+
+       round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+
+       return 1;
+}
+
+float Invasion_CheckPlayers()
+{
+       return true;
+}
+
+void Invasion_RoundStart()
+{
+       entity e;
+       float numplayers = 0;
+       FOR_EACH_PLAYER(e)
+       {
+               e.player_blocked = 0;
+               ++numplayers;
+       }
+
+       if(inv_roundcnt < inv_maxrounds)
+               inv_roundcnt += 1; // a limiter to stop crazy counts
+
+       inv_monsterskill = inv_roundcnt + max(1, numplayers * 0.3);
+
+       inv_maxcurrent = 0;
+       inv_numspawned = 0;
+       inv_numkilled = 0;
+
+       inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
+
+       if(teamplay)
+       {
+               DistributeEvenly_Init(inv_maxspawned, invasion_teams);
+               inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1);
+               inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1);
+               if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1);
+               if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(inv, MonsterDies)
+{SELFPARAM();
+       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
+       {
+               inv_numkilled += 1;
+               inv_maxcurrent -= 1;
+               if(teamplay) { inv_monsters_perteam[self.team] -= 1; }
+
+               if(IS_PLAYER(frag_attacker))
+               if(SAME_TEAM(frag_attacker, self)) // in non-teamplay modes, same team = same player, so this works
+                       PlayerScore_Add(frag_attacker, SP_KILLS, -1);
+               else
+               {
+                       PlayerScore_Add(frag_attacker, SP_KILLS, +1);
+                       if(teamplay)
+                               TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(inv, MonsterSpawn)
+{SELFPARAM();
+       if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
+               return true;
+
+       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
+       {
+               inv_numspawned += 1;
+               inv_maxcurrent += 1;
+       }
+
+       self.monster_skill = inv_monsterskill;
+
+       if((get_monsterinfo(self.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, self.monster_name);
+
+       self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(inv, OnEntityPreSpawn)
+{SELFPARAM();
+       if(startsWith(self.classname, "monster_"))
+       if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
+               return true;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(inv, SV_StartFrame)
+{
+       monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
+       monsters_killed = inv_numkilled;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(inv, PlayerRegen)
+{
+       // no regeneration in invasion
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, PlayerSpawn)
+{SELFPARAM();
+       self.bot_attack = false;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(inv, PlayerDamage_Calculate)
+{
+       if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target)
+       {
+               frag_damage = 0;
+               frag_force = '0 0 0';
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(inv, SV_ParseClientCommand)
+{SELFPARAM();
+       if(MUTATOR_RETURNVALUE) // command was already handled?
+               return false;
+
+       if(cmd_name == "debuginvasion")
+       {
+               sprint(self, strcat("inv_maxspawned = ", ftos(inv_maxspawned), "\n"));
+               sprint(self, strcat("inv_numspawned = ", ftos(inv_numspawned), "\n"));
+               sprint(self, strcat("inv_numkilled = ", ftos(inv_numkilled), "\n"));
+               sprint(self, strcat("inv_roundcnt = ", ftos(inv_roundcnt), "\n"));
+               sprint(self, strcat("monsters_total = ", ftos(monsters_total), "\n"));
+               sprint(self, strcat("monsters_killed = ", ftos(monsters_killed), "\n"));
+               sprint(self, strcat("inv_monsterskill = ", ftos(inv_monsterskill), "\n"));
+
+               return true;
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(inv, BotShouldAttack)
+{
+       if(!IS_MONSTER(checkentity))
+               return true;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(inv, SetStartItems)
+{
+       start_health = 200;
+       start_armorvalue = 200;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid)
+{
+       if(IS_MONSTER(frag_target))
+               return MUT_ACCADD_INVALID;
+       return MUT_ACCADD_INDIFFERENT;
+}
+
+MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning)
+{
+       // monster spawning disabled during an invasion
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, GetTeamCount, CBC_ORDER_EXCLUSIVE)
+{
+       ret_float = invasion_teams;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(inv, AllowMobButcher)
+{
+       ret_string = "This command does not work during an invasion!";
+       return true;
+}
+
+void invasion_ScoreRules(float inv_teams)
+{
+       if(inv_teams) { CheckAllowedTeams(world); }
+       ScoreRules_basics(inv_teams, 0, 0, false);
+       if(inv_teams) ScoreInfo_SetLabel_TeamScore(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
+       ScoreRules_basics_end();
+}
+
+void invasion_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
+{
+       if(autocvar_g_invasion_teams)
+               invasion_teams = bound(2, autocvar_g_invasion_teams, 4);
+       else
+               invasion_teams = 0;
+
+       independent_players = 1; // to disable extra useless scores
+
+       invasion_ScoreRules(invasion_teams);
+
+       independent_players = 0;
+
+       round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart);
+       round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+
+       inv_roundcnt = 0;
+       inv_maxrounds = 15; // 15?
+}
+
+void invasion_Initialize()
+{
+       if(autocvar_g_invasion_zombies_only) {
+               Monster mon = MON_ZOMBIE;
+               mon.mr_precache(mon);
+       } else
+       {
+               float i;
+               entity mon;
+               for(i = MON_FIRST; i <= MON_LAST; ++i)
+               {
+                       mon = get_monsterinfo(i);
+                       if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM))
+                               continue; // flying/swimming monsters not yet supported
+
+                       mon.mr_precache(mon);
+               }
+       }
+
+       InitializeEntity(world, invasion_DelayedInit, INITPRIO_GAMETYPE);
+}
+
+REGISTER_MUTATOR(inv, IS_GAMETYPE(INVASION))
+{
+       SetLimits(autocvar_g_invasion_point_limit, -1, -1, -1);
+       if(autocvar_g_invasion_teams >= 2)
+       {
+               ActivateTeamplay();
+               if(autocvar_g_invasion_team_spawns)
+                       have_team_spawns = -1; // request team spawns
+       }
+
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+               g_invasion = true;
+               invasion_Initialize();
+
+               cvar_settemp("g_monsters", "1");
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back invasion_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/gamemode_keepaway.qc b/qcsrc/server/mutators/mutator/gamemode_keepaway.qc
new file mode 100644 (file)
index 0000000..855b766
--- /dev/null
@@ -0,0 +1,500 @@
+#ifndef GAMEMODE_KEEPAWAY_H
+#define GAMEMODE_KEEPAWAY_H
+
+
+entity ka_ball;
+
+const float SP_KEEPAWAY_PICKUPS = 4;
+const float SP_KEEPAWAY_CARRIERKILLS = 5;
+const float SP_KEEPAWAY_BCTIME = 6;
+
+void() havocbot_role_ka_carrier;
+void() havocbot_role_ka_collector;
+
+void ka_DropEvent(entity plyr);
+#endif
+
+#ifdef IMPLEMENTATION
+
+int autocvar_g_keepaway_ballcarrier_effects;
+float autocvar_g_keepaway_ballcarrier_damage;
+float autocvar_g_keepaway_ballcarrier_force;
+float autocvar_g_keepaway_ballcarrier_highspeed;
+float autocvar_g_keepaway_ballcarrier_selfdamage;
+float autocvar_g_keepaway_ballcarrier_selfforce;
+float autocvar_g_keepaway_noncarrier_damage;
+float autocvar_g_keepaway_noncarrier_force;
+float autocvar_g_keepaway_noncarrier_selfdamage;
+float autocvar_g_keepaway_noncarrier_selfforce;
+bool autocvar_g_keepaway_noncarrier_warn;
+int autocvar_g_keepaway_score_bckill;
+int autocvar_g_keepaway_score_killac;
+int autocvar_g_keepaway_score_timepoints;
+float autocvar_g_keepaway_score_timeinterval;
+float autocvar_g_keepawayball_damageforcescale;
+int autocvar_g_keepawayball_effects;
+float autocvar_g_keepawayball_respawntime;
+int autocvar_g_keepawayball_trail_color;
+
+float ka_ballcarrier_waypointsprite_visible_for_player(entity e) // runs on waypoints which are attached to ballcarriers, updates once per frame
+{
+       if(e.ballcarried)
+               if(IS_SPEC(other))
+                       return false; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen
+
+       // TODO: Make the ballcarrier lack a waypointsprite whenever they have the invisibility powerup
+
+       return true;
+}
+
+void ka_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
+{
+       if(autocvar_sv_eventlog)
+               GameLogEcho(strcat(":ka:", mode, ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void ka_TouchEvent();
+void ka_RespawnBall() // runs whenever the ball needs to be relocated
+{SELFPARAM();
+       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))
+       {
+               entity spot = SelectSpawnPoint(true);
+               setorigin(self, spot.origin);
+               self.angles = spot.angles;
+       }
+
+       makevectors(self.angles);
+       self.movetype = MOVETYPE_BOUNCE;
+       self.velocity = '0 0 200';
+       self.angles = '0 0 0';
+       self.effects = autocvar_g_keepawayball_effects;
+       self.touch = ka_TouchEvent;
+       self.think = ka_RespawnBall;
+       self.nextthink = time + autocvar_g_keepawayball_respawntime;
+
+       Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1);
+       Send_Effect(EFFECT_ELECTRO_COMBO, self.origin, '0 0 0', 1);
+
+       WaypointSprite_Spawn(WP_KaBall, 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
+       WaypointSprite_Ping(self.waypointsprite_attachedforcarrier);
+
+       sound(self, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+}
+
+void ka_TimeScoring()
+{SELFPARAM();
+       if(self.owner.ballcarried)
+       { // add points for holding the ball after a certain amount of time
+               if(autocvar_g_keepaway_score_timepoints)
+                       PlayerScore_Add(self.owner, SP_SCORE, autocvar_g_keepaway_score_timepoints);
+
+               PlayerScore_Add(self.owner, SP_KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds"
+               self.nextthink = time + autocvar_g_keepaway_score_timeinterval;
+       }
+}
+
+void ka_TouchEvent() // runs any time that the ball comes in contact with something
+{SELFPARAM();
+       if(gameover) { return; }
+       if(!self) { return; }
+       if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
+       { // The ball fell off the map, respawn it since players can't get to it
+               ka_RespawnBall();
+               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
+               Send_Effect(EFFECT_BALL_SPARKS, self.origin, '0 0 0', 1);
+               sound(self, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM);
+               return;
+       }
+       else if(self.wait > time) { return; }
+
+       // attach the ball to the player
+       self.owner = other;
+       other.ballcarried = self;
+       setattachment(self, other, "");
+       setorigin(self, '0 0 0');
+
+       // make the ball invisible/unable to do anything/set up time scoring
+       self.velocity = '0 0 0';
+       self.movetype = MOVETYPE_NONE;
+       self.effects |= EF_NODRAW;
+       self.touch = func_null;
+       self.think = ka_TimeScoring;
+       self.nextthink = time + autocvar_g_keepaway_score_timeinterval;
+       self.takedamage = DAMAGE_NO;
+
+       // apply effects to player
+       other.glow_color = autocvar_g_keepawayball_trail_color;
+       other.glow_trail = true;
+       other.effects |= autocvar_g_keepaway_ballcarrier_effects;
+
+       // messages and sounds
+       ka_EventLog("pickup", other);
+       Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_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, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+
+       // scoring
+       PlayerScore_Add(other, SP_KEEPAWAY_PICKUPS, 1);
+
+       // waypoints
+       WaypointSprite_AttachCarrier(WP_KaBallCarrier, other, RADARICON_FLAGCARRIER);
+       other.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
+       WaypointSprite_UpdateRule(other.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+       WaypointSprite_Ping(other.waypointsprite_attachedforcarrier);
+       WaypointSprite_Kill(self.waypointsprite_attachedforcarrier);
+}
+
+void ka_DropEvent(entity plyr) // runs any time that a player is supposed to lose the ball
+{
+       entity ball;
+       ball = plyr.ballcarried;
+
+       if(!ball) { return; }
+
+       // reset the ball
+       setattachment(ball, world, "");
+       ball.movetype = MOVETYPE_BOUNCE;
+       ball.wait = time + 1;
+       ball.touch = ka_TouchEvent;
+       ball.think = ka_RespawnBall;
+       ball.nextthink = time + autocvar_g_keepawayball_respawntime;
+       ball.takedamage = DAMAGE_YES;
+       ball.effects &= ~EF_NODRAW;
+       setorigin(ball, plyr.origin + '0 0 10');
+       ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
+       ball.owner.ballcarried = world; // I hope nothing checks to see if the world has the ball in the rest of my code :P
+       ball.owner = world;
+
+       // reset the player effects
+       plyr.glow_trail = false;
+       plyr.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
+
+       // messages and sounds
+       ka_EventLog("dropped", plyr);
+       Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname);
+       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname);
+       sound(other, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+
+       // scoring
+       // PlayerScore_Add(plyr, SP_KEEPAWAY_DROPS, 1); Not anymore, this is 100% the same as pickups and is useless.
+
+       // waypoints
+       WaypointSprite_Spawn(WP_KaBall, 0, 0, ball, '0 0 64', world, ball.team, ball, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
+       WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+       WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier);
+       WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier);
+}
+
+void ka_Reset() // used to clear the ballcarrier whenever the match switches from warmup to normal
+{SELFPARAM();
+       if((self.owner) && (IS_PLAYER(self.owner)))
+               ka_DropEvent(self.owner);
+
+       if(time < game_starttime)
+       {
+               self.think = ka_RespawnBall;
+               self.touch = func_null;
+               self.nextthink = game_starttime;
+       }
+       else
+               ka_RespawnBall();
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+void havocbot_goalrating_ball(float ratingscale, vector org)
+{SELFPARAM();
+       float t;
+       entity ball_owner;
+       ball_owner = ka_ball.owner;
+
+       if (ball_owner == self)
+               return;
+
+       // If ball is carried by player then hunt them down.
+       if (ball_owner)
+       {
+               t = (self.health + self.armorvalue) / (ball_owner.health + ball_owner.armorvalue);
+               navigation_routerating(ball_owner, t * ratingscale, 2000);
+       }
+       else // Ball has been dropped so collect.
+               navigation_routerating(ka_ball, ratingscale, 2000);
+}
+
+void havocbot_role_ka_carrier()
+{SELFPARAM();
+       if (self.deadflag != DEAD_NO)
+               return;
+
+       if (time > self.bot_strategytime)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               havocbot_goalrating_items(10000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
+               //havocbot_goalrating_waypoints(1, self.origin, 1000);
+               navigation_goalrating_end();
+       }
+
+       if (!self.ballcarried)
+       {
+               self.havocbot_role = havocbot_role_ka_collector;
+               self.bot_strategytime = 0;
+       }
+}
+
+void havocbot_role_ka_collector()
+{SELFPARAM();
+       if (self.deadflag != DEAD_NO)
+               return;
+
+       if (time > self.bot_strategytime)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               havocbot_goalrating_items(10000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(1000, self.origin, 10000);
+               havocbot_goalrating_ball(20000, self.origin);
+               navigation_goalrating_end();
+       }
+
+       if (self.ballcarried)
+       {
+               self.havocbot_role = havocbot_role_ka_carrier;
+               self.bot_strategytime = 0;
+       }
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(ka, PlayerDies)
+{SELFPARAM();
+       if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)))
+       {
+               if(frag_target.ballcarried) { // add to amount of times killing carrier
+                       PlayerScore_Add(frag_attacker, SP_KEEPAWAY_CARRIERKILLS, 1);
+                       if(autocvar_g_keepaway_score_bckill) // add bckills to the score
+                               PlayerScore_Add(frag_attacker, SP_SCORE, autocvar_g_keepaway_score_bckill);
+               }
+               else if(!frag_attacker.ballcarried)
+                       if(autocvar_g_keepaway_noncarrier_warn)
+                               Send_Notification(NOTIF_ONE_ONLY, frag_attacker, MSG_CENTER, CENTER_KEEPAWAY_WARN);
+
+               if(frag_attacker.ballcarried) // add to amount of kills while ballcarrier
+                       PlayerScore_Add(frag_attacker, SP_SCORE, autocvar_g_keepaway_score_killac);
+       }
+
+       if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has died, drop it
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ka, GiveFragsForKill)
+{
+       frag_score = 0; // no frags counted in keepaway
+       return 1; // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPreThink)
+{SELFPARAM();
+       // clear the item used for the ball in keepaway
+       self.items &= ~IT_KEY1;
+
+       // if the player has the ball, make sure they have the item for it (Used for HUD primarily)
+       if(self.ballcarried)
+               self.items |= IT_KEY1;
+
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerUseKey)
+{SELFPARAM();
+       if(MUTATOR_RETURNVALUE == 0)
+       if(self.ballcarried)
+       {
+               ka_DropEvent(self);
+               return 1;
+       }
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
+{
+       if(frag_attacker.ballcarried) // if the attacker is a ballcarrier
+       {
+               if(frag_target == frag_attacker) // damage done to yourself
+               {
+                       frag_damage *= autocvar_g_keepaway_ballcarrier_selfdamage;
+                       frag_force *= autocvar_g_keepaway_ballcarrier_selfforce;
+               }
+               else // damage done to noncarriers
+               {
+                       frag_damage *= autocvar_g_keepaway_ballcarrier_damage;
+                       frag_force *= autocvar_g_keepaway_ballcarrier_force;
+               }
+       }
+       else if (!frag_target.ballcarried) // if the target is a noncarrier
+       {
+               if(frag_target == frag_attacker) // damage done to yourself
+               {
+                       frag_damage *= autocvar_g_keepaway_noncarrier_selfdamage;
+                       frag_force *= autocvar_g_keepaway_noncarrier_selfforce;
+               }
+               else // damage done to other noncarriers
+               {
+                       frag_damage *= autocvar_g_keepaway_noncarrier_damage;
+                       frag_force *= autocvar_g_keepaway_noncarrier_force;
+               }
+       }
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ka, ClientDisconnect)
+{SELFPARAM();
+       if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has left the match, drop it
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ka, MakePlayerObserver)
+{SELFPARAM();
+       if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has left the match, drop it
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPowerups)
+{SELFPARAM();
+       // In the future this hook is supposed to allow me to do some extra stuff with waypointsprites and invisibility powerup
+       // So bare with me until I can fix a certain bug with ka_ballcarrier_waypointsprite_visible_for_player()
+
+       self.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
+
+       if(self.ballcarried)
+               self.effects |= autocvar_g_keepaway_ballcarrier_effects;
+
+       return 0;
+}
+
+.float stat_sv_airspeedlimit_nonqw;
+.float stat_sv_maxspeed;
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPhysics)
+{SELFPARAM();
+       if(self.ballcarried)
+       {
+               self.stat_sv_airspeedlimit_nonqw *= autocvar_g_keepaway_ballcarrier_highspeed;
+               self.stat_sv_maxspeed *= autocvar_g_keepaway_ballcarrier_highspeed;
+       }
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ka, BotShouldAttack)
+{SELFPARAM();
+       // if neither player has ball then don't attack unless the ball is on the ground
+       if(!checkentity.ballcarried && !self.ballcarried && ka_ball.owner)
+               return true;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ka, HavocBot_ChooseRole)
+{SELFPARAM();
+       if (self.ballcarried)
+               self.havocbot_role = havocbot_role_ka_carrier;
+       else
+               self.havocbot_role = havocbot_role_ka_collector;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ka, DropSpecialItems)
+{
+       if(frag_target.ballcarried)
+               ka_DropEvent(frag_target);
+
+       return false;
+}
+
+
+// ==============
+// Initialization
+// ==============
+
+void ka_SpawnBall() // loads various values for the ball, runs only once at start of match
+{
+       entity e;
+       e = spawn();
+       e.model = "models/orbs/orbblue.md3";
+       precache_model(e.model);
+       _setmodel(e, e.model);
+       setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
+       e.classname = "keepawayball";
+       e.damageforcescale = autocvar_g_keepawayball_damageforcescale;
+       e.takedamage = DAMAGE_YES;
+       e.solid = SOLID_TRIGGER;
+       e.movetype = MOVETYPE_BOUNCE;
+       e.glow_color = autocvar_g_keepawayball_trail_color;
+       e.glow_trail = true;
+       e.flags = FL_ITEM;
+       e.reset = ka_Reset;
+       e.touch = ka_TouchEvent;
+       e.owner = world;
+       ka_ball = e;
+
+       InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So.
+}
+
+void ka_ScoreRules()
+{
+       ScoreRules_basics(0, SFL_SORT_PRIO_PRIMARY, 0, true); // SFL_SORT_PRIO_PRIMARY
+       ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_PICKUPS,                     "pickups",              0);
+       ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_CARRIERKILLS,        "bckills",              0);
+       ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_BCTIME,                      "bctime",               SFL_SORT_PRIO_SECONDARY);
+       ScoreRules_basics_end();
+}
+
+void ka_Initialize() // run at the start of a match, initiates game mode
+{
+       ka_ScoreRules();
+       ka_SpawnBall();
+}
+
+
+REGISTER_MUTATOR(ka, IS_GAMETYPE(KEEPAWAY))
+{
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+               ka_Initialize();
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back ka_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/gamemode_keyhunt.qc b/qcsrc/server/mutators/mutator/gamemode_keyhunt.qc
new file mode 100644 (file)
index 0000000..3b86b66
--- /dev/null
@@ -0,0 +1,1399 @@
+#ifndef GAMEMODE_KEYHUNT_H
+#define GAMEMODE_KEYHUNT_H
+
+#define FOR_EACH_KH_KEY(v) for(v = kh_worldkeylist; v; v = v.kh_worldkeynext )
+
+// ALL OF THESE should be removed in the future, as other code should not have to care
+
+// used by bots:
+float kh_tracking_enabled;
+.entity kh_next;
+float kh_Key_AllOwnedByWhichTeam();
+
+typedef void(void) kh_Think_t;
+void kh_StartRound();
+void kh_Controller_SetThink(float t, kh_Think_t func);
+
+entity kh_worldkeylist;
+.entity kh_worldkeynext;
+#endif
+
+#ifdef IMPLEMENTATION
+
+float autocvar_g_balance_keyhunt_damageforcescale;
+float autocvar_g_balance_keyhunt_delay_collect;
+float autocvar_g_balance_keyhunt_delay_return;
+float autocvar_g_balance_keyhunt_delay_round;
+float autocvar_g_balance_keyhunt_delay_tracking;
+float autocvar_g_balance_keyhunt_dropvelocity;
+float autocvar_g_balance_keyhunt_maxdist;
+float autocvar_g_balance_keyhunt_protecttime;
+
+int autocvar_g_balance_keyhunt_score_capture;
+int autocvar_g_balance_keyhunt_score_carrierfrag;
+int autocvar_g_balance_keyhunt_score_collect;
+int autocvar_g_balance_keyhunt_score_destroyed;
+int autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
+int autocvar_g_balance_keyhunt_score_push;
+float autocvar_g_balance_keyhunt_throwvelocity;
+
+int autocvar_g_keyhunt_point_leadlimit;
+bool autocvar_g_keyhunt_team_spawns;
+#define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit")
+int autocvar_g_keyhunt_teams;
+int autocvar_g_keyhunt_teams_override;
+
+// #define KH_PLAYER_USE_ATTACHMENT
+// #define KH_PLAYER_USE_CARRIEDMODEL
+
+#ifdef KH_PLAYER_USE_ATTACHMENT
+const vector KH_PLAYER_ATTACHMENT_DIST_ROTATED = '0 -4 0';
+const vector KH_PLAYER_ATTACHMENT_DIST = '4 0 0';
+const vector KH_PLAYER_ATTACHMENT = '0 0 0';
+const vector KH_PLAYER_ATTACHMENT_ANGLES = '0 0 0';
+const string KH_PLAYER_ATTACHMENT_BONE = "";
+#else
+const float KH_KEY_ZSHIFT = 22;
+const float KH_KEY_XYDIST = 24;
+const float KH_KEY_XYSPEED = 45;
+#endif
+const float KH_KEY_WP_ZSHIFT = 20;
+
+const vector KH_KEY_MIN = '-10 -10 -46';
+const vector KH_KEY_MAX = '10 10 3';
+const float KH_KEY_BRIGHTNESS = 2;
+
+float kh_no_radar_circles;
+
+// kh_state
+//     bits  0- 4: team of key 1, or 0 for no such key, or 30 for dropped, or 31 for self
+//     bits  5- 9: team of key 2, or 0 for no such key, or 30 for dropped, or 31 for self
+//     bits 10-14: team of key 3, or 0 for no such key, or 30 for dropped, or 31 for self
+//     bits 15-19: team of key 4, or 0 for no such key, or 30 for dropped, or 31 for self
+.float kh_state;
+.float siren_time;  //  time delay the siren
+//.float stuff_time;  //  time delay to stuffcmd a cvar
+
+float kh_keystatus[17];
+//kh_keystatus[0] = status of dropped keys, kh_keystatus[1 - 16] = player #
+//replace 17 with cvar("maxplayers") or similar !!!!!!!!!
+//for(i = 0; i < maxplayers; ++i)
+//     kh_keystatus[i] = "0";
+
+float kh_Team_ByID(float t)
+{
+       if(t == 0) return NUM_TEAM_1;
+       if(t == 1) return NUM_TEAM_2;
+       if(t == 2) return NUM_TEAM_3;
+       if(t == 3) return NUM_TEAM_4;
+       return 0;
+}
+
+//entity kh_worldkeylist;
+.entity kh_worldkeynext;
+entity kh_controller;
+//float kh_tracking_enabled;
+float kh_teams;
+float kh_interferemsg_time, kh_interferemsg_team;
+.entity kh_next, kh_prev; // linked list
+.float kh_droptime;
+.float kh_dropperteam;
+.entity kh_previous_owner;
+.float kh_previous_owner_playerid;
+.float kh_cp_duration;
+
+float kh_key_dropped, kh_key_carried;
+
+const float ST_KH_CAPS = 1;
+const float SP_KH_CAPS = 4;
+const float SP_KH_PUSHES = 5;
+const float SP_KH_DESTROYS = 6;
+const float SP_KH_PICKUPS = 7;
+const float SP_KH_KCKILLS = 8;
+const float SP_KH_LOSSES = 9;
+void kh_ScoreRules(float teams)
+{
+       ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, true);
+       ScoreInfo_SetLabel_TeamScore(  ST_KH_CAPS,      "caps",      SFL_SORT_PRIO_SECONDARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_KH_CAPS,      "caps",      SFL_SORT_PRIO_SECONDARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_KH_PUSHES,    "pushes",    0);
+       ScoreInfo_SetLabel_PlayerScore(SP_KH_DESTROYS,  "destroyed", SFL_LOWER_IS_BETTER);
+       ScoreInfo_SetLabel_PlayerScore(SP_KH_PICKUPS,   "pickups",   0);
+       ScoreInfo_SetLabel_PlayerScore(SP_KH_KCKILLS,   "kckills",   0);
+       ScoreInfo_SetLabel_PlayerScore(SP_KH_LOSSES,    "losses",    SFL_LOWER_IS_BETTER);
+       ScoreRules_basics_end();
+}
+
+float kh_KeyCarrier_waypointsprite_visible_for_player(entity e)  // runs all the time
+{SELFPARAM();
+       if(!IS_PLAYER(e) || self.team != e.team)
+               if(!kh_tracking_enabled)
+                       return false;
+
+       return true;
+}
+
+float kh_Key_waypointsprite_visible_for_player(entity e) // ??
+{SELFPARAM();
+       if(!kh_tracking_enabled)
+               return false;
+       if(!self.owner)
+               return true;
+       if(!self.owner.owner)
+               return true;
+       return false;  // draw only when key is not owned
+}
+
+void kh_update_state()
+{
+       entity player;
+       entity key;
+       float s;
+       float f;
+
+       s = 0;
+       FOR_EACH_KH_KEY(key)
+       {
+               if(key.owner)
+                       f = key.team;
+               else
+                       f = 30;
+               s |= pow(32, key.count) * f;
+       }
+
+       FOR_EACH_CLIENT(player)
+       {
+               player.kh_state = s;
+       }
+
+       FOR_EACH_KH_KEY(key)
+       {
+               if(key.owner)
+                       key.owner.kh_state |= pow(32, key.count) * 31;
+       }
+       //print(ftos((nextent(world)).kh_state), "\n");
+}
+
+
+
+
+var kh_Think_t kh_Controller_Thinkfunc;
+void kh_Controller_SetThink(float t, kh_Think_t func)  // runs occasionaly
+{
+       kh_Controller_Thinkfunc = func;
+       kh_controller.cnt = ceil(t);
+       if(t == 0)
+               kh_controller.nextthink = time; // force
+}
+void kh_WaitForPlayers();
+void kh_Controller_Think()  // called a lot
+{SELFPARAM();
+       if(intermission_running)
+               return;
+       if(self.cnt > 0)
+       { if(self.think != kh_WaitForPlayers) { self.cnt -= 1; } }
+       else if(self.cnt == 0)
+       {
+               self.cnt -= 1;
+               kh_Controller_Thinkfunc();
+       }
+       self.nextthink = time + 1;
+}
+
+// frags f: take from cvar * f
+// frags 0: no frags
+void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner)  // update the score when a key is captured
+{
+       string s;
+       if(intermission_running)
+               return;
+
+       if(frags_player)
+               UpdateFrags(player, frags_player);
+
+       if(key && key.owner && frags_owner)
+               UpdateFrags(key.owner, frags_owner);
+
+       if(!autocvar_sv_eventlog)  //output extra info to the console or text file
+               return;
+
+       s = strcat(":keyhunt:", what, ":", ftos(player.playerid), ":", ftos(frags_player));
+
+       if(key && key.owner)
+               s = strcat(s, ":", ftos(key.owner.playerid));
+       else
+               s = strcat(s, ":0");
+
+       s = strcat(s, ":", ftos(frags_owner), ":");
+
+       if(key)
+               s = strcat(s, key.netname);
+
+       GameLogEcho(s);
+}
+
+vector kh_AttachedOrigin(entity e)  // runs when a team captures the flag, it can run 2 or 3 times.
+{
+       if(e.tag_entity)
+       {
+               makevectors(e.tag_entity.angles);
+               return e.tag_entity.origin + e.origin.x * v_forward - e.origin.y * v_right + e.origin.z * v_up;
+       }
+       else
+               return e.origin;
+}
+
+void kh_Key_Attach(entity key)  // runs when a player picks up a key and several times when a key is assigned to a player at the start of a round
+{
+#ifdef KH_PLAYER_USE_ATTACHMENT
+       entity first;
+       first = key.owner.kh_next;
+       if(key == first)
+       {
+               setattachment(key, key.owner, KH_PLAYER_ATTACHMENT_BONE);
+               if(key.kh_next)
+               {
+                       setattachment(key.kh_next, key, "");
+                       setorigin(key, key.kh_next.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);
+                       setorigin(key.kh_next, KH_PLAYER_ATTACHMENT_DIST_ROTATED);
+                       key.kh_next.angles = '0 0 0';
+               }
+               else
+                       setorigin(key, KH_PLAYER_ATTACHMENT);
+               key.angles = KH_PLAYER_ATTACHMENT_ANGLES;
+       }
+       else
+       {
+               setattachment(key, key.kh_prev, "");
+               if(key.kh_next)
+                       setattachment(key.kh_next, key, "");
+               setorigin(key, KH_PLAYER_ATTACHMENT_DIST_ROTATED);
+               setorigin(first, first.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST);
+               key.angles = '0 0 0';
+       }
+#else
+       setattachment(key, key.owner, "");
+       setorigin(key, '0 0 1' * KH_KEY_ZSHIFT);  // fixing x, y in think
+       key.angles_y -= key.owner.angles.y;
+#endif
+       key.flags = 0;
+       key.solid = SOLID_NOT;
+       key.movetype = MOVETYPE_NONE;
+       key.team = key.owner.team;
+       key.nextthink = time;
+       key.damageforcescale = 0;
+       key.takedamage = DAMAGE_NO;
+       key.modelindex = kh_key_carried;
+}
+
+void kh_Key_Detach(entity key) // runs every time a key is dropped or lost. Runs several times times when all the keys are captured
+{
+#ifdef KH_PLAYER_USE_ATTACHMENT
+       entity first;
+       first = key.owner.kh_next;
+       if(key == first)
+       {
+               if(key.kh_next)
+               {
+                       setattachment(key.kh_next, key.owner, KH_PLAYER_ATTACHMENT_BONE);
+                       setorigin(key.kh_next, key.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);
+                       key.kh_next.angles = KH_PLAYER_ATTACHMENT_ANGLES;
+               }
+       }
+       else
+       {
+               if(key.kh_next)
+                       setattachment(key.kh_next, key.kh_prev, "");
+               setorigin(first, first.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST);
+       }
+       // in any case:
+       setattachment(key, world, "");
+       setorigin(key, key.owner.origin + '0 0 1' * (PL_MIN.z - KH_KEY_MIN_z));
+       key.angles = key.owner.angles;
+#else
+       setorigin(key, key.owner.origin + key.origin.z * '0 0 1');
+       setattachment(key, world, "");
+       key.angles_y += key.owner.angles.y;
+#endif
+       key.flags = FL_ITEM;
+       key.solid = SOLID_TRIGGER;
+       key.movetype = MOVETYPE_TOSS;
+       key.pain_finished = time + autocvar_g_balance_keyhunt_delay_return;
+       key.damageforcescale = autocvar_g_balance_keyhunt_damageforcescale;
+       key.takedamage = DAMAGE_YES;
+       // let key.team stay
+       key.modelindex = kh_key_dropped;
+       key.kh_previous_owner = key.owner;
+       key.kh_previous_owner_playerid = key.owner.playerid;
+}
+
+void kh_Key_AssignTo(entity key, entity player)  // runs every time a key is picked up or assigned. Runs prior to kh_key_attach
+{
+       entity k;
+       float ownerteam0, ownerteam;
+       if(key.owner == player)
+               return;
+
+       ownerteam0 = kh_Key_AllOwnedByWhichTeam();
+
+       if(key.owner)
+       {
+               kh_Key_Detach(key);
+
+               // remove from linked list
+               if(key.kh_next)
+                       key.kh_next.kh_prev = key.kh_prev;
+               key.kh_prev.kh_next = key.kh_next;
+               key.kh_next = world;
+               key.kh_prev = world;
+
+               if(key.owner.kh_next == world)
+               {
+                       // No longer a key carrier
+                       if(!kh_no_radar_circles)
+                               WaypointSprite_Ping(key.owner.waypointsprite_attachedforcarrier);
+                       WaypointSprite_DetachCarrier(key.owner);
+               }
+       }
+
+       key.owner = player;
+
+       if(player)
+       {
+               // insert into linked list
+               key.kh_next = player.kh_next;
+               key.kh_prev = player;
+               player.kh_next = key;
+               if(key.kh_next)
+                       key.kh_next.kh_prev = key;
+
+               float i;
+               i = kh_keystatus[key.owner.playerid];
+                       if(key.netname == "^1red key")
+                               i += 1;
+                       if(key.netname == "^4blue key")
+                               i += 2;
+                       if(key.netname == "^3yellow key")
+                               i += 4;
+                       if(key.netname == "^6pink key")
+                               i += 8;
+               kh_keystatus[key.owner.playerid] = i;
+
+               kh_Key_Attach(key);
+
+               if(key.kh_next == world)
+               {
+                       // player is now a key carrier
+                       entity wp = WaypointSprite_AttachCarrier(WP_Null, player, RADARICON_FLAGCARRIER);
+                       wp.colormod = colormapPaletteColor(player.team - 1, 0);
+                       player.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_KeyCarrier_waypointsprite_visible_for_player;
+                       WaypointSprite_UpdateRule(player.waypointsprite_attachedforcarrier, player.team, SPRITERULE_TEAMPLAY);
+                       if(player.team == NUM_TEAM_1)
+                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierRed, WP_KeyCarrierFriend, WP_KeyCarrierRed);
+                       else if(player.team == NUM_TEAM_2)
+                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierBlue, WP_KeyCarrierFriend, WP_KeyCarrierBlue);
+                       else if(player.team == NUM_TEAM_3)
+                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierYellow, WP_KeyCarrierFriend, WP_KeyCarrierYellow);
+                       else if(player.team == NUM_TEAM_4)
+                               WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierPink, WP_KeyCarrierFriend, WP_KeyCarrierPink);
+                       if(!kh_no_radar_circles)
+                               WaypointSprite_Ping(player.waypointsprite_attachedforcarrier);
+               }
+       }
+
+       // moved that here, also update if there's no player
+       kh_update_state();
+
+       key.pusher = world;
+
+       ownerteam = kh_Key_AllOwnedByWhichTeam();
+       if(ownerteam != ownerteam0)
+       {
+               if(ownerteam != -1)
+               {
+                       kh_interferemsg_time = time + 0.2;
+                       kh_interferemsg_team = player.team;
+
+                       // audit all key carrier sprites, update them to RUN HERE
+                       FOR_EACH_KH_KEY(k)
+                       {
+                               if (!k.owner) continue;
+                               entity first = WP_Null;
+                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, LAMBDA(first = it; break));
+                               entity third = WP_Null;
+                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, LAMBDA(third = it; break));
+                               WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFinish, third);
+                       }
+               }
+               else
+               {
+                       kh_interferemsg_time = 0;
+
+                       // audit all key carrier sprites, update them to RUN HERE
+                       FOR_EACH_KH_KEY(k)
+                       {
+                               if (!k.owner) continue;
+                               entity first = WP_Null;
+                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, LAMBDA(first = it; break));
+                               entity third = WP_Null;
+                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, LAMBDA(third = it; break));
+                               WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third);
+                       }
+               }
+       }
+}
+
+void kh_Key_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{SELFPARAM();
+       if(self.owner)
+               return;
+       if(ITEM_DAMAGE_NEEDKILL(deathtype))
+       {
+               // touching lava, or hurt trigger
+               // what shall we do?
+               // immediately return is bad
+               // maybe start a shorter countdown?
+       }
+       if(vlen(force) <= 0)
+               return;
+       if(time > self.pushltime)
+               if(IS_PLAYER(attacker))
+                       self.team = attacker.team;
+}
+
+void kh_Key_Collect(entity key, entity player)  //a player picks up a dropped key
+{
+       sound(player, CH_TRIGGER, SND_KH_COLLECT, VOL_BASE, ATTEN_NORM);
+
+       if(key.kh_dropperteam != player.team)
+       {
+               kh_Scores_Event(player, key, "collect", autocvar_g_balance_keyhunt_score_collect, 0);
+               PlayerScore_Add(player, SP_KH_PICKUPS, 1);
+       }
+       key.kh_dropperteam = 0;
+       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_PICKUP_), player.netname);
+
+       kh_Key_AssignTo(key, player); // this also updates .kh_state
+}
+
+void kh_Key_Touch()  // runs many, many times when a key has been dropped and can be picked up
+{SELFPARAM();
+       if(intermission_running)
+               return;
+
+       if(self.owner) // already carried
+               return;
+
+       if(ITEM_TOUCH_NEEDKILL())
+       {
+               // touching sky, or nodrop
+               // what shall we do?
+               // immediately return is bad
+               // maybe start a shorter countdown?
+       }
+
+       if (!IS_PLAYER(other))
+               return;
+       if(other.deadflag != DEAD_NO)
+               return;
+       if(other == self.enemy)
+               if(time < self.kh_droptime + autocvar_g_balance_keyhunt_delay_collect)
+                       return;  // you just dropped it!
+       kh_Key_Collect(self, other);
+}
+
+void kh_Key_Remove(entity key)  // runs after when all the keys have been collected or when a key has been dropped for more than X seconds
+{
+       entity o;
+       o = key.owner;
+       kh_Key_AssignTo(key, world);
+       if(o) // it was attached
+               WaypointSprite_Kill(key.waypointsprite_attachedforcarrier);
+       else // it was dropped
+               WaypointSprite_DetachCarrier(key);
+
+       // remove key from key list
+       if (kh_worldkeylist == key)
+               kh_worldkeylist = kh_worldkeylist.kh_worldkeynext;
+       else
+       {
+               o = kh_worldkeylist;
+               while (o)
+               {
+                       if (o.kh_worldkeynext == key)
+                       {
+                               o.kh_worldkeynext = o.kh_worldkeynext.kh_worldkeynext;
+                               break;
+                       }
+                       o = o.kh_worldkeynext;
+               }
+       }
+
+       remove(key);
+
+       kh_update_state();
+}
+
+void kh_FinishRound()  // runs when a team captures the keys
+{
+       // prepare next round
+       kh_interferemsg_time = 0;
+       entity key;
+
+       kh_no_radar_circles = true;
+       FOR_EACH_KH_KEY(key)
+               kh_Key_Remove(key);
+       kh_no_radar_circles = false;
+
+       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
+       kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
+}
+
+void kh_WinnerTeam(float teem)  // runs when a team wins // Samual: Teem?.... TEEM?!?! what the fuck is wrong with you people
+{
+       // all key carriers get some points
+       vector firstorigin, lastorigin, midpoint;
+       float first;
+       entity key;
+       float score;
+       score = (kh_teams - 1) * autocvar_g_balance_keyhunt_score_capture;
+       DistributeEvenly_Init(score, kh_teams);
+       // twice the score for 3 team games, three times the score for 4 team games!
+       // note: for a win by destroying the key, this should NOT be applied
+       FOR_EACH_KH_KEY(key)
+       {
+               float f;
+               f = DistributeEvenly_Get(1);
+               kh_Scores_Event(key.owner, key, "capture", f, 0);
+               PlayerTeamScore_Add(key.owner, SP_KH_CAPS, ST_KH_CAPS, 1);
+               nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high);
+       }
+
+       first = true;
+       string keyowner = "";
+       FOR_EACH_KH_KEY(key)
+               if(key.owner.kh_next == key)
+               {
+                       if(!first)
+                               keyowner = strcat(keyowner, ", ");
+                       keyowner = key.owner.netname;
+                       first = false;
+               }
+
+       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(teem, INFO_KEYHUNT_CAPTURE_), keyowner);
+
+       first = true;
+       midpoint = '0 0 0';
+       firstorigin = '0 0 0';
+       lastorigin = '0 0 0';
+       FOR_EACH_KH_KEY(key)
+       {
+               vector thisorigin;
+
+               thisorigin = kh_AttachedOrigin(key);
+               //dprint("Key origin: ", vtos(thisorigin), "\n");
+               midpoint += thisorigin;
+
+               if(!first)
+                       te_lightning2(world, lastorigin, thisorigin);
+               lastorigin = thisorigin;
+               if(first)
+                       firstorigin = thisorigin;
+               first = false;
+       }
+       if(kh_teams > 2)
+       {
+               te_lightning2(world, lastorigin, firstorigin);
+       }
+       midpoint = midpoint * (1 / kh_teams);
+       te_customflash(midpoint, 1000, 1, Team_ColorRGB(teem) * 0.5 + '0.5 0.5 0.5');  // make the color >=0.5 in each component
+
+       play2all(SND(KH_CAPTURE));
+       kh_FinishRound();
+}
+
+void kh_LoserTeam(float teem, entity lostkey)  // runs when a player pushes a flag carrier off the map
+{
+       entity player, key, attacker;
+       float players;
+       float keys;
+       float f;
+
+       attacker = world;
+       if(lostkey.pusher)
+               if(lostkey.pusher.team != teem)
+                       if(IS_PLAYER(lostkey.pusher))
+                               attacker = lostkey.pusher;
+
+       players = keys = 0;
+
+       if(attacker)
+       {
+               if(lostkey.kh_previous_owner)
+                       kh_Scores_Event(lostkey.kh_previous_owner, world, "pushed", 0, -autocvar_g_balance_keyhunt_score_push);
+                       // don't actually GIVE him the -nn points, just log
+               kh_Scores_Event(attacker, world, "push", autocvar_g_balance_keyhunt_score_push, 0);
+               PlayerScore_Add(attacker, SP_KH_PUSHES, 1);
+               //centerprint(attacker, "Your push is the best!"); // does this really need to exist?
+       }
+       else
+       {
+               float of, fragsleft, i, j, thisteam;
+               of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
+
+               FOR_EACH_PLAYER(player)
+                       if(player.team != teem)
+                               ++players;
+
+               FOR_EACH_KH_KEY(key)
+                       if(key.owner && key.team != teem)
+                               ++keys;
+
+               if(lostkey.kh_previous_owner)
+                       kh_Scores_Event(lostkey.kh_previous_owner, world, "destroyed", 0, -autocvar_g_balance_keyhunt_score_destroyed);
+                       // don't actually GIVE him the -nn points, just log
+
+               if(lostkey.kh_previous_owner.playerid == lostkey.kh_previous_owner_playerid)
+                       PlayerScore_Add(lostkey.kh_previous_owner, SP_KH_DESTROYS, 1);
+
+               DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players);
+
+               FOR_EACH_KH_KEY(key)
+                       if(key.owner && key.team != teem)
+                       {
+                               f = DistributeEvenly_Get(of);
+                               kh_Scores_Event(key.owner, world, "destroyed_holdingkey", f, 0);
+                       }
+
+               fragsleft = DistributeEvenly_Get(players);
+
+               // Now distribute these among all other teams...
+               j = kh_teams - 1;
+               for(i = 0; i < kh_teams; ++i)
+               {
+                       thisteam = kh_Team_ByID(i);
+                       if(thisteam == teem) // bad boy, no cookie - this WILL happen
+                               continue;
+
+                       players = 0;
+                       FOR_EACH_PLAYER(player)
+                               if(player.team == thisteam)
+                                       ++players;
+
+                       DistributeEvenly_Init(fragsleft, j);
+                       fragsleft = DistributeEvenly_Get(j - 1);
+                       DistributeEvenly_Init(DistributeEvenly_Get(1), players);
+
+                       FOR_EACH_PLAYER(player)
+                               if(player.team == thisteam)
+                               {
+                                       f = DistributeEvenly_Get(1);
+                                       kh_Scores_Event(player, world, "destroyed", f, 0);
+                               }
+
+                       --j;
+               }
+       }
+
+       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(lostkey, INFO_KEYHUNT_LOST_), lostkey.kh_previous_owner.netname);
+
+       play2all(SND(KH_DESTROY));
+       te_tarexplosion(lostkey.origin);
+
+       kh_FinishRound();
+}
+
+void kh_Key_Think()  // runs all the time
+{SELFPARAM();
+       entity head;
+       //entity player;  // needed by FOR_EACH_PLAYER
+
+       if(intermission_running)
+               return;
+
+       if(self.owner)
+       {
+#ifndef KH_PLAYER_USE_ATTACHMENT
+               makevectors('0 1 0' * (self.cnt + (time % 360) * KH_KEY_XYSPEED));
+               setorigin(self, v_forward * KH_KEY_XYDIST + '0 0 1' * self.origin.z);
+#endif
+       }
+
+       // if in nodrop or time over, end the round
+       if(!self.owner)
+               if(time > self.pain_finished)
+                       kh_LoserTeam(self.team, self);
+
+       if(self.owner)
+       if(kh_Key_AllOwnedByWhichTeam() != -1)
+       {
+               if(self.siren_time < time)
+               {
+                       sound(self.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM);  // play a simple alarm
+                       self.siren_time = time + 2.5;  // repeat every 2.5 seconds
+               }
+
+               entity key;
+               vector p;
+               p = self.owner.origin;
+               FOR_EACH_KH_KEY(key)
+                       if(vlen(key.owner.origin - p) > autocvar_g_balance_keyhunt_maxdist)
+                               goto not_winning;
+               kh_WinnerTeam(self.team);
+:not_winning
+       }
+
+       if(kh_interferemsg_time && time > kh_interferemsg_time)
+       {
+               kh_interferemsg_time = 0;
+               FOR_EACH_PLAYER(head)
+               {
+                       if(head.team == kh_interferemsg_team)
+                               if(head.kh_next)
+                                       Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_KEYHUNT_MEET);
+                               else
+                                       Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_KEYHUNT_HELP);
+                       else
+                               Send_Notification(NOTIF_ONE, head, MSG_CENTER, APP_TEAM_NUM_4(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE_));
+               }
+       }
+
+       self.nextthink = time + 0.05;
+}
+
+void key_reset()
+{SELFPARAM();
+       kh_Key_AssignTo(self, world);
+       kh_Key_Remove(self);
+}
+
+const string STR_ITEM_KH_KEY = "item_kh_key";
+void kh_Key_Spawn(entity initial_owner, float angle, float i)  // runs every time a new flag is created, ie after all the keys have been collected
+{
+       entity key;
+       key = spawn();
+       key.count = i;
+       key.classname = STR_ITEM_KH_KEY;
+       key.touch = kh_Key_Touch;
+       key.think = kh_Key_Think;
+       key.nextthink = time;
+       key.items = IT_KEY1 | IT_KEY2;
+       key.cnt = angle;
+       key.angles = '0 360 0' * random();
+       key.event_damage = kh_Key_Damage;
+       key.takedamage = DAMAGE_YES;
+       key.modelindex = kh_key_dropped;
+       key.model = "key";
+       key.kh_dropperteam = 0;
+       key.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+       setsize(key, KH_KEY_MIN, KH_KEY_MAX);
+       key.colormod = Team_ColorRGB(initial_owner.team) * KH_KEY_BRIGHTNESS;
+       key.reset = key_reset;
+
+       switch(initial_owner.team)
+       {
+               case NUM_TEAM_1:
+                       key.netname = "^1red key";
+                       break;
+               case NUM_TEAM_2:
+                       key.netname = "^4blue key";
+                       break;
+               case NUM_TEAM_3:
+                       key.netname = "^3yellow key";
+                       break;
+               case NUM_TEAM_4:
+                       key.netname = "^6pink key";
+                       break;
+               default:
+                       key.netname = "NETGIER key";
+                       break;
+       }
+
+       // link into key list
+       key.kh_worldkeynext = kh_worldkeylist;
+       kh_worldkeylist = key;
+
+       Send_Notification(NOTIF_ONE, initial_owner, MSG_CENTER, APP_TEAM_NUM_4(initial_owner.team, CENTER_KEYHUNT_START_));
+
+       WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, world, key.team, key, waypointsprite_attachedforcarrier, false, RADARICON_FLAG);
+       key.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_Key_waypointsprite_visible_for_player;
+
+       kh_Key_AssignTo(key, initial_owner);
+}
+
+// -1 when no team completely owns all keys yet
+float kh_Key_AllOwnedByWhichTeam()  // constantly called. check to see if all the keys are owned by the same team
+{
+       entity key;
+       float teem;
+       float keys;
+
+       teem = -1;
+       keys = kh_teams;
+       FOR_EACH_KH_KEY(key)
+       {
+               if(!key.owner)
+                       return -1;
+               if(teem == -1)
+                       teem = key.team;
+               else if(teem != key.team)
+                       return -1;
+               --keys;
+       }
+       if(keys != 0)
+               return -1;
+       return teem;
+}
+
+void kh_Key_DropOne(entity key)
+{
+       // prevent collecting this one for some time
+       entity player;
+       player = key.owner;
+
+       key.kh_droptime = time;
+       key.enemy = player;
+
+       kh_Scores_Event(player, key, "dropkey", 0, 0);
+       PlayerScore_Add(player, SP_KH_LOSSES, 1);
+       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_DROP_), player.netname);
+
+       kh_Key_AssignTo(key, world);
+       makevectors(player.v_angle);
+       key.velocity = W_CalculateProjectileVelocity(player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false);
+       key.pusher = world;
+       key.pushltime = time + autocvar_g_balance_keyhunt_protecttime;
+       key.kh_dropperteam = key.team;
+
+       sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM);
+}
+
+void kh_Key_DropAll(entity player, float suicide) // runs whenever a player dies
+{
+       entity key;
+       entity mypusher;
+       if(player.kh_next)
+       {
+               mypusher = world;
+               if(player.pusher)
+                       if(time < player.pushltime)
+                               mypusher = player.pusher;
+               while((key = player.kh_next))
+               {
+                       kh_Scores_Event(player, key, "losekey", 0, 0);
+                       PlayerScore_Add(player, SP_KH_LOSSES, 1);
+                       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_LOST_), player.netname);
+                       kh_Key_AssignTo(key, world);
+                       makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random());
+                       key.velocity = W_CalculateProjectileVelocity(player.velocity, autocvar_g_balance_keyhunt_dropvelocity * v_forward, false);
+                       key.pusher = mypusher;
+                       key.pushltime = time + autocvar_g_balance_keyhunt_protecttime;
+                       if(suicide)
+                               key.kh_dropperteam = player.team;
+               }
+               sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM);
+       }
+}
+
+float kh_CheckPlayers(float num)
+{
+       if(num < kh_teams)
+       {
+               float t_team = kh_Team_ByID(num);
+               float players = 0;
+               entity tmp_player;
+               FOR_EACH_PLAYER(tmp_player)
+                       if(tmp_player.deadflag == DEAD_NO)
+                               if(!tmp_player.BUTTON_CHAT)
+                                       if(tmp_player.team == t_team)
+                                               ++players;
+
+               if (!players) { return t_team; }
+       }
+       return 0;
+}
+
+#define KH_READY_TEAMS() (!p1 + !p2 + ((kh_teams >= 3) ? !p3 : p3) + ((kh_teams >= 4) ? !p4 : p4))
+#define KH_READY_TEAMS_OK() (KH_READY_TEAMS() == kh_teams)
+void kh_WaitForPlayers()  // delay start of the round until enough players are present
+{
+       if(time < game_starttime)
+       {
+               kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
+               return;
+       }
+
+       static float prev_missing_teams_mask;
+       float p1 = kh_CheckPlayers(0), p2 = kh_CheckPlayers(1), p3 = kh_CheckPlayers(2), p4 = kh_CheckPlayers(3);
+       if(KH_READY_TEAMS_OK())
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
+               kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
+       }
+       else
+       {
+               if(player_count == 0)
+               {
+                       if(prev_missing_teams_mask > 0)
+                               Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
+                       prev_missing_teams_mask = -1;
+               }
+               else
+               {
+                       float missing_teams_mask = (!!p1) + (!!p2) * 2;
+                       if(kh_teams >= 3) missing_teams_mask += (!!p3) * 4;
+                       if(kh_teams >= 4) missing_teams_mask += (!!p4) * 8;
+                       if(prev_missing_teams_mask != missing_teams_mask)
+                       {
+                               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
+                               prev_missing_teams_mask = missing_teams_mask;
+                       }
+               }
+               kh_Controller_SetThink(1, kh_WaitForPlayers);
+       }
+}
+
+void kh_EnableTrackingDevice()  // runs after each round
+{
+       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT);
+       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT_OTHER);
+
+       kh_tracking_enabled = true;
+}
+
+void kh_StartRound()  // runs at the start of each round
+{
+       float i, players, teem;
+       entity player;
+
+       if(time < game_starttime)
+       {
+               kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
+               return;
+       }
+
+       float p1 = kh_CheckPlayers(0), p2 = kh_CheckPlayers(1), p3 = kh_CheckPlayers(2), p4 = kh_CheckPlayers(3);
+       if(!KH_READY_TEAMS_OK())
+       {
+               kh_Controller_SetThink(1, kh_WaitForPlayers);
+               return;
+       }
+
+       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT);
+       Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT_OTHER);
+
+       for(i = 0; i < kh_teams; ++i)
+       {
+               teem = kh_Team_ByID(i);
+               players = 0;
+               entity my_player = world;
+               FOR_EACH_PLAYER(player)
+                       if(player.deadflag == DEAD_NO)
+                               if(!player.BUTTON_CHAT)
+                                       if(player.team == teem)
+                                       {
+                                               ++players;
+                                               if(random() * players <= 1)
+                                                       my_player = player;
+                                       }
+               kh_Key_Spawn(my_player, 360 * i / kh_teams, i);
+       }
+
+       kh_tracking_enabled = false;
+       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_SCAN, autocvar_g_balance_keyhunt_delay_tracking);
+       kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_tracking, kh_EnableTrackingDevice);
+}
+
+float kh_HandleFrags(entity attacker, entity targ, float f)  // adds to the player score
+{
+       if(attacker == targ)
+               return f;
+
+       if(targ.kh_next)
+       {
+               if(attacker.team == targ.team)
+               {
+                       entity k;
+                       float nk;
+                       nk = 0;
+                       for(k = targ.kh_next; k != world; k = k.kh_next)
+                               ++nk;
+                       kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", -nk * autocvar_g_balance_keyhunt_score_collect, 0);
+               }
+               else
+               {
+                       kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", autocvar_g_balance_keyhunt_score_carrierfrag-1, 0);
+                       PlayerScore_Add(attacker, SP_KH_KCKILLS, 1);
+                       // the frag gets added later
+               }
+       }
+
+       return f;
+}
+
+void kh_Initialize()  // sets up th KH environment
+{
+       // setup variables
+       kh_teams = autocvar_g_keyhunt_teams_override;
+       if(kh_teams < 2)
+               kh_teams = autocvar_g_keyhunt_teams;
+       kh_teams = bound(2, kh_teams, 4);
+
+       // make a KH entity for controlling the game
+       kh_controller = spawn();
+       kh_controller.think = kh_Controller_Think;
+       kh_Controller_SetThink(0, kh_WaitForPlayers);
+
+       setmodel(kh_controller, MDL_KH_KEY);
+       kh_key_dropped = kh_controller.modelindex;
+       /*
+       dprint(vtos(kh_controller.mins));
+       dprint(vtos(kh_controller.maxs));
+       dprint("\n");
+       */
+#ifdef KH_PLAYER_USE_CARRIEDMODEL
+       setmodel(kh_controller, MDL_KH_KEY_CARRIED);
+       kh_key_carried = kh_controller.modelindex;
+#else
+       kh_key_carried = kh_key_dropped;
+#endif
+
+       kh_controller.model = "";
+       kh_controller.modelindex = 0;
+
+       addstat(STAT_KH_KEYS, AS_INT, kh_state);
+
+       kh_ScoreRules(kh_teams);
+}
+
+void kh_finalize()
+{
+       // to be called before intermission
+       kh_FinishRound();
+       remove(kh_controller);
+       kh_controller = world;
+}
+
+// legacy bot role
+
+void() havocbot_role_kh_carrier;
+void() havocbot_role_kh_defense;
+void() havocbot_role_kh_offense;
+void() havocbot_role_kh_freelancer;
+
+
+void havocbot_goalrating_kh(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
+{SELFPARAM();
+       entity head;
+       for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
+       {
+               if(head.owner == self)
+                       continue;
+               if(!kh_tracking_enabled)
+               {
+                       // if it's carried by our team we know about it
+                       // otherwise we have to see it to know about it
+                       if(!head.owner || head.team != self.team)
+                       {
+                               traceline(self.origin + self.view_ofs, head.origin, MOVE_NOMONSTERS, self);
+                               if (trace_fraction < 1 && trace_ent != head)
+                                       continue; // skip what I can't see
+                       }
+               }
+               if(!head.owner)
+                       navigation_routerating(head, ratingscale_dropped * BOT_PICKUP_RATING_HIGH, 100000);
+               else if(head.team == self.team)
+                       navigation_routerating(head.owner, ratingscale_team * BOT_PICKUP_RATING_HIGH, 100000);
+               else
+                       navigation_routerating(head.owner, ratingscale_enemy * BOT_PICKUP_RATING_HIGH, 100000);
+       }
+
+       havocbot_goalrating_items(1, self.origin, 10000);
+}
+
+void havocbot_role_kh_carrier()
+{SELFPARAM();
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       if (!(self.kh_next))
+       {
+               LOG_TRACE("changing role to freelancer\n");
+               self.havocbot_role = havocbot_role_kh_freelancer;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+
+               if(kh_Key_AllOwnedByWhichTeam() == self.team)
+                       havocbot_goalrating_kh(10, 0.1, 0.1); // bring home
+               else
+                       havocbot_goalrating_kh(4, 4, 1); // play defensively
+
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_kh_defense()
+{SELFPARAM();
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       if (self.kh_next)
+       {
+               LOG_TRACE("changing role to carrier\n");
+               self.havocbot_role = havocbot_role_kh_carrier;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + random() * 10 + 20;
+       if (time > self.havocbot_role_timeout)
+       {
+               LOG_TRACE("changing role to freelancer\n");
+               self.havocbot_role = havocbot_role_kh_freelancer;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               float key_owner_team;
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+
+               key_owner_team = kh_Key_AllOwnedByWhichTeam();
+               if(key_owner_team == self.team)
+                       havocbot_goalrating_kh(10, 0.1, 0.1); // defend key carriers
+               else if(key_owner_team == -1)
+                       havocbot_goalrating_kh(4, 1, 0.1); // play defensively
+               else
+                       havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY
+
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_kh_offense()
+{SELFPARAM();
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       if (self.kh_next)
+       {
+               LOG_TRACE("changing role to carrier\n");
+               self.havocbot_role = havocbot_role_kh_carrier;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + random() * 10 + 20;
+       if (time > self.havocbot_role_timeout)
+       {
+               LOG_TRACE("changing role to freelancer\n");
+               self.havocbot_role = havocbot_role_kh_freelancer;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               float key_owner_team;
+
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+
+               key_owner_team = kh_Key_AllOwnedByWhichTeam();
+               if(key_owner_team == self.team)
+                       havocbot_goalrating_kh(10, 0.1, 0.1); // defend anyway
+               else if(key_owner_team == -1)
+                       havocbot_goalrating_kh(0.1, 1, 4); // play offensively
+               else
+                       havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK! EMERGENCY!
+
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_kh_freelancer()
+{SELFPARAM();
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       if (self.kh_next)
+       {
+               LOG_TRACE("changing role to carrier\n");
+               self.havocbot_role = havocbot_role_kh_carrier;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + random() * 10 + 10;
+       if (time > self.havocbot_role_timeout)
+       {
+               if (random() < 0.5)
+               {
+                       LOG_TRACE("changing role to offense\n");
+                       self.havocbot_role = havocbot_role_kh_offense;
+               }
+               else
+               {
+                       LOG_TRACE("changing role to defense\n");
+                       self.havocbot_role = havocbot_role_kh_defense;
+               }
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               float key_owner_team;
+
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+
+               key_owner_team = kh_Key_AllOwnedByWhichTeam();
+               if(key_owner_team == self.team)
+                       havocbot_goalrating_kh(10, 0.1, 0.1); // defend anyway
+               else if(key_owner_team == -1)
+                       havocbot_goalrating_kh(1, 10, 4); // prefer dropped keys
+               else
+                       havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY
+
+               navigation_goalrating_end();
+       }
+}
+
+
+// register this as a mutator
+
+MUTATOR_HOOKFUNCTION(kh, ClientDisconnect)
+{SELFPARAM();
+       kh_Key_DropAll(self, true);
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver)
+{SELFPARAM();
+       kh_Key_DropAll(self, true);
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(kh, PlayerDies)
+{SELFPARAM();
+       if(self == other)
+               kh_Key_DropAll(self, true);
+       else if(IS_PLAYER(other))
+               kh_Key_DropAll(self, false);
+       else
+               kh_Key_DropAll(self, true);
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+       frag_score = kh_HandleFrags(frag_attacker, frag_target, frag_score);
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(kh, MatchEnd)
+{
+       kh_finalize();
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(kh, GetTeamCount, CBC_ORDER_EXCLUSIVE)
+{
+       ret_float = kh_teams;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(kh, SpectateCopy)
+{SELFPARAM();
+       self.kh_state = other.kh_state;
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(kh, PlayerUseKey)
+{SELFPARAM();
+       if(MUTATOR_RETURNVALUE == 0)
+       {
+               entity k;
+               k = self.kh_next;
+               if(k)
+               {
+                       kh_Key_DropOne(k);
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole)
+{
+       if(self.deadflag != DEAD_NO)
+               return true;
+
+       float r = random() * 3;
+       if (r < 1)
+               self.havocbot_role = havocbot_role_kh_offense;
+       else if (r < 2)
+               self.havocbot_role = havocbot_role_kh_defense;
+       else
+               self.havocbot_role = havocbot_role_kh_freelancer;
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(kh, DropSpecialItems)
+{
+       kh_Key_DropAll(frag_target, false);
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(kh, reset_map_global)
+{
+       kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round + (game_starttime - time), kh_StartRound);
+       return false;
+}
+
+REGISTER_MUTATOR(kh, IS_GAMETYPE(KEYHUNT))
+{
+       ActivateTeamplay();
+       SetLimits(autocvar_g_keyhunt_point_limit, autocvar_g_keyhunt_point_leadlimit, -1, -1);
+       if(autocvar_g_keyhunt_team_spawns)
+               have_team_spawns = -1; // request team spawns
+
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+               kh_Initialize();
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back kh_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/gamemode_lms.qc b/qcsrc/server/mutators/mutator/gamemode_lms.qc
new file mode 100644 (file)
index 0000000..30789a5
--- /dev/null
@@ -0,0 +1,313 @@
+#ifndef GAMEMODE_LMS_H
+#define GAMEMODE_LMS_H
+
+// scoreboard stuff
+const float SP_LMS_LIVES = 4;
+const float SP_LMS_RANK = 5;
+
+// lives related defs
+float lms_lowest_lives;
+float lms_next_place;
+float LMS_NewPlayerLives();
+
+#endif
+
+#ifdef IMPLEMENTATION
+
+#include "../../campaign.qh"
+#include "../../command/cmd.qh"
+
+int autocvar_g_lms_extra_lives;
+bool autocvar_g_lms_join_anytime;
+int autocvar_g_lms_last_join;
+#define autocvar_g_lms_lives_override cvar("g_lms_lives_override")
+bool autocvar_g_lms_regenerate;
+
+// main functions
+float LMS_NewPlayerLives()
+{
+       float fl;
+       fl = autocvar_fraglimit;
+       if(fl == 0)
+               fl = 999;
+
+       // first player has left the game for dying too much? Nobody else can get in.
+       if(lms_lowest_lives < 1)
+               return 0;
+
+       if(!autocvar_g_lms_join_anytime)
+               if(lms_lowest_lives < fl - autocvar_g_lms_last_join)
+                       return 0;
+
+       return bound(1, lms_lowest_lives, fl);
+}
+
+// mutator hooks
+MUTATOR_HOOKFUNCTION(lms, reset_map_global)
+{
+       lms_lowest_lives = 999;
+       lms_next_place = player_count;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(lms, reset_map_players)
+{SELFPARAM();
+       entity e;
+       if(restart_mapalreadyrestarted || (time < game_starttime))
+       FOR_EACH_CLIENT(e)
+       if(IS_PLAYER(e))
+       {
+               WITH(entity, self, e, PlayerScore_Add(e, SP_LMS_LIVES, LMS_NewPlayerLives()));
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
+{SELFPARAM();
+       // player is dead and becomes observer
+       // FIXME fix LMS scoring for new system
+       if(PlayerScore_Add(self, SP_LMS_RANK, 0) > 0)
+       {
+               self.classname = "observer";
+               Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_LMS_NOLIVES);
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerDies)
+{SELFPARAM();
+       self.respawn_flags |= RESPAWN_FORCE;
+
+       return false;
+}
+
+void lms_RemovePlayer(entity player)
+{
+       // Only if the player cannot play at all
+       if(PlayerScore_Add(player, SP_LMS_RANK, 0) == 666)
+               player.frags = FRAGS_SPECTATOR;
+       else
+               player.frags = FRAGS_LMS_LOSER;
+
+       if(player.killcount != -666)
+               if(PlayerScore_Add(player, SP_LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
+                       Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
+               else
+                       Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
+{SELFPARAM();
+       lms_RemovePlayer(self);
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
+{SELFPARAM();
+       lms_RemovePlayer(self);
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientConnect)
+{SELFPARAM();
+       self.classname = "player";
+       campaign_bots_may_start = 1;
+
+       if(PlayerScore_Add(self, SP_LMS_LIVES, LMS_NewPlayerLives()) <= 0)
+       {
+               PlayerScore_Add(self, SP_LMS_RANK, 666);
+               self.frags = FRAGS_SPECTATOR;
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
+{SELFPARAM();
+       if(self.deadflag == DEAD_DYING)
+               self.deadflag = DEAD_RESPAWNING;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
+{
+       if(autocvar_g_lms_regenerate)
+               return false;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
+{
+       // forbode!
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
+{
+       // remove a life
+       float tl;
+       tl = PlayerScore_Add(frag_target, SP_LMS_LIVES, -1);
+       if(tl < lms_lowest_lives)
+               lms_lowest_lives = tl;
+       if(tl <= 0)
+       {
+               if(!lms_next_place)
+                       lms_next_place = player_count;
+               else
+                       lms_next_place = min(lms_next_place, player_count);
+               PlayerScore_Add(frag_target, SP_LMS_RANK, lms_next_place); // won't ever spawn again
+               --lms_next_place;
+       }
+       frag_score = 0;
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, SetStartItems)
+{
+       start_items &= ~IT_UNLIMITED_AMMO;
+       start_health       = warmup_start_health       = cvar("g_lms_start_health");
+       start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
+       start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
+       start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
+       start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
+       start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
+       start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
+       start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
+{
+       // don't clear player score
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, FilterItem)
+{SELFPARAM();
+       if(autocvar_g_lms_extra_lives)
+       if(self.itemdef == ITEM_HealthMega)
+       {
+               self.max_health = 1;
+               return false;
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ItemTouch)
+{SELFPARAM();
+       // give extra lives for mega health
+       if (self.items & ITEM_HealthMega.m_itemid)
+       {
+               Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_EXTRALIVES);
+               PlayerScore_Add(other, SP_LMS_LIVES, autocvar_g_lms_extra_lives);
+       }
+
+       return MUT_ITEMTOUCH_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
+{
+       entity head;
+       FOR_EACH_REALCLIENT(head)
+       {
+               ++bot_activerealplayers;
+               ++bot_realplayers;
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
+{
+       if(self.lms_spectate_warning)
+       {
+               // for the forfeit message...
+               self.lms_spectate_warning = 2;
+               // mark player as spectator
+               PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0));
+       }
+       else
+       {
+               self.lms_spectate_warning = 1;
+               sprint(self, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n");
+               return MUT_SPECCMD_RETURN;
+       }
+       return MUT_SPECCMD_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(lms, CheckRules_World)
+{
+       ret_float = WinningCondition_LMS();
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, WantWeapon)
+{
+       want_allguns = true;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
+{
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
+{
+       if(gameover)
+       if(score_field == SP_LMS_RANK)
+               return true; // allow writing to this field in intermission as it is needed for newly joining players
+       return false;
+}
+
+// scoreboard stuff
+void lms_ScoreRules()
+{
+       ScoreRules_basics(0, 0, 0, false);
+       ScoreInfo_SetLabel_PlayerScore(SP_LMS_LIVES,    "lives",     SFL_SORT_PRIO_SECONDARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_LMS_RANK,     "rank",      SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
+       ScoreRules_basics_end();
+}
+
+void lms_Initialize()
+{
+       lms_lowest_lives = 9999;
+       lms_next_place = 0;
+
+       lms_ScoreRules();
+}
+
+REGISTER_MUTATOR(lms, IS_GAMETYPE(LMS))
+{
+       SetLimits(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override), 0, -1, -1);
+
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+               lms_Initialize();
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back lms_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/gamemode_onslaught.qc b/qcsrc/server/mutators/mutator/gamemode_onslaught.qc
new file mode 100644 (file)
index 0000000..d01b48c
--- /dev/null
@@ -0,0 +1,2315 @@
+#ifndef GAMEMODE_ONSLAUGHT_H
+#define GAMEMODE_ONSLAUGHT_H
+
+#ifdef SVQC
+
+.entity ons_toucher; // player who touched the control point
+
+// control point / generator constants
+const float ONS_CP_THINKRATE = 0.2;
+const float GEN_THINKRATE = 1;
+#define CPGEN_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13))
+const vector CPGEN_WAYPOINT_OFFSET = ('0 0 128');
+const vector CPICON_OFFSET = ('0 0 96');
+
+// list of generators on the map
+entity ons_worldgeneratorlist;
+.entity ons_worldgeneratornext;
+.entity ons_stalegeneratornext;
+
+// list of control points on the map
+entity ons_worldcplist;
+.entity ons_worldcpnext;
+.entity ons_stalecpnext;
+
+// list of links on the map
+entity ons_worldlinklist;
+.entity ons_worldlinknext;
+.entity ons_stalelinknext;
+
+// definitions
+.entity sprite;
+.string target2;
+.int iscaptured;
+.int islinked;
+.int isshielded;
+.float lasthealth;
+.int lastteam;
+.int lastshielded;
+.int lastcaptured;
+
+.bool waslinked;
+
+bool ons_stalemate;
+
+.float teleport_antispam;
+
+.bool ons_roundlost;
+
+// waypoint sprites
+.entity bot_basewaypoint; // generator waypointsprite
+
+.bool isgenneighbor[17];
+.bool iscpneighbor[17];
+float ons_notification_time[17];
+
+.float ons_overtime_damagedelay;
+
+.vector ons_deathloc;
+
+.entity ons_spawn_by;
+
+// declarations for functions used outside gamemode_onslaught.qc
+void ons_Generator_UpdateSprite(entity e);
+void ons_ControlPoint_UpdateSprite(entity e);
+bool ons_ControlPoint_Attackable(entity cp, int teamnumber);
+
+// CaptureShield: Prevent capturing or destroying control point/generator if it is not available yet
+float ons_captureshield_force; // push force of the shield
+
+// bot player logic
+const int HAVOCBOT_ONS_ROLE_NONE               = 0;
+const int HAVOCBOT_ONS_ROLE_DEFENSE    = 2;
+const int HAVOCBOT_ONS_ROLE_ASSISTANT  = 4;
+const int HAVOCBOT_ONS_ROLE_OFFENSE    = 8;
+
+.entity havocbot_ons_target;
+
+.int havocbot_role_flags;
+.float havocbot_attack_time;
+
+void havocbot_role_ons_defense();
+void havocbot_role_ons_offense();
+void havocbot_role_ons_assistant();
+
+void havocbot_ons_reset_role(entity bot);
+void havocbot_goalrating_items(float ratingscale, vector org, float sradius);
+void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradius);
+
+// score rule declarations
+const int ST_ONS_CAPS = 1;
+const int SP_ONS_CAPS = 4;
+const int SP_ONS_TAKES = 6;
+
+#endif
+#endif
+
+#ifdef IMPLEMENTATION
+
+#include "../../controlpoint.qh"
+#include "../../generator.qh"
+
+bool g_onslaught;
+
+float autocvar_g_onslaught_debug;
+float autocvar_g_onslaught_teleport_wait;
+bool autocvar_g_onslaught_spawn_at_controlpoints;
+bool autocvar_g_onslaught_spawn_at_generator;
+float autocvar_g_onslaught_cp_proxydecap;
+float autocvar_g_onslaught_cp_proxydecap_distance = 512;
+float autocvar_g_onslaught_cp_proxydecap_dps = 100;
+float autocvar_g_onslaught_spawn_at_controlpoints_chance = 0.5;
+float autocvar_g_onslaught_spawn_at_controlpoints_random;
+float autocvar_g_onslaught_spawn_at_generator_chance;
+float autocvar_g_onslaught_spawn_at_generator_random;
+float autocvar_g_onslaught_cp_buildhealth;
+float autocvar_g_onslaught_cp_buildtime;
+float autocvar_g_onslaught_cp_health;
+float autocvar_g_onslaught_cp_regen;
+float autocvar_g_onslaught_gen_health;
+float autocvar_g_onslaught_shield_force = 100;
+float autocvar_g_onslaught_allow_vehicle_touch;
+float autocvar_g_onslaught_round_timelimit;
+float autocvar_g_onslaught_point_limit;
+float autocvar_g_onslaught_warmup;
+float autocvar_g_onslaught_teleport_radius;
+float autocvar_g_onslaught_spawn_choose;
+float autocvar_g_onslaught_click_radius;
+
+void FixSize(entity e);
+
+// =======================
+// CaptureShield Functions
+// =======================
+
+bool ons_CaptureShield_Customize()
+{SELFPARAM();
+       entity e = WaypointSprite_getviewentity(other);
+
+       if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, e.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return false; }
+       if(SAME_TEAM(self, e)) { return false; }
+
+       return true;
+}
+
+void ons_CaptureShield_Touch()
+{SELFPARAM();
+       if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, other.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return; }
+       if(!IS_PLAYER(other)) { return; }
+       if(SAME_TEAM(other, self)) { return; }
+
+       vector mymid = (self.absmin + self.absmax) * 0.5;
+       vector othermid = (other.absmin + other.absmax) * 0.5;
+
+       Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ons_captureshield_force);
+
+       if(IS_REAL_CLIENT(other))
+       {
+               play2(other, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
+
+               if(self.enemy.classname == "onslaught_generator")
+                       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_GENERATOR_SHIELDED);
+               else
+                       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_CONTROLPOINT_SHIELDED);
+       }
+}
+
+void ons_CaptureShield_Reset()
+{SELFPARAM();
+       self.colormap = self.enemy.colormap;
+       self.team = self.enemy.team;
+}
+
+void ons_CaptureShield_Spawn(entity generator, bool is_generator)
+{
+       entity shield = spawn();
+
+       shield.enemy = generator;
+       shield.team = generator.team;
+       shield.colormap = generator.colormap;
+       shield.reset = ons_CaptureShield_Reset;
+       shield.touch = ons_CaptureShield_Touch;
+       shield.customizeentityforclient = ons_CaptureShield_Customize;
+       shield.classname = "ons_captureshield";
+       shield.effects = EF_ADDITIVE;
+       shield.movetype = MOVETYPE_NOCLIP;
+       shield.solid = SOLID_TRIGGER;
+       shield.avelocity = '7 0 11';
+       shield.scale = 1;
+       shield.model = ((is_generator) ? "models/onslaught/generator_shield.md3" : "models/onslaught/controlpoint_shield.md3");
+
+       precache_model(shield.model);
+       setorigin(shield, generator.origin);
+       _setmodel(shield, shield.model);
+       setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
+}
+
+
+// ==========
+// Junk Pile
+// ==========
+
+void ons_debug(string input)
+{
+       switch(autocvar_g_onslaught_debug)
+       {
+               case 1: LOG_TRACE(input); break;
+               case 2: LOG_INFO(input); break;
+       }
+}
+
+void setmodel_fixsize(entity e, Model m)
+{
+       setmodel(e, m);
+       FixSize(e);
+}
+
+void onslaught_updatelinks()
+{
+       entity l;
+       // first check if the game has ended
+       ons_debug("--- updatelinks ---\n");
+       // mark generators as being shielded and networked
+       for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
+       {
+               if (l.iscaptured)
+                       ons_debug(strcat(etos(l), " (generator) belongs to team ", ftos(l.team), "\n"));
+               else
+                       ons_debug(strcat(etos(l), " (generator) is destroyed\n"));
+               l.islinked = l.iscaptured;
+               l.isshielded = l.iscaptured;
+               l.sprite.SendFlags |= 16;
+       }
+       // mark points as shielded and not networked
+       for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
+       {
+               l.islinked = false;
+               l.isshielded = true;
+               int i;
+               for(i = 0; i < 17; ++i) { l.isgenneighbor[i] = false; l.iscpneighbor[i] = false; }
+               ons_debug(strcat(etos(l), " (point) belongs to team ", ftos(l.team), "\n"));
+               l.sprite.SendFlags |= 16;
+       }
+       // flow power outward from the generators through the network
+       bool stop = false;
+       while (!stop)
+       {
+               stop = true;
+               for(l = ons_worldlinklist; l; l = l.ons_worldlinknext)
+               {
+                       // if both points are captured by the same team, and only one of
+                       // them is powered, mark the other one as powered as well
+                       if (l.enemy.iscaptured && l.goalentity.iscaptured)
+                               if (l.enemy.islinked != l.goalentity.islinked)
+                                       if(SAME_TEAM(l.enemy, l.goalentity))
+                                       {
+                                               if (!l.goalentity.islinked)
+                                               {
+                                                       stop = false;
+                                                       l.goalentity.islinked = true;
+                                                       ons_debug(strcat(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)\n"));
+                                               }
+                                               else if (!l.enemy.islinked)
+                                               {
+                                                       stop = false;
+                                                       l.enemy.islinked = true;
+                                                       ons_debug(strcat(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)\n"));
+                                               }
+                                       }
+               }
+       }
+       // now that we know which points are powered we can mark their neighbors
+       // as unshielded if team differs
+       for(l = ons_worldlinklist; l; l = l.ons_worldlinknext)
+       {
+               if (l.goalentity.islinked)
+               {
+                       if(DIFF_TEAM(l.goalentity, l.enemy))
+                       {
+                               ons_debug(strcat(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)\n"));
+                               l.enemy.isshielded = false;
+                       }
+                       if(l.goalentity.classname == "onslaught_generator")
+                               l.enemy.isgenneighbor[l.goalentity.team] = true;
+                       else
+                               l.enemy.iscpneighbor[l.goalentity.team] = true;
+               }
+               if (l.enemy.islinked)
+               {
+                       if(DIFF_TEAM(l.goalentity, l.enemy))
+                       {
+                               ons_debug(strcat(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)\n"));
+                               l.goalentity.isshielded = false;
+                       }
+                       if(l.enemy.classname == "onslaught_generator")
+                               l.goalentity.isgenneighbor[l.enemy.team] = true;
+                       else
+                               l.goalentity.iscpneighbor[l.enemy.team] = true;
+               }
+       }
+       // now update the generators
+       for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
+       {
+               if (l.isshielded)
+               {
+                       ons_debug(strcat(etos(l), " (generator) is shielded\n"));
+                       l.takedamage = DAMAGE_NO;
+                       l.bot_attack = false;
+               }
+               else
+               {
+                       ons_debug(strcat(etos(l), " (generator) is not shielded\n"));
+                       l.takedamage = DAMAGE_AIM;
+                       l.bot_attack = true;
+               }
+
+               ons_Generator_UpdateSprite(l);
+       }
+       // now update the takedamage and alpha variables on control point icons
+       for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
+       {
+               if (l.isshielded)
+               {
+                       ons_debug(strcat(etos(l), " (point) is shielded\n"));
+                       if (l.goalentity)
+                       {
+                               l.goalentity.takedamage = DAMAGE_NO;
+                               l.goalentity.bot_attack = false;
+                       }
+               }
+               else
+               {
+                       ons_debug(strcat(etos(l), " (point) is not shielded\n"));
+                       if (l.goalentity)
+                       {
+                               l.goalentity.takedamage = DAMAGE_AIM;
+                               l.goalentity.bot_attack = true;
+                       }
+               }
+               ons_ControlPoint_UpdateSprite(l);
+       }
+       l = findchain(classname, "ons_captureshield");
+       while(l)
+       {
+               l.team = l.enemy.team;
+               l.colormap = l.enemy.colormap;
+               l = l.chain;
+       }
+}
+
+
+// ===================
+// Main Link Functions
+// ===================
+
+bool ons_Link_Send(entity this, entity to, int sendflags)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_RADARLINK);
+       WriteByte(MSG_ENTITY, sendflags);
+       if(sendflags & 1)
+       {
+               WriteCoord(MSG_ENTITY, self.goalentity.origin_x);
+               WriteCoord(MSG_ENTITY, self.goalentity.origin_y);
+               WriteCoord(MSG_ENTITY, self.goalentity.origin_z);
+       }
+       if(sendflags & 2)
+       {
+               WriteCoord(MSG_ENTITY, self.enemy.origin_x);
+               WriteCoord(MSG_ENTITY, self.enemy.origin_y);
+               WriteCoord(MSG_ENTITY, self.enemy.origin_z);
+       }
+       if(sendflags & 4)
+       {
+               WriteByte(MSG_ENTITY, self.clientcolors); // which is goalentity's color + enemy's color * 16
+       }
+       return true;
+}
+
+void ons_Link_CheckUpdate()
+{SELFPARAM();
+       // TODO check if the two sides have moved (currently they won't move anyway)
+       float cc = 0, cc1 = 0, cc2 = 0;
+
+       if(self.goalentity.islinked || self.goalentity.iscaptured) { cc1 = (self.goalentity.team - 1) * 0x01; }
+       if(self.enemy.islinked || self.enemy.iscaptured) { cc2 = (self.enemy.team - 1) * 0x10; }
+
+       cc = cc1 + cc2;
+
+       if(cc != self.clientcolors)
+       {
+               self.clientcolors = cc;
+               self.SendFlags |= 4;
+       }
+
+       self.nextthink = time;
+}
+
+void ons_DelayedLinkSetup()
+{SELFPARAM();
+       self.goalentity = find(world, targetname, self.target);
+       self.enemy = find(world, targetname, self.target2);
+       if(!self.goalentity) { objerror("can not find target\n"); }
+       if(!self.enemy) { objerror("can not find target2\n"); }
+
+       ons_debug(strcat(etos(self.goalentity), " linked with ", etos(self.enemy), "\n"));
+       self.SendFlags |= 3;
+       self.think = ons_Link_CheckUpdate;
+       self.nextthink = time;
+}
+
+
+// =============================
+// Main Control Point Functions
+// =============================
+
+int ons_ControlPoint_CanBeLinked(entity cp, int teamnumber)
+{
+       if(cp.isgenneighbor[teamnumber]) { return 2; }
+       if(cp.iscpneighbor[teamnumber]) { return 1; }
+
+       return 0;
+}
+
+int ons_ControlPoint_Attackable(entity cp, int teamnumber)
+       // -2: SAME TEAM, attackable by enemy!
+       // -1: SAME TEAM!
+       // 0: off limits
+       // 1: attack it
+       // 2: touch it
+       // 3: attack it (HIGH PRIO)
+       // 4: touch it (HIGH PRIO)
+{
+       int a;
+
+       if(cp.isshielded)
+       {
+               return 0;
+       }
+       else if(cp.goalentity)
+       {
+               // if there's already an icon built, nothing happens
+               if(cp.team == teamnumber)
+               {
+                       a = ons_ControlPoint_CanBeLinked(cp, teamnumber);
+                       if(a) // attackable by enemy?
+                               return -2; // EMERGENCY!
+                       return -1;
+               }
+               // we know it can be linked, so no need to check
+               // but...
+               a = ons_ControlPoint_CanBeLinked(cp, teamnumber);
+               if(a == 2) // near our generator?
+                       return 3; // EMERGENCY!
+               return 1;
+       }
+       else
+       {
+               // free point
+               if(ons_ControlPoint_CanBeLinked(cp, teamnumber))
+               {
+                       a = ons_ControlPoint_CanBeLinked(cp, teamnumber); // why was this here NUM_TEAM_1 + NUM_TEAM_2 - t
+                       if(a == 2)
+                               return 4; // GET THIS ONE NOW!
+                       else
+                               return 2; // TOUCH ME
+               }
+       }
+       return 0;
+}
+
+void ons_ControlPoint_Icon_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{SELFPARAM();
+       if(damage <= 0) { return; }
+
+       if (self.owner.isshielded)
+       {
+               // this is protected by a shield, so ignore the damage
+               if (time > self.pain_finished)
+                       if (IS_PLAYER(attacker))
+                       {
+                               play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
+                               self.pain_finished = time + 1;
+                               attacker.typehitsound += 1; // play both sounds (shield is way too quiet)
+                       }
+
+               return;
+       }
+
+       if(IS_PLAYER(attacker))
+       if(time - ons_notification_time[self.team] > 10)
+       {
+               play2team(self.team, SND(ONS_CONTROLPOINT_UNDERATTACK));
+               ons_notification_time[self.team] = time;
+       }
+
+       self.health = self.health - damage;
+       if(self.owner.iscaptured)
+               WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
+       else
+               WaypointSprite_UpdateBuildFinished(self.owner.sprite, time + (self.max_health - self.health) / (self.count / ONS_CP_THINKRATE));
+       self.pain_finished = time + 1;
+       // particles on every hit
+       pointparticles(particleeffectnum(EFFECT_SPARKS), hitloc, force*-1, 1);
+       //sound on every hit
+       if (random() < 0.5)
+               sound(self, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE+0.3, ATTEN_NORM);
+       else
+               sound(self, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE+0.3, ATTEN_NORM);
+
+       if (self.health < 0)
+       {
+               sound(self, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM);
+               pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), self.origin, '0 0 0', 1);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_CPDESTROYED_), self.owner.message, attacker.netname);
+
+               PlayerScore_Add(attacker, SP_ONS_TAKES, 1);
+               PlayerScore_Add(attacker, SP_SCORE, 10);
+
+               self.owner.goalentity = world;
+               self.owner.islinked = false;
+               self.owner.iscaptured = false;
+               self.owner.team = 0;
+               self.owner.colormap = 1024;
+
+               WaypointSprite_UpdateMaxHealth(self.owner.sprite, 0);
+
+               onslaught_updatelinks();
+
+               // Use targets now (somebody make sure this is in the right place..)
+               setself(self.owner);
+               activator = self;
+               SUB_UseTargets ();
+               setself(this);
+
+               self.owner.waslinked = self.owner.islinked;
+               if(self.owner.model != "models/onslaught/controlpoint_pad.md3")
+                       setmodel_fixsize(self.owner, MDL_ONS_CP_PAD1);
+               //setsize(self, '-32 -32 0', '32 32 8');
+
+               remove(self);
+       }
+
+       self.SendFlags |= CPSF_STATUS;
+}
+
+void ons_ControlPoint_Icon_Think()
+{SELFPARAM();
+       self.nextthink = time + ONS_CP_THINKRATE;
+
+       if(autocvar_g_onslaught_cp_proxydecap)
+       {
+        int _enemy_count = 0;
+        int _friendly_count = 0;
+        float _dist;
+        entity _player;
+
+        FOR_EACH_PLAYER(_player)
+        {
+            if(!_player.deadflag)
+            {
+                _dist = vlen(_player.origin - self.origin);
+                if(_dist < autocvar_g_onslaught_cp_proxydecap_distance)
+                {
+                                       if(SAME_TEAM(_player, self))
+                        ++_friendly_count;
+                    else
+                        ++_enemy_count;
+                }
+            }
+        }
+
+        _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE);
+        _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE);
+
+        self.health = bound(0, self.health + (_friendly_count - _enemy_count), self.max_health);
+               self.SendFlags |= CPSF_STATUS;
+        if(self.health <= 0)
+        {
+            ons_ControlPoint_Icon_Damage(self, self, 1, 0, self.origin, '0 0 0');
+            return;
+        }
+    }
+
+       if (time > self.pain_finished + 5)
+       {
+               if(self.health < self.max_health)
+               {
+                       self.health = self.health + self.count;
+                       if (self.health >= self.max_health)
+                               self.health = self.max_health;
+                       WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
+               }
+       }
+
+       if(self.owner.islinked != self.owner.waslinked)
+       {
+               // unteam the spawnpoint if needed
+               int t = self.owner.team;
+               if(!self.owner.islinked)
+                       self.owner.team = 0;
+
+               setself(self.owner);
+               activator = self;
+               SUB_UseTargets ();
+               setself(this);
+
+               self.owner.team = t;
+
+               self.owner.waslinked = self.owner.islinked;
+       }
+
+       // damaged fx
+       if(random() < 0.6 - self.health / self.max_health)
+       {
+               Send_Effect(EFFECT_ELECTRIC_SPARKS, self.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1);
+
+               if(random() > 0.8)
+                       sound(self, CH_PAIN, SND_ONS_SPARK1, VOL_BASE, ATTEN_NORM);
+               else if (random() > 0.5)
+                       sound(self, CH_PAIN, SND_ONS_SPARK2, VOL_BASE, ATTEN_NORM);
+       }
+}
+
+void ons_ControlPoint_Icon_BuildThink()
+{SELFPARAM();
+       int a;
+
+       self.nextthink = time + ONS_CP_THINKRATE;
+
+       // only do this if there is power
+       a = ons_ControlPoint_CanBeLinked(self.owner, self.owner.team);
+       if(!a)
+               return;
+
+       self.health = self.health + self.count;
+
+       self.SendFlags |= CPSF_STATUS;
+
+       if (self.health >= self.max_health)
+       {
+               self.health = self.max_health;
+               self.count = autocvar_g_onslaught_cp_regen * ONS_CP_THINKRATE; // slow repair rate from now on
+               self.think = ons_ControlPoint_Icon_Think;
+               sound(self, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILT, VOL_BASE, ATTEN_NORM);
+               self.owner.iscaptured = true;
+               self.solid = SOLID_BBOX;
+
+               Send_Effect(EFFECT_CAP(self.owner.team), self.owner.origin, '0 0 0', 1);
+
+               WaypointSprite_UpdateMaxHealth(self.owner.sprite, self.max_health);
+               WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
+
+               if(IS_PLAYER(self.owner.ons_toucher))
+               {
+                       Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ONSLAUGHT_CAPTURE, self.owner.ons_toucher.netname, self.owner.message);
+                       Send_Notification(NOTIF_ALL_EXCEPT, self.owner.ons_toucher, MSG_CENTER, APP_TEAM_ENT_4(self.owner.ons_toucher, CENTER_ONS_CAPTURE_), self.owner.message);
+                       Send_Notification(NOTIF_ONE, self.owner.ons_toucher, MSG_CENTER, CENTER_ONS_CAPTURE, self.owner.message);
+                       PlayerScore_Add(self.owner.ons_toucher, SP_ONS_CAPS, 1);
+                       PlayerTeamScore_AddScore(self.owner.ons_toucher, 10);
+               }
+
+               self.owner.ons_toucher = world;
+
+               onslaught_updatelinks();
+
+               // Use targets now (somebody make sure this is in the right place..)
+               setself(self.owner);
+               activator = self;
+               SUB_UseTargets ();
+               setself(this);
+
+               self.SendFlags |= CPSF_SETUP;
+       }
+       if(self.owner.model != MDL_ONS_CP_PAD2.model_str())
+               setmodel_fixsize(self.owner, MDL_ONS_CP_PAD2);
+
+       if(random() < 0.9 - self.health / self.max_health)
+               Send_Effect(EFFECT_RAGE, self.origin + 10 * randomvec(), '0 0 -1', 1);
+}
+
+void onslaught_controlpoint_icon_link(entity e, void() spawnproc);
+
+void ons_ControlPoint_Icon_Spawn(entity cp, entity player)
+{
+       entity e = spawn();
+
+       setsize(e, CPICON_MIN, CPICON_MAX);
+       setorigin(e, cp.origin + CPICON_OFFSET);
+
+       e.classname = "onslaught_controlpoint_icon";
+       e.owner = cp;
+       e.max_health = autocvar_g_onslaught_cp_health;
+       e.health = autocvar_g_onslaught_cp_buildhealth;
+       e.solid = SOLID_NOT;
+       e.takedamage = DAMAGE_AIM;
+       e.bot_attack = true;
+       e.event_damage = ons_ControlPoint_Icon_Damage;
+       e.team = player.team;
+       e.colormap = 1024 + (e.team - 1) * 17;
+       e.count = (e.max_health - e.health) * ONS_CP_THINKRATE / autocvar_g_onslaught_cp_buildtime; // how long it takes to build
+
+       sound(e, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILD, VOL_BASE, ATTEN_NORM);
+
+       cp.goalentity = e;
+       cp.team = e.team;
+       cp.colormap = e.colormap;
+
+       Send_Effect(EFFECT_FLAG_TOUCH(player.team), e.origin, '0 0 0', 1);
+
+       WaypointSprite_UpdateBuildFinished(cp.sprite, time + (e.max_health - e.health) / (e.count / ONS_CP_THINKRATE));
+       WaypointSprite_UpdateRule(cp.sprite,cp.team,SPRITERULE_TEAMPLAY);
+       cp.sprite.SendFlags |= 16;
+
+       onslaught_controlpoint_icon_link(e, ons_ControlPoint_Icon_BuildThink);
+}
+
+entity ons_ControlPoint_Waypoint(entity e)
+{
+       if(e.team)
+       {
+               int a = ons_ControlPoint_Attackable(e, e.team);
+
+               if(a == -2) { return WP_OnsCPDefend; } // defend now
+               if(a == -1 || a == 1 || a == 2) { return WP_OnsCP; } // touch
+               if(a == 3 || a == 4) { return WP_OnsCPAttack; } // attack
+       }
+       else
+               return WP_OnsCP;
+
+       return WP_Null;
+}
+
+void ons_ControlPoint_UpdateSprite(entity e)
+{
+       entity s1 = ons_ControlPoint_Waypoint(e);
+       WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1);
+
+       bool sh;
+       sh = !(ons_ControlPoint_CanBeLinked(e, NUM_TEAM_1) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_2) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_3) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_4));
+
+       if(e.lastteam != e.team + 2 || e.lastshielded != sh || e.iscaptured != e.lastcaptured)
+       {
+               if(e.iscaptured) // don't mess up build bars!
+               {
+                       if(sh)
+                       {
+                               WaypointSprite_UpdateMaxHealth(e.sprite, 0);
+                       }
+                       else
+                       {
+                               WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health);
+                               WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health);
+                       }
+               }
+               if(e.lastshielded)
+               {
+                       if(e.team)
+                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, false));
+                       else
+                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5');
+               }
+               else
+               {
+                       if(e.team)
+                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, false));
+                       else
+                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75');
+               }
+               WaypointSprite_Ping(e.sprite);
+
+               e.lastteam = e.team + 2;
+               e.lastshielded = sh;
+               e.lastcaptured = e.iscaptured;
+       }
+}
+
+void ons_ControlPoint_Touch()
+{SELFPARAM();
+       entity toucher = other;
+       int attackable;
+
+       if(IS_VEHICLE(toucher) && toucher.owner)
+       if(autocvar_g_onslaught_allow_vehicle_touch)
+               toucher = toucher.owner;
+       else
+               return;
+
+       if(!IS_PLAYER(toucher)) { return; }
+       if(toucher.frozen) { return; }
+       if(toucher.deadflag != DEAD_NO) { return; }
+
+       if ( SAME_TEAM(self,toucher) )
+       if ( self.iscaptured )
+       {
+               if(time <= toucher.teleport_antispam)
+                       Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT_ANTISPAM, rint(toucher.teleport_antispam - time));
+               else
+                       Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT);
+       }
+
+       attackable = ons_ControlPoint_Attackable(self, toucher.team);
+       if(attackable != 2 && attackable != 4)
+               return;
+       // we've verified that this player has a legitimate claim to this point,
+       // so start building the captured point icon (which only captures this
+       // point if it successfully builds without being destroyed first)
+       ons_ControlPoint_Icon_Spawn(self, toucher);
+
+       self.ons_toucher = toucher;
+
+       onslaught_updatelinks();
+}
+
+void ons_ControlPoint_Think()
+{SELFPARAM();
+       self.nextthink = time + ONS_CP_THINKRATE;
+       CSQCMODEL_AUTOUPDATE(self);
+}
+
+void ons_ControlPoint_Reset()
+{SELFPARAM();
+       if(self.goalentity)
+               remove(self.goalentity);
+
+       self.goalentity = world;
+       self.team = 0;
+       self.colormap = 1024;
+       self.iscaptured = false;
+       self.islinked = false;
+       self.isshielded = true;
+       self.think = ons_ControlPoint_Think;
+       self.ons_toucher = world;
+       self.nextthink = time + ONS_CP_THINKRATE;
+       setmodel_fixsize(self, MDL_ONS_CP_PAD1);
+
+       WaypointSprite_UpdateMaxHealth(self.sprite, 0);
+       WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY);
+
+       onslaught_updatelinks();
+
+       activator = self;
+       SUB_UseTargets(); // to reset the structures, playerspawns etc.
+
+       CSQCMODEL_AUTOUPDATE(self);
+}
+
+void ons_DelayedControlPoint_Setup(void)
+{SELFPARAM();
+       onslaught_updatelinks();
+
+       // captureshield setup
+       ons_CaptureShield_Spawn(self, false);
+
+       CSQCMODEL_AUTOINIT(self);
+}
+
+void ons_ControlPoint_Setup(entity cp)
+{SELFPARAM();
+       // declarations
+       setself(cp); // for later usage with droptofloor()
+
+       // main setup
+       cp.ons_worldcpnext = ons_worldcplist; // link control point into ons_worldcplist
+       ons_worldcplist = cp;
+
+       cp.netname = "Control point";
+       cp.team = 0;
+       cp.solid = SOLID_BBOX;
+       cp.movetype = MOVETYPE_NONE;
+       cp.touch = ons_ControlPoint_Touch;
+       cp.think = ons_ControlPoint_Think;
+       cp.nextthink = time + ONS_CP_THINKRATE;
+       cp.reset = ons_ControlPoint_Reset;
+       cp.colormap = 1024;
+       cp.iscaptured = false;
+       cp.islinked = false;
+       cp.isshielded = true;
+
+       if(cp.message == "") { cp.message = "a"; }
+
+       // appearence
+       setmodel_fixsize(cp, MDL_ONS_CP_PAD1);
+
+       // control point placement
+       if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location
+       {
+               cp.noalign = true;
+               cp.movetype = MOVETYPE_NONE;
+       }
+       else // drop to floor, automatically find a platform and set that as spawn origin
+       {
+               setorigin(cp, cp.origin + '0 0 20');
+               cp.noalign = false;
+               setself(cp);
+               droptofloor();
+               cp.movetype = MOVETYPE_TOSS;
+       }
+
+       // waypointsprites
+       WaypointSprite_SpawnFixed(WP_Null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE);
+       WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY);
+
+       InitializeEntity(cp, ons_DelayedControlPoint_Setup, INITPRIO_SETLOCATION);
+}
+
+
+// =========================
+// Main Generator Functions
+// =========================
+
+entity ons_Generator_Waypoint(entity e)
+{
+       if (e.isshielded)
+               return WP_OnsGenShielded;
+       return WP_OnsGen;
+}
+
+void ons_Generator_UpdateSprite(entity e)
+{
+       entity s1 = ons_Generator_Waypoint(e);
+       WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1);
+
+       if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded)
+       {
+               e.lastteam = e.team + 2;
+               e.lastshielded = e.isshielded;
+               if(e.lastshielded)
+               {
+                       if(e.team)
+                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, false));
+                       else
+                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5');
+               }
+               else
+               {
+                       if(e.team)
+                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, false));
+                       else
+                               WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75');
+               }
+               WaypointSprite_Ping(e.sprite);
+       }
+}
+
+void ons_GeneratorDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{SELFPARAM();
+       if(damage <= 0) { return; }
+       if(warmup_stage || gameover) { return; }
+       if(!round_handler_IsRoundStarted()) { return; }
+
+       if (attacker != self)
+       {
+               if (self.isshielded)
+               {
+                       // this is protected by a shield, so ignore the damage
+                       if (time > self.pain_finished)
+                               if (IS_PLAYER(attacker))
+                               {
+                                       play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
+                                       attacker.typehitsound += 1;
+                                       self.pain_finished = time + 1;
+                               }
+                       return;
+               }
+               if (time > self.pain_finished)
+               {
+                       self.pain_finished = time + 10;
+                       entity head;
+                       FOR_EACH_REALPLAYER(head) if(SAME_TEAM(head, self)) { Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_GENERATOR_UNDERATTACK); }
+                       play2team(self.team, SND(ONS_GENERATOR_UNDERATTACK));
+               }
+       }
+       self.health = self.health - damage;
+       WaypointSprite_UpdateHealth(self.sprite, self.health);
+       // choose an animation frame based on health
+       self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1);
+       // see if the generator is still functional, or dying
+       if (self.health > 0)
+       {
+               self.lasthealth = self.health;
+       }
+       else
+       {
+               if (attacker == self)
+                       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_OVERTIME_));
+               else
+               {
+                       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_));
+                       PlayerScore_Add(attacker, SP_SCORE, 100);
+               }
+               self.iscaptured = false;
+               self.islinked = false;
+               self.isshielded = false;
+               self.takedamage = DAMAGE_NO; // can't be hurt anymore
+               self.event_damage = func_null; // won't do anything if hurt
+               self.count = 0; // reset counter
+               self.think = func_null;
+               self.nextthink = 0;
+               //self.think(); // do the first explosion now
+
+               WaypointSprite_UpdateMaxHealth(self.sprite, 0);
+               WaypointSprite_Ping(self.sprite);
+               //WaypointSprite_Kill(self.sprite); // can't do this yet, code too poor
+
+               onslaught_updatelinks();
+       }
+
+       // Throw some flaming gibs on damage, more damage = more chance for gib
+       if(random() < damage/220)
+       {
+               sound(self, CH_TRIGGER, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
+       }
+       else
+       {
+               // particles on every hit
+               Send_Effect(EFFECT_SPARKS, hitloc, force * -1, 1);
+
+               //sound on every hit
+               if (random() < 0.5)
+                       sound(self, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE, ATTEN_NORM);
+               else
+                       sound(self, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE, ATTEN_NORM);
+       }
+
+       self.SendFlags |= GSF_STATUS;
+}
+
+void ons_GeneratorThink()
+{SELFPARAM();
+       entity e;
+       self.nextthink = time + GEN_THINKRATE;
+       if (!gameover)
+       {
+        if(!self.isshielded && self.wait < time)
+        {
+            self.wait = time + 5;
+            FOR_EACH_REALPLAYER(e)
+            {
+                               if(SAME_TEAM(e, self))
+                               {
+                                       Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM);
+                    soundto(MSG_ONE, e, CHAN_AUTO, SND(KH_ALARM), VOL_BASE, ATTEN_NONE);    // FIXME: unique sound?
+                }
+                               else
+                                       Send_Notification(NOTIF_ONE, e, MSG_CENTER, APP_TEAM_NUM_4(self.team, CENTER_ONS_NOTSHIELDED_));
+            }
+        }
+       }
+}
+
+void ons_GeneratorReset()
+{SELFPARAM();
+       self.team = self.team_saved;
+       self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health;
+       self.takedamage = DAMAGE_AIM;
+       self.bot_attack = true;
+       self.iscaptured = true;
+       self.islinked = true;
+       self.isshielded = true;
+       self.event_damage = ons_GeneratorDamage;
+       self.think = ons_GeneratorThink;
+       self.nextthink = time + GEN_THINKRATE;
+
+       Net_LinkEntity(self, false, 0, generator_send);
+
+       self.SendFlags = GSF_SETUP; // just incase
+       self.SendFlags |= GSF_STATUS;
+
+       WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+       WaypointSprite_UpdateHealth(self.sprite, self.health);
+       WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY);
+
+       onslaught_updatelinks();
+}
+
+void ons_DelayedGeneratorSetup()
+{SELFPARAM();
+       // bot waypoints
+       waypoint_spawnforitem_force(self, self.origin);
+       self.nearestwaypointtimeout = 0; // activate waypointing again
+       self.bot_basewaypoint = self.nearestwaypoint;
+
+       // captureshield setup
+       ons_CaptureShield_Spawn(self, true);
+
+       onslaught_updatelinks();
+
+       Net_LinkEntity(self, false, 0, generator_send);
+}
+
+
+void onslaught_generator_touch()
+{SELFPARAM();
+       if ( IS_PLAYER(other) )
+       if ( SAME_TEAM(self,other) )
+       if ( self.iscaptured )
+       {
+               Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_TELEPORT);
+       }
+}
+
+void ons_GeneratorSetup(entity gen) // called when spawning a generator entity on the map as a spawnfunc
+{SELFPARAM();
+       // declarations
+       int teamnumber = gen.team;
+       setself(gen); // for later usage with droptofloor()
+
+       // main setup
+       gen.ons_worldgeneratornext = ons_worldgeneratorlist; // link generator into ons_worldgeneratorlist
+       ons_worldgeneratorlist = gen;
+
+       gen.netname = sprintf("%s generator", Team_ColoredFullName(teamnumber));
+       gen.classname = "onslaught_generator";
+       gen.solid = SOLID_BBOX;
+       gen.team_saved = teamnumber;
+       gen.movetype = MOVETYPE_NONE;
+       gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health;
+       gen.takedamage = DAMAGE_AIM;
+       gen.bot_attack = true;
+       gen.event_damage = ons_GeneratorDamage;
+       gen.reset = ons_GeneratorReset;
+       gen.think = ons_GeneratorThink;
+       gen.nextthink = time + GEN_THINKRATE;
+       gen.iscaptured = true;
+       gen.islinked = true;
+       gen.isshielded = true;
+       gen.touch = onslaught_generator_touch;
+
+       // appearence
+       // model handled by CSQC
+       setsize(gen, GENERATOR_MIN, GENERATOR_MAX);
+       setorigin(gen, (gen.origin + CPGEN_SPAWN_OFFSET));
+       gen.colormap = 1024 + (teamnumber - 1) * 17;
+
+       // generator placement
+       setself(gen);
+       droptofloor();
+
+       // waypointsprites
+       WaypointSprite_SpawnFixed(WP_Null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE);
+       WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY);
+       WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+       WaypointSprite_UpdateHealth(self.sprite, self.health);
+
+       InitializeEntity(gen, ons_DelayedGeneratorSetup, INITPRIO_SETLOCATION);
+}
+
+
+// ===============
+//  Round Handler
+// ===============
+
+int total_generators;
+void Onslaught_count_generators()
+{
+       entity e;
+       total_generators = redowned = blueowned = yellowowned = pinkowned = 0;
+       for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext)
+       {
+               ++total_generators;
+               redowned += (e.team == NUM_TEAM_1 && e.health > 0);
+               blueowned += (e.team == NUM_TEAM_2 && e.health > 0);
+               yellowowned += (e.team == NUM_TEAM_3 && e.health > 0);
+               pinkowned += (e.team == NUM_TEAM_4 && e.health > 0);
+       }
+}
+
+int Onslaught_GetWinnerTeam()
+{
+       int winner_team = 0;
+       if(redowned > 0)
+               winner_team = NUM_TEAM_1;
+       if(blueowned > 0)
+       {
+               if(winner_team) return 0;
+               winner_team = NUM_TEAM_2;
+       }
+       if(yellowowned > 0)
+       {
+               if(winner_team) return 0;
+               winner_team = NUM_TEAM_3;
+       }
+       if(pinkowned > 0)
+       {
+               if(winner_team) return 0;
+               winner_team = NUM_TEAM_4;
+       }
+       if(winner_team)
+               return winner_team;
+       return -1; // no generators left?
+}
+
+#define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
+#define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1)
+bool Onslaught_CheckWinner()
+{
+       entity e;
+
+       if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0))
+       {
+               ons_stalemate = true;
+
+               if (!wpforenemy_announced)
+               {
+                       Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT);
+                       sound(world, CH_INFO, SND_ONS_GENERATOR_DECAY, VOL_BASE, ATTEN_NONE);
+
+                       wpforenemy_announced = true;
+               }
+
+               entity tmp_entity; // temporary entity
+               float d;
+               for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) if(time >= tmp_entity.ons_overtime_damagedelay)
+               {
+                       // tmp_entity.max_health / 300 gives 5 minutes of overtime.
+                       // control points reduce the overtime duration.
+                       d = 1;
+                       for(e = ons_worldcplist; e; e = e.ons_worldcpnext)
+                       {
+                               if(DIFF_TEAM(e, tmp_entity))
+                               if(e.islinked)
+                                       d = d + 1;
+                       }
+
+                       if(autocvar_g_campaign && autocvar__campaign_testrun)
+                               d = d * tmp_entity.max_health;
+                       else
+                               d = d * tmp_entity.max_health / max(30, 60 * autocvar_timelimit_suddendeath);
+
+                       Damage(tmp_entity, tmp_entity, tmp_entity, d, DEATH_HURTTRIGGER.m_id, tmp_entity.origin, '0 0 0');
+
+                       tmp_entity.sprite.SendFlags |= 16;
+
+                       tmp_entity.ons_overtime_damagedelay = time + 1;
+               }
+       }
+       else { wpforenemy_announced = false; ons_stalemate = false; }
+
+       Onslaught_count_generators();
+
+       if(ONS_OWNED_GENERATORS_OK())
+               return 0;
+
+       int winner_team = Onslaught_GetWinnerTeam();
+
+       if(winner_team > 0)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
+               TeamScore_AddToTeam(winner_team, ST_ONS_CAPS, +1);
+       }
+       else if(winner_team == -1)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
+       }
+
+       ons_stalemate = false;
+
+       play2all(SND(CTF_CAPTURE(winner_team)));
+
+       round_handler_Init(7, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit);
+
+       FOR_EACH_PLAYER(e)
+       {
+               e.ons_roundlost = true;
+               e.player_blocked = true;
+
+               nades_Clear(e);
+       }
+
+       return 1;
+}
+
+bool Onslaught_CheckPlayers()
+{
+       return 1;
+}
+
+void Onslaught_RoundStart()
+{
+       entity tmp_entity;
+       FOR_EACH_PLAYER(tmp_entity) { tmp_entity.player_blocked = false; }
+
+       for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext)
+               tmp_entity.sprite.SendFlags |= 16;
+
+       for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
+               tmp_entity.sprite.SendFlags |= 16;
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+// NOTE: LEGACY CODE, needs to be re-written!
+
+void havocbot_goalrating_ons_offenseitems(float ratingscale, vector org, float sradius)
+{SELFPARAM();
+       entity head;
+       float t, c;
+       int i;
+       bool needarmor = false, needweapons = false;
+
+       // Needs armor/health?
+       if(self.health<100)
+               needarmor = true;
+
+       // Needs weapons?
+       c = 0;
+       for(i = WEP_FIRST; i <= WEP_LAST ; ++i)
+       {
+               // Find weapon
+               if(self.weapons & WepSet_FromWeapon(i))
+               if(++c>=4)
+                       break;
+       }
+
+       if(c<4)
+               needweapons = true;
+
+       if(!needweapons && !needarmor)
+               return;
+
+       ons_debug(strcat(self.netname, " needs weapons ", ftos(needweapons) , "\n"));
+       ons_debug(strcat(self.netname, " needs armor ", ftos(needarmor) , "\n"));
+
+       // See what is around
+       head = findchainfloat(bot_pickup, true);
+       while (head)
+       {
+               // gather health and armor only
+               if (head.solid)
+               if ( ((head.health || head.armorvalue) && needarmor) || (head.weapons && needweapons ) )
+               if (vlen(head.origin - org) < sradius)
+               {
+                       t = head.bot_pickupevalfunc(self, head);
+                       if (t > 0)
+                               navigation_routerating(head, t * ratingscale, 500);
+               }
+               head = head.chain;
+       }
+}
+
+void havocbot_role_ons_setrole(entity bot, int role)
+{
+       ons_debug(strcat(bot.netname," switched to "));
+       switch(role)
+       {
+               case HAVOCBOT_ONS_ROLE_DEFENSE:
+                       ons_debug("defense");
+                       bot.havocbot_role = havocbot_role_ons_defense;
+                       bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_ONS_ROLE_ASSISTANT:
+                       ons_debug("assistant");
+                       bot.havocbot_role = havocbot_role_ons_assistant;
+                       bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_ONS_ROLE_OFFENSE:
+                       ons_debug("offense");
+                       bot.havocbot_role = havocbot_role_ons_offense;
+                       bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+       }
+       ons_debug("\n");
+}
+
+int havocbot_ons_teamcount(entity bot, int role)
+{SELFPARAM();
+       int c = 0;
+       entity head;
+
+       FOR_EACH_PLAYER(head)
+       if(SAME_TEAM(head, self))
+       if(head.havocbot_role_flags & role)
+               ++c;
+
+       return c;
+}
+
+void havocbot_goalrating_ons_controlpoints_attack(float ratingscale)
+{SELFPARAM();
+       entity cp, cp1, cp2, best, pl, wp;
+       float radius, bestvalue;
+       int c;
+       bool found;
+
+       // Filter control points
+       for(cp2 = ons_worldcplist; cp2; cp2 = cp2.ons_worldcpnext)
+       {
+               cp2.wpcost = c = 0;
+               cp2.wpconsidered = false;
+
+               if(cp2.isshielded)
+                       continue;
+
+               // Ignore owned controlpoints
+               if(!(cp2.isgenneighbor[self.team] || cp2.iscpneighbor[self.team]))
+                       continue;
+
+               // Count team mates interested in this control point
+               // (easier and cleaner than keeping counters per cp and teams)
+               FOR_EACH_PLAYER(pl)
+               if(SAME_TEAM(pl, self))
+               if(pl.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE)
+               if(pl.havocbot_ons_target==cp2)
+                       ++c;
+
+               // NOTE: probably decrease the cost of attackable control points
+               cp2.wpcost = c;
+               cp2.wpconsidered = true;
+       }
+
+       // We'll consider only the best case
+       bestvalue = 99999999999;
+       cp = world;
+       for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext)
+       {
+               if (!cp1.wpconsidered)
+                       continue;
+
+               if(cp1.wpcost<bestvalue)
+               {
+                       bestvalue = cp1.wpcost;
+                       cp = cp1;
+                       self.havocbot_ons_target = cp1;
+               }
+       }
+
+       if (!cp)
+               return;
+
+       ons_debug(strcat(self.netname, " chose cp ranked ", ftos(bestvalue), "\n"));
+
+       if(cp.goalentity)
+       {
+               // Should be attacked
+               // Rate waypoints near it
+               found = false;
+               best = world;
+               bestvalue = 99999999999;
+               for(radius=0; radius<1000 && !found; radius+=500)
+               {
+                       for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
+                       {
+                               if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
+                               if(wp.classname=="waypoint")
+                               if(checkpvs(wp.origin,cp))
+                               {
+                                       found = true;
+                                       if(wp.cnt<bestvalue)
+                                       {
+                                               best = wp;
+                                               bestvalue = wp.cnt;
+                                       }
+                               }
+                       }
+               }
+
+               if(best)
+               {
+                       navigation_routerating(best, ratingscale, 10000);
+                       best.cnt += 1;
+
+                       self.havocbot_attack_time = 0;
+                       if(checkpvs(self.view_ofs,cp))
+                       if(checkpvs(self.view_ofs,best))
+                               self.havocbot_attack_time = time + 2;
+               }
+               else
+               {
+                       navigation_routerating(cp, ratingscale, 10000);
+               }
+               ons_debug(strcat(self.netname, " found an attackable controlpoint at ", vtos(cp.origin) ,"\n"));
+       }
+       else
+       {
+               // Should be touched
+               ons_debug(strcat(self.netname, " found a touchable controlpoint at ", vtos(cp.origin) ,"\n"));
+               found = false;
+
+               // Look for auto generated waypoint
+               if (!bot_waypoints_for_items)
+               for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
+               {
+                       if(wp.classname=="waypoint")
+                       {
+                               navigation_routerating(wp, ratingscale, 10000);
+                               found = true;
+                       }
+               }
+
+               // Nothing found, rate the controlpoint itself
+               if (!found)
+                       navigation_routerating(cp, ratingscale, 10000);
+       }
+}
+
+bool havocbot_goalrating_ons_generator_attack(float ratingscale)
+{SELFPARAM();
+       entity g, wp, bestwp;
+       bool found;
+       int best;
+
+       for(g = ons_worldgeneratorlist; g; g = g.ons_worldgeneratornext)
+       {
+               if(SAME_TEAM(g, self) || g.isshielded)
+                       continue;
+
+               // Should be attacked
+               // Rate waypoints near it
+               found = false;
+               bestwp = world;
+               best = 99999999999;
+
+               for(wp=findradius(g.origin,400); wp; wp=wp.chain)
+               {
+                       if(wp.classname=="waypoint")
+                       if(checkpvs(wp.origin,g))
+                       {
+                               found = true;
+                               if(wp.cnt<best)
+                               {
+                                       bestwp = wp;
+                                       best = wp.cnt;
+                               }
+                       }
+               }
+
+               if(bestwp)
+               {
+                       ons_debug("waypoints found around generator\n");
+                       navigation_routerating(bestwp, ratingscale, 10000);
+                       bestwp.cnt += 1;
+
+                       self.havocbot_attack_time = 0;
+                       if(checkpvs(self.view_ofs,g))
+                       if(checkpvs(self.view_ofs,bestwp))
+                               self.havocbot_attack_time = time + 5;
+
+                       return true;
+               }
+               else
+               {
+                       ons_debug("generator found without waypoints around\n");
+                       // if there aren't waypoints near the generator go straight to it
+                       navigation_routerating(g, ratingscale, 10000);
+                       self.havocbot_attack_time = 0;
+                       return true;
+               }
+       }
+       return false;
+}
+
+void havocbot_role_ons_offense()
+{SELFPARAM();
+       if(self.deadflag != DEAD_NO)
+       {
+               self.havocbot_attack_time = 0;
+               havocbot_ons_reset_role(self);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 120;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ons_reset_role(self);
+               return;
+       }
+
+       if(self.havocbot_attack_time>time)
+               return;
+
+       if (self.bot_strategytime < time)
+       {
+               navigation_goalrating_start();
+               havocbot_goalrating_enemyplayers(20000, self.origin, 650);
+               if(!havocbot_goalrating_ons_generator_attack(20000))
+                       havocbot_goalrating_ons_controlpoints_attack(20000);
+               havocbot_goalrating_ons_offenseitems(10000, self.origin, 10000);
+               navigation_goalrating_end();
+
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+       }
+}
+
+void havocbot_role_ons_assistant()
+{SELFPARAM();
+       havocbot_ons_reset_role(self);
+}
+
+void havocbot_role_ons_defense()
+{SELFPARAM();
+       havocbot_ons_reset_role(self);
+}
+
+void havocbot_ons_reset_role(entity bot)
+{SELFPARAM();
+       entity head;
+       int c = 0;
+
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       bot.havocbot_ons_target = world;
+
+       // TODO: Defend control points or generator if necessary
+
+       // if there is only me on the team switch to offense
+       c = 0;
+       FOR_EACH_PLAYER(head)
+       if(SAME_TEAM(head, self))
+               ++c;
+
+       if(c==1)
+       {
+               havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
+               return;
+       }
+
+       havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
+}
+
+
+/*
+ * Find control point or generator owned by the same team self which is nearest to pos
+ * if max_dist is positive, only control points within this range will be considered
+ */
+entity ons_Nearest_ControlPoint(vector pos, float max_dist)
+{SELFPARAM();
+       entity tmp_entity, closest_target = world;
+       tmp_entity = findchain(classname, "onslaught_controlpoint");
+       while(tmp_entity)
+       {
+               if(SAME_TEAM(tmp_entity, self))
+               if(tmp_entity.iscaptured)
+               if(max_dist <= 0 || vlen(tmp_entity.origin - pos) <= max_dist)
+               if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world)
+                       closest_target = tmp_entity;
+               tmp_entity = tmp_entity.chain;
+       }
+       tmp_entity = findchain(classname, "onslaught_generator");
+       while(tmp_entity)
+       {
+               if(SAME_TEAM(tmp_entity, self))
+               if(max_dist <= 0 || vlen(tmp_entity.origin - pos) < max_dist)
+               if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world)
+                       closest_target = tmp_entity;
+               tmp_entity = tmp_entity.chain;
+       }
+
+       return closest_target;
+}
+
+/*
+ * Find control point or generator owned by the same team self which is nearest to pos
+ * if max_dist is positive, only control points within this range will be considered
+ * This function only check distances on the XY plane, disregarding Z
+ */
+entity ons_Nearest_ControlPoint_2D(vector pos, float max_dist)
+{SELFPARAM();
+       entity tmp_entity, closest_target = world;
+       vector delta;
+       float smallest_distance = 0, distance;
+
+       tmp_entity = findchain(classname, "onslaught_controlpoint");
+       while(tmp_entity)
+       {
+               delta = tmp_entity.origin - pos;
+               delta_z = 0;
+               distance = vlen(delta);
+
+               if(SAME_TEAM(tmp_entity, self))
+               if(tmp_entity.iscaptured)
+               if(max_dist <= 0 || distance <= max_dist)
+               if(closest_target == world || distance <= smallest_distance )
+               {
+                       closest_target = tmp_entity;
+                       smallest_distance = distance;
+               }
+
+               tmp_entity = tmp_entity.chain;
+       }
+       tmp_entity = findchain(classname, "onslaught_generator");
+       while(tmp_entity)
+       {
+               delta = tmp_entity.origin - pos;
+               delta_z = 0;
+               distance = vlen(delta);
+
+               if(SAME_TEAM(tmp_entity, self))
+               if(max_dist <= 0 || distance <= max_dist)
+               if(closest_target == world || distance <= smallest_distance )
+               {
+                       closest_target = tmp_entity;
+                       smallest_distance = distance;
+               }
+
+               tmp_entity = tmp_entity.chain;
+       }
+
+       return closest_target;
+}
+/**
+ * find the number of control points and generators in the same team as self
+ */
+int ons_Count_SelfControlPoints()
+{SELFPARAM();
+       entity tmp_entity;
+       tmp_entity = findchain(classname, "onslaught_controlpoint");
+       int n = 0;
+       while(tmp_entity)
+       {
+               if(SAME_TEAM(tmp_entity, self))
+               if(tmp_entity.iscaptured)
+                       n++;
+               tmp_entity = tmp_entity.chain;
+       }
+       tmp_entity = findchain(classname, "onslaught_generator");
+       while(tmp_entity)
+       {
+               if(SAME_TEAM(tmp_entity, self))
+                       n++;
+               tmp_entity = tmp_entity.chain;
+       }
+       return n;
+}
+
+/**
+ * Teleport player to a random position near tele_target
+ * if tele_effects is true, teleport sound+particles are created
+ * return false on failure
+ */
+bool ons_Teleport(entity player, entity tele_target, float range, bool tele_effects)
+{
+       if ( !tele_target )
+               return false;
+
+       int i;
+       vector loc;
+       float theta;
+       for(i = 0; i < 16; ++i)
+       {
+               theta = random() * 2 * M_PI;
+               loc_y = sin(theta);
+               loc_x = cos(theta);
+               loc_z = 0;
+               loc *= random() * range;
+
+               loc += tele_target.origin + '0 0 128';
+
+               tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, player);
+               if(trace_fraction == 1.0 && !trace_startsolid)
+               {
+                       traceline(tele_target.origin, loc, MOVE_NOMONSTERS, tele_target); // double check to make sure we're not spawning outside the world
+                       if(trace_fraction == 1.0 && !trace_startsolid)
+                       {
+                               if ( tele_effects )
+                               {
+                                       Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1);
+                                       sound (player, CH_TRIGGER, SND_TELEPORT, VOL_BASE, ATTEN_NORM);
+                               }
+                               setorigin(player, loc);
+                               player.angles = '0 1 0' * ( theta * RAD2DEG + 180 );
+                               makevectors(player.angles);
+                               player.fixangle = true;
+                               player.teleport_antispam = time + autocvar_g_onslaught_teleport_wait;
+
+                               if ( tele_effects )
+                                       Send_Effect(EFFECT_TELEPORT, player.origin + v_forward * 32, '0 0 0', 1);
+                               return true;
+                       }
+               }
+       }
+
+       return false;
+}
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(ons, reset_map_global)
+{SELFPARAM();
+       entity e;
+       FOR_EACH_PLAYER(e)
+       {
+               e.ons_roundlost = false;
+               e.ons_deathloc = '0 0 0';
+               WITH(entity, self, e, PutClientInServer());
+       }
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, ClientDisconnect)
+{SELFPARAM();
+       self.ons_deathloc = '0 0 0';
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, MakePlayerObserver)
+{SELFPARAM();
+       self.ons_deathloc = '0 0 0';
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, PlayerSpawn)
+{SELFPARAM();
+       if(!round_handler_IsRoundStarted())
+       {
+               self.player_blocked = true;
+               return false;
+       }
+
+       entity l;
+       for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
+       {
+               l.sprite.SendFlags |= 16;
+       }
+       for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
+       {
+               l.sprite.SendFlags |= 16;
+       }
+
+       if(ons_stalemate) { Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); }
+
+       if ( autocvar_g_onslaught_spawn_choose )
+       if ( self.ons_spawn_by )
+       if ( ons_Teleport(self,self.ons_spawn_by,autocvar_g_onslaught_teleport_radius,false) )
+       {
+               self.ons_spawn_by = world;
+               return false;
+       }
+
+       if(autocvar_g_onslaught_spawn_at_controlpoints)
+       if(random() <= autocvar_g_onslaught_spawn_at_controlpoints_chance)
+       {
+               float random_target = autocvar_g_onslaught_spawn_at_controlpoints_random;
+               entity tmp_entity, closest_target = world;
+               vector spawn_loc = self.ons_deathloc;
+
+               // new joining player or round reset, don't bother checking
+               if(spawn_loc == '0 0 0') { return false; }
+
+               if(random_target) { RandomSelection_Init(); }
+
+               for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext)
+               {
+                       if(SAME_TEAM(tmp_entity, self))
+                       if(random_target)
+                               RandomSelection_Add(tmp_entity, 0, string_null, 1, 1);
+                       else if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world)
+                               closest_target = tmp_entity;
+               }
+
+               if(random_target) { closest_target = RandomSelection_chosen_ent; }
+
+               if(closest_target)
+               {
+                       float i;
+                       vector loc;
+                       for(i = 0; i < 10; ++i)
+                       {
+                               loc = closest_target.origin + '0 0 96';
+                               loc += ('0 1 0' * random()) * 128;
+                               tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self);
+                               if(trace_fraction == 1.0 && !trace_startsolid)
+                               {
+                                       traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world
+                                       if(trace_fraction == 1.0 && !trace_startsolid)
+                                       {
+                                               setorigin(self, loc);
+                                               self.angles = normalize(loc - closest_target.origin) * RAD2DEG;
+                                               return false;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       if(autocvar_g_onslaught_spawn_at_generator)
+       if(random() <= autocvar_g_onslaught_spawn_at_generator_chance)
+       {
+               float random_target = autocvar_g_onslaught_spawn_at_generator_random;
+               entity tmp_entity, closest_target = world;
+               vector spawn_loc = self.ons_deathloc;
+
+               // new joining player or round reset, don't bother checking
+               if(spawn_loc == '0 0 0') { return false; }
+
+               if(random_target) { RandomSelection_Init(); }
+
+               for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
+               {
+                       if(random_target)
+                               RandomSelection_Add(tmp_entity, 0, string_null, 1, 1);
+                       else
+                       {
+                               if(SAME_TEAM(tmp_entity, self))
+                               if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world)
+                                       closest_target = tmp_entity;
+                       }
+               }
+
+               if(random_target) { closest_target = RandomSelection_chosen_ent; }
+
+               if(closest_target)
+               {
+                       float i;
+                       vector loc;
+                       for(i = 0; i < 10; ++i)
+                       {
+                               loc = closest_target.origin + '0 0 128';
+                               loc += ('0 1 0' * random()) * 256;
+                               tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self);
+                               if(trace_fraction == 1.0 && !trace_startsolid)
+                               {
+                                       traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world
+                                       if(trace_fraction == 1.0 && !trace_startsolid)
+                                       {
+                                               setorigin(self, loc);
+                                               self.angles = normalize(loc - closest_target.origin) * RAD2DEG;
+                                               return false;
+                                       }
+                               }
+                       }
+               }
+       }
+
+    return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, PlayerDies)
+{SELFPARAM();
+       frag_target.ons_deathloc = frag_target.origin;
+       entity l;
+       for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
+       {
+               l.sprite.SendFlags |= 16;
+       }
+       for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
+       {
+               l.sprite.SendFlags |= 16;
+       }
+
+       if ( autocvar_g_onslaught_spawn_choose )
+       if ( ons_Count_SelfControlPoints() > 1 )
+               stuffcmd(self, "qc_cmd_cl hud clickradar\n");
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, MonsterMove)
+{SELFPARAM();
+       entity e = find(world, targetname, self.target);
+       if (e != world)
+               self.team = e.team;
+
+       return false;
+}
+
+void ons_MonsterSpawn_Delayed()
+{SELFPARAM();
+       entity e, own = self.owner;
+
+       if(!own) { remove(self); return; }
+
+       if(own.targetname)
+       {
+               e = find(world, target, own.targetname);
+               if(e != world)
+               {
+                       own.team = e.team;
+
+                       activator = e;
+                       own.use();
+               }
+       }
+
+       remove(self);
+}
+
+MUTATOR_HOOKFUNCTION(ons, MonsterSpawn)
+{SELFPARAM();
+       entity e = spawn();
+       e.owner = self;
+       InitializeEntity(e, ons_MonsterSpawn_Delayed, INITPRIO_FINDTARGET);
+
+       return false;
+}
+
+void ons_TurretSpawn_Delayed()
+{SELFPARAM();
+       entity e, own = self.owner;
+
+       if(!own) { remove(self); return; }
+
+       if(own.targetname)
+       {
+               e = find(world, target, own.targetname);
+               if(e != world)
+               {
+                       own.team = e.team;
+                       own.active = ACTIVE_NOT;
+
+                       activator = e;
+                       own.use();
+               }
+       }
+
+       remove(self);
+}
+
+MUTATOR_HOOKFUNCTION(ons, TurretSpawn)
+{SELFPARAM();
+       entity e = spawn();
+       e.owner = self;
+       InitializeEntity(e, ons_TurretSpawn_Delayed, INITPRIO_FINDTARGET);
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, HavocBot_ChooseRole)
+{SELFPARAM();
+       havocbot_ons_reset_role(self);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ons, GetTeamCount)
+{
+       // onslaught is special
+       for(entity tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
+       {
+               switch(tmp_entity.team)
+               {
+                       case NUM_TEAM_1: c1 = 0; break;
+                       case NUM_TEAM_2: c2 = 0; break;
+                       case NUM_TEAM_3: c3 = 0; break;
+                       case NUM_TEAM_4: c4 = 0; break;
+               }
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ons, SpectateCopy)
+{SELFPARAM();
+       self.ons_roundlost = other.ons_roundlost; // make spectators see it too
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, SV_ParseClientCommand)
+{SELFPARAM();
+       if(MUTATOR_RETURNVALUE) // command was already handled?
+               return false;
+
+       if ( cmd_name == "ons_spawn" )
+       {
+               vector pos = self.origin;
+               if(cmd_argc > 1)
+                       pos_x = stof(argv(1));
+               if(cmd_argc > 2)
+                       pos_y = stof(argv(2));
+               if(cmd_argc > 3)
+                       pos_z = stof(argv(3));
+
+               if ( IS_PLAYER(self) )
+               {
+                       if ( !self.frozen )
+                       {
+                               entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius);
+
+                               if ( !source_point && self.health > 0 )
+                               {
+                                       sprint(self, "\nYou need to be next to a control point\n");
+                                       return 1;
+                               }
+
+
+                               entity closest_target = ons_Nearest_ControlPoint_2D(pos, autocvar_g_onslaught_click_radius);
+
+                               if ( closest_target == world )
+                               {
+                                       sprint(self, "\nNo control point found\n");
+                                       return 1;
+                               }
+
+                               if ( self.health <= 0 )
+                               {
+                                       self.ons_spawn_by = closest_target;
+                                       self.respawn_flags = self.respawn_flags | RESPAWN_FORCE;
+                               }
+                               else
+                               {
+                                       if ( source_point == closest_target )
+                                       {
+                                               sprint(self, "\nTeleporting to the same point\n");
+                                               return 1;
+                                       }
+
+                                       if ( !ons_Teleport(self,closest_target,autocvar_g_onslaught_teleport_radius,true) )
+                                               sprint(self, "\nUnable to teleport there\n");
+                               }
+
+                               return 1;
+                       }
+
+                       sprint(self, "\nNo teleportation for you\n");
+               }
+
+               return 1;
+       }
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ons, PlayerUseKey)
+{SELFPARAM();
+       if(MUTATOR_RETURNVALUE || gameover) { return false; }
+
+       if((time > self.teleport_antispam) && (self.deadflag == DEAD_NO) && !self.vehicle)
+       {
+               entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius);
+               if ( source_point )
+               {
+                       stuffcmd(self, "qc_cmd_cl hud clickradar\n");
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, PlayHitsound)
+{
+       return (frag_victim.classname == "onslaught_generator" && !frag_victim.isshielded)
+               || (frag_victim.classname == "onslaught_controlpoint_icon" && !frag_victim.owner.isshielded);
+}
+
+MUTATOR_HOOKFUNCTION(ons, SendWaypoint)
+{
+       if(wp_sendflags & 16)
+       {
+               if(self.owner.classname == "onslaught_controlpoint")
+               {
+                       entity wp_owner = self.owner;
+                       entity e = WaypointSprite_getviewentity(wp_sendto);
+                       if(SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { wp_flag |= 2; }
+                       if(!ons_ControlPoint_Attackable(wp_owner, e.team)) { wp_flag |= 2; }
+               }
+               if(self.owner.classname == "onslaught_generator")
+               {
+                       entity wp_owner = self.owner;
+                       if(wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { wp_flag |= 2; }
+                       if(wp_owner.health <= 0) { wp_flag |= 2; }
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, TurretValidateTarget)
+{
+       if(substring(turret_target.classname, 0, 10) == "onslaught_") // don't attack onslaught targets, that's the player's job!
+       {
+               ret_float = -3;
+               return true;
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, TurretThink)
+{
+       // ONS uses somewhat backwards linking.
+       if(self.target)
+       {
+               entity e = find(world, targetname, self.target);
+               if (e != world)
+                       self.team = e.team;
+       }
+
+       if(self.team != self.tur_head.team)
+               turret_respawn();
+
+       return false;
+}
+
+
+// ==========
+// Spawnfuncs
+// ==========
+
+/*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16)
+  Link between control points.
+
+  This entity targets two different spawnfunc_onslaught_controlpoint or spawnfunc_onslaught_generator entities, and suppresses shielding on both if they are owned by different teams.
+
+keys:
+"target" - first control point.
+"target2" - second control point.
+ */
+spawnfunc(onslaught_link)
+{
+       if(!g_onslaught) { remove(self); return; }
+
+       if (self.target == "" || self.target2 == "")
+               objerror("target and target2 must be set\n");
+
+       self.ons_worldlinknext = ons_worldlinklist; // link into ons_worldlinklist
+       ons_worldlinklist = self;
+
+       InitializeEntity(self, ons_DelayedLinkSetup, INITPRIO_FINDTARGET);
+       Net_LinkEntity(self, false, 0, ons_Link_Send);
+}
+
+/*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128)
+  Control point. Be sure to give this enough clearance so that the shootable part has room to exist
+
+  This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity.
+
+keys:
+"targetname" - name that spawnfunc_onslaught_link entities will use to target this.
+"target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities.
+"message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc)
+ */
+
+spawnfunc(onslaught_controlpoint)
+{
+       if(!g_onslaught) { remove(self); return; }
+
+       ons_ControlPoint_Setup(self);
+}
+
+/*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64)
+  Base generator.
+
+  spawnfunc_onslaught_link entities can target this.
+
+keys:
+"team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET.
+"targetname" - name that spawnfunc_onslaught_link entities will use to target this.
+ */
+spawnfunc(onslaught_generator)
+{
+       if(!g_onslaught) { remove(self); return; }
+       if(!self.team) { objerror("team must be set"); }
+
+       ons_GeneratorSetup(self);
+}
+
+// scoreboard setup
+void ons_ScoreRules()
+{
+       CheckAllowedTeams(world);
+       ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), SFL_SORT_PRIO_PRIMARY, 0, true);
+       ScoreInfo_SetLabel_TeamScore  (ST_ONS_CAPS,     "destroyed", SFL_SORT_PRIO_PRIMARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_ONS_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
+       ScoreInfo_SetLabel_PlayerScore(SP_ONS_TAKES,    "takes",     0);
+       ScoreRules_basics_end();
+}
+
+void ons_DelayedInit() // Do this check with a delay so we can wait for teams to be set up
+{
+       ons_ScoreRules();
+
+       round_handler_Spawn(Onslaught_CheckPlayers, Onslaught_CheckWinner, Onslaught_RoundStart);
+       round_handler_Init(5, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit);
+}
+
+void ons_Initialize()
+{
+       g_onslaught = true;
+       ons_captureshield_force = autocvar_g_onslaught_shield_force;
+
+       addstat(STAT_ROUNDLOST, AS_INT, ons_roundlost);
+
+       InitializeEntity(world, ons_DelayedInit, INITPRIO_GAMETYPE);
+}
+
+REGISTER_MUTATOR(ons, IS_GAMETYPE(ONSLAUGHT))
+{
+       ActivateTeamplay();
+       SetLimits(autocvar_g_onslaught_point_limit, -1, -1, -1);
+       have_team_spawns = -1; // request team spawns
+
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+               ons_Initialize();
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back ons_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return false;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/gamemode_race.qc b/qcsrc/server/mutators/mutator/gamemode_race.qc
new file mode 100644 (file)
index 0000000..4b9201d
--- /dev/null
@@ -0,0 +1,466 @@
+#ifndef GAMEMODE_RACE_H
+#define GAMEMODE_RACE_H
+
+float g_race_qualifying;
+float race_teams;
+
+// scores
+const float ST_RACE_LAPS = 1;
+const float SP_RACE_LAPS = 4;
+const float SP_RACE_TIME = 5;
+const float SP_RACE_FASTEST = 6;
+#endif
+
+#ifdef IMPLEMENTATION
+
+#include "../../race.qh"
+
+#define autocvar_g_race_laps_limit cvar("g_race_laps_limit")
+float autocvar_g_race_qualifying_timelimit;
+float autocvar_g_race_qualifying_timelimit_override;
+int autocvar_g_race_teams;
+
+// legacy bot roles
+.float race_checkpoint;
+void havocbot_role_race()
+{SELFPARAM();
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       entity e;
+       if (self.bot_strategytime < time)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+
+               for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; )
+               {
+                       if(e.cnt == self.race_checkpoint)
+                       {
+                               navigation_routerating(e, 1000000, 5000);
+                       }
+                       else if(self.race_checkpoint == -1)
+                       {
+                               navigation_routerating(e, 1000000, 5000);
+                       }
+               }
+
+               navigation_goalrating_end();
+       }
+}
+
+void race_ScoreRules()
+{
+       ScoreRules_basics(race_teams, 0, 0, false);
+       if(race_teams)
+       {
+               ScoreInfo_SetLabel_TeamScore(  ST_RACE_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
+               ScoreInfo_SetLabel_PlayerScore(SP_RACE_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
+               ScoreInfo_SetLabel_PlayerScore(SP_RACE_TIME,    "time",      SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+               ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest",   SFL_LOWER_IS_BETTER | SFL_TIME);
+       }
+       else if(g_race_qualifying)
+       {
+               ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest",   SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+       }
+       else
+       {
+               ScoreInfo_SetLabel_PlayerScore(SP_RACE_LAPS,    "laps",      SFL_SORT_PRIO_PRIMARY);
+               ScoreInfo_SetLabel_PlayerScore(SP_RACE_TIME,    "time",      SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+               ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest",   SFL_LOWER_IS_BETTER | SFL_TIME);
+       }
+       ScoreRules_basics_end();
+}
+
+void race_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
+{
+       if(autocvar_sv_eventlog)
+               GameLogEcho(strcat(":race:", mode, ":", ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerPhysics)
+{SELFPARAM();
+       self.race_movetime_frac += PHYS_INPUT_TIMELENGTH;
+       float f = floor(self.race_movetime_frac);
+       self.race_movetime_frac -= f;
+       self.race_movetime_count += f;
+       self.race_movetime = self.race_movetime_frac + self.race_movetime_count;
+
+#ifdef SVQC
+       if(IS_PLAYER(self))
+       {
+               if (self.race_penalty)
+                       if (time > self.race_penalty)
+                               self.race_penalty = 0;
+               if(self.race_penalty)
+               {
+                       self.velocity = '0 0 0';
+                       self.movetype = MOVETYPE_NONE;
+                       self.disableclientprediction = 2;
+               }
+       }
+#endif
+
+       // force kbd movement for fairness
+       float wishspeed;
+       vector wishvel;
+
+       // if record times matter
+       // ensure nothing EVIL is being done (i.e. div0_evade)
+       // this hinders joystick users though
+       // but it still gives SOME analog control
+       wishvel.x = fabs(self.movement.x);
+       wishvel.y = fabs(self.movement.y);
+       if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y)
+       {
+               wishvel.z = 0;
+               wishspeed = vlen(wishvel);
+               if(wishvel.x >= 2 * wishvel.y)
+               {
+                       // pure X motion
+                       if(self.movement.x > 0)
+                               self.movement_x = wishspeed;
+                       else
+                               self.movement_x = -wishspeed;
+                       self.movement_y = 0;
+               }
+               else if(wishvel.y >= 2 * wishvel.x)
+               {
+                       // pure Y motion
+                       self.movement_x = 0;
+                       if(self.movement.y > 0)
+                               self.movement_y = wishspeed;
+                       else
+                               self.movement_y = -wishspeed;
+               }
+               else
+               {
+                       // diagonal
+                       if(self.movement.x > 0)
+                               self.movement_x = M_SQRT1_2 * wishspeed;
+                       else
+                               self.movement_x = -M_SQRT1_2 * wishspeed;
+                       if(self.movement.y > 0)
+                               self.movement_y = M_SQRT1_2 * wishspeed;
+                       else
+                               self.movement_y = -M_SQRT1_2 * wishspeed;
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, reset_map_global)
+{
+       float s;
+
+       Score_NicePrint(world);
+
+       race_ClearRecords();
+       PlayerScore_Sort(race_place, 0, 1, 0);
+
+       entity e;
+       FOR_EACH_CLIENT(e)
+       {
+               if(e.race_place)
+               {
+                       s = PlayerScore_Add(e, SP_RACE_FASTEST, 0);
+                       if(!s)
+                               e.race_place = 0;
+               }
+               race_EventLog(ftos(e.race_place), e);
+       }
+
+       if(g_race_qualifying == 2)
+       {
+               g_race_qualifying = 0;
+               independent_players = 0;
+               cvar_set("fraglimit", ftos(race_fraglimit));
+               cvar_set("leadlimit", ftos(race_leadlimit));
+               cvar_set("timelimit", ftos(race_timelimit));
+               race_ScoreRules();
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerPreThink)
+{SELFPARAM();
+       if(IS_SPEC(self) || IS_OBSERVER(self))
+       if(g_race_qualifying)
+       if(msg_entity.enemy.race_laptime)
+               race_SendNextCheckpoint(msg_entity.enemy, 1);
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, ClientConnect)
+{SELFPARAM();
+       race_PreparePlayer();
+       self.race_checkpoint = -1;
+
+       string rr = RACE_RECORD;
+
+       if(IS_REAL_CLIENT(self))
+       {
+               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);
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver)
+{SELFPARAM();
+       if(g_race_qualifying)
+       if(PlayerScore_Add(self, SP_RACE_FASTEST, 0))
+               self.frags = FRAGS_LMS_LOSER;
+       else
+               self.frags = FRAGS_SPECTATOR;
+
+       race_PreparePlayer();
+       self.race_checkpoint = -1;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerSpawn)
+{SELFPARAM();
+       if(spawn_spot.target == "")
+               // Emergency: this wasn't a real spawnpoint. Can this ever happen?
+               race_PreparePlayer();
+
+       // if we need to respawn, do it right
+       self.race_respawn_checkpoint = self.race_checkpoint;
+       self.race_respawn_spotref = spawn_spot;
+
+       self.race_place = 0;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, PutClientInServer)
+{SELFPARAM();
+       if(IS_PLAYER(self))
+       if(!gameover)
+       {
+               if(self.killcount == -666 /* initial spawn */ || g_race_qualifying) // spawn
+                       race_PreparePlayer();
+               else // respawn
+                       race_RetractPlayer();
+
+               race_AbandonRaceCheck(self);
+       }
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerDies)
+{SELFPARAM();
+       self.respawn_flags |= RESPAWN_FORCE;
+       race_AbandonRaceCheck(self);
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole)
+{SELFPARAM();
+       self.havocbot_role = havocbot_role_race;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(rc, GetPressedKeys)
+{SELFPARAM();
+       if(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 (!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 = RACE_RECORD;
+                       race_send_speedaward(MSG_ALL);
+                       speedaward_lastsent = speedaward_speed;
+                       if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
+                       {
+                               speedaward_alltimebest = speedaward_speed;
+                               speedaward_alltimebest_holder = speedaward_holder;
+                               speedaward_alltimebest_uid = speedaward_uid;
+                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
+                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
+                               race_send_speedaward_alltimebest(MSG_ALL);
+                       }
+               }
+       }
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear)
+{
+       if(g_race_qualifying)
+               return true; // in qualifying, you don't lose score by observing
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, GetTeamCount, CBC_ORDER_EXCLUSIVE)
+{
+       ret_float = race_teams;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
+{
+       // announce remaining frags if not in qualifying mode
+       if(!g_race_qualifying)
+               return true;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, GetRecords)
+{
+       for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
+       {
+               if(MapInfo_Get_ByID(i))
+               {
+                       float r = race_readTime(MapInfo_Map_bspname, 1);
+
+                       if(!r)
+                               continue;
+
+                       string h = race_readName(MapInfo_Map_bspname, 1);
+                       ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n");
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, FixClientCvars)
+{
+       stuffcmd(fix_client, "cl_cmd settemp cl_movecliptokeyboard 2\n");
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, CheckRules_World)
+{
+       if(g_race_qualifying == 2 && checkrules_timelimit >= 0)
+       {
+               ret_float = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
+               return true;
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
+{
+       if(g_race_qualifying == 2)
+               warmup_stage = 0;
+       return false;
+}
+
+void race_Initialize()
+{
+       race_ScoreRules();
+       if(g_race_qualifying == 2)
+               warmup_stage = 0;
+}
+
+void rc_SetLimits()
+{
+       int fraglimit_override, leadlimit_override;
+       float timelimit_override, qualifying_override;
+
+       if(autocvar_g_race_teams)
+       {
+               ActivateTeamplay();
+               race_teams = bound(2, autocvar_g_race_teams, 4);
+               have_team_spawns = -1; // request team spawns
+       }
+       else
+               race_teams = 0;
+
+       qualifying_override = autocvar_g_race_qualifying_timelimit_override;
+       fraglimit_override = autocvar_g_race_laps_limit;
+       leadlimit_override = 0; // currently not supported by race
+       timelimit_override = -1; // use default if we don't set it below
+
+       // we need to find out the correct value for g_race_qualifying
+       float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0;
+
+       if(autocvar_g_campaign)
+       {
+               g_race_qualifying = 1;
+               independent_players = 1;
+       }
+       else if(!autocvar_g_campaign && want_qualifying)
+       {
+               g_race_qualifying = 2;
+               independent_players = 1;
+               race_fraglimit = (race_fraglimit >= 0) ? fraglimit_override : autocvar_fraglimit;
+               race_leadlimit = (race_leadlimit >= 0) ? leadlimit_override : autocvar_leadlimit;
+               race_timelimit = (race_timelimit >= 0) ? timelimit_override : autocvar_timelimit;
+               fraglimit_override = 0;
+               leadlimit_override = 0;
+               timelimit_override = autocvar_g_race_qualifying_timelimit;
+       }
+       else
+               g_race_qualifying = 0;
+
+       SetLimits(fraglimit_override, leadlimit_override, timelimit_override, qualifying_override);
+}
+
+REGISTER_MUTATOR(rc, g_race)
+{
+       rc_SetLimits();
+
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+               race_Initialize();
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back race_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/gamemode_tdm.qc b/qcsrc/server/mutators/mutator/gamemode_tdm.qc
new file mode 100644 (file)
index 0000000..aaa3d51
--- /dev/null
@@ -0,0 +1,91 @@
+#ifdef IMPLEMENTATION
+bool autocvar_g_tdm_team_spawns;
+int autocvar_g_tdm_point_limit;
+int autocvar_g_tdm_point_leadlimit;
+int autocvar_g_tdm_teams;
+int autocvar_g_tdm_teams_override;
+
+/*QUAKED spawnfunc_tdm_team (0 .5 .8) (-16 -16 -24) (16 16 32)
+Team declaration for TDM gameplay, this allows you to decide what team names and control point models are used in your map.
+Note: If you use spawnfunc_tdm_team entities you must define at least 2!  However, unlike domination, you don't need to make a blank one too.
+Keys:
+"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
+"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
+spawnfunc(tdm_team)
+{
+       if(!g_tdm || !self.cnt) { remove(self); return; }
+
+       self.classname = "tdm_team";
+       self.team = self.cnt + 1;
+}
+
+// code from here on is just to support maps that don't have team entities
+void tdm_SpawnTeam (string teamname, float teamcolor)
+{
+       entity this = new(tdm_team);
+       this.netname = teamname;
+       this.cnt = teamcolor;
+       this.spawnfunc_checked = true;
+       WITH(entity, self, this, spawnfunc_tdm_team(this));
+}
+
+void tdm_DelayedInit()
+{
+       // if no teams are found, spawn defaults
+       if(find(world, classname, "tdm_team") == world)
+       {
+               LOG_INFO("No ""tdm_team"" entities found on this map, creating them anyway.\n");
+
+               int numteams = min(4, autocvar_g_tdm_teams_override);
+
+               if(numteams < 2) { numteams = autocvar_g_tdm_teams; }
+               numteams = bound(2, numteams, 4);
+
+               float i;
+               for(i = 1; i <= numteams; ++i)
+                       tdm_SpawnTeam(Team_ColorName(Team_NumberToTeam(i)), Team_NumberToTeam(i) - 1);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(tdm, GetTeamCount, CBC_ORDER_EXCLUSIVE)
+{
+       ret_string = "tdm_team";
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining)
+{
+       // announce remaining frags
+       return true;
+}
+
+REGISTER_MUTATOR(tdm, g_tdm)
+{
+       ActivateTeamplay();
+       SetLimits(autocvar_g_tdm_point_limit, autocvar_g_tdm_point_leadlimit, -1, -1);
+       if(autocvar_g_tdm_team_spawns)
+               have_team_spawns = -1; // request team spawns
+
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+               InitializeEntity(world, tdm_DelayedInit, INITPRIO_GAMETYPE);
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back tdm_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_bloodloss.qc b/qcsrc/server/mutators/mutator/mutator_bloodloss.qc
new file mode 100644 (file)
index 0000000..ca37166
--- /dev/null
@@ -0,0 +1,45 @@
+#ifdef IMPLEMENTATION
+REGISTER_MUTATOR(bloodloss, cvar("g_bloodloss"));
+
+.float bloodloss_timer;
+
+MUTATOR_HOOKFUNCTION(bloodloss, PlayerPreThink)
+{SELFPARAM();
+       if(IS_PLAYER(self))
+       if(self.health <= autocvar_g_bloodloss && self.deadflag == DEAD_NO)
+       {
+               self.BUTTON_CROUCH = true;
+
+               if(time >= self.bloodloss_timer)
+               {
+                       if(self.vehicle)
+                               vehicles_exit(VHEF_RELEASE);
+                       if(self.event_damage)
+                               self.event_damage(self, self, 1, DEATH_ROT.m_id, self.origin, '0 0 0');
+                       self.bloodloss_timer = time + 0.5 + random() * 0.5;
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(bloodloss, PlayerJump)
+{SELFPARAM();
+       if(self.health <= autocvar_g_bloodloss)
+               return true;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(bloodloss, BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":bloodloss");
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(bloodloss, BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", Blood loss");
+       return false;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_breakablehook.qc b/qcsrc/server/mutators/mutator/mutator_breakablehook.qc
new file mode 100644 (file)
index 0000000..cb9463b
--- /dev/null
@@ -0,0 +1,29 @@
+#ifdef IMPLEMENTATION
+#include "../../../common/deathtypes/all.qh"
+#include "../../g_hook.qh"
+
+REGISTER_MUTATOR(breakablehook, cvar("g_breakablehook"));
+
+bool autocvar_g_breakablehook; // allow toggling mid match?
+bool autocvar_g_breakablehook_owner;
+
+MUTATOR_HOOKFUNCTION(breakablehook, PlayerDamage_Calculate)
+{
+       if(frag_target.classname == "grapplinghook")
+       {
+               if((!autocvar_g_breakablehook)
+               || (!autocvar_g_breakablehook_owner && frag_attacker == frag_target.realowner)
+                       ) { frag_damage = 0; }
+
+               // hurt the owner of the hook
+               if(DIFF_TEAM(frag_attacker, frag_target.realowner))
+               {
+                       Damage (frag_target.realowner, frag_attacker, frag_attacker, 5, WEP_HOOK.m_id | HITTYPE_SPLASH, frag_target.realowner.origin, '0 0 0');
+                       RemoveGrapplingHook(frag_target.realowner);
+                       return false; // dead
+               }
+       }
+
+       return false;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_buffs.qc b/qcsrc/server/mutators/mutator/mutator_buffs.qc
new file mode 100644 (file)
index 0000000..4f0758f
--- /dev/null
@@ -0,0 +1,1000 @@
+#ifndef MUTATOR_BUFFS_H
+#define MUTATOR_BUFFS_H
+
+// ammo
+.float buff_ammo_prev_infitems;
+.int buff_ammo_prev_clipload;
+// invisible
+.float buff_invisible_prev_alpha;
+// flight
+.float buff_flight_prev_gravity;
+// disability
+.float buff_disability_time;
+.float buff_disability_effect_time;
+// common buff variables
+.float buff_effect_delay;
+
+// buff definitions
+.float buff_active;
+.float buff_activetime;
+.float buff_activetime_updated;
+.entity buff_waypoint;
+.int oldbuffs; // for updating effects
+.entity buff_model; // controls effects (TODO: make csqc)
+
+const vector BUFF_MIN = ('-16 -16 -20');
+const vector BUFF_MAX = ('16 16 20');
+
+// client side options
+.float cvar_cl_buffs_autoreplace;
+#endif
+
+#ifdef IMPLEMENTATION
+
+#include "../../../common/triggers/target/music.qh"
+#include "../../../common/gamemodes/all.qh"
+#include "../../../common/buffs/all.qh"
+
+.float buff_time;
+void buffs_DelayedInit();
+
+REGISTER_MUTATOR(buffs, cvar("g_buffs"))
+{
+       MUTATOR_ONADD
+       {
+               addstat(STAT_BUFFS, AS_INT, buffs);
+               addstat(STAT_BUFF_TIME, AS_FLOAT, buff_time);
+
+               InitializeEntity(world, buffs_DelayedInit, INITPRIO_FINDTARGET);
+       }
+}
+
+entity buff_FirstFromFlags(int _buffs)
+{
+       if (flags)
+       {
+               FOREACH(Buffs, it.m_itemid & _buffs, LAMBDA(return it));
+       }
+       return BUFF_Null;
+}
+
+bool buffs_BuffModel_Customize()
+{SELFPARAM();
+       entity player, myowner;
+       bool 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(MUTATOR_CALLHOOK(BuffModel_Customize, self, player))
+               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;
+}
+
+void buffs_BuffModel_Spawn(entity player)
+{
+       player.buff_model = spawn();
+       setmodel(player.buff_model, MDL_BUFF);
+       setsize(player.buff_model, '0 0 -40', '0 0 40');
+       setattachment(player.buff_model, player, "");
+       setorigin(player.buff_model, '0 0 1' * (player.buff_model.maxs.z * 1));
+       player.buff_model.owner = player;
+       player.buff_model.scale = 0.7;
+       player.buff_model.pflags = PFLAGS_FULLDYNAMIC;
+       player.buff_model.light_lev = 200;
+       player.buff_model.customizeentityforclient = buffs_BuffModel_Customize;
+}
+
+vector buff_GlowColor(entity buff)
+{
+       //if(buff.team) { return Team_ColorRGB(buff.team); }
+       return buff.m_color;
+}
+
+void buff_Effect(entity player, string eff)
+{SELFPARAM();
+       if(!autocvar_g_buffs_effects) { return; }
+
+       if(time >= self.buff_effect_delay)
+       {
+               Send_Effect_(eff, player.origin + ((player.mins + player.maxs) * 0.5), '0 0 0', 1);
+               self.buff_effect_delay = time + 0.05; // prevent spam
+       }
+}
+
+// buff item
+float buff_Waypoint_visible_for_player(entity plr)
+{SELFPARAM();
+       if(!self.owner.buff_active && !self.owner.buff_activetime)
+               return false;
+
+       if (plr.buffs)
+       {
+               return plr.cvar_cl_buffs_autoreplace == false || plr.buffs != self.owner.buffs;
+       }
+
+       return WaypointSprite_visible_for_player(plr);
+}
+
+void buff_Waypoint_Spawn(entity e)
+{
+       entity buff = buff_FirstFromFlags(e.buffs);
+       entity wp = WaypointSprite_Spawn(WP_Buff, 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs.z, world, e.team, e, buff_waypoint, true, RADARICON_Buff);
+       wp.wp_extra = buff.m_id;
+       WaypointSprite_UpdateTeamRadar(e.buff_waypoint, RADARICON_Buff, e.glowmod);
+       e.buff_waypoint.waypointsprite_visible_for_player = buff_Waypoint_visible_for_player;
+}
+
+void buff_SetCooldown(float cd)
+{SELFPARAM();
+       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)
+{SELFPARAM();
+       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;
+
+       Send_Effect(EFFECT_ELECTRO_COMBO, oldbufforigin + ((ent.mins + ent.maxs) * 0.5), '0 0 0', 1);
+       Send_Effect(EFFECT_ELECTRO_COMBO, CENTER_OR_VIEWOFS(ent), '0 0 0', 1);
+
+       WaypointSprite_Ping(ent.buff_waypoint);
+
+       sound(ent, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+}
+
+void buff_Touch()
+{SELFPARAM();
+       if(gameover) { return; }
+
+       if(ITEM_TOUCH_NEEDKILL())
+       {
+               buff_Respawn(self);
+               return;
+       }
+
+       if((self.team && DIFF_TEAM(other, self))
+       || (other.frozen)
+       || (other.vehicle)
+       || (!self.buff_active)
+       )
+       {
+               // can't touch this
+               return;
+       }
+
+       if(MUTATOR_CALLHOOK(BuffTouch, self, other))
+               return;
+
+       if(!IS_PLAYER(other))
+               return; // incase mutator changed other
+
+       if (other.buffs)
+       {
+               if (other.cvar_cl_buffs_autoreplace && other.buffs != self.buffs)
+               {
+                       int buffid = buff_FirstFromFlags(other.buffs).m_id;
+                       //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, buffid);
+
+                       other.buffs = 0;
+                       //sound(other, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
+               }
+               else { return; } // do nothing
+       }
+
+       self.owner = other;
+       self.buff_active = false;
+       self.lifetime = 0;
+       int buffid = buff_FirstFromFlags(self.buffs).m_id;
+       Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_GOT, buffid);
+       Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_INFO, INFO_ITEM_BUFF, other.netname, buffid);
+
+       Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(self), '0 0 0', 1);
+       sound(other, CH_TRIGGER, SND_SHIELD_RESPAWN, VOL_BASE, ATTN_NORM);
+       other.buffs |= (self.buffs);
+}
+
+float buff_Available(entity buff)
+{
+       if (buff == BUFF_Null)
+               return false;
+       if (buff == BUFF_AMMO && ((start_items & IT_UNLIMITED_WEAPON_AMMO) || (start_items & IT_UNLIMITED_AMMO) || (cvar("g_melee_only"))))
+               return false;
+       if (buff == BUFF_VAMPIRE && cvar("g_vampire"))
+               return false;
+       return cvar(strcat("g_buffs_", buff.m_name));
+}
+
+.int buff_seencount;
+
+void buff_NewType(entity ent, float cb)
+{
+       RandomSelection_Init();
+       FOREACH(Buffs, buff_Available(it), LAMBDA(
+               it.buff_seencount += 1;
+               // if it's already been chosen, give it a lower priority
+               RandomSelection_Add(world, it.m_itemid, string_null, 1, max(0.2, 1 / it.buff_seencount));
+       ));
+       ent.buffs = RandomSelection_chosen_float;
+}
+
+void buff_Think()
+{SELFPARAM();
+       if(self.buffs != self.oldbuffs)
+       {
+               entity buff = buff_FirstFromFlags(self.buffs);
+               self.color = buff.m_color;
+               self.glowmod = buff_GlowColor(buff);
+               self.skin = buff.m_skin;
+
+               setmodel(self, MDL_BUFF);
+
+               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(!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 & 64))
+                       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, SND_STRENGTH_RESPAWN, VOL_BASE, ATTN_NORM);
+                       Send_Effect(EFFECT_ITEM_RESPAWN, CENTER_OR_VIEWOFS(self), '0 0 0', 1);
+               }
+       }
+
+       if(self.buff_active)
+       {
+               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()
+{SELFPARAM();
+       WaypointSprite_Kill(self.buff_waypoint);
+
+       if(self.buff_activetime) { buff_Waypoint_Spawn(self); }
+}
+
+void buff_Reset()
+{SELFPARAM();
+       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 & 64))
+               buff_Respawn(self);
+}
+
+float buff_Customize()
+{SELFPARAM();
+       entity player = WaypointSprite_getviewentity(other);
+       if(!self.buff_active || (self.team && DIFF_TEAM(player, self)))
+       {
+               self.alpha = 0.3;
+               if(self.effects & EF_FULLBRIGHT) { self.effects &= ~(EF_FULLBRIGHT); }
+               self.pflags = 0;
+       }
+       else
+       {
+               self.alpha = 1;
+               if(!(self.effects & EF_FULLBRIGHT)) { self.effects |= EF_FULLBRIGHT; }
+               self.light_lev = 220 + 36 * sin(time);
+               self.pflags = PFLAGS_FULLDYNAMIC;
+       }
+       return true;
+}
+
+void buff_Init(entity ent)
+{SELFPARAM();
+       if(!cvar("g_buffs")) { remove(ent); return; }
+
+       if(!teamplay && ent.team) { ent.team = 0; }
+
+       entity buff = buff_FirstFromFlags(self.buffs);
+
+       setself(ent);
+       if(!self.buffs || buff_Available(buff))
+               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.m_skin;
+       self.effects = EF_FULLBRIGHT | EF_STARDUST | EF_NOSHADOW;
+       self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY;
+       self.customizeentityforclient = buff_Customize;
+       //self.gravity = 100;
+       self.color = buff.m_color;
+       self.glowmod = buff_GlowColor(self);
+       buff_SetCooldown(autocvar_g_buffs_cooldown_activate + game_starttime);
+       self.buff_active = !self.buff_activetime;
+       self.pflags = PFLAGS_FULLDYNAMIC;
+
+       if(self.spawnflags & 1)
+               self.noalign = true;
+
+       if(self.noalign)
+               self.movetype = MOVETYPE_NONE; // reset by random location
+
+       setmodel(self, MDL_BUFF);
+       setsize(self, BUFF_MIN, BUFF_MAX);
+
+       if(cvar("g_buffs_random_location") || (self.spawnflags & 64))
+               buff_Respawn(self);
+
+       setself(this);
+}
+
+void buff_Init_Compat(entity ent, entity replacement)
+{
+       if (ent.spawnflags & 2)
+               ent.team = NUM_TEAM_1;
+       else if (ent.spawnflags & 4)
+               ent.team = NUM_TEAM_2;
+
+       ent.buffs = replacement.m_itemid;
+
+       buff_Init(ent);
+}
+
+void buff_SpawnReplacement(entity ent, entity old)
+{
+       setorigin(ent, old.origin);
+       ent.angles = old.angles;
+       ent.noalign = (old.noalign || (old.spawnflags & 1));
+
+       buff_Init(ent);
+}
+
+void buff_Vengeance_DelayedDamage()
+{SELFPARAM();
+       if(self.enemy)
+               Damage(self.enemy, self.owner, self.owner, self.dmg, DEATH_BUFF.m_id, self.enemy.origin, '0 0 0');
+
+       remove(self);
+       return;
+}
+
+float buff_Inferno_CalculateTime(float x, float offset_x, float offset_y, float intersect_x, float intersect_y, float base)
+{
+       return offset_y + (intersect_y - offset_y) * logn(((x - offset_x) * ((base - 1) / intersect_x)) + 1, base);
+}
+
+// mutator hooks
+MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_SplitHealthArmor)
+{
+       if(frag_deathtype == DEATH_BUFF.m_id) { return false; }
+
+       if(frag_target.buffs & BUFF_RESISTANCE.m_itemid)
+       {
+               vector v = healtharmor_applydamage(50, autocvar_g_buffs_resistance_blockpercent, frag_deathtype, frag_damage);
+               damage_take = v.x;
+               damage_save = v.y;
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_Calculate)
+{
+       if(frag_deathtype == DEATH_BUFF.m_id) { return false; }
+
+       if(frag_target.buffs & BUFF_SPEED.m_itemid)
+       if(frag_target != frag_attacker)
+               frag_damage *= autocvar_g_buffs_speed_damage_take;
+
+       if(frag_target.buffs & BUFF_MEDIC.m_itemid)
+       if((frag_target.health - frag_damage) <= 0)
+       if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
+       if(frag_attacker)
+       if(random() <= autocvar_g_buffs_medic_survive_chance)
+               frag_damage = max(5, frag_target.health - autocvar_g_buffs_medic_survive_health);
+
+       if(frag_target.buffs & BUFF_JUMP.m_itemid)
+       if(frag_deathtype == DEATH_FALL.m_id)
+               frag_damage = 0;
+
+       if(frag_target.buffs & BUFF_VENGEANCE.m_itemid)
+       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.m_itemid)
+       if(frag_attacker != frag_target)
+       if(vlen(frag_force))
+               frag_force = '0 0 0';
+
+       if(frag_attacker.buffs & BUFF_BASH.m_itemid)
+       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.m_itemid)
+       if(frag_target != frag_attacker)
+               frag_target.buff_disability_time = time + autocvar_g_buffs_disability_slowtime;
+
+       if(frag_attacker.buffs & BUFF_MEDIC.m_itemid)
+       if(DEATH_WEAPONOF(frag_deathtype) != WEP_ARC)
+       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;
+       }
+
+       if(frag_attacker.buffs & BUFF_INFERNO.m_itemid)
+       if(frag_target != frag_attacker) {
+               float time = buff_Inferno_CalculateTime(
+                       frag_damage,
+                       0,
+                       autocvar_g_buffs_inferno_burntime_min_time,
+                       autocvar_g_buffs_inferno_burntime_target_damage,
+                       autocvar_g_buffs_inferno_burntime_target_time,
+                       autocvar_g_buffs_inferno_burntime_factor
+               );
+               Fire_AddDamage(frag_target, frag_attacker, (frag_damage * autocvar_g_buffs_inferno_damagemultiplier) * time, time, DEATH_BUFF.m_id);
+       }
+
+       // this... is ridiculous (TODO: fix!)
+       if(frag_attacker.buffs & BUFF_VAMPIRE.m_itemid)
+       if(!frag_target.vehicle)
+       if(DEATH_WEAPONOF(frag_deathtype) != WEP_ARC)
+       if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
+       if(frag_target.deadflag == DEAD_NO)
+       if(IS_PLAYER(frag_target) || IS_MONSTER(frag_target))
+       if(frag_attacker != frag_target)
+       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);
+               if(frag_target.armorvalue)
+                       frag_attacker.armorvalue = bound(0, frag_attacker.armorvalue + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.armorvalue), g_pickup_armorsmall_max);
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(buffs,PlayerSpawn)
+{SELFPARAM();
+       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;
+}
+
+.float stat_sv_maxspeed;
+.float stat_sv_airspeedlimit_nonqw;
+.float stat_sv_jumpvelocity;
+
+MUTATOR_HOOKFUNCTION(buffs, PlayerPhysics)
+{SELFPARAM();
+       if(self.buffs & BUFF_SPEED.m_itemid)
+       {
+               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;
+       }
+
+       if(self.buffs & BUFF_JUMP.m_itemid)
+       {
+               // automatically reset, no need to worry
+               self.stat_sv_jumpvelocity = autocvar_g_buffs_jump_height;
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(buffs, PlayerJump)
+{SELFPARAM();
+       if(self.buffs & BUFF_JUMP.m_itemid)
+               player_jumpheight = autocvar_g_buffs_jump_height;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(buffs, MonsterMove)
+{SELFPARAM();
+       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)
+{SELFPARAM();
+       if(self.buffs)
+       {
+               int buffid = buff_FirstFromFlags(self.buffs).m_id;
+               Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, buffid);
+               self.buffs = 0;
+
+               if(self.buff_model)
+               {
+                       remove(self.buff_model);
+                       self.buff_model = world;
+               }
+       }
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(buffs, PlayerUseKey, CBC_ORDER_FIRST)
+{SELFPARAM();
+       if(MUTATOR_RETURNVALUE || gameover) { return false; }
+       if(self.buffs)
+       {
+               int buffid = buff_FirstFromFlags(self.buffs).m_id;
+               Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_BUFF_DROP, buffid);
+               Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, buffid);
+
+               self.buffs = 0;
+               sound(self, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
+               return true;
+       }
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(buffs, ForbidThrowCurrentWeapon)
+{SELFPARAM();
+       if(MUTATOR_RETURNVALUE || gameover) { return false; }
+
+       if(self.buffs & BUFF_SWAPPER.m_itemid)
+       {
+               float best_distance = autocvar_g_buffs_swapper_range;
+               entity closest = world;
+               entity player;
+               FOR_EACH_PLAYER(player)
+               if(DIFF_TEAM(self, player))
+               if(player.deadflag == DEAD_NO && !player.frozen && !player.vehicle)
+               if(vlen(self.origin - player.origin) <= best_distance)
+               {
+                       best_distance = vlen(self.origin - player.origin);
+                       closest = player;
+               }
+
+               if(closest)
+               {
+                       vector my_org, my_vel, my_ang, their_org, their_vel, their_ang;
+
+                       my_org = self.origin;
+                       my_vel = self.velocity;
+                       my_ang = self.angles;
+                       their_org = closest.origin;
+                       their_vel = closest.velocity;
+                       their_ang = closest.angles;
+
+                       Drop_Special_Items(closest);
+
+                       MUTATOR_CALLHOOK(PortalTeleport, self); // initiate flag dropper
+
+                       setorigin(self, their_org);
+                       setorigin(closest, my_org);
+
+                       closest.velocity = my_vel;
+                       closest.angles = my_ang;
+                       closest.fixangle = true;
+                       closest.oldorigin = my_org;
+                       closest.oldvelocity = my_vel;
+                       self.velocity = their_vel;
+                       self.angles = their_ang;
+                       self.fixangle = true;
+                       self.oldorigin = their_org;
+                       self.oldvelocity = their_vel;
+
+                       // set pusher so self gets the kill if they fall into void
+                       closest.pusher = self;
+                       closest.pushltime = time + autocvar_g_maxpushtime;
+                       closest.istypefrag = closest.BUTTON_CHAT;
+
+                       Send_Effect(EFFECT_ELECTRO_COMBO, their_org, '0 0 0', 1);
+                       Send_Effect(EFFECT_ELECTRO_COMBO, my_org, '0 0 0', 1);
+
+                       sound(self, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM);
+                       sound(closest, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM);
+
+                       // TODO: add a counter to handle how many times one can teleport, and a delay to prevent spam
+                       self.buffs = 0;
+                       return true;
+               }
+       }
+       return false;
+}
+
+bool buffs_RemovePlayer(entity player)
+{
+       if(player.buff_model)
+       {
+               remove(player.buff_model);
+               player.buff_model = world;
+       }
+
+       // also reset timers here to prevent them continuing after spectating
+       player.buff_disability_time = 0;
+       player.buff_disability_effect_time = 0;
+
+       return false;
+}
+MUTATOR_HOOKFUNCTION(buffs, MakePlayerObserver) { return buffs_RemovePlayer(self); }
+MUTATOR_HOOKFUNCTION(buffs, ClientDisconnect) { return buffs_RemovePlayer(self); }
+
+MUTATOR_HOOKFUNCTION(buffs, CustomizeWaypoint)
+{SELFPARAM();
+       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.m_itemid) && (e == other))
+       if(DIFF_TEAM(self.owner, e))
+               return true;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(buffs, OnEntityPreSpawn, CBC_ORDER_LAST)
+{SELFPARAM();
+       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, WeaponRateFactor)
+{SELFPARAM();
+       if(self.buffs & BUFF_SPEED.m_itemid)
+               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, WeaponSpeedFactor)
+{SELFPARAM();
+       if(self.buffs & BUFF_SPEED.m_itemid)
+               ret_float *= autocvar_g_buffs_speed_weaponspeed;
+
+       if(time < self.buff_disability_time)
+               ret_float *= autocvar_g_buffs_disability_weaponspeed;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink)
+{SELFPARAM();
+       if(gameover || self.deadflag != DEAD_NO) { return false; }
+
+       if(time < self.buff_disability_time)
+       if(time >= self.buff_disability_effect_time)
+       {
+               Send_Effect(EFFECT_SMOKING, self.origin + ((self.mins + self.maxs) * 0.5), '0 0 0', 1);
+               self.buff_disability_effect_time = time + 0.5;
+       }
+
+       // handle buff lost status
+       // 1: notify everyone else
+       // 2: notify carrier as well
+       int buff_lost = 0;
+
+       if(self.buff_time)
+       if(time >= self.buff_time)
+               buff_lost = 2;
+
+       if(self.frozen) { buff_lost = 1; }
+
+       if(buff_lost)
+       {
+               if(self.buffs)
+               {
+                       int buffid = buff_FirstFromFlags(self.buffs).m_id;
+                       Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, buffid);
+                       if(buff_lost >= 2)
+                       {
+                               Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_BUFF_DROP, buffid); // TODO: special timeout message?
+                               sound(self, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
+                       }
+                       self.buffs = 0;
+               }
+       }
+
+       if(self.buffs & BUFF_MAGNET.m_itemid)
+       {
+               vector pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_item;
+               for(other = world; (other = findflags(other, flags, FL_ITEM)); )
+               if(boxesoverlap(self.absmin - pickup_size, self.absmax + pickup_size, other.absmin, other.absmax))
+               {
+                       setself(other);
+                       other = this;
+                       if(self.touch)
+                               self.touch();
+                       other = self;
+                       setself(this);
+               }
+       }
+
+       if(self.buffs & BUFF_AMMO.m_itemid)
+       if(self.clip_size)
+               self.clip_load = self.(weapon_load[self.switchweapon]) = self.clip_size;
+
+       if((self.buffs & BUFF_INVISIBLE.m_itemid) && (self.oldbuffs & BUFF_INVISIBLE.m_itemid))
+       if(self.alpha != autocvar_g_buffs_invisible_alpha)
+               self.alpha = autocvar_g_buffs_invisible_alpha; // powerups reset alpha, so we must enforce this (TODO)
+
+#define BUFF_ONADD(b) if ( (self.buffs & (b).m_itemid) && !(self.oldbuffs & (b).m_itemid))
+#define BUFF_ONREM(b) if (!(self.buffs & (b).m_itemid) &&  (self.oldbuffs & (b).m_itemid))
+
+       if(self.buffs != self.oldbuffs)
+       {
+               entity buff = buff_FirstFromFlags(self.buffs);
+               float bufftime = buff != BUFF_Null ? buff.m_time(buff) : 0;
+               self.buff_time = (bufftime) ? time + bufftime : 0;
+
+               BUFF_ONADD(BUFF_AMMO)
+               {
+                       self.buff_ammo_prev_infitems = (self.items & IT_UNLIMITED_WEAPON_AMMO);
+                       self.items |= IT_UNLIMITED_WEAPON_AMMO;
+
+                       if(self.clip_load)
+                               self.buff_ammo_prev_clipload = self.clip_load;
+                       self.clip_load = self.(weapon_load[self.switchweapon]) = self.clip_size;
+               }
+
+               BUFF_ONREM(BUFF_AMMO)
+               {
+                       if(self.buff_ammo_prev_infitems)
+                               self.items |= IT_UNLIMITED_WEAPON_AMMO;
+                       else
+                               self.items &= ~IT_UNLIMITED_WEAPON_AMMO;
+
+                       if(self.buff_ammo_prev_clipload)
+                               self.clip_load = self.buff_ammo_prev_clipload;
+               }
+
+               BUFF_ONADD(BUFF_INVISIBLE)
+               {
+                       if(time < self.strength_finished && g_instagib)
+                               self.alpha = autocvar_g_instagib_invis_alpha;
+                       else
+                               self.alpha = self.buff_invisible_prev_alpha;
+                       self.alpha = autocvar_g_buffs_invisible_alpha;
+               }
+
+               BUFF_ONREM(BUFF_INVISIBLE)
+                       self.alpha = self.buff_invisible_prev_alpha;
+
+               BUFF_ONADD(BUFF_FLIGHT)
+               {
+                       self.buff_flight_prev_gravity = self.gravity;
+                       self.gravity = autocvar_g_buffs_flight_gravity;
+               }
+
+               BUFF_ONREM(BUFF_FLIGHT)
+                       self.gravity = self.buff_flight_prev_gravity;
+
+               self.oldbuffs = self.buffs;
+               if(self.buffs)
+               {
+                       if(!self.buff_model)
+                               buffs_BuffModel_Spawn(self);
+
+                       self.buff_model.color = buff.m_color;
+                       self.buff_model.glowmod = buff_GlowColor(self.buff_model);
+                       self.buff_model.skin = buff.m_skin;
+
+                       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;
+       }
+
+#undef BUFF_ONADD
+#undef BUFF_ONREM
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(buffs, SpectateCopy)
+{SELFPARAM();
+       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)
+{SELFPARAM();
+       if(self.buffs & BUFF_MEDIC.m_itemid)
+       {
+               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.m_itemid)
+               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 |= 64; // always randomize
+                       e.velocity = randomvec() * 250; // this gets reset anyway if random location works
+                       buff_Init(e);
+               }
+       }
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_campcheck.qc b/qcsrc/server/mutators/mutator/mutator_campcheck.qc
new file mode 100644 (file)
index 0000000..be5bfce
--- /dev/null
@@ -0,0 +1,86 @@
+#ifdef IMPLEMENTATION
+REGISTER_MUTATOR(campcheck, cvar("g_campcheck"));
+
+.float campcheck_nextcheck;
+.float campcheck_traveled_distance;
+
+MUTATOR_HOOKFUNCTION(campcheck, PlayerDies)
+{SELFPARAM();
+       Kill_Notification(NOTIF_ONE, self, MSG_CENTER_CPID, CPID_CAMPCHECK);
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(campcheck, PlayerDamage_Calculate)
+{
+       if(IS_PLAYER(frag_target))
+       if(IS_PLAYER(frag_attacker))
+       if(frag_attacker != frag_target)
+       {
+               frag_target.campcheck_traveled_distance = autocvar_g_campcheck_distance;
+               frag_attacker.campcheck_traveled_distance = autocvar_g_campcheck_distance;
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(campcheck, PlayerPreThink)
+{SELFPARAM();
+       if(!gameover)
+       if(!warmup_stage) // don't consider it camping during warmup?
+       if(time >= game_starttime)
+       if(IS_PLAYER(self))
+       if(IS_REAL_CLIENT(self)) // bots may camp, but that's no reason to constantly kill them
+       if(self.deadflag == DEAD_NO)
+       if(!self.frozen)
+       if(!self.BUTTON_CHAT)
+       if(autocvar_g_campcheck_interval)
+       {
+               vector dist;
+
+               // calculate player movement (in 2 dimensions only, so jumping on one spot doesn't count as movement)
+               dist = self.prevorigin - self.origin;
+               dist.z = 0;
+               self.campcheck_traveled_distance += fabs(vlen(dist));
+
+               if((autocvar_g_campaign && !campaign_bots_may_start) || (time < game_starttime) || (round_handler_IsActive() && !round_handler_IsRoundStarted()))
+               {
+                       self.campcheck_nextcheck = time + autocvar_g_campcheck_interval * 2;
+                       self.campcheck_traveled_distance = 0;
+               }
+
+               if(time > self.campcheck_nextcheck)
+               {
+                       if(self.campcheck_traveled_distance < autocvar_g_campcheck_distance)
+                       {
+                               Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_CAMPCHECK);
+                               if(self.vehicle)
+                                       Damage(self.vehicle, self, self, autocvar_g_campcheck_damage * 2, DEATH_CAMP.m_id, self.vehicle.origin, '0 0 0');
+                               else
+                                       Damage(self, self, self, bound(0, autocvar_g_campcheck_damage, self.health + self.armorvalue * autocvar_g_balance_armor_blockpercent + 5), DEATH_CAMP.m_id, self.origin, '0 0 0');
+                       }
+                       self.campcheck_nextcheck = time + autocvar_g_campcheck_interval;
+                       self.campcheck_traveled_distance = 0;
+               }
+
+               return false;
+       }
+
+       self.campcheck_nextcheck = time + autocvar_g_campcheck_interval; // one of the above checks failed, so keep the timer up to date
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(campcheck, PlayerSpawn)
+{SELFPARAM();
+       self.campcheck_nextcheck = time + autocvar_g_campcheck_interval * 2;
+       self.campcheck_traveled_distance = 0;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(campcheck, BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":CampCheck");
+       return false;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_dodging.qc b/qcsrc/server/mutators/mutator/mutator_dodging.qc
new file mode 100644 (file)
index 0000000..85b9fea
--- /dev/null
@@ -0,0 +1,323 @@
+#ifdef IMPLEMENTATION
+
+#ifdef CSQC
+       #define PHYS_DODGING_FRAMETIME                          (1 / (frametime <= 0 ? 60 : frametime))
+       #define PHYS_DODGING                                            getstati(STAT_DODGING)
+       #define PHYS_DODGING_DELAY                                      getstatf(STAT_DODGING_DELAY)
+       #define PHYS_DODGING_TIMEOUT(s)                         getstatf(STAT_DODGING_TIMEOUT)
+       #define PHYS_DODGING_HORIZ_SPEED_FROZEN         getstatf(STAT_DODGING_HORIZ_SPEED_FROZEN)
+       #define PHYS_DODGING_FROZEN_NODOUBLETAP         getstati(STAT_DODGING_FROZEN_NO_DOUBLETAP)
+       #define PHYS_DODGING_HORIZ_SPEED                        getstatf(STAT_DODGING_HORIZ_SPEED)
+       #define PHYS_DODGING_PRESSED_KEYS(s)            s.pressedkeys
+       #define PHYS_DODGING_HEIGHT_THRESHOLD           getstatf(STAT_DODGING_HEIGHT_THRESHOLD)
+       #define PHYS_DODGING_DISTANCE_THRESHOLD         getstatf(STAT_DODGING_DISTANCE_THRESHOLD)
+       #define PHYS_DODGING_RAMP_TIME                          getstatf(STAT_DODGING_RAMP_TIME)
+       #define PHYS_DODGING_UP_SPEED                           getstatf(STAT_DODGING_UP_SPEED)
+       #define PHYS_DODGING_WALL                                       getstatf(STAT_DODGING_WALL)
+#elif defined(SVQC)
+       #define PHYS_DODGING_FRAMETIME                          sys_frametime
+       #define PHYS_DODGING                                            g_dodging
+       #define PHYS_DODGING_DELAY                                      autocvar_sv_dodging_delay
+       #define PHYS_DODGING_TIMEOUT(s)                         s.cvar_cl_dodging_timeout
+       #define PHYS_DODGING_HORIZ_SPEED_FROZEN         autocvar_sv_dodging_horiz_speed_frozen
+       #define PHYS_DODGING_FROZEN_NODOUBLETAP         autocvar_sv_dodging_frozen_doubletap
+       #define PHYS_DODGING_HORIZ_SPEED                        autocvar_sv_dodging_horiz_speed
+       #define PHYS_DODGING_PRESSED_KEYS(s)            s.pressedkeys
+       #define PHYS_DODGING_HEIGHT_THRESHOLD           autocvar_sv_dodging_height_threshold
+       #define PHYS_DODGING_DISTANCE_THRESHOLD         autocvar_sv_dodging_wall_distance_threshold
+       #define PHYS_DODGING_RAMP_TIME                          autocvar_sv_dodging_ramp_time
+       #define PHYS_DODGING_UP_SPEED                           autocvar_sv_dodging_up_speed
+       #define PHYS_DODGING_WALL                                       autocvar_sv_dodging_wall_dodging
+#endif
+
+#ifdef SVQC
+
+float g_dodging;
+
+// set to 1 to indicate dodging has started.. reset by physics hook after dodge has been done..
+.float dodging_action;
+
+// the jump part of the dodge cannot be ramped
+.float dodging_single_action;
+
+#include "../../../common/animdecide.qh"
+#include "../../../common/physics.qh"
+
+.float cvar_cl_dodging_timeout;
+
+.float stat_dodging;
+.float stat_dodging_delay;
+.float stat_dodging_horiz_speed_frozen;
+.float stat_dodging_frozen_nodoubletap;
+.float stat_dodging_frozen;
+.float stat_dodging_horiz_speed;
+.float stat_dodging_height_threshold;
+.float stat_dodging_distance_threshold;
+.float stat_dodging_ramp_time;
+.float stat_dodging_up_speed;
+.float stat_dodging_wall;
+
+REGISTER_MUTATOR(dodging, cvar("g_dodging"))
+{
+       // this just turns on the cvar.
+       MUTATOR_ONADD
+       {
+               g_dodging = cvar("g_dodging");
+               addstat(STAT_DODGING, AS_INT, stat_dodging);
+               addstat(STAT_DODGING_DELAY, AS_FLOAT, stat_dodging_delay);
+               addstat(STAT_DODGING_TIMEOUT, AS_FLOAT, cvar_cl_dodging_timeout); // we stat this, so it is updated on the client when updated on server (otherwise, chaos)
+               addstat(STAT_DODGING_FROZEN_NO_DOUBLETAP, AS_INT, stat_dodging_frozen_nodoubletap);
+               addstat(STAT_DODGING_HORIZ_SPEED_FROZEN, AS_FLOAT, stat_dodging_horiz_speed_frozen);
+               addstat(STAT_DODGING_FROZEN, AS_INT, stat_dodging_frozen);
+               addstat(STAT_DODGING_HORIZ_SPEED, AS_FLOAT, stat_dodging_horiz_speed);
+               addstat(STAT_DODGING_HEIGHT_THRESHOLD, AS_FLOAT, stat_dodging_height_threshold);
+               addstat(STAT_DODGING_DISTANCE_THRESHOLD, AS_FLOAT, stat_dodging_distance_threshold);
+               addstat(STAT_DODGING_RAMP_TIME, AS_FLOAT, stat_dodging_ramp_time);
+               addstat(STAT_DODGING_UP_SPEED, AS_FLOAT, stat_dodging_up_speed);
+               addstat(STAT_DODGING_WALL, AS_FLOAT, stat_dodging_wall);
+       }
+
+       // this just turns off the cvar.
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               g_dodging = 0;
+       }
+
+       return false;
+}
+
+#endif
+
+// set to 1 to indicate dodging has started.. reset by physics hook after dodge has been done..
+.float dodging_action;
+
+// the jump part of the dodge cannot be ramped
+.float dodging_single_action;
+
+
+// these are used to store the last key press time for each of the keys..
+.float last_FORWARD_KEY_time;
+.float last_BACKWARD_KEY_time;
+.float last_LEFT_KEY_time;
+.float last_RIGHT_KEY_time;
+
+// these store the movement direction at the time of the dodge action happening.
+.vector dodging_direction;
+
+// this indicates the last time a dodge was executed. used to check if another one is allowed
+// and to ramp up the dodge acceleration in the physics hook.
+.float last_dodging_time;
+
+// This is the velocity gain to be added over the ramp time.
+// It will decrease from frame to frame during dodging_action = 1
+// until it's 0.
+.float dodging_velocity_gain;
+
+#ifdef CSQC
+.int pressedkeys;
+
+#elif defined(SVQC)
+
+void dodging_UpdateStats()
+{SELFPARAM();
+       self.stat_dodging = PHYS_DODGING;
+       self.stat_dodging_delay = PHYS_DODGING_DELAY;
+       self.stat_dodging_horiz_speed_frozen = PHYS_DODGING_HORIZ_SPEED_FROZEN;
+       self.stat_dodging_frozen = PHYS_DODGING_FROZEN;
+       self.stat_dodging_frozen_nodoubletap = PHYS_DODGING_FROZEN_NODOUBLETAP;
+       self.stat_dodging_height_threshold = PHYS_DODGING_HEIGHT_THRESHOLD;
+       self.stat_dodging_distance_threshold = PHYS_DODGING_DISTANCE_THRESHOLD;
+       self.stat_dodging_ramp_time = PHYS_DODGING_RAMP_TIME;
+       self.stat_dodging_up_speed = PHYS_DODGING_UP_SPEED;
+       self.stat_dodging_wall = PHYS_DODGING_WALL;
+}
+
+#endif
+
+// returns 1 if the player is close to a wall
+bool check_close_to_wall(float threshold)
+{SELFPARAM();
+       if (PHYS_DODGING_WALL == 0) { return false; }
+
+       #define X(OFFSET)                                                                                                                               \
+       tracebox(self.origin, self.mins, self.maxs, self.origin + OFFSET, true, self);  \
+       if (trace_fraction < 1 && vlen (self.origin - trace_endpos) < threshold)                \
+               return true;
+       X(1000*v_right);
+       X(-1000*v_right);
+       X(1000*v_forward);
+       X(-1000*v_forward);
+       #undef X
+
+       return false;
+}
+
+bool check_close_to_ground(float threshold)
+{SELFPARAM();
+       return IS_ONGROUND(self) ? true : false;
+}
+
+float PM_dodging_checkpressedkeys()
+{SELFPARAM();
+       if(!PHYS_DODGING)
+               return false;
+
+       float frozen_dodging = (PHYS_FROZEN(self) && PHYS_DODGING_FROZEN);
+       float frozen_no_doubletap = (frozen_dodging && !PHYS_DODGING_FROZEN_NODOUBLETAP);
+
+       // first check if the last dodge is far enough back in time so we can dodge again
+       if ((time - self.last_dodging_time) < PHYS_DODGING_DELAY)
+               return false;
+
+       makevectors(self.angles);
+
+       if (check_close_to_ground(PHYS_DODGING_HEIGHT_THRESHOLD) != 1
+               && check_close_to_wall(PHYS_DODGING_DISTANCE_THRESHOLD) != 1)
+               return true;
+
+       float tap_direction_x = 0;
+       float tap_direction_y = 0;
+       float dodge_detected = 0;
+
+       #define X(COND,BTN,RESULT)                                                                                                                      \
+       if (self.movement_##COND)                                                                                               \
+               /* is this a state change? */                                                                                                   \
+               if(!(PHYS_DODGING_PRESSED_KEYS(self) & KEY_##BTN) || frozen_no_doubletap) {             \
+                               tap_direction_##RESULT;                                                                                                 \
+                               if ((time - self.last_##BTN##_KEY_time) < PHYS_DODGING_TIMEOUT(self))   \
+                                       dodge_detected = 1;                                                                                                     \
+                               self.last_##BTN##_KEY_time = time;                                                                              \
+               }
+       X(x < 0, BACKWARD,      x--);
+       X(x > 0, FORWARD,       x++);
+       X(y < 0, LEFT,          y--);
+       X(y > 0, RIGHT,         y++);
+       #undef X
+
+       if (dodge_detected == 1)
+       {
+               self.last_dodging_time = time;
+
+               self.dodging_action = 1;
+               self.dodging_single_action = 1;
+
+               self.dodging_velocity_gain = PHYS_DODGING_HORIZ_SPEED;
+
+               self.dodging_direction_x = tap_direction_x;
+               self.dodging_direction_y = tap_direction_y;
+
+               // normalize the dodging_direction vector.. (unlike UT99) XD
+               float length = self.dodging_direction_x * self.dodging_direction_x
+                                       + self.dodging_direction_y * self.dodging_direction_y;
+               length = sqrt(length);
+
+               self.dodging_direction_x = self.dodging_direction_x * 1.0 / length;
+               self.dodging_direction_y = self.dodging_direction_y * 1.0 / length;
+               return true;
+       }
+       return false;
+}
+
+void PM_dodging()
+{SELFPARAM();
+       if (!PHYS_DODGING)
+               return;
+
+#ifdef SVQC
+       dodging_UpdateStats();
+#endif
+
+    if (PHYS_DEAD(self))
+        return;
+
+       // when swimming, no dodging allowed..
+       if (self.waterlevel >= WATERLEVEL_SWIMMING)
+       {
+               self.dodging_action = 0;
+               self.dodging_direction_x = 0;
+               self.dodging_direction_y = 0;
+               return;
+       }
+
+       // make sure v_up, v_right and v_forward are sane
+       makevectors(self.angles);
+
+       // if we have e.g. 0.5 sec ramptime and a frametime of 0.25, then the ramp code
+       // will be called ramp_time/frametime times = 2 times. so, we need to
+       // add 0.5 * the total speed each frame until the dodge action is done..
+       float common_factor = PHYS_DODGING_FRAMETIME / PHYS_DODGING_RAMP_TIME;
+
+       // if ramp time is smaller than frametime we get problems ;D
+       common_factor = min(common_factor, 1);
+
+       float horiz_speed = PHYS_FROZEN(self) ? PHYS_DODGING_HORIZ_SPEED_FROZEN : PHYS_DODGING_HORIZ_SPEED;
+       float new_velocity_gain = self.dodging_velocity_gain - (common_factor * horiz_speed);
+       new_velocity_gain = max(0, new_velocity_gain);
+
+       float velocity_difference = self.dodging_velocity_gain - new_velocity_gain;
+
+       // ramp up dodging speed by adding some velocity each frame.. TODO: do it! :D
+       if (self.dodging_action == 1)
+       {
+               //disable jump key during dodge accel phase
+               if(self.movement_z > 0) { self.movement_z = 0; }
+
+               self.velocity += ((self.dodging_direction_y * velocity_difference) * v_right)
+                                       + ((self.dodging_direction_x * velocity_difference) * v_forward);
+
+               self.dodging_velocity_gain = self.dodging_velocity_gain - velocity_difference;
+       }
+
+       // the up part of the dodge is a single shot action
+       if (self.dodging_single_action == 1)
+       {
+               UNSET_ONGROUND(self);
+
+               self.velocity += PHYS_DODGING_UP_SPEED * v_up;
+
+#ifdef SVQC
+               if (autocvar_sv_dodging_sound)
+                       PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND);
+
+               animdecide_setaction(self, ANIMACTION_JUMP, true);
+#endif
+
+               self.dodging_single_action = 0;
+       }
+
+       // are we done with the dodging ramp yet?
+       if((self.dodging_action == 1) && ((time - self.last_dodging_time) > PHYS_DODGING_RAMP_TIME))
+       {
+               // reset state so next dodge can be done correctly
+               self.dodging_action = 0;
+               self.dodging_direction_x = 0;
+               self.dodging_direction_y = 0;
+       }
+}
+
+#ifdef SVQC
+
+MUTATOR_HOOKFUNCTION(dodging, GetCvars)
+{
+       GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_dodging_timeout, "cl_dodging_timeout");
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(dodging, PlayerPhysics)
+{
+       // print("dodging_PlayerPhysics\n");
+       PM_dodging();
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(dodging, GetPressedKeys)
+{
+       PM_dodging_checkpressedkeys();
+
+       return false;
+}
+
+#endif
+
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_hook.qc b/qcsrc/server/mutators/mutator/mutator_hook.qc
new file mode 100644 (file)
index 0000000..b298e7b
--- /dev/null
@@ -0,0 +1,42 @@
+#ifdef IMPLEMENTATION
+AUTOCVAR(g_grappling_hook, bool, false, _("let players spawn with the grappling hook which allows them to pull themselves up"));
+#ifdef SVQC
+REGISTER_MUTATOR(hook, autocvar_g_grappling_hook) {
+    MUTATOR_ONADD {
+        g_grappling_hook = true;
+        WEP_HOOK.ammo_factor = 0;
+    }
+    MUTATOR_ONROLLBACK_OR_REMOVE {
+        g_grappling_hook = false;
+        WEP_HOOK.ammo_factor = 1;
+    }
+}
+
+MUTATOR_HOOKFUNCTION(hook, BuildMutatorsString)
+{
+    ret_string = strcat(ret_string, ":grappling_hook");
+}
+
+MUTATOR_HOOKFUNCTION(hook, BuildMutatorsPrettyString)
+{
+    ret_string = strcat(ret_string, ", Hook");
+}
+
+MUTATOR_HOOKFUNCTION(hook, BuildGameplayTipsString)
+{
+    ret_string = strcat(ret_string, "\n\n^3grappling hook^8 is enabled, press 'e' to use it\n");
+}
+
+MUTATOR_HOOKFUNCTION(hook, PlayerSpawn)
+{
+    SELFPARAM();
+    self.offhand = OFFHAND_HOOK;
+}
+
+MUTATOR_HOOKFUNCTION(hook, FilterItem)
+{
+    return self.weapon == WEP_HOOK.m_id;
+}
+
+#endif
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_invincibleproj.qc b/qcsrc/server/mutators/mutator/mutator_invincibleproj.qc
new file mode 100644 (file)
index 0000000..5a781a8
--- /dev/null
@@ -0,0 +1,25 @@
+#ifdef IMPLEMENTATION
+REGISTER_MUTATOR(invincibleprojectiles, cvar("g_invincible_projectiles"));
+
+MUTATOR_HOOKFUNCTION(invincibleprojectiles, EditProjectile)
+{
+       if(other.health)
+       {
+               // disable health which in effect disables damage calculations
+               other.health = 0;
+       }
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(invincibleprojectiles, BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":InvincibleProjectiles");
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(invincibleprojectiles, BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", Invincible Projectiles");
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_melee_only.qc b/qcsrc/server/mutators/mutator/mutator_melee_only.qc
new file mode 100644 (file)
index 0000000..5b03f46
--- /dev/null
@@ -0,0 +1,40 @@
+#ifdef IMPLEMENTATION
+REGISTER_MUTATOR(melee_only, cvar("g_melee_only") && !cvar("g_instagib") && !g_nexball);
+
+MUTATOR_HOOKFUNCTION(melee_only, SetStartItems)
+{
+       start_ammo_shells = warmup_start_ammo_shells = 0;
+       start_weapons = warmup_start_weapons = WEPSET(SHOTGUN);
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(melee_only, ForbidThrowCurrentWeapon)
+{
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(melee_only, FilterItem)
+{SELFPARAM();
+       switch (self.items)
+       {
+               case ITEM_HealthSmall.m_itemid:
+               case ITEM_ArmorSmall.m_itemid:
+                       return false;
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(melee_only, BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":MeleeOnly");
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(melee_only, BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", Melee Only Arena");
+       return false;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_midair.qc b/qcsrc/server/mutators/mutator/mutator_midair.qc
new file mode 100644 (file)
index 0000000..bf34283
--- /dev/null
@@ -0,0 +1,47 @@
+#ifdef IMPLEMENTATION
+REGISTER_MUTATOR(midair, cvar("g_midair"));
+
+.float midair_shieldtime;
+
+MUTATOR_HOOKFUNCTION(midair, PlayerDamage_Calculate)
+{SELFPARAM();
+       if(IS_PLAYER(frag_attacker))
+       if(IS_PLAYER(frag_target))
+       if(time < self.midair_shieldtime)
+               frag_damage = false;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(midair, PlayerPowerups)
+{SELFPARAM();
+       if(time >= game_starttime)
+       if(self.flags & FL_ONGROUND)
+       {
+               self.effects |= (EF_ADDITIVE | EF_FULLBRIGHT);
+               self.midair_shieldtime = max(self.midair_shieldtime, time + autocvar_g_midair_shieldtime);
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(midair, PlayerSpawn)
+{SELFPARAM();
+       if(IS_BOT_CLIENT(self))
+               self.bot_moveskill = 0; // disable bunnyhopping
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(midair, BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":midair");
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(midair, BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", Midair");
+       return false;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_multijump.qc b/qcsrc/server/mutators/mutator/mutator_multijump.qc
new file mode 100644 (file)
index 0000000..f01a801
--- /dev/null
@@ -0,0 +1,182 @@
+#ifdef IMPLEMENTATION
+#ifdef SVQC
+       #include "../../antilag.qh"
+#endif
+#include "../../../common/physics.qh"
+
+.int multijump_count;
+.bool multijump_ready;
+.bool cvar_cl_multijump;
+
+#ifdef CSQC
+
+#define PHYS_MULTIJUMP                                 getstati(STAT_MULTIJUMP)
+#define PHYS_MULTIJUMP_SPEED           getstatf(STAT_MULTIJUMP_SPEED)
+#define PHYS_MULTIJUMP_ADD                     getstati(STAT_MULTIJUMP_ADD)
+#define PHYS_MULTIJUMP_MAXSPEED        getstatf(STAT_MULTIJUMP_MAXSPEED)
+#define PHYS_MULTIJUMP_DODGING                 getstati(STAT_MULTIJUMP_DODGING)
+
+#elif defined(SVQC)
+
+#define PHYS_MULTIJUMP                                 autocvar_g_multijump
+#define PHYS_MULTIJUMP_SPEED           autocvar_g_multijump_speed
+#define PHYS_MULTIJUMP_ADD                     autocvar_g_multijump_add
+#define PHYS_MULTIJUMP_MAXSPEED        autocvar_g_multijump_maxspeed
+#define PHYS_MULTIJUMP_DODGING                 autocvar_g_multijump_dodging
+
+
+.float stat_multijump;
+.float stat_multijump_speed;
+.float stat_multijump_add;
+.float stat_multijump_maxspeed;
+.float stat_multijump_dodging;
+
+void multijump_UpdateStats()
+{SELFPARAM();
+       self.stat_multijump = PHYS_MULTIJUMP;
+       self.stat_multijump_speed = PHYS_MULTIJUMP_SPEED;
+       self.stat_multijump_add = PHYS_MULTIJUMP_ADD;
+       self.stat_multijump_maxspeed = PHYS_MULTIJUMP_MAXSPEED;
+       self.stat_multijump_dodging = PHYS_MULTIJUMP_DODGING;
+}
+
+void multijump_AddStats()
+{
+       addstat(STAT_MULTIJUMP, AS_INT, stat_multijump);
+       addstat(STAT_MULTIJUMP_SPEED, AS_FLOAT, stat_multijump_speed);
+       addstat(STAT_MULTIJUMP_ADD, AS_INT, stat_multijump_add);
+       addstat(STAT_MULTIJUMP_MAXSPEED, AS_FLOAT, stat_multijump_maxspeed);
+       addstat(STAT_MULTIJUMP_DODGING, AS_INT, stat_multijump_dodging);
+}
+
+#endif
+
+void PM_multijump()
+{SELFPARAM();
+       if(!PHYS_MULTIJUMP) { return; }
+
+       if(IS_ONGROUND(self))
+       {
+               self.multijump_count = 0;
+       }
+}
+
+bool PM_multijump_checkjump()
+{SELFPARAM();
+       if(!PHYS_MULTIJUMP) { return false; }
+
+#ifdef SVQC
+       bool client_multijump = self.cvar_cl_multijump;
+#elif defined(CSQC)
+       bool client_multijump = cvar("cl_multijump");
+
+       if(cvar("cl_multijump") > 1)
+               return false; // nope
+#endif
+
+       if (!IS_JUMP_HELD(self) && !IS_ONGROUND(self) && client_multijump) // jump button pressed this frame and we are in midair
+               self.multijump_ready = true;  // this is necessary to check that we released the jump button and pressed it again
+       else
+               self.multijump_ready = false;
+
+       int phys_multijump = PHYS_MULTIJUMP;
+
+#ifdef CSQC
+       phys_multijump = (PHYS_MULTIJUMP) ? -1 : 0;
+#endif
+
+       if(!player_multijump && self.multijump_ready && (self.multijump_count < phys_multijump || phys_multijump == -1) && self.velocity_z > PHYS_MULTIJUMP_SPEED && (!PHYS_MULTIJUMP_MAXSPEED || vlen(self.velocity) <= PHYS_MULTIJUMP_MAXSPEED))
+       {
+               if (PHYS_MULTIJUMP)
+               {
+                       if (!PHYS_MULTIJUMP_ADD) // in this case we make the z velocity == jumpvelocity
+                       {
+                               if (self.velocity_z < PHYS_JUMPVELOCITY)
+                               {
+                                       player_multijump = true;
+                                       self.velocity_z = 0;
+                               }
+                       }
+                       else
+                               player_multijump = true;
+
+                       if(player_multijump)
+                       {
+                               if(PHYS_MULTIJUMP_DODGING)
+                               if(self.movement_x != 0 || self.movement_y != 0) // don't remove all speed if player isnt pressing any movement keys
+                               {
+                                       float curspeed;
+                                       vector wishvel, wishdir;
+
+/*#ifdef SVQC
+                                       curspeed = max(
+                                               vlen(vec2(self.velocity)), // current xy speed
+                                               vlen(vec2(antilag_takebackavgvelocity(self, max(self.lastteleporttime + sys_frametime, time - 0.25), time))) // average xy topspeed over the last 0.25 secs
+                                       );
+#elif defined(CSQC)*/
+                                       curspeed = vlen(vec2(self.velocity));
+//#endif
+
+                                       makevectors(self.v_angle_y * '0 1 0');
+                                       wishvel = v_forward * self.movement_x + v_right * self.movement_y;
+                                       wishdir = normalize(wishvel);
+
+                                       self.velocity_x = wishdir_x * curspeed; // allow "dodging" at a multijump
+                                       self.velocity_y = wishdir_y * curspeed;
+                                       // keep velocity_z unchanged!
+                               }
+                               if (PHYS_MULTIJUMP > 0)
+                               {
+                                       self.multijump_count += 1;
+                               }
+                       }
+               }
+               self.multijump_ready = false; // require releasing and pressing the jump button again for the next jump
+       }
+
+       return false;
+}
+
+#ifdef SVQC
+REGISTER_MUTATOR(multijump, cvar("g_multijump"))
+{
+       MUTATOR_ONADD
+       {
+               multijump_AddStats();
+       }
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(multijump, PlayerPhysics)
+{
+       multijump_UpdateStats();
+       PM_multijump();
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(multijump, PlayerJump)
+{
+       return PM_multijump_checkjump();
+}
+
+MUTATOR_HOOKFUNCTION(multijump, GetCvars)
+{
+       GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_multijump, "cl_multijump");
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(multijump, BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":multijump");
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(multijump, BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", Multi jump");
+       return false;
+}
+
+#endif
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_nades.qc b/qcsrc/server/mutators/mutator/mutator_nades.qc
new file mode 100644 (file)
index 0000000..d9fd1c8
--- /dev/null
@@ -0,0 +1,1233 @@
+#ifndef MUTATOR_NADES_H
+#define MUTATOR_NADES_H
+
+.entity nade;
+.entity fake_nade;
+.float nade_timer;
+.float nade_refire;
+.float bonus_nades;
+.float nade_special_time;
+.float bonus_nade_score;
+.float nade_type;
+.string pokenade_type;
+.entity nade_damage_target;
+.float cvar_cl_nade_type;
+.string cvar_cl_pokenade_type;
+.float toss_time;
+.float stat_healing_orb;
+.float stat_healing_orb_alpha;
+.float nade_show_particles;
+
+// Remove nades that are being thrown
+void(entity player) nades_Clear;
+
+// Give a bonus grenade to a player
+void(entity player, float score) nades_GiveBonus;
+// Remove all bonus nades from a player
+void(entity player) nades_RemoveBonus;
+
+#endif
+#ifdef IMPLEMENTATION
+
+#include "../../../common/nades/all.qh"
+#include "../../../common/gamemodes/all.qh"
+#include "../../../common/monsters/spawn.qh"
+#include "../../../common/monsters/sv_monsters.qh"
+#include "../../g_subs.qh"
+
+REGISTER_MUTATOR(nades, cvar("g_nades"))
+{
+       MUTATOR_ONADD
+       {
+               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);
+       }
+
+       return false;
+}
+
+.float nade_time_primed;
+
+.entity nade_spawnloc;
+
+void nade_timer_think()
+{SELFPARAM();
+       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)
+{
+       CSQCProjectile(_nade, true, Nades[_nade.nade_type].m_projectile[true], true);
+}
+
+void nade_spawn(entity _nade)
+{
+       entity timer = spawn();
+       setmodel(timer, MDL_NADE_TIMER);
+       setattachment(timer, _nade, "");
+       timer.classname = "nade_timer";
+       timer.colormap = _nade.colormap;
+       timer.glowmod = _nade.glowmod;
+       timer.think = nade_timer_think;
+       timer.nextthink = time;
+       timer.wait = _nade.wait;
+       timer.owner = _nade;
+       timer.skin = 10;
+
+       _nade.effects |= EF_LOWPRECISION;
+
+       CSQCProjectile(_nade, true, Nades[_nade.nade_type].m_projectile[false], true);
+}
+
+void napalm_damage(float dist, float damage, float edgedamage, float burntime)
+{SELFPARAM();
+       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)
+       {
+               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(EFFECT_FIREBALL_LASER), self.origin, RandomSelection_chosen_ent.fireball_impactvec);
+               Send_Effect(EFFECT_FIREBALL_LASER, self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1);
+       }
+}
+
+
+void napalm_ball_think()
+{SELFPARAM();
+       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()
+{SELFPARAM();
+       entity proj;
+       vector kick;
+
+       spamsound(self, CH_SHOTS, SND(FIREBALL_FIRE), 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.m_id;
+       PROJECTILE_MAKETRIGGER(proj);
+       setmodel(proj, MDL_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()
+{SELFPARAM();
+
+       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()
+{SELFPARAM();
+       entity fountain;
+       int 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.m_id;
+       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;
+       Send_Effect(EFFECT_ELECTRO_IMPACT, frost_target.origin, '0 0 0', 1);
+       Freeze(frost_target, 1/freeze_time, 3, false);
+
+       Drop_Special_Items(frost_target);
+}
+
+void nade_ice_think()
+{SELFPARAM();
+
+       if(round_handler_IsActive())
+       if(!round_handler_IsRoundStarted())
+       {
+               remove(self);
+               return;
+       }
+
+       if(time >= self.ltime)
+       {
+               if ( autocvar_g_nades_ice_explode )
+               {
+                       entity expef = EFFECT_NADE_EXPLODE(self.realowner.team);
+                       Send_Effect(expef, self.origin + '0 0 1', '0 0 0', 1);
+                       sound(self, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
+
+                       RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
+                               autocvar_g_nades_nade_radius, self, world, 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;
+       Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, self.origin + randomp, '0 0 0', 1);
+
+       if(time >= self.nade_special_time)
+       {
+               self.nade_special_time = time+0.7;
+
+               Send_Effect(EFFECT_ELECTRO_IMPACT, self.origin, '0 0 0', 1);
+               Send_Effect(EFFECT_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()
+{SELFPARAM();
+       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.m_id;
+       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, MDL_PROJECTILE_GRENADE);
+               entity timer = spawn();
+               setmodel(timer, MDL_NADE_TIMER);
+               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, MDL_Null);
+}
+
+void nade_translocate_boom()
+{SELFPARAM();
+       if(self.realowner.vehicle)
+               return;
+
+       vector locout = self.origin + '0 0 1' * (1 - self.realowner.mins.z - 24);
+       tracebox(locout, self.realowner.mins, self.realowner.maxs, locout, MOVE_NOMONSTERS, self.realowner);
+       locout = trace_endpos;
+
+       makevectors(self.realowner.angles);
+
+       MUTATOR_CALLHOOK(PortalTeleport, self.realowner);
+
+       TeleportPlayer(self, self.realowner, locout, self.realowner.angles, v_forward * vlen(self.realowner.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER);
+}
+
+void nade_spawn_boom()
+{SELFPARAM();
+       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()
+{SELFPARAM();
+       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()
+{SELFPARAM();
+       float maxhealth;
+       float health_factor;
+       if(IS_PLAYER(other) || IS_MONSTER(other))
+       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 = (IS_MONSTER(other)) ? other.max_health : g_pickup_healthmega_max;
+                       if ( other.health < maxhealth )
+                       {
+                               if ( self.nade_show_particles )
+                                       Send_Effect(EFFECT_HEALING, 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.m_id,other.origin,'0 0 0');
+               }
+
+       }
+
+       if ( IS_REAL_CLIENT(other) || IS_VEHICLE(other) )
+       {
+               entity show_red = (IS_VEHICLE(other)) ? 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()
+{SELFPARAM();
+       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, MDL_NADE_HEAL);
+       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()
+{SELFPARAM();
+       entity e = spawnmonster(self.pokenade_type, 0, self.realowner, self.realowner, self.origin, false, false, 1);
+
+       if(autocvar_g_nades_pokenade_monster_lifetime > 0)
+               e.monster_lifetime = time + autocvar_g_nades_pokenade_monster_lifetime;
+       e.monster_skill = MONSTER_SKILL_INSANE;
+}
+
+void nade_boom()
+{SELFPARAM();
+       entity expef = NULL;
+       bool nade_blast = true;
+
+       switch ( Nades[self.nade_type] )
+       {
+               case NADE_TYPE_NAPALM:
+                       nade_blast = autocvar_g_nades_napalm_blast;
+                       expef = EFFECT_EXPLOSION_MEDIUM;
+                       break;
+               case NADE_TYPE_ICE:
+                       nade_blast = false;
+                       expef = EFFECT_ELECTRO_COMBO; // hookbomb_explode electro_combo bigplasma_impact
+                       break;
+               case NADE_TYPE_TRANSLOCATE:
+                       nade_blast = false;
+                       break;
+               case NADE_TYPE_MONSTER:
+               case NADE_TYPE_SPAWN:
+                       nade_blast = false;
+                       switch(self.realowner.team)
+                       {
+                               case NUM_TEAM_1: expef = EFFECT_SPAWN_RED; break;
+                               case NUM_TEAM_2: expef = EFFECT_SPAWN_BLUE; break;
+                               case NUM_TEAM_3: expef = EFFECT_SPAWN_YELLOW; break;
+                               case NUM_TEAM_4: expef = EFFECT_SPAWN_PINK; break;
+                               default: expef = EFFECT_SPAWN_NEUTRAL; break;
+                       }
+                       break;
+               case NADE_TYPE_HEAL:
+                       nade_blast = false;
+                       expef = EFFECT_SPAWN_RED;
+                       break;
+
+               default:
+               case NADE_TYPE_NORMAL:
+                       expef = EFFECT_NADE_EXPLODE(self.realowner.team);
+                       break;
+       }
+
+       if(expef)
+               Send_Effect(expef, findbetterlocation(self.origin, 8), '0 0 0', 1);
+
+       sound(self, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM);
+       sound(self, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
+
+       self.event_damage = func_null; // prevent somehow calling damage in the next call
+
+       if(nade_blast)
+       {
+               RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
+                                autocvar_g_nades_nade_radius, self, world, 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);
+       }
+
+       if(self.takedamage)
+       switch ( Nades[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;
+       }
+
+       entity head;
+       for(head = world; (head = find(head, classname, "grapplinghook")); )
+       if(head.aiment == self)
+               RemoveGrapplingHook(head.realowner);
+
+       remove(self);
+}
+
+void nade_touch()
+{SELFPARAM();
+       /*float is_weapclip = 0;
+       if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
+       if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID))
+       if (!(trace_dphitcontents & DPCONTENTS_OPAQUE))
+               is_weapclip = 1;*/
+       if(ITEM_TOUCH_NEEDKILL()) // || is_weapclip)
+       {
+               entity head;
+               for(head = world; (head = find(head, classname, "grapplinghook")); )
+               if(head.aiment == self)
+                       RemoveGrapplingHook(head.realowner);
+               remove(self);
+               return;
+       }
+
+       PROJECTILE_TOUCH;
+
+       //setsize(self, '-2 -2 -2', '2 2 2');
+       //UpdateCSQCProjectile(self);
+       if(self.health == self.max_health)
+       {
+               spamsound(self, CH_SHOTS, SND(GRENADE_BOUNCE_RANDOM()), VOL_BASE, ATTEN_NORM);
+               return;
+       }
+
+       self.enemy = other;
+       nade_boom();
+}
+
+void nade_beep()
+{SELFPARAM();
+       sound(self, CH_SHOTS_SINGLE, SND_NADE_BEEP, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX));
+       self.think = nade_boom;
+       self.nextthink = max(self.wait, time);
+}
+
+void nade_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{SELFPARAM();
+       if(ITEM_DAMAGE_NEEDKILL(deathtype))
+       {
+               self.takedamage = DAMAGE_NO;
+               nade_boom();
+               return;
+       }
+
+       if(self.nade_type == NADE_TYPE_TRANSLOCATE.m_id || self.nade_type == NADE_TYPE_SPAWN.m_id)
+               return;
+
+       if(DEATH_ISWEAPON(deathtype, WEP_BLASTER))
+       {
+               force *= 1.5;
+               damage = 0;
+       }
+
+       if(DEATH_ISWEAPON(deathtype, WEP_VAPORIZER) && (deathtype & HITTYPE_SECONDARY))
+       {
+               force *= 0.5; // too much
+               frag_damage = 0;
+       }
+
+       if(DEATH_ISWEAPON(deathtype, WEP_VORTEX) || DEATH_ISWEAPON(deathtype, WEP_VAPORIZER))
+       {
+               force *= 6;
+               damage = self.max_health * 0.55;
+       }
+
+       if(DEATH_ISWEAPON(deathtype, WEP_MACHINEGUN) || DEATH_ISWEAPON(deathtype, WEP_HMG))
+               damage = self.max_health * 0.1;
+
+       if(DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN)) // WEAPONTODO
+       if(deathtype & HITTYPE_SECONDARY)
+       {
+               damage = self.max_health * 0.1;
+               force *= 10;
+       }
+       else
+               damage = self.max_health * 1.15;
+
+       self.velocity += force;
+       UpdateCSQCProjectile(self);
+
+       if(damage <= 0 || ((self.flags & FL_ONGROUND) && IS_PLAYER(attacker)))
+               return;
+
+       if(self.health == self.max_health)
+       {
+               sound(self, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX));
+               self.nextthink = max(time + autocvar_g_nades_nade_lifetime, time);
+               self.think = nade_beep;
+       }
+
+       self.health -= damage;
+
+       if ( self.nade_type != NADE_TYPE_HEAL.m_id || IS_PLAYER(attacker) )
+               self.realowner = attacker;
+
+       if(self.health <= 0)
+               W_PrepareExplosionByDamage(attacker, nade_boom);
+       else
+               nade_burn_spawn(self);
+}
+
+void toss_nade(entity e, vector _velocity, float _time)
+{SELFPARAM();
+       if(e.nade == world)
+               return;
+
+       entity _nade = e.nade;
+       e.nade = world;
+
+       remove(e.fake_nade);
+       e.fake_nade = world;
+
+       makevectors(e.v_angle);
+
+       W_SetupShot(e, false, false, "", CH_WEAPON_A, 0);
+
+       Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER_CPID, CPID_NADES);
+
+       vector offset = (v_forward * autocvar_g_nades_throw_offset.x)
+                                 + (v_right * autocvar_g_nades_throw_offset.y)
+                                 + (v_up * autocvar_g_nades_throw_offset.z);
+       if(autocvar_g_nades_throw_offset == '0 0 0')
+               offset = '0 0 0';
+
+       setorigin(_nade, w_shotorg + offset + (v_right * 25) * -1);
+       //setmodel(_nade, MDL_PROJECTILE_NADE);
+       //setattachment(_nade, world, "");
+       PROJECTILE_MAKETRIGGER(_nade);
+       setsize(_nade, '-16 -16 -16', '16 16 16');
+       _nade.movetype = MOVETYPE_BOUNCE;
+
+       tracebox(_nade.origin, _nade.mins, _nade.maxs, _nade.origin, false, _nade);
+       if (trace_startsolid)
+               setorigin(_nade, e.origin);
+
+       if(self.v_angle.x >= 70 && self.v_angle.x <= 110 && self.BUTTON_CROUCH)
+               _nade.velocity = '0 0 100';
+       else if(autocvar_g_nades_nade_newton_style == 1)
+               _nade.velocity = e.velocity + _velocity;
+       else if(autocvar_g_nades_nade_newton_style == 2)
+               _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.missile_flags = MIF_SPLASH | MIF_ARC;
+       _nade.damagedbycontents = true;
+       _nade.angles = vectoangles(_nade.velocity);
+       _nade.flags = FL_PROJECTILE;
+       _nade.projectiledeathtype = DEATH_NADE.m_id;
+       _nade.toss_time = time;
+       _nade.solid = SOLID_CORPSE; //((_nade.nade_type == NADE_TYPE_TRANSLOCATE) ? SOLID_CORPSE : SOLID_BBOX);
+
+       if(_nade.nade_type == NADE_TYPE_TRANSLOCATE.m_id || _nade.nade_type == NADE_TYPE_SPAWN.m_id)
+               _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+       else
+               _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY;
+
+       nade_spawn(_nade);
+
+       if(_time)
+       {
+               _nade.think = nade_boom;
+               _nade.nextthink = _time;
+       }
+
+       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, SND(KH_ALARM));
+                       player.bonus_nades++;
+                       player.bonus_nade_score -= 1;
+               }
+       }
+}
+
+void nades_RemoveBonus(entity player)
+{
+       player.bonus_nades = player.bonus_nade_score = 0;
+}
+
+float nade_customize()
+{SELFPARAM();
+       //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(Nades[self.nade_type].m_projectile[false], self.team).eent_eff_name);
+               self.alpha = 1;
+       }
+
+       return true;
+}
+
+void nade_prime()
+{SELFPARAM();
+       if(autocvar_g_nades_bonus_only)
+       if(!self.bonus_nades)
+               return; // only allow bonus nades
+
+       if(self.nade)
+               remove(self.nade);
+
+       if(self.fake_nade)
+               remove(self.fake_nade);
+
+       entity n = spawn(), fn = spawn();
+
+       n.classname = "nade";
+       fn.classname = "fake_nade";
+
+       if(self.items & ITEM_Strength.m_itemid && 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, Nades_COUNT);
+
+       setmodel(n, MDL_PROJECTILE_NADE);
+       //setattachment(n, self, "bip01 l hand");
+       n.exteriormodeltoclient = self;
+       n.customizeentityforclient = nade_customize;
+       n.traileffectnum = _particleeffectnum(Nade_TrailEffect(Nades[n.nade_type].m_projectile[false], self.team).eent_eff_name);
+       n.colormod = Nades[n.nade_type].m_color;
+       n.realowner = self;
+       n.colormap = self.colormap;
+       n.glowmod = self.glowmod;
+       n.wait = time + autocvar_g_nades_nade_lifetime;
+       n.nade_time_primed = time;
+       n.think = nade_beep;
+       n.nextthink = max(n.wait - 3, time);
+       n.projectiledeathtype = DEATH_NADE.m_id;
+
+       setmodel(fn, MDL_NADE_VIEW);
+       setattachment(fn, self.weaponentity, "");
+       fn.realowner = fn.owner = self;
+       fn.colormod = Nades[n.nade_type].m_color;
+       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()
+{SELFPARAM();
+       if(self.vehicle)
+               return false;
+
+       if(gameover)
+               return false;
+
+       if(self.deadflag != DEAD_NO)
+               return false;
+
+       if (!autocvar_g_nades)
+               return false; // allow turning them off mid match
+
+       if(forbidWeaponUse(self))
+               return false;
+
+       if (!IS_PLAYER(self))
+               return false;
+
+       return true;
+}
+
+.bool nade_altbutton;
+
+void nades_CheckThrow()
+{SELFPARAM();
+       if(!CanThrowNade())
+               return;
+
+       entity held_nade = self.nade;
+       if (!held_nade)
+       {
+               self.nade_altbutton = true;
+               if(time > self.nade_refire)
+               {
+                       Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_NADE_THROW);
+                       nade_prime();
+                       self.nade_refire = time + autocvar_g_nades_nade_refire;
+               }
+       }
+       else
+       {
+               self.nade_altbutton = false;
+               if (time >= held_nade.nade_time_primed + 1) {
+                       makevectors(self.v_angle);
+                       float _force = time - held_nade.nade_time_primed;
+                       _force /= autocvar_g_nades_nade_lifetime;
+                       _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce));
+                       toss_nade(self, (v_forward * 0.75 + v_up * 0.2 + v_right * 0.05) * _force, 0);
+               }
+       }
+}
+
+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, VehicleEnter)
+{
+       if(vh_player.nade)
+               toss_nade(vh_player, '0 0 100', max(vh_player.nade.wait, time + 0.05));
+
+       return false;
+}
+
+CLASS(NadeOffhand, OffhandWeapon)
+    METHOD(NadeOffhand, offhand_think, void(NadeOffhand this, entity player, bool key_pressed))
+    {
+       entity held_nade = player.nade;
+               if (held_nade)
+               {
+                       player.nade_timer = bound(0, (time - held_nade.nade_time_primed) / autocvar_g_nades_nade_lifetime, 1);
+                       // LOG_TRACEF("%d %d\n", player.nade_timer, time - held_nade.nade_time_primed);
+                       makevectors(player.angles);
+                       held_nade.velocity = player.velocity;
+                       setorigin(held_nade, player.origin + player.view_ofs + v_forward * 8 + v_right * -8 + v_up * 0);
+                       held_nade.angles_y = player.angles.y;
+
+                       if (time + 0.1 >= held_nade.wait)
+                               toss_nade(player, '0 0 0', time + 0.05);
+               }
+
+        if (!CanThrowNade()) return;
+        if (!(time > player.nade_refire)) return;
+               if (key_pressed) {
+                       if (!held_nade) {
+                               nade_prime();
+                               held_nade = player.nade;
+                       }
+               } else if (time >= held_nade.nade_time_primed + 1) {
+                       if (held_nade) {
+                               makevectors(player.v_angle);
+                               float _force = time - held_nade.nade_time_primed;
+                               _force /= autocvar_g_nades_nade_lifetime;
+                               _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce));
+                               toss_nade(player, (v_forward * 0.7 + v_up * 0.2 + v_right * 0.1) * _force, 0);
+                       }
+               }
+    }
+ENDCLASS(NadeOffhand)
+NadeOffhand OFFHAND_NADE; STATIC_INIT(OFFHAND_NADE) { OFFHAND_NADE = NEW(NadeOffhand); }
+
+MUTATOR_HOOKFUNCTION(nades, ForbidThrowCurrentWeapon, CBC_ORDER_LAST)
+{
+       if (self.offhand != OFFHAND_NADE || (self.weapons & WEPSET(HOOK)) || autocvar_g_nades_override_dropweapon) {
+               nades_CheckThrow();
+               return true;
+       }
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(nades, PlayerPreThink)
+{SELFPARAM();
+       if (!IS_PLAYER(self)) { return false; }
+
+       if (self.nade && (self.offhand != OFFHAND_NADE || (self.weapons & WEPSET(HOOK)))) OFFHAND_NADE.offhand_think(OFFHAND_NADE, self, self.nade_altbutton);
+
+       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; }
+
+                       float time_score;
+                       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, Nades_COUNT);
+
+                       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;
+}
+
+MUTATOR_HOOKFUNCTION(nades, PlayerSpawn)
+{SELFPARAM();
+       if(autocvar_g_nades_spawn)
+               self.nade_refire = time + autocvar_g_spawnshieldtime;
+       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.offhand) self.offhand = OFFHAND_NADE;
+
+       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, CBC_ORDER_LAST)
+{
+       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_Calculate)
+{
+       if(frag_target.frozen)
+       if(autocvar_g_freezetag_revive_nade)
+       if(frag_attacker == frag_target)
+       if(frag_deathtype == DEATH_NADE.m_id)
+       if(time - frag_inflictor.toss_time <= 0.1)
+       {
+               Unfreeze(frag_target);
+               frag_target.health = autocvar_g_freezetag_revive_nade_health;
+               Send_Effect(EFFECT_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)
+{SELFPARAM();
+       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, DropSpecialItems)
+{
+       if(frag_target.nade)
+               toss_nade(frag_target, '0 0 0', time + 0.05);
+
+       return false;
+}
+
+bool nades_RemovePlayer()
+{SELFPARAM();
+       nades_Clear(self);
+       nades_RemoveBonus(self);
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(nades, MakePlayerObserver) { nades_RemovePlayer(); }
+MUTATOR_HOOKFUNCTION(nades, ClientDisconnect) { nades_RemovePlayer(); }
+MUTATOR_HOOKFUNCTION(nades, reset_map_global) { nades_RemovePlayer(); }
+
+MUTATOR_HOOKFUNCTION(nades, SpectateCopy)
+{SELFPARAM();
+       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;
+}
+
+MUTATOR_HOOKFUNCTION(nades, BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":Nades");
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(nades, BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", Nades");
+       return false;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_new_toys.qc b/qcsrc/server/mutators/mutator/mutator_new_toys.qc
new file mode 100644 (file)
index 0000000..78904ff
--- /dev/null
@@ -0,0 +1,227 @@
+#ifdef IMPLEMENTATION
+/*
+
+CORE    laser   vortex     lg      rl      cry     gl      elec    hagar   fireb   hook
+                                                                       vaporizer  porto
+                                                                       tuba
+
+NEW             rifle   hlac    minel                           seeker
+IDEAS                                   OPEN    flak    OPEN            FUN FUN FUN FUN
+
+
+
+How this mutator works:
+ =======================
+
+When a gun tries to spawn, this mutator is called. It will provide alternate
+weaponreplace lists.
+
+Entity:
+
+{
+"classname" "weapon_vortex"
+"new_toys" "rifle"
+}
+-> This will spawn as Rifle in this mutator ONLY, and as Vortex otherwise.
+
+{
+"classname" "weapon_vortext"
+"new_toys" "vortex rifle"
+}
+-> This will spawn as either Vortex or Rifle in this mutator ONLY, and as Vortex otherwise.
+
+{
+"classname" "weapon_vortex"
+"new_toys" "vortex"
+}
+-> This is always a Vortex.
+
+If the map specifies no "new_toys" argument
+
+There will be two default replacements selectable: "replace all" and "replace random".
+In "replace all" mode, e.g. Vortex will have the default replacement "rifle".
+In "replace random" mode, Vortex will have the default replacement "vortex rifle".
+
+This mutator's replacements run BEFORE regular weaponreplace!
+
+The New Toys guns do NOT get a spawn function, so they can only ever be spawned
+when this mutator is active.
+
+Likewise, warmup, give all, give ALL and impulse 99 will not give them unless
+this mutator is active.
+
+Outside this mutator, they still can be spawned by:
+- setting their start weapon cvar to 1
+- give weaponname
+- weaponreplace
+- weaponarena (but all and most weapons arena again won't include them)
+
+This mutator performs the default replacements on the DEFAULTS of the
+start weapon selection.
+
+These weapons appear in the menu's priority list, BUT get a suffix
+"(Mutator weapon)".
+
+Picking up a "new toys" weapon will not play standard weapon pickup sound, but
+roflsound "New toys, new toys!" sound.
+
+*/
+
+bool nt_IsNewToy(int w);
+
+REGISTER_MUTATOR(nt, cvar("g_new_toys") && !cvar("g_instagib") && !cvar("g_overkill"))
+{
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This cannot be added at runtime\n");
+
+               // mark the guns as ok to use by e.g. impulse 99
+               for(int i = WEP_FIRST; i <= WEP_LAST; ++i)
+                       if(nt_IsNewToy(i))
+                               get_weaponinfo(i).spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               for(int i = WEP_FIRST; i <= WEP_LAST; ++i)
+                       if(nt_IsNewToy(i))
+                               get_weaponinfo(i).spawnflags |= WEP_FLAG_MUTATORBLOCKED;
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This cannot be removed at runtime\n");
+               return -1;
+       }
+
+       return 0;
+}
+
+.string new_toys;
+
+float autocvar_g_new_toys_autoreplace;
+bool autocvar_g_new_toys_use_pickupsound = true;
+const float NT_AUTOREPLACE_NEVER = 0;
+const float NT_AUTOREPLACE_ALWAYS = 1;
+const float NT_AUTOREPLACE_RANDOM = 2;
+
+MUTATOR_HOOKFUNCTION(nt, SetModname)
+{
+       modname = "NewToys";
+       return 0;
+}
+
+bool nt_IsNewToy(int w)
+{
+       switch(w)
+       {
+               case WEP_SEEKER.m_id:
+               case WEP_MINE_LAYER.m_id:
+               case WEP_HLAC.m_id:
+               case WEP_RIFLE.m_id:
+               case WEP_SHOCKWAVE.m_id:
+                       return true;
+               default:
+                       return false;
+       }
+}
+
+string nt_GetFullReplacement(string w)
+{
+       switch(w)
+       {
+               case "hagar": return "seeker";
+               case "devastator": return "minelayer";
+               case "machinegun": return "hlac";
+               case "vortex": return "rifle";
+               //case "shotgun": return "shockwave";
+               default: return string_null;
+       }
+}
+
+string nt_GetReplacement(string w, float m)
+{
+       if(m == NT_AUTOREPLACE_NEVER)
+               return w;
+       string s = nt_GetFullReplacement(w);
+       if (!s)
+               return w;
+       if(m == NT_AUTOREPLACE_RANDOM)
+               s = strcat(w, " ", s);
+       return s;
+}
+
+MUTATOR_HOOKFUNCTION(nt, SetStartItems)
+{
+       // rearrange start_weapon_default
+       // apply those bits that are set by start_weapon_defaultmask
+       // same for warmup
+
+       float i, j, k, n;
+
+       WepSet newdefault;
+       WepSet warmup_newdefault;
+
+       newdefault = '0 0 0';
+       warmup_newdefault = '0 0 0';
+
+       for(i = WEP_FIRST; i <= WEP_LAST; ++i)
+       {
+               entity e = get_weaponinfo(i);
+               if(!e.weapon)
+                       continue;
+
+               n = tokenize_console(nt_GetReplacement(e.netname, autocvar_g_new_toys_autoreplace));
+
+               for(j = 0; j < n; ++j)
+                       for(k = WEP_FIRST; k <= WEP_LAST; ++k)
+                               if(get_weaponinfo(k).netname == argv(j))
+                               {
+                                       if(start_weapons & WepSet_FromWeapon(i))
+                                               newdefault |= WepSet_FromWeapon(k);
+                                       if(warmup_start_weapons & WepSet_FromWeapon(i))
+                                               warmup_newdefault |= WepSet_FromWeapon(k);
+                               }
+       }
+
+       newdefault &= start_weapons_defaultmask;
+       start_weapons &= ~start_weapons_defaultmask;
+       start_weapons |= newdefault;
+
+       warmup_newdefault &= warmup_start_weapons_defaultmask;
+       warmup_start_weapons &= ~warmup_start_weapons_defaultmask;
+       warmup_start_weapons |= warmup_newdefault;
+
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(nt, SetWeaponreplace)
+{SELFPARAM();
+       // otherwise, we do replace
+       if(self.new_toys)
+       {
+               // map defined replacement:
+               ret_string = self.new_toys;
+       }
+       else
+       {
+               // auto replacement:
+               ret_string = nt_GetReplacement(other.netname, autocvar_g_new_toys_autoreplace);
+       }
+
+       // apply regular weaponreplace
+       ret_string = W_Apply_Weaponreplace(ret_string);
+
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(nt, FilterItem)
+{SELFPARAM();
+       if(nt_IsNewToy(self.weapon) && autocvar_g_new_toys_use_pickupsound) {
+               self.item_pickupsound = string_null;
+               self.item_pickupsound_ent = SND_WEAPONPICKUP_NEW_TOYS;
+       }
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_nix.qc b/qcsrc/server/mutators/mutator/mutator_nix.qc
new file mode 100644 (file)
index 0000000..259547a
--- /dev/null
@@ -0,0 +1,267 @@
+#ifdef IMPLEMENTATION
+float g_nix_with_blaster;
+// WEAPONTODO
+int nix_weapon;
+float nix_nextchange;
+float nix_nextweapon;
+.float nix_lastchange_id;
+.float nix_lastinfotime;
+.float nix_nextincr;
+
+bool NIX_CanChooseWeapon(int wpn);
+
+REGISTER_MUTATOR(nix, cvar("g_nix") && !cvar("g_instagib") && !cvar("g_overkill"))
+{
+       MUTATOR_ONADD
+       {
+               g_nix_with_blaster = autocvar_g_nix_with_blaster;
+
+               nix_nextchange = 0;
+               nix_nextweapon = 0;
+
+               for (int i = WEP_FIRST; i <= WEP_LAST; ++i)
+                       if (NIX_CanChooseWeapon(i)) {
+                               Weapon w = get_weaponinfo(i);
+                               w.wr_init(w);
+                       }
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // nothing to roll back
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               // as the PlayerSpawn hook will no longer run, NIX is turned off by this!
+               entity e;
+               FOR_EACH_PLAYER(e) if(e.deadflag == DEAD_NO)
+               {
+                       e.ammo_cells = start_ammo_cells;
+                       e.ammo_plasma = start_ammo_plasma;
+                       e.ammo_shells = start_ammo_shells;
+                       e.ammo_nails = start_ammo_nails;
+                       e.ammo_rockets = start_ammo_rockets;
+                       e.ammo_fuel = start_ammo_fuel;
+                       e.weapons = start_weapons;
+                       if(!client_hasweapon(e, e.weapon, true, false))
+                               e.switchweapon = w_getbestweapon(self);
+               }
+       }
+
+       return 0;
+}
+
+bool NIX_CanChooseWeapon(int wpn)
+{
+       entity e = get_weaponinfo(wpn);
+       if(!e.weapon) // skip dummies
+               return false;
+       if(g_weaponarena)
+       {
+               if(!(g_weaponarena_weapons & WepSet_FromWeapon(wpn)))
+                       return false;
+       }
+       else
+       {
+               if(wpn == WEP_BLASTER.m_id && g_nix_with_blaster)
+                       return false;
+               if(e.spawnflags & WEP_FLAG_MUTATORBLOCKED)
+                       return false;
+               if (!(e.spawnflags & WEP_FLAG_NORMAL))
+                       return false;
+       }
+       return true;
+}
+void NIX_ChooseNextWeapon()
+{
+       float j;
+       RandomSelection_Init();
+       for(j = WEP_FIRST; j <= WEP_LAST; ++j)
+               if(NIX_CanChooseWeapon(j))
+                       RandomSelection_Add(world, j, string_null, 1, (j != nix_weapon));
+       nix_nextweapon = RandomSelection_chosen_float;
+}
+
+void NIX_GiveCurrentWeapon()
+{SELFPARAM();
+       float dt;
+
+       if(!nix_nextweapon)
+               NIX_ChooseNextWeapon();
+
+       dt = ceil(nix_nextchange - time);
+
+       if(dt <= 0)
+       {
+               nix_weapon = nix_nextweapon;
+               nix_nextweapon = 0;
+               if (!nix_nextchange) // no round played yet?
+                       nix_nextchange = time; // start the first round now!
+               else
+                       nix_nextchange = time + autocvar_g_balance_nix_roundtime;
+               // Weapon w = get_weaponinfo(nix_weapon);
+               // w.wr_init(w); // forget it, too slow
+       }
+
+       // get weapon info
+       entity e = get_weaponinfo(nix_weapon);
+
+       if(nix_nextchange != self.nix_lastchange_id) // this shall only be called once per round!
+       {
+               self.ammo_shells = self.ammo_nails = self.ammo_rockets = self.ammo_cells = self.ammo_plasma = self.ammo_fuel = 0;
+
+               if(self.items & IT_UNLIMITED_WEAPON_AMMO)
+               {
+                       switch(e.ammo_field)
+                       {
+                               case ammo_shells:  self.ammo_shells  = autocvar_g_pickup_shells_max;  break;
+                               case ammo_nails:   self.ammo_nails   = autocvar_g_pickup_nails_max;   break;
+                               case ammo_rockets: self.ammo_rockets = autocvar_g_pickup_rockets_max; break;
+                               case ammo_cells:   self.ammo_cells   = autocvar_g_pickup_cells_max;   break;
+                               case ammo_plasma:  self.ammo_plasma  = autocvar_g_pickup_plasma_max;   break;
+                               case ammo_fuel:    self.ammo_fuel    = autocvar_g_pickup_fuel_max;    break;
+                       }
+               }
+               else
+               {
+                       switch(e.ammo_field)
+                       {
+                               case ammo_shells:  self.ammo_shells  = autocvar_g_balance_nix_ammo_shells;  break;
+                               case ammo_nails:   self.ammo_nails   = autocvar_g_balance_nix_ammo_nails;   break;
+                               case ammo_rockets: self.ammo_rockets = autocvar_g_balance_nix_ammo_rockets; break;
+                               case ammo_cells:   self.ammo_cells   = autocvar_g_balance_nix_ammo_cells;   break;
+                               case ammo_plasma:  self.ammo_plasma  = autocvar_g_balance_nix_ammo_plasma;   break;
+                               case ammo_fuel:    self.ammo_fuel    = autocvar_g_balance_nix_ammo_fuel;    break;
+                       }
+               }
+
+               self.nix_nextincr = time + autocvar_g_balance_nix_incrtime;
+               if(dt >= 1 && dt <= 5)
+                       self.nix_lastinfotime = -42;
+               else
+                       Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_NIX_NEWWEAPON, nix_weapon);
+
+               Weapon w = get_weaponinfo(nix_weapon);
+               w.wr_resetplayer(w);
+
+               // all weapons must be fully loaded when we spawn
+               if(e.spawnflags & WEP_FLAG_RELOADABLE) // prevent accessing undefined cvars
+                       self.(weapon_load[nix_weapon]) = e.reloading_ammo;
+
+               // vortex too
+               if(WEP_CVAR(vortex, charge))
+               {
+                       if(WEP_CVAR_SEC(vortex, chargepool))
+                               self.vortex_chargepool_ammo = 1;
+                       self.vortex_charge = WEP_CVAR(vortex, charge_start);
+               }
+
+               // set last change info
+               self.nix_lastchange_id = nix_nextchange;
+       }
+       if(self.nix_lastinfotime != dt)
+       {
+               self.nix_lastinfotime = dt; // initial value 0 should count as "not seen"
+               if(dt >= 1 && dt <= 5)
+                       Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_NIX_COUNTDOWN, nix_nextweapon, dt);
+       }
+
+       if(!(self.items & IT_UNLIMITED_WEAPON_AMMO) && time > self.nix_nextincr)
+       {
+               switch(e.ammo_field)
+               {
+                       case ammo_shells:  self.ammo_shells  += autocvar_g_balance_nix_ammoincr_shells;  break;
+                       case ammo_nails:   self.ammo_nails   += autocvar_g_balance_nix_ammoincr_nails;   break;
+                       case ammo_rockets: self.ammo_rockets += autocvar_g_balance_nix_ammoincr_rockets; break;
+                       case ammo_cells:   self.ammo_cells   += autocvar_g_balance_nix_ammoincr_cells;   break;
+                       case ammo_plasma:  self.ammo_plasma  += autocvar_g_balance_nix_ammoincr_plasma;   break;
+                       case ammo_fuel:    self.ammo_fuel    += autocvar_g_balance_nix_ammoincr_fuel;    break;
+               }
+
+               self.nix_nextincr = time + autocvar_g_balance_nix_incrtime;
+       }
+
+       self.weapons = '0 0 0';
+       if(g_nix_with_blaster)
+               self.weapons |= WEPSET(BLASTER);
+       self.weapons |= WepSet_FromWeapon(nix_weapon);
+
+       if(self.switchweapon != nix_weapon)
+               if(!client_hasweapon(self, self.switchweapon, true, false))
+                       if(client_hasweapon(self, nix_weapon, true, false))
+                               W_SwitchWeapon(nix_weapon);
+}
+
+MUTATOR_HOOKFUNCTION(nix, ForbidThrowCurrentWeapon)
+{
+       return 1; // no throwing in NIX
+}
+
+MUTATOR_HOOKFUNCTION(nix, BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":NIX");
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(nix, BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", NIX");
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(nix, FilterItem)
+{SELFPARAM();
+       switch (self.items)
+       {
+               case ITEM_HealthSmall.m_itemid:
+               case ITEM_HealthMedium.m_itemid:
+               case ITEM_HealthLarge.m_itemid:
+               case ITEM_HealthMega.m_itemid:
+               case ITEM_ArmorSmall.m_itemid:
+               case ITEM_ArmorMedium.m_itemid:
+               case ITEM_ArmorLarge.m_itemid:
+               case ITEM_ArmorMega.m_itemid:
+                       if (autocvar_g_nix_with_healtharmor)
+                               return 0;
+                       break;
+               case ITEM_Strength.m_itemid:
+               case ITEM_Shield.m_itemid:
+                       if (autocvar_g_nix_with_powerups)
+                               return 0;
+                       break;
+       }
+
+       return 1; // delete all other items
+}
+
+MUTATOR_HOOKFUNCTION(nix, OnEntityPreSpawn)
+{SELFPARAM();
+       if(self.classname == "target_items") // items triggers cannot work in nix (as they change weapons/ammo)
+               return 1;
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(nix, PlayerPreThink)
+{SELFPARAM();
+       if(!intermission_running)
+       if(self.deadflag == DEAD_NO)
+       if(IS_PLAYER(self))
+               NIX_GiveCurrentWeapon();
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(nix, PlayerSpawn)
+{SELFPARAM();
+       self.nix_lastchange_id = -1;
+       NIX_GiveCurrentWeapon(); // overrides the weapons you got when spawning
+       self.items |= IT_UNLIMITED_SUPERWEAPONS;
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(nix, SetModname, CBC_ORDER_LAST)
+{
+       modname = "NIX";
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_overkill.qc b/qcsrc/server/mutators/mutator/mutator_overkill.qc
new file mode 100644 (file)
index 0000000..ea5adbf
--- /dev/null
@@ -0,0 +1,363 @@
+#ifdef IMPLEMENTATION
+.vector ok_deathloc;
+.float ok_spawnsys_timer;
+.float ok_lastwep;
+.float ok_item;
+
+.float ok_notice_time;
+.float ammo_charge[Weapons_MAX];
+.float ok_use_ammocharge;
+.float ok_ammo_charge;
+
+.float ok_pauseregen_finished;
+
+void(entity ent, float wep) ok_DecreaseCharge;
+
+void ok_Initialize();
+
+REGISTER_MUTATOR(ok, cvar("g_overkill") && !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill")
+{
+       MUTATOR_ONADD
+       {
+               ok_Initialize();
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               WEP_RPC.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
+               WEP_HMG.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
+       }
+
+       return false;
+}
+
+void W_Blaster_Attack(entity, float, float, float, float, float, float, float, float, float, float);
+spawnfunc(weapon_hmg);
+spawnfunc(weapon_rpc);
+
+void ok_DecreaseCharge(entity ent, int wep)
+{
+       if(!ent.ok_use_ammocharge) return;
+
+       entity wepent = get_weaponinfo(wep);
+
+       if(wepent.weapon == 0)
+               return; // dummy
+
+       ent.ammo_charge[wep] -= max(0, cvar(sprintf("g_overkill_ammo_decharge_%s", wepent.netname)));
+}
+
+void ok_IncreaseCharge(entity ent, int wep)
+{
+       entity wepent = get_weaponinfo(wep);
+
+       if(wepent.weapon == 0)
+               return; // dummy
+
+       if(ent.ok_use_ammocharge)
+       if(!ent.BUTTON_ATCK) // not while attacking?
+               ent.ammo_charge[wep] = min(autocvar_g_overkill_ammo_charge_limit, ent.ammo_charge[wep] + cvar(sprintf("g_overkill_ammo_charge_rate_%s", wepent.netname)) * frametime / W_TICSPERFRAME);
+}
+
+float ok_CheckWeaponCharge(entity ent, int wep)
+{
+       if(!ent.ok_use_ammocharge) return true;
+
+       entity wepent = get_weaponinfo(wep);
+
+       if(wepent.weapon == 0)
+               return 0; // dummy
+
+       return (ent.ammo_charge[wep] >= cvar(sprintf("g_overkill_ammo_decharge_%s", wepent.netname)));
+}
+
+MUTATOR_HOOKFUNCTION(ok, PlayerDamage_Calculate, CBC_ORDER_LAST)
+{
+       if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target))
+       if(DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER))
+       {
+               frag_damage = 0;
+
+               if(frag_attacker != frag_target)
+               if(frag_target.health > 0)
+               if(frag_target.frozen == 0)
+               if(frag_target.deadflag == DEAD_NO)
+               {
+                       Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE);
+                       frag_force = '0 0 0';
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ok, PlayerDamage_SplitHealthArmor)
+{SELFPARAM();
+       if(damage_take)
+               self.ok_pauseregen_finished = max(self.ok_pauseregen_finished, time + 2);
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ok, PlayerDies)
+{SELFPARAM();
+       entity targ = ((frag_attacker) ? frag_attacker : frag_target);
+
+       if(IS_MONSTER(self))
+       {
+               remove(other); // remove default item
+               other = world;
+       }
+
+       setself(spawn());
+       self.ok_item = true;
+       self.noalign = true;
+       self.pickup_anyway = true;
+       spawnfunc_item_armor_small(this);
+       self.movetype = MOVETYPE_TOSS;
+       self.gravity = 1;
+       self.reset = SUB_Remove;
+       setorigin(self, frag_target.origin + '0 0 32');
+       self.velocity = '0 0 200' + normalize(targ.origin - self.origin) * 500;
+       self.classname = "droppedweapon"; // hax
+       SUB_SetFade(self, time + 5, 1);
+       setself(this);
+
+       self.ok_lastwep = self.switchweapon;
+
+       return false;
+}
+MUTATOR_HOOKFUNCTION(ok, MonsterDropItem) { ok_PlayerDies(); }
+
+MUTATOR_HOOKFUNCTION(ok, PlayerRegen)
+{SELFPARAM();
+       // overkill's values are different, so use custom regen
+       if(!self.frozen)
+       {
+               self.armorvalue = CalcRotRegen(self.armorvalue, autocvar_g_balance_armor_regenstable, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, 1 * frametime * (time > self.ok_pauseregen_finished), 0, 0, 1, 1 * frametime * (time > self.pauserotarmor_finished), autocvar_g_balance_armor_limit);
+               self.health = CalcRotRegen(self.health, autocvar_g_balance_health_regenstable, 0, 100, 1 * frametime * (time > self.ok_pauseregen_finished), 200, 0, autocvar_g_balance_health_rotlinear, 1 * frametime * (time > self.pauserothealth_finished), autocvar_g_balance_health_limit);
+
+               float minf, maxf, limitf;
+
+               maxf = autocvar_g_balance_fuel_rotstable;
+               minf = autocvar_g_balance_fuel_regenstable;
+               limitf = autocvar_g_balance_fuel_limit;
+
+               self.ammo_fuel = CalcRotRegen(self.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > self.pauseregen_finished) * ((self.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > self.pauserotfuel_finished), limitf);
+       }
+       return true; // return true anyway, as frozen uses no regen
+}
+
+MUTATOR_HOOKFUNCTION(ok, ForbidThrowCurrentWeapon)
+{
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ok, PlayerPreThink)
+{SELFPARAM();
+       if(intermission_running || gameover)
+               return false;
+
+       if(self.deadflag != DEAD_NO || !IS_PLAYER(self) || self.frozen)
+               return false;
+
+       if(self.ok_lastwep)
+       {
+               self.switchweapon = self.ok_lastwep;
+               self.ok_lastwep = 0;
+       }
+
+       ok_IncreaseCharge(self, self.weapon);
+
+       if(self.BUTTON_ATCK2)
+       if(!forbidWeaponUse(self) || self.weapon_blocked) // allow if weapon is blocked
+       if(time >= self.jump_interval)
+       {
+               self.jump_interval = time + WEP_CVAR_PRI(blaster, refire) * W_WeaponRateFactor();
+               makevectors(self.v_angle);
+
+               int oldwep = self.weapon;
+               self.weapon = WEP_BLASTER.m_id;
+               W_Blaster_Attack(
+                       self,
+                       WEP_BLASTER.m_id | HITTYPE_SECONDARY,
+                       WEP_CVAR_SEC(vaporizer, shotangle),
+                       WEP_CVAR_SEC(vaporizer, damage),
+                       WEP_CVAR_SEC(vaporizer, edgedamage),
+                       WEP_CVAR_SEC(vaporizer, radius),
+                       WEP_CVAR_SEC(vaporizer, force),
+                       WEP_CVAR_SEC(vaporizer, speed),
+                       WEP_CVAR_SEC(vaporizer, spread),
+                       WEP_CVAR_SEC(vaporizer, delay),
+                       WEP_CVAR_SEC(vaporizer, lifetime)
+               );
+               self.weapon = oldwep;
+       }
+
+       self.weapon_blocked = false;
+
+       self.ok_ammo_charge = self.ammo_charge[self.weapon];
+
+       if(self.ok_use_ammocharge)
+       if(!ok_CheckWeaponCharge(self, self.weapon))
+       {
+               if(autocvar_g_overkill_ammo_charge_notice && time > self.ok_notice_time && self.BUTTON_ATCK && IS_REAL_CLIENT(self) && self.weapon == self.switchweapon)
+               {
+                       //Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_OVERKILL_CHARGE);
+                       self.ok_notice_time = time + 2;
+                       play2(self, SND(DRYFIRE));
+               }
+               Weapon wpn = get_weaponinfo(self.weapon);
+               if(self.weaponentity.state != WS_CLEAR)
+                       w_ready(wpn, self, self.BUTTON_ATCK, self.BUTTON_ATCK2);
+
+               self.weapon_blocked = true;
+       }
+
+       self.BUTTON_ATCK2 = 0;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ok, PlayerSpawn)
+{SELFPARAM();
+       if(autocvar_g_overkill_ammo_charge)
+       {
+               for(int i = WEP_FIRST; i <= WEP_LAST; ++i)
+                       self.ammo_charge[i] = autocvar_g_overkill_ammo_charge_limit;
+
+               self.ok_use_ammocharge = 1;
+               self.ok_notice_time = time;
+       }
+       else
+               self.ok_use_ammocharge = 0;
+
+       self.ok_pauseregen_finished = time + 2;
+
+       return false;
+}
+
+void _spawnfunc_weapon_hmg() { SELFPARAM(); spawnfunc_weapon_hmg(this); }
+void _spawnfunc_weapon_rpc() { SELFPARAM(); spawnfunc_weapon_rpc(this); }
+
+MUTATOR_HOOKFUNCTION(ok, OnEntityPreSpawn)
+{SELFPARAM();
+       if(autocvar_g_powerups)
+       if(autocvar_g_overkill_powerups_replace)
+       {
+               if(self.classname == "item_strength")
+               {
+                       entity wep = spawn();
+                       setorigin(wep, self.origin);
+                       setmodel(wep, MDL_OK_HMG);
+                       wep.classname = "weapon_hmg";
+                       wep.ok_item = true;
+                       wep.noalign = self.noalign;
+                       wep.cnt = self.cnt;
+                       wep.team = self.team;
+                       wep.respawntime = autocvar_g_overkill_superguns_respawn_time;
+                       wep.pickup_anyway = true;
+                       wep.think = _spawnfunc_weapon_hmg;
+                       wep.nextthink = time + 0.1;
+                       return true;
+               }
+
+               if(self.classname == "item_invincible")
+               {
+                       entity wep = spawn();
+                       setorigin(wep, self.origin);
+                       setmodel(wep, MDL_OK_RPC);
+                       wep.classname = "weapon_rpc";
+                       wep.ok_item = true;
+                       wep.noalign = self.noalign;
+                       wep.cnt = self.cnt;
+                       wep.team = self.team;
+                       wep.respawntime = autocvar_g_overkill_superguns_respawn_time;
+                       wep.pickup_anyway = true;
+                       wep.think = _spawnfunc_weapon_rpc;
+                       wep.nextthink = time + 0.1;
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ok, FilterItem)
+{SELFPARAM();
+       if(self.ok_item)
+               return false;
+
+       switch(self.items)
+       {
+               case ITEM_HealthMega.m_itemid: return !(autocvar_g_overkill_100h_anyway);
+               case ITEM_ArmorMega.m_itemid: return !(autocvar_g_overkill_100a_anyway);
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ok, SpectateCopy)
+{SELFPARAM();
+       self.ammo_charge[self.weapon] = other.ammo_charge[other.weapon];
+       self.ok_use_ammocharge = other.ok_use_ammocharge;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ok, SetStartItems)
+{
+       WepSet ok_start_items = (WEPSET(MACHINEGUN) | WEPSET(VORTEX) | WEPSET(SHOTGUN));
+
+       if(WEP_RPC.weaponstart > 0) { ok_start_items |= WEPSET(RPC); }
+       if(WEP_HMG.weaponstart > 0) { ok_start_items |= WEPSET(HMG); }
+
+       start_items |= IT_UNLIMITED_WEAPON_AMMO;
+       start_weapons = warmup_start_weapons = ok_start_items;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ok, BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":OK");
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ok, BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", Overkill");
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ok, SetModname)
+{
+       modname = "Overkill";
+       return true;
+}
+
+void ok_SetCvars()
+{
+       // hack to force overkill playermodels
+       cvar_settemp("sv_defaultcharacter", "1");
+       cvar_settemp("sv_defaultplayermodel", "models/ok_player/okrobot1.dpm models/ok_player/okrobot2.dpm models/ok_player/okrobot3.dpm models/ok_player/okrobot4.dpm models/ok_player/okmale1.dpm models/ok_player/okmale2.dpm models/ok_player/okmale3.dpm models/ok_player/okmale4.dpm");
+       cvar_settemp("sv_defaultplayermodel_red", "models/ok_player/okrobot1.dpm models/ok_player/okrobot2.dpm models/ok_player/okrobot3.dpm models/ok_player/okrobot4.dpm");
+       cvar_settemp("sv_defaultplayermodel_blue", "models/ok_player/okmale1.dpm models/ok_player/okmale2.dpm models/ok_player/okmale3.dpm models/ok_player/okmale4.dpm");
+}
+
+void ok_Initialize()
+{
+       ok_SetCvars();
+
+       precache_all_playermodels("models/ok_player/*.dpm");
+
+       addstat(STAT_OK_AMMO_CHARGE, AS_FLOAT, ok_use_ammocharge);
+       addstat(STAT_OK_AMMO_CHARGEPOOL, AS_FLOAT, ok_ammo_charge);
+
+       WEP_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+       WEP_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+
+       WEP_SHOTGUN.mdl = "ok_shotgun";
+       WEP_MACHINEGUN.mdl = "ok_mg";
+       WEP_VORTEX.mdl = "ok_sniper";
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_physical_items.qc b/qcsrc/server/mutators/mutator/mutator_physical_items.qc
new file mode 100644 (file)
index 0000000..58a01ca
--- /dev/null
@@ -0,0 +1,139 @@
+#ifdef IMPLEMENTATION
+REGISTER_MUTATOR(physical_items, cvar("g_physical_items"))
+{
+       // check if we have a physics engine
+       MUTATOR_ONADD
+       {
+               if (!(autocvar_physics_ode && checkextension("DP_PHYSICS_ODE")))
+               {
+                       LOG_TRACE("Warning: Physical items are enabled but no physics engine can be used. Reverting to old items.\n");
+                       return -1;
+               }
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // nothing to roll back
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               LOG_INFO("This cannot be removed at runtime\n");
+               return -1;
+       }
+
+       return 0;
+}
+
+.vector spawn_origin, spawn_angles;
+
+void physical_item_think()
+{SELFPARAM();
+       self.nextthink = time;
+
+       self.alpha = self.owner.alpha; // apply fading and ghosting
+
+       if(!self.cnt) // map item, not dropped
+       {
+               // copy ghost item properties
+               self.colormap = self.owner.colormap;
+               self.colormod = self.owner.colormod;
+               self.glowmod = self.owner.glowmod;
+
+               // if the item is not spawned, make sure the invisible / ghost item returns to its origin and stays there
+               if(autocvar_g_physical_items_reset)
+               {
+                       if(self.owner.wait > time) // awaiting respawn
+                       {
+                               setorigin(self, self.spawn_origin);
+                               self.angles = self.spawn_angles;
+                               self.solid = SOLID_NOT;
+                               self.alpha = -1;
+                               self.movetype = MOVETYPE_NONE;
+                       }
+                       else
+                       {
+                               self.alpha = 1;
+                               self.solid = SOLID_CORPSE;
+                               self.movetype = MOVETYPE_PHYSICS;
+                       }
+               }
+       }
+
+       if(!self.owner.modelindex)
+               remove(self); // the real item is gone, remove this
+}
+
+void physical_item_touch()
+{SELFPARAM();
+       if(!self.cnt) // not for dropped items
+       if (ITEM_TOUCH_NEEDKILL())
+       {
+               setorigin(self, self.spawn_origin);
+               self.angles = self.spawn_angles;
+       }
+}
+
+void physical_item_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{SELFPARAM();
+       if(!self.cnt) // not for dropped items
+       if(ITEM_DAMAGE_NEEDKILL(deathtype))
+       {
+               setorigin(self, self.spawn_origin);
+               self.angles = self.spawn_angles;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(physical_items, Item_Spawn)
+{SELFPARAM();
+       if(self.owner == world && autocvar_g_physical_items <= 1)
+               return false;
+       if (self.spawnflags & 1) // floating item
+               return false;
+
+       // The actual item can't be physical and trigger at the same time, so make it invisible and use a second entity for physics.
+       // Ugly hack, but unless SOLID_TRIGGER is gotten to work with MOVETYPE_PHYSICS in the engine it can't be fixed.
+       entity wep;
+       wep = spawn();
+       _setmodel(wep, self.model);
+       setsize(wep, self.mins, self.maxs);
+       setorigin(wep, self.origin);
+       wep.angles = self.angles;
+       wep.velocity = self.velocity;
+
+       wep.owner = self;
+       wep.solid = SOLID_CORPSE;
+       wep.movetype = MOVETYPE_PHYSICS;
+       wep.takedamage = DAMAGE_AIM;
+       wep.effects |= EF_NOMODELFLAGS; // disable the spinning
+       wep.colormap = self.owner.colormap;
+       wep.glowmod = self.owner.glowmod;
+       wep.damageforcescale = autocvar_g_physical_items_damageforcescale;
+       wep.dphitcontentsmask = self.dphitcontentsmask;
+       wep.cnt = (self.owner != world);
+
+       wep.think = physical_item_think;
+       wep.nextthink = time;
+       wep.touch = physical_item_touch;
+       wep.event_damage = physical_item_damage;
+
+       if(!wep.cnt)
+       {
+               // fix the spawn origin
+               setorigin(wep, wep.origin + '0 0 1');
+               entity oldself;
+               oldself = self;
+               WITH(entity, self, wep, builtin_droptofloor());
+       }
+
+       wep.spawn_origin = wep.origin;
+       wep.spawn_angles = self.angles;
+
+       self.effects |= EF_NODRAW; // hide the original weapon
+       self.movetype = MOVETYPE_FOLLOW;
+       self.aiment = wep; // attach the original weapon
+       self.SendEntity = func_null;
+
+       return false;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_pinata.qc b/qcsrc/server/mutators/mutator/mutator_pinata.qc
new file mode 100644 (file)
index 0000000..a806b29
--- /dev/null
@@ -0,0 +1,27 @@
+#ifdef IMPLEMENTATION
+REGISTER_MUTATOR(pinata, cvar("g_pinata") && !cvar("g_instagib") && !cvar("g_overkill"));
+
+MUTATOR_HOOKFUNCTION(pinata, PlayerDies)
+{SELFPARAM();
+       for(int j = WEP_FIRST; j <= WEP_LAST; ++j)
+       if(self.weapons & WepSet_FromWeapon(j))
+       if(self.switchweapon != j)
+       if(W_IsWeaponThrowable(j))
+               W_ThrowNewWeapon(self, j, false, self.origin + (self.mins + self.maxs) * 0.5, randomvec() * 175 + '0 0 325');
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(pinata, BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":Pinata");
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(pinata, BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", Piñata");
+       return false;
+}
+
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_random_gravity.qc b/qcsrc/server/mutators/mutator/mutator_random_gravity.qc
new file mode 100644 (file)
index 0000000..1b17c9f
--- /dev/null
@@ -0,0 +1,49 @@
+#ifdef IMPLEMENTATION
+// Random Gravity
+//
+// Mutator by Mario
+// Inspired by Player 2
+
+REGISTER_MUTATOR(random_gravity, cvar("g_random_gravity"))
+{
+       MUTATOR_ONADD
+       {
+               cvar_settemp("sv_gravity", cvar_string("sv_gravity")); // settemp current gravity so it's restored on match end
+       }
+
+       return false;
+}
+
+float gravity_delay;
+
+MUTATOR_HOOKFUNCTION(random_gravity, SV_StartFrame)
+{
+       if(gameover || !cvar("g_random_gravity")) return false;
+       if(time < gravity_delay) return false;
+       if(time < game_starttime) return false;
+       if(round_handler_IsActive() && !round_handler_IsRoundStarted()) return false;
+
+    if(random() >= autocvar_g_random_gravity_negative_chance)
+        cvar_set("sv_gravity", ftos(bound(autocvar_g_random_gravity_min, random() - random() * -autocvar_g_random_gravity_negative, autocvar_g_random_gravity_max)));
+    else
+        cvar_set("sv_gravity", ftos(bound(autocvar_g_random_gravity_min, random() * autocvar_g_random_gravity_positive, autocvar_g_random_gravity_max)));
+
+       gravity_delay = time + autocvar_g_random_gravity_delay;
+
+       LOG_TRACE("Gravity is now: ", ftos(autocvar_sv_gravity), "\n");
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(random_gravity, BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":RandomGravity");
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(random_gravity, BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", Random gravity");
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_rocketflying.qc b/qcsrc/server/mutators/mutator/mutator_rocketflying.qc
new file mode 100644 (file)
index 0000000..f23d991
--- /dev/null
@@ -0,0 +1,25 @@
+#ifdef IMPLEMENTATION
+REGISTER_MUTATOR(rocketflying, cvar("g_rocket_flying"));
+
+MUTATOR_HOOKFUNCTION(rocketflying, EditProjectile)
+{
+       if(other.classname == "rocket" || other.classname == "mine")
+       {
+               // kill detonate delay of rockets
+               other.spawnshieldtime = time;
+       }
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(rocketflying, BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":RocketFlying");
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(rocketflying, BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", Rocket Flying");
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_rocketminsta.qc b/qcsrc/server/mutators/mutator/mutator_rocketminsta.qc
new file mode 100644 (file)
index 0000000..f8a1709
--- /dev/null
@@ -0,0 +1,35 @@
+#ifdef IMPLEMENTATION
+#include "../../../common/deathtypes/all.qh"
+#include "../../round_handler.qh"
+
+REGISTER_MUTATOR(rm, cvar("g_instagib"));
+
+MUTATOR_HOOKFUNCTION(rm, PlayerDamage_Calculate)
+{
+       // we do it this way, so rm can be toggled during the match
+       if(!autocvar_g_rm) { return false; }
+
+       if(DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR))
+       if(frag_attacker == frag_target || frag_target.classname == "nade")
+               frag_damage = 0;
+
+       if(autocvar_g_rm_laser)
+       if(DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO))
+       if(frag_attacker == frag_target || (round_handler_IsActive() && !round_handler_IsRoundStarted()))
+               frag_damage = 0;
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(rm, PlayerDies)
+{
+       // we do it this way, so rm can be toggled during the match
+       if(!autocvar_g_rm) { return false; }
+
+       if(DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR) || DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO))
+               frag_damage = 1000; // always gib if it was a vaporizer death
+
+       return false;
+}
+
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_spawn_near_teammate.qc b/qcsrc/server/mutators/mutator/mutator_spawn_near_teammate.qc
new file mode 100644 (file)
index 0000000..24147b2
--- /dev/null
@@ -0,0 +1,167 @@
+#ifdef IMPLEMENTATION
+REGISTER_MUTATOR(spawn_near_teammate, cvar("g_spawn_near_teammate") && teamplay);
+
+.entity msnt_lookat;
+
+.float msnt_timer;
+.vector msnt_deathloc;
+
+.float cvar_cl_spawn_near_teammate;
+
+MUTATOR_HOOKFUNCTION(spawn_near_teammate, Spawn_Score)
+{SELFPARAM();
+       if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && self.cvar_cl_spawn_near_teammate))
+               return 0;
+
+       entity p;
+
+       spawn_spot.msnt_lookat = world;
+
+       if(!teamplay)
+               return 0;
+
+       RandomSelection_Init();
+       FOR_EACH_PLAYER(p) if(p != self) if(p.team == self.team) if(!p.deadflag)
+       {
+               float l = vlen(spawn_spot.origin - p.origin);
+               if(l > autocvar_g_spawn_near_teammate_distance)
+                       continue;
+               if(l < 48)
+                       continue;
+               if(!checkpvs(spawn_spot.origin, p))
+                       continue;
+               RandomSelection_Add(p, 0, string_null, 1, 1);
+       }
+
+       if(RandomSelection_chosen_ent)
+       {
+               spawn_spot.msnt_lookat = RandomSelection_chosen_ent;
+               spawn_score.x += SPAWN_PRIO_NEAR_TEAMMATE_FOUND;
+       }
+       else if(self.team == spawn_spot.team)
+               spawn_score.x += SPAWN_PRIO_NEAR_TEAMMATE_SAMETEAM; // prefer same team, if we can't find a spawn near teammate
+
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn)
+{SELFPARAM();
+       // Note: when entering this, fixangle is already set.
+       if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && self.cvar_cl_spawn_near_teammate))
+       {
+               if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death)
+                       self.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death;
+
+               entity team_mate, best_mate = world;
+               vector best_spot = '0 0 0';
+               float pc = 0, best_dist = 0, dist = 0;
+               FOR_EACH_PLAYER(team_mate)
+               {
+                       if((autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health >= 0 && team_mate.health >= autocvar_g_balance_health_regenstable) || autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health == 0)
+                       if(team_mate.deadflag == DEAD_NO)
+                       if(team_mate.msnt_timer < time)
+                       if(SAME_TEAM(self, team_mate))
+                       if(time > team_mate.spawnshieldtime) // spawn shielding
+                       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);
+                               if(trace_fraction != 1.0)
+                               if(!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY))
+                               {
+                                       pc = pointcontents(trace_endpos + '0 0 1');
+                                       if(pc == CONTENT_EMPTY)
+                                       {
+                                               if(vlen(team_mate.velocity) > 5)
+                                                       fixedmakevectors(vectoangles(team_mate.velocity));
+                                               else
+                                                       fixedmakevectors(team_mate.angles);
+
+                                               for(pc = 0; pc != 5; ++pc) // test 5 diffrent spots close to mate
+                                               {
+                                                       switch(pc)
+                                                       {
+                                                               case 0:
+                                                                       tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin + v_right * 128, MOVE_NORMAL, team_mate);
+                                                                       break;
+                                                               case 1:
+                                                                       tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin - v_right * 128 , MOVE_NORMAL, team_mate);
+                                                                       break;
+                                                               case 2:
+                                                                       tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin + v_right * 64 - v_forward * 64, MOVE_NORMAL, team_mate);
+                                                                       break;
+                                                               case 3:
+                                                                       tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin - v_right * 64 - v_forward * 64, MOVE_NORMAL, team_mate);
+                                                                       break;
+                                                               case 4:
+                                                                       tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin - v_forward * 128, MOVE_NORMAL, team_mate);
+                                                                       break;
+                                                       }
+
+                                                       if(trace_fraction == 1.0)
+                                                       {
+                                                               traceline(trace_endpos + '0 0 4', trace_endpos - '0 0 100', MOVE_NORMAL, team_mate);
+                                                               if(trace_fraction != 1.0)
+                                                               {
+                                                                       if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath)
+                                                                       {
+                                                                               dist = vlen(trace_endpos - self.msnt_deathloc);
+                                                                               if(dist < best_dist || best_dist == 0)
+                                                                               {
+                                                                                       best_dist = dist;
+                                                                                       best_spot = trace_endpos;
+                                                                                       best_mate = team_mate;
+                                                                               }
+                                                                       }
+                                                                       else
+                                                                       {
+                                                                               setorigin(self, trace_endpos);
+                                                                               self.angles = team_mate.angles;
+                                                                               self.angles_z = 0; // never spawn tilted even if the spot says to
+                                                                               team_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
+                                                                               return 0;
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath)
+               if(best_dist)
+               {
+                       setorigin(self, best_spot);
+                       self.angles = best_mate.angles;
+                       self.angles_z = 0; // never spawn tilted even if the spot says to
+                       best_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
+               }
+       }
+       else if(spawn_spot.msnt_lookat)
+       {
+               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
+               /*
+               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");
+               sprint(self, "angles: ", vtos(self.angles), "\n");
+               */
+       }
+
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerDies)
+{SELFPARAM();
+       self.msnt_deathloc = self.origin;
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(spawn_near_teammate, GetCvars)
+{
+       GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_spawn_near_teammate, "cl_spawn_near_teammate");
+       return false;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_superspec.qc b/qcsrc/server/mutators/mutator/mutator_superspec.qc
new file mode 100644 (file)
index 0000000..f24b323
--- /dev/null
@@ -0,0 +1,480 @@
+#ifdef IMPLEMENTATION
+REGISTER_MUTATOR(superspec, cvar("g_superspectate"));
+
+#define _SSMAGIX "SUPERSPEC_OPTIONSFILE_V1"
+#define _ISLOCAL ((edict_num(1) == self) ? true : false)
+
+const float ASF_STRENGTH               = 1;
+const float ASF_SHIELD                         = 2;
+const float ASF_MEGA_AR                = 4;
+const float ASF_MEGA_HP                = 8;
+const float ASF_FLAG_GRAB              = 16;
+const float ASF_OBSERVER_ONLY  = 32;
+const float ASF_SHOWWHAT               = 64;
+const float ASF_SSIM                   = 128;
+const float ASF_FOLLOWKILLER   = 256;
+const float ASF_ALL                    = 0xFFFFFF;
+.float autospec_flags;
+
+const float SSF_SILENT = 1;
+const float SSF_VERBOSE = 2;
+const float SSF_ITEMMSG = 4;
+.float superspec_flags;
+
+.string superspec_itemfilter; //"classname1 classname2 ..."
+
+bool superspec_Spectate(entity _player)
+{SELFPARAM();
+       if(Spectate(_player) == 1)
+               self.classname = "spectator";
+
+       return true;
+}
+
+void superspec_save_client_conf()
+{SELFPARAM();
+       string fn = "superspec-local.options";
+       float fh;
+
+       if (!_ISLOCAL)
+       {
+               if(self.crypto_idfp == "")
+                       return;
+
+               fn = sprintf("superspec-%s.options", uri_escape(self.crypto_idfp));
+       }
+
+       fh = fopen(fn, FILE_WRITE);
+       if(fh < 0)
+       {
+               LOG_TRACE("^1ERROR: ^7 superspec can not open ", fn, " for writing.\n");
+       }
+       else
+       {
+               fputs(fh, _SSMAGIX);
+               fputs(fh, "\n");
+               fputs(fh, ftos(self.autospec_flags));
+               fputs(fh, "\n");
+               fputs(fh, ftos(self.superspec_flags));
+               fputs(fh, "\n");
+               fputs(fh, self.superspec_itemfilter);
+               fputs(fh, "\n");
+               fclose(fh);
+       }
+}
+
+void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel)
+{
+       sprint(_to, strcat(_con_title, _msg));
+
+       if(_to.superspec_flags & SSF_SILENT)
+               return;
+
+       if(_spamlevel > 1)
+               if (!(_to.superspec_flags & SSF_VERBOSE))
+                       return;
+
+       centerprint(_to, strcat(_center_title, _msg));
+}
+
+float superspec_filteritem(entity _for, entity _item)
+{
+       float i;
+
+       if(_for.superspec_itemfilter == "")
+               return true;
+
+       if(_for.superspec_itemfilter == "")
+               return true;
+
+       float l = tokenize_console(_for.superspec_itemfilter);
+       for(i = 0; i < l; ++i)
+       {
+               if(argv(i) == _item.classname)
+                       return true;
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(superspec, ItemTouch)
+{SELFPARAM();
+       entity _item = self;
+
+       entity e;
+       FOR_EACH_SPEC(e)
+       {
+               setself(e);
+               if(self.superspec_flags & SSF_ITEMMSG)
+                       if(superspec_filteritem(self, _item))
+                       {
+                               if(self.superspec_flags & SSF_VERBOSE)
+                                       superspec_msg("", "", self, sprintf("Player %s^7 just picked up ^3%s\n", other.netname, _item.netname), 1);
+                               else
+                                       superspec_msg("", "", self, sprintf("Player %s^7 just picked up ^3%s\n^8(%s^8)\n", other.netname, _item.netname, _item.classname), 1);
+                               if((self.autospec_flags & ASF_SSIM) && self.enemy != other)
+                               {
+                                       superspec_Spectate(other);
+
+                                       setself(this);
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+
+               if((self.autospec_flags & ASF_SHIELD && _item.invincible_finished) ||
+                       (self.autospec_flags & ASF_STRENGTH && _item.strength_finished) ||
+                       (self.autospec_flags & ASF_MEGA_AR && _item.itemdef == ITEM_ArmorMega) ||
+                       (self.autospec_flags & ASF_MEGA_HP && _item.itemdef == ITEM_HealthMega) ||
+                       (self.autospec_flags & ASF_FLAG_GRAB && _item.classname == "item_flag_team"))
+               {
+
+                       if((self.enemy != other) || IS_OBSERVER(self))
+                       {
+                               if(self.autospec_flags & ASF_OBSERVER_ONLY && !IS_OBSERVER(self))
+                               {
+                                       if(self.superspec_flags & SSF_VERBOSE)
+                                               superspec_msg("", "", self, sprintf("^8Ignored that ^7%s^8 grabbed %s^8 since the observer_only option is ON\n", other.netname, _item.netname), 2);
+                               }
+                               else
+                               {
+                                       if(self.autospec_flags & ASF_SHOWWHAT)
+                                               superspec_msg("", "", self, sprintf("^7Following %s^7 due to picking up %s\n", other.netname, _item.netname), 2);
+
+                                       superspec_Spectate(other);
+                               }
+                       }
+               }
+       }
+
+       setself(this);
+
+       return MUT_ITEMTOUCH_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(superspec, SV_ParseClientCommand)
+{SELFPARAM();
+#define OPTIONINFO(flag,var,test,text,long,short) \
+    var = strcat(var, ((flag & test) ? "^2[ON]  ^7" : "^1[OFF] ^7")); \
+    var = strcat(var, text," ^7(^3 ", long, "^7 | ^3", short, " ^7)\n")
+
+       if(MUTATOR_RETURNVALUE) // command was already handled?
+               return false;
+
+       if(IS_PLAYER(self))
+               return false;
+
+       if(cmd_name == "superspec_itemfilter")
+       {
+               if(argv(1) == "help")
+               {
+                       string _aspeco;
+                       _aspeco = "^7 superspec_itemfilter ^3\"item_classname1 item_classname2\"^7 only show thise items when ^2superspec ^3item_message^7 is on\n";
+                       _aspeco = strcat(_aspeco, "^3 clear^7 Remove the filter (show all pickups)\n");
+                       _aspeco = strcat(_aspeco, "^3 show ^7 Display current filter\n");
+                       superspec_msg("^3superspec_itemfilter help:\n\n\n", "\n^3superspec_itemfilter help:\n", self, _aspeco, 1);
+               }
+               else if(argv(1) == "clear")
+               {
+                       if(self.superspec_itemfilter != "")
+                               strunzone(self.superspec_itemfilter);
+
+                       self.superspec_itemfilter = "";
+               }
+               else if(argv(1) == "show" || argv(1) == "")
+               {
+                       if(self.superspec_itemfilter == "")
+                       {
+                               superspec_msg("^3superspec_itemfilter^7 is ^1not^7 set", "\n^3superspec_itemfilter^7 is ^1not^7 set\n", self, "", 1);
+                               return true;
+                       }
+                       float i;
+                       float l = tokenize_console(self.superspec_itemfilter);
+                       string _msg = "";
+                       for(i = 0; i < l; ++i)
+                               _msg = strcat(_msg, "^3#", ftos(i), " ^7", argv(i), "\n");
+                               //_msg = sprintf("^3#%d^7 %s\n%s", i, _msg, argv(i));
+
+                       _msg = strcat(_msg,"\n");
+
+                       superspec_msg("^3superspec_itemfilter is:\n\n\n", "\n^3superspec_itemfilter is:\n", self, _msg, 1);
+               }
+               else
+               {
+                       if(self.superspec_itemfilter != "")
+                               strunzone(self.superspec_itemfilter);
+
+                       self.superspec_itemfilter = strzone(argv(1));
+               }
+
+               return true;
+       }
+
+       if(cmd_name == "superspec")
+       {
+               string _aspeco;
+
+               if(cmd_argc > 1)
+               {
+                       float i, _bits = 0, _start = 1;
+                       if(argv(1) == "help")
+                       {
+                               _aspeco = "use cmd superspec [option] [on|off] to set options\n\n";
+                               _aspeco = strcat(_aspeco, "^3 silent ^7(short^5 si^7) supresses ALL messages from superspectate.\n");
+                               _aspeco = strcat(_aspeco, "^3 verbose ^7(short^5 ve^7) makes superspectate print some additional information.\n");
+                               _aspeco = strcat(_aspeco, "^3 item_message ^7(short^5 im^7) makes superspectate print items that were picked up.\n");
+                               _aspeco = strcat(_aspeco, "^7    Use cmd superspec_itemfilter \"item_class1 item_class2\" to set up a filter of what to show with ^3item_message.\n");
+                               superspec_msg("^2Available Super Spectate ^3options:\n\n\n", "\n^2Available Super Spectate ^3options:\n", self, _aspeco, 1);
+                               return true;
+                       }
+
+                       if(argv(1) == "clear")
+                       {
+                               self.superspec_flags = 0;
+                               _start = 2;
+                       }
+
+                       for(i = _start; i < cmd_argc; ++i)
+                       {
+                               if(argv(i) == "on" || argv(i) == "1")
+                               {
+                                       self.superspec_flags |= _bits;
+                                       _bits = 0;
+                               }
+                               else if(argv(i) == "off" || argv(i) == "0")
+                               {
+                                       if(_start == 1)
+                                               self.superspec_flags &= ~_bits;
+
+                                       _bits = 0;
+                               }
+                               else
+                               {
+                                       if((argv(i) == "silent") || (argv(i) == "si")) _bits |= SSF_SILENT ;
+                                       if((argv(i) == "verbose") || (argv(i) == "ve")) _bits |= SSF_VERBOSE;
+                                       if((argv(i) == "item_message") || (argv(i) == "im")) _bits |= SSF_ITEMMSG;
+                               }
+                       }
+               }
+
+               _aspeco = "";
+               OPTIONINFO(self.superspec_flags, _aspeco, SSF_SILENT, "Silent", "silent", "si");
+               OPTIONINFO(self.superspec_flags, _aspeco, SSF_VERBOSE, "Verbose", "verbose", "ve");
+               OPTIONINFO(self.superspec_flags, _aspeco, SSF_ITEMMSG, "Item pickup messages", "item_message", "im");
+
+               superspec_msg("^3Current Super Spectate options are:\n\n\n\n\n", "\n^3Current Super Spectate options are:\n", self, _aspeco, 1);
+
+               return true;
+       }
+
+/////////////////////
+
+       if(cmd_name == "autospec")
+       {
+               string _aspeco;
+               if(cmd_argc > 1)
+               {
+                       if(argv(1) == "help")
+                       {
+                               _aspeco = "use cmd autospec [option] [on|off] to set options\n\n";
+                               _aspeco = strcat(_aspeco, "^3 strength ^7(short^5 st^7) for automatic spectate on strength powerup\n");
+                               _aspeco = strcat(_aspeco, "^3 shield ^7(short^5 sh^7) for automatic spectate on shield powerup\n");
+                               _aspeco = strcat(_aspeco, "^3 mega_health ^7(short^5 mh^7) for automatic spectate on mega health\n");
+                               _aspeco = strcat(_aspeco, "^3 mega_armor ^7(short^5 ma^7) for automatic spectate on mega armor\n");
+                               _aspeco = strcat(_aspeco, "^3 flag_grab ^7(short^5 fg^7) for automatic spectate on CTF flag grab\n");
+                               _aspeco = strcat(_aspeco, "^3 observer_only ^7(short^5 oo^7) for automatic spectate only if in observer mode\n");
+                               _aspeco = strcat(_aspeco, "^3 show_what ^7(short^5 sw^7) to display what event triggered autospectate\n");
+                               _aspeco = strcat(_aspeco, "^3 item_msg ^7(short^5 im^7) to autospec when item_message in superspectate is triggered\n");
+                               _aspeco = strcat(_aspeco, "^3 followkiller ^7(short ^5fk^7) to autospec the killer/off\n");
+                               _aspeco = strcat(_aspeco, "^3 all ^7(short ^5aa^7) to turn everything on/off\n");
+                               superspec_msg("^2Available Auto Spectate ^3options:\n\n\n", "\n^2Available Auto Spectate ^3options:\n", self, _aspeco, 1);
+                               return true;
+                       }
+
+                       float i, _bits = 0, _start = 1;
+                       if(argv(1) == "clear")
+                       {
+                               self.autospec_flags = 0;
+                               _start = 2;
+                       }
+
+                       for(i = _start; i < cmd_argc; ++i)
+                       {
+                               if(argv(i) == "on" || argv(i) == "1")
+                               {
+                                       self.autospec_flags |= _bits;
+                                       _bits = 0;
+                               }
+                               else if(argv(i) == "off" || argv(i) == "0")
+                               {
+                                       if(_start == 1)
+                                               self.autospec_flags &= ~_bits;
+
+                                       _bits = 0;
+                               }
+                               else
+                               {
+                                       if((argv(i) == "strength") || (argv(i) == "st")) _bits |= ASF_STRENGTH;
+                                       if((argv(i) == "shield") || (argv(i) == "sh")) _bits |= ASF_SHIELD;
+                                       if((argv(i) == "mega_health") || (argv(i) == "mh")) _bits |= ASF_MEGA_HP;
+                                       if((argv(i) == "mega_armor") || (argv(i) == "ma")) _bits |= ASF_MEGA_AR;
+                                       if((argv(i) == "flag_grab") || (argv(i) == "fg")) _bits |= ASF_FLAG_GRAB;
+                                       if((argv(i) == "observer_only") || (argv(i) == "oo")) _bits |= ASF_OBSERVER_ONLY;
+                                       if((argv(i) == "show_what") || (argv(i) == "sw")) _bits |= ASF_SHOWWHAT;
+                                       if((argv(i) == "item_msg") || (argv(i) == "im")) _bits |= ASF_SSIM;
+                                       if((argv(i) == "followkiller") || (argv(i) == "fk")) _bits |= ASF_FOLLOWKILLER;
+                                       if((argv(i) == "all") || (argv(i) == "aa")) _bits |= ASF_ALL;
+                               }
+                       }
+               }
+
+               _aspeco = "";
+               OPTIONINFO(self.autospec_flags, _aspeco, ASF_STRENGTH, "Strength", "strength", "st");
+               OPTIONINFO(self.autospec_flags, _aspeco, ASF_SHIELD, "Shield", "shield", "sh");
+               OPTIONINFO(self.autospec_flags, _aspeco, ASF_MEGA_HP, "Mega Health", "mega_health", "mh");
+               OPTIONINFO(self.autospec_flags, _aspeco, ASF_MEGA_AR, "Mega Armor", "mega_armor", "ma");
+               OPTIONINFO(self.autospec_flags, _aspeco, ASF_FLAG_GRAB, "Flag grab", "flag_grab","fg");
+               OPTIONINFO(self.autospec_flags, _aspeco, ASF_OBSERVER_ONLY, "Only switch if observer", "observer_only", "oo");
+               OPTIONINFO(self.autospec_flags, _aspeco, ASF_SHOWWHAT, "Show what item triggered spectate", "show_what", "sw");
+               OPTIONINFO(self.autospec_flags, _aspeco, ASF_SSIM, "Switch on superspec item message", "item_msg", "im");
+               OPTIONINFO(self.autospec_flags, _aspeco, ASF_FOLLOWKILLER, "Followkiller", "followkiller", "fk");
+
+               superspec_msg("^3Current auto spectate options are:\n\n\n\n\n", "\n^3Current auto spectate options are:\n", self, _aspeco, 1);
+               return true;
+       }
+
+       if(cmd_name == "followpowerup")
+       {
+               entity _player;
+               FOR_EACH_PLAYER(_player)
+               {
+                       if(_player.strength_finished > time || _player.invincible_finished > time)
+                               return superspec_Spectate(_player);
+               }
+
+               superspec_msg("", "", self, "No active powerup\n", 1);
+               return true;
+       }
+
+       if(cmd_name == "followstrength")
+       {
+               entity _player;
+               FOR_EACH_PLAYER(_player)
+               {
+                       if(_player.strength_finished > time)
+                               return superspec_Spectate(_player);
+               }
+
+               superspec_msg("", "", self, "No active Strength\n", 1);
+               return true;
+       }
+
+       if(cmd_name == "followshield")
+       {
+               entity _player;
+               FOR_EACH_PLAYER(_player)
+               {
+                       if(_player.invincible_finished > time)
+                               return superspec_Spectate(_player);
+               }
+
+               superspec_msg("", "", self, "No active Shield\n", 1);
+               return true;
+       }
+
+       return false;
+#undef OPTIONINFO
+}
+
+MUTATOR_HOOKFUNCTION(superspec, BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":SS");
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(superspec, BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", Super Spectators");
+       return 0;
+}
+
+void superspec_hello()
+{SELFPARAM();
+       if(self.enemy.crypto_idfp == "")
+               Send_Notification(NOTIF_ONE_ONLY, self.enemy, MSG_INFO, INFO_SUPERSPEC_MISSING_UID);
+
+       remove(self);
+}
+
+MUTATOR_HOOKFUNCTION(superspec, ClientConnect)
+{SELFPARAM();
+       if(!IS_REAL_CLIENT(self))
+               return false;
+
+       string fn = "superspec-local.options";
+       float fh;
+
+       self.superspec_flags = SSF_VERBOSE;
+       self.superspec_itemfilter = "";
+
+       entity _hello = spawn();
+       _hello.enemy = self;
+       _hello.think = superspec_hello;
+       _hello.nextthink = time + 5;
+
+       if (!_ISLOCAL)
+       {
+               if(self.crypto_idfp == "")
+                       return false;
+
+               fn = sprintf("superspec-%s.options", uri_escape(self.crypto_idfp));
+       }
+
+       fh = fopen(fn, FILE_READ);
+       if(fh < 0)
+       {
+               LOG_TRACE("^1ERROR: ^7 superspec can not open ", fn, " for reading.\n");
+       }
+       else
+       {
+               string _magic = fgets(fh);
+               if(_magic != _SSMAGIX)
+               {
+                       LOG_TRACE("^1ERROR^7 While reading superspec options file: unknown magic\n");
+               }
+               else
+               {
+                       self.autospec_flags = stof(fgets(fh));
+                       self.superspec_flags = stof(fgets(fh));
+                       self.superspec_itemfilter = strzone(fgets(fh));
+               }
+               fclose(fh);
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(superspec, PlayerDies)
+{SELFPARAM();
+       entity e;
+       FOR_EACH_SPEC(e)
+       {
+               setself(e);
+               if(self.autospec_flags & ASF_FOLLOWKILLER && IS_PLAYER(frag_attacker) && self.enemy == this)
+               {
+                       if(self.autospec_flags & ASF_SHOWWHAT)
+                               superspec_msg("", "", self, sprintf("^7Following %s^7 due to followkiller\n", frag_attacker.netname), 2);
+
+                       superspec_Spectate(frag_attacker);
+               }
+       }
+
+       setself(this);
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(superspec, ClientDisconnect)
+{
+       superspec_save_client_conf();
+       return false;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_touchexplode.qc b/qcsrc/server/mutators/mutator/mutator_touchexplode.qc
new file mode 100644 (file)
index 0000000..29d9a2c
--- /dev/null
@@ -0,0 +1,43 @@
+#ifdef IMPLEMENTATION
+REGISTER_MUTATOR(touchexplode, cvar("g_touchexplode"));
+
+.float touchexplode_time;
+
+void PlayerTouchExplode(entity p1, entity p2)
+{SELFPARAM();
+       vector org = (p1.origin + p2.origin) * 0.5;
+       org.z += (p1.mins.z + p2.mins.z) * 0.5;
+
+       sound(self, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM);
+       Send_Effect(EFFECT_EXPLOSION_SMALL, org, '0 0 0', 1);
+
+       entity e = spawn();
+       setorigin(e, org);
+       RadiusDamage(e, world, autocvar_g_touchexplode_damage, autocvar_g_touchexplode_edgedamage, autocvar_g_touchexplode_radius, world, world, autocvar_g_touchexplode_force, DEATH_TOUCHEXPLODE.m_id, world);
+       remove(e);
+}
+
+MUTATOR_HOOKFUNCTION(touchexplode, PlayerPreThink)
+{SELFPARAM();
+       if(time > self.touchexplode_time)
+       if(!gameover)
+       if(!self.frozen)
+       if(IS_PLAYER(self))
+       if(self.deadflag == DEAD_NO)
+       if (!IS_INDEPENDENT_PLAYER(self))
+       FOR_EACH_PLAYER(other) if(self != other)
+       {
+               if(time > other.touchexplode_time)
+               if(!other.frozen)
+               if(other.deadflag == DEAD_NO)
+               if (!IS_INDEPENDENT_PLAYER(other))
+               if(boxesoverlap(self.absmin, self.absmax, other.absmin, other.absmax))
+               {
+                       PlayerTouchExplode(self, other);
+                       self.touchexplode_time = other.touchexplode_time = time + 0.2;
+               }
+       }
+
+       return false;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_vampire.qc b/qcsrc/server/mutators/mutator/mutator_vampire.qc
new file mode 100644 (file)
index 0000000..315da7d
--- /dev/null
@@ -0,0 +1,28 @@
+#ifdef IMPLEMENTATION
+REGISTER_MUTATOR(vampire, cvar("g_vampire") && !cvar("g_instagib"));
+
+MUTATOR_HOOKFUNCTION(vampire, PlayerDamage_SplitHealthArmor)
+{
+       if(time >= frag_target.spawnshieldtime)
+       if(frag_target != frag_attacker)
+       if(frag_target.deadflag == DEAD_NO)
+       {
+               frag_attacker.health += bound(0, damage_take, frag_target.health);
+               frag_attacker.health = bound(0, frag_attacker.health, autocvar_g_balance_health_limit);
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(vampire, BuildMutatorsString)
+{
+       ret_string = strcat(ret_string, ":Vampire");
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(vampire, BuildMutatorsPrettyString)
+{
+       ret_string = strcat(ret_string, ", Vampire");
+       return 0;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator/mutator_vampirehook.qc b/qcsrc/server/mutators/mutator/mutator_vampirehook.qc
new file mode 100644 (file)
index 0000000..f669f6a
--- /dev/null
@@ -0,0 +1,38 @@
+#ifdef IMPLEMENTATION
+REGISTER_MUTATOR(vh, cvar("g_vampirehook"));
+
+bool autocvar_g_vampirehook_teamheal;
+float autocvar_g_vampirehook_damage;
+float autocvar_g_vampirehook_damagerate;
+float autocvar_g_vampirehook_health_steal;
+
+.float last_dmg;
+
+MUTATOR_HOOKFUNCTION(vh, GrappleHookThink)
+{SELFPARAM();
+       entity dmgent = ((SAME_TEAM(self.owner, self.aiment) && autocvar_g_vampirehook_teamheal) ? self.owner : self.aiment);
+
+       if(IS_PLAYER(self.aiment))
+       if(self.last_dmg < time)
+       if(!self.aiment.frozen)
+       if(time >= game_starttime)
+       if(DIFF_TEAM(self.owner, self.aiment) || autocvar_g_vampirehook_teamheal)
+       if(self.aiment.health > 0)
+       if(autocvar_g_vampirehook_damage)
+       {
+               self.last_dmg = time + autocvar_g_vampirehook_damagerate;
+               self.owner.damage_dealt += autocvar_g_vampirehook_damage;
+               Damage(dmgent, self, self.owner, autocvar_g_vampirehook_damage, WEP_HOOK.m_id, self.origin, '0 0 0');
+               if(SAME_TEAM(self.owner, self.aiment))
+                       self.aiment.health = min(self.aiment.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max);
+               else
+                       self.owner.health = min(self.owner.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max);
+
+               if(dmgent == self.owner)
+                       dmgent.health -= autocvar_g_vampirehook_damage; // FIXME: friendly fire?!
+       }
+
+       return false;
+}
+
+#endif
diff --git a/qcsrc/server/mutators/mutator/sandbox.qc b/qcsrc/server/mutators/mutator/sandbox.qc
new file mode 100644 (file)
index 0000000..caa87a0
--- /dev/null
@@ -0,0 +1,834 @@
+#ifdef IMPLEMENTATION
+float autosave_time;
+void sandbox_Database_Load();
+
+REGISTER_MUTATOR(sandbox, cvar("g_sandbox"))
+{
+       MUTATOR_ONADD
+       {
+               autosave_time = time + autocvar_g_sandbox_storage_autosave; // don't save the first server frame
+               if(autocvar_g_sandbox_storage_autoload)
+                       sandbox_Database_Load();
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // nothing to roll back
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               // nothing to remove
+       }
+
+       return false;
+}
+
+const float MAX_STORAGE_ATTACHMENTS = 16;
+float object_count;
+.float object_flood;
+.entity object_attach;
+.string material;
+
+.float touch_timer;
+void sandbox_ObjectFunction_Touch()
+{SELFPARAM();
+       // apply material impact effects
+
+       if(!self.material)
+               return;
+       if(self.touch_timer > time)
+               return; // don't execute each frame
+       self.touch_timer = time + 0.1;
+
+       // make particle count and sound volume depend on impact speed
+       float intensity;
+       intensity = vlen(self.velocity) + vlen(other.velocity);
+       if(intensity) // avoid divisions by 0
+               intensity /= 2; // average the two velocities
+       if (!(intensity >= autocvar_g_sandbox_object_material_velocity_min))
+               return; // impact not strong enough to do anything
+       // now offset intensity and apply it to the effects
+       intensity -= autocvar_g_sandbox_object_material_velocity_min; // start from minimum velocity, not actual velocity
+       intensity = bound(0, intensity * autocvar_g_sandbox_object_material_velocity_factor, 1);
+
+       _sound(self, CH_TRIGGER, strcat("object/impact_", self.material, "_", ftos(ceil(random() * 5)) , ".wav"), VOL_BASE * intensity, ATTEN_NORM);
+       Send_Effect_(strcat("impact_", self.material), self.origin, '0 0 0', ceil(intensity * 10)); // allow a count from 1 to 10
+}
+
+void sandbox_ObjectFunction_Think()
+{SELFPARAM();
+       entity e;
+
+       // decide if and how this object can be grabbed
+       if(autocvar_g_sandbox_readonly)
+               self.grab = 0; // no grabbing
+       else if(autocvar_g_sandbox_editor_free < 2 && self.crypto_idfp)
+               self.grab = 1; // owner only
+       else
+               self.grab = 3; // anyone
+
+       // Object owner is stored via player UID, but we also need the owner as an entity (if the player is available on the server).
+       // Therefore, scan for all players, and update the owner as long as the player is present. We must always do this,
+       // since if the owning player disconnects, the object's owner should also be reset.
+       FOR_EACH_REALPLAYER(e) // bots can't have objects
+       {
+               if(self.crypto_idfp == e.crypto_idfp)
+               {
+                       self.realowner = e;
+                       break;
+               }
+               self.realowner = world;
+       }
+
+       self.nextthink = time;
+
+       CSQCMODEL_AUTOUPDATE(self);
+}
+
+.float old_solid, old_movetype;
+entity sandbox_ObjectEdit_Get(float permissions)
+{SELFPARAM();
+       // Returns the traced entity if the player can edit it, and world if not.
+       // If permissions if false, the object is returned regardless of editing rights.
+       // Attached objects are SOLID_NOT and do not get traced.
+
+       crosshair_trace_plusvisibletriggers(self);
+       if(vlen(self.origin - trace_ent.origin) > autocvar_g_sandbox_editor_distance_edit)
+               return world; // out of trace range
+       if(trace_ent.classname != "object")
+               return world; // entity is not an object
+       if(!permissions)
+               return trace_ent; // don't check permissions, anyone can edit this object
+       if(trace_ent.crypto_idfp == "")
+               return trace_ent; // the player who spawned this object did not have an UID, so anyone can edit it
+       if (!(trace_ent.realowner != self && autocvar_g_sandbox_editor_free < 2))
+               return trace_ent; // object does not belong to the player, and players can only edit their own objects on this server
+       return world;
+}
+
+void sandbox_ObjectEdit_Scale(entity e, float f)
+{
+       e.scale = f;
+       if(e.scale)
+       {
+               e.scale = bound(autocvar_g_sandbox_object_scale_min, e.scale, autocvar_g_sandbox_object_scale_max);
+               _setmodel(e, e.model); // reset mins and maxs based on mesh
+               setsize(e, e.mins * e.scale, e.maxs * e.scale); // adapt bounding box size to model size
+       }
+}
+
+void sandbox_ObjectAttach_Remove(entity e);
+void sandbox_ObjectAttach_Set(entity e, entity parent, string s)
+{
+       // attaches e to parent on string s
+
+       // we can't attach to an attachment, for obvious reasons
+       sandbox_ObjectAttach_Remove(e);
+
+       e.old_solid = e.solid; // persist solidity
+       e.old_movetype = e.movetype; // persist physics
+       e.movetype = MOVETYPE_FOLLOW;
+       e.solid = SOLID_NOT;
+       e.takedamage = DAMAGE_NO;
+
+       setattachment(e, parent, s);
+       e.owner = parent;
+}
+
+void sandbox_ObjectAttach_Remove(entity e)
+{
+       // detaches any object attached to e
+
+       entity head;
+       for(head = world; (head = find(head, classname, "object")); )
+       {
+               if(head.owner == e)
+               {
+                       vector org;
+                       org = gettaginfo(head, 0);
+                       setattachment(head, world, "");
+                       head.owner = world;
+
+                       // objects change origin and angles when detached, so apply previous position
+                       setorigin(head, org);
+                       head.angles = e.angles; // don't allow detached objects to spin or roll
+
+                       head.solid = head.old_solid; // restore persisted solidity
+                       head.movetype = head.old_movetype; // restore persisted physics
+                       head.takedamage = DAMAGE_AIM;
+               }
+       }
+}
+
+entity sandbox_ObjectSpawn(float database)
+{SELFPARAM();
+       // spawn a new object with default properties
+
+       entity e = spawn();
+       e.classname = "object";
+       e.takedamage = DAMAGE_AIM;
+       e.damageforcescale = 1;
+       e.solid = SOLID_BBOX; // SOLID_BSP would be best, but can lag the server badly
+       e.movetype = MOVETYPE_TOSS;
+       e.frame = 0;
+       e.skin = 0;
+       e.material = string_null;
+       e.touch = sandbox_ObjectFunction_Touch;
+       e.think = sandbox_ObjectFunction_Think;
+       e.nextthink = time;
+       //e.effects |= EF_SELECTABLE; // don't do this all the time, maybe just when editing objects?
+
+       if(!database)
+       {
+               // set the object's owner via player UID
+               // if the player does not have an UID, the owner cannot be stored and his objects may be edited by anyone
+               if(self.crypto_idfp != "")
+                       e.crypto_idfp = strzone(self.crypto_idfp);
+               else
+                       print_to(self, "^1SANDBOX - WARNING: ^7You spawned an object, but lack a player UID. ^1Your objects are not secured and can be edited by any player!");
+
+               // set public object information
+               e.netname = strzone(self.netname); // name of the owner
+               e.message = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); // creation time
+               e.message2 = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); // last editing time
+
+               // set origin and direction based on player position and view angle
+               makevectors(self.v_angle);
+               WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * autocvar_g_sandbox_editor_distance_spawn, MOVE_NORMAL, self);
+               setorigin(e, trace_endpos);
+               e.angles_y = self.v_angle.y;
+       }
+
+       WITH(entity, self, e, CSQCMODEL_AUTOINIT(e));
+
+       object_count += 1;
+       return e;
+}
+
+void sandbox_ObjectRemove(entity e)
+{
+       sandbox_ObjectAttach_Remove(e); // detach child objects
+
+       // if the object being removed has been selected for attachment by a player, unset it
+       entity head;
+       FOR_EACH_REALPLAYER(head) // bots can't have objects
+       {
+               if(head.object_attach == e)
+                       head.object_attach = world;
+       }
+
+       if(e.material)  {       strunzone(e.material);  e.material = string_null;       }
+       if(e.crypto_idfp)       {       strunzone(e.crypto_idfp);       e.crypto_idfp = string_null;    }
+       if(e.netname)   {       strunzone(e.netname);   e.netname = string_null;        }
+       if(e.message)   {       strunzone(e.message);   e.message = string_null;        }
+       if(e.message2)  {       strunzone(e.message2);  e.message2 = string_null;       }
+       remove(e);
+       e = world;
+
+       object_count -= 1;
+}
+
+string port_string[MAX_STORAGE_ATTACHMENTS]; // fteqcc crashes if this isn't defined as a global
+
+string sandbox_ObjectPort_Save(entity e, float database)
+{
+       // save object properties, and return them as a string
+       float i = 0;
+       string s;
+       entity head;
+
+       for(head = world; (head = find(head, classname, "object")); )
+       {
+               // the main object needs to be first in the array [0] with attached objects following
+               float slot, physics, solidity;
+               if(head == e) // this is the main object, place it first
+               {
+                       slot = 0;
+                       solidity = head.solid; // applied solidity is normal solidity for children
+                       physics = head.movetype; // applied physics are normal physics for parents
+               }
+               else if(head.owner == e) // child object, list them in order
+               {
+                       i += 1; // children start from 1
+                       slot = i;
+                       solidity = head.old_solid; // persisted solidity is normal solidity for children
+                       physics = head.old_movetype; // persisted physics are normal physics for children
+                       gettaginfo(head.owner, head.tag_index); // get the name of the tag our object is attached to, used further below
+               }
+               else
+                       continue;
+
+               // ---------------- OBJECT PROPERTY STORAGE: SAVE ----------------
+               if(slot)
+               {
+                       // properties stored only for child objects
+                       if(gettaginfo_name)     port_string[slot] = strcat(port_string[slot], "\"", gettaginfo_name, "\" ");    else    port_string[slot] = strcat(port_string[slot], "\"\" "); // none
+               }
+               else
+               {
+                       // properties stored only for parent objects
+                       if(database)
+                       {
+                               port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.origin), " ");
+                               port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.angles), " ");
+                       }
+               }
+               // properties stored for all objects
+               port_string[slot] = strcat(port_string[slot], "\"", head.model, "\" ");
+               port_string[slot] = strcat(port_string[slot], ftos(head.skin), " ");
+               port_string[slot] = strcat(port_string[slot], ftos(head.alpha), " ");
+               port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.colormod), " ");
+               port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.glowmod), " ");
+               port_string[slot] = strcat(port_string[slot], ftos(head.frame), " ");
+               port_string[slot] = strcat(port_string[slot], ftos(head.scale), " ");
+               port_string[slot] = strcat(port_string[slot], ftos(solidity), " ");
+               port_string[slot] = strcat(port_string[slot], ftos(physics), " ");
+               port_string[slot] = strcat(port_string[slot], ftos(head.damageforcescale), " ");
+               if(head.material)       port_string[slot] = strcat(port_string[slot], "\"", head.material, "\" ");      else    port_string[slot] = strcat(port_string[slot], "\"\" "); // none
+               if(database)
+               {
+                       // properties stored only for the database
+                       if(head.crypto_idfp)    port_string[slot] = strcat(port_string[slot], "\"", head.crypto_idfp, "\" ");   else    port_string[slot] = strcat(port_string[slot], "\"\" "); // none
+                       port_string[slot] = strcat(port_string[slot], "\"", e.netname, "\" ");
+                       port_string[slot] = strcat(port_string[slot], "\"", e.message, "\" ");
+                       port_string[slot] = strcat(port_string[slot], "\"", e.message2, "\" ");
+               }
+       }
+
+       // now apply the array to a simple string, with the ; symbol separating objects
+       s = "";
+       for(i = 0; i <= MAX_STORAGE_ATTACHMENTS; ++i)
+       {
+               if(port_string[i])
+                       s = strcat(s, port_string[i], "; ");
+               port_string[i] = string_null; // fully clear the string
+       }
+
+       return s;
+}
+
+entity sandbox_ObjectPort_Load(string s, float database)
+{
+       // load object properties, and spawn a new object with them
+       float n, i;
+       entity e = world, parent = world;
+
+       // separate objects between the ; symbols
+       n = tokenizebyseparator(s, "; ");
+       for(i = 0; i < n; ++i)
+               port_string[i] = argv(i);
+
+       // now separate and apply the properties of each object
+       for(i = 0; i < n; ++i)
+       {
+               float argv_num;
+               string tagname = string_null;
+               argv_num = 0;
+               tokenize_console(port_string[i]);
+               e = sandbox_ObjectSpawn(database);
+
+               // ---------------- OBJECT PROPERTY STORAGE: LOAD ----------------
+               if(i)
+               {
+                       // properties stored only for child objects
+                       if(argv(argv_num) != "")        tagname = argv(argv_num);       else tagname = string_null;     ++argv_num;
+               }
+               else
+               {
+                       // properties stored only for parent objects
+                       if(database)
+                       {
+                               setorigin(e, stov(argv(argv_num)));     ++argv_num;
+                               e.angles = stov(argv(argv_num));        ++argv_num;
+                       }
+                       parent = e; // mark parent objects as such
+               }
+               // properties stored for all objects
+               _setmodel(e, argv(argv_num));   ++argv_num;
+               e.skin = stof(argv(argv_num));  ++argv_num;
+               e.alpha = stof(argv(argv_num)); ++argv_num;
+               e.colormod = stov(argv(argv_num));      ++argv_num;
+               e.glowmod = stov(argv(argv_num));       ++argv_num;
+               e.frame = stof(argv(argv_num)); ++argv_num;
+               sandbox_ObjectEdit_Scale(e, stof(argv(argv_num)));      ++argv_num;
+               e.solid = e.old_solid = stof(argv(argv_num));   ++argv_num;
+               e.movetype = e.old_movetype = stof(argv(argv_num));     ++argv_num;
+               e.damageforcescale = stof(argv(argv_num));      ++argv_num;
+               if(e.material)  strunzone(e.material);  if(argv(argv_num) != "")        e.material = strzone(argv(argv_num));   else    e.material = string_null;       ++argv_num;
+               if(database)
+               {
+                       // properties stored only for the database
+                       if(e.crypto_idfp)       strunzone(e.crypto_idfp);       if(argv(argv_num) != "")        e.crypto_idfp = strzone(argv(argv_num));        else    e.crypto_idfp = string_null;    ++argv_num;
+                       if(e.netname)   strunzone(e.netname);   e.netname = strzone(argv(argv_num));    ++argv_num;
+                       if(e.message)   strunzone(e.message);   e.message = strzone(argv(argv_num));    ++argv_num;
+                       if(e.message2)  strunzone(e.message2);  e.message2 = strzone(argv(argv_num));   ++argv_num;
+               }
+
+               // attach last
+               if(i)
+                       sandbox_ObjectAttach_Set(e, parent, tagname);
+       }
+
+       for(i = 0; i <= MAX_STORAGE_ATTACHMENTS; ++i)
+               port_string[i] = string_null; // fully clear the string
+
+       return e;
+}
+
+void sandbox_Database_Save()
+{
+       // saves all objects to the database file
+       entity head;
+       string file_name;
+       float file_get;
+
+       file_name = strcat("sandbox/storage_", autocvar_g_sandbox_storage_name, "_", GetMapname(), ".txt");
+       file_get = fopen(file_name, FILE_WRITE);
+       fputs(file_get, strcat("// sandbox storage \"", autocvar_g_sandbox_storage_name, "\" for map \"", GetMapname(), "\" last updated ", strftime(true, "%d-%m-%Y %H:%M:%S")));
+       fputs(file_get, strcat(" containing ", ftos(object_count), " objects\n"));
+
+       for(head = world; (head = find(head, classname, "object")); )
+       {
+               // attached objects are persisted separately, ignore them here
+               if(head.owner != world)
+                       continue;
+
+               // use a line of text for each object, listing all properties
+               fputs(file_get, strcat(sandbox_ObjectPort_Save(head, true), "\n"));
+       }
+       fclose(file_get);
+}
+
+void sandbox_Database_Load()
+{
+       // loads all objects from the database file
+       string file_read, file_name;
+       float file_get, i;
+
+       file_name = strcat("sandbox/storage_", autocvar_g_sandbox_storage_name, "_", GetMapname(), ".txt");
+       file_get = fopen(file_name, FILE_READ);
+       if(file_get < 0)
+       {
+               if(autocvar_g_sandbox_info > 0)
+                       LOG_INFO(strcat("^3SANDBOX - SERVER: ^7could not find storage file ^3", file_name, "^7, no objects were loaded\n"));
+       }
+       else
+       {
+               for (;;)
+               {
+                       file_read = fgets(file_get);
+                       if(file_read == "")
+                               break;
+                       if(substring(file_read, 0, 2) == "//")
+                               continue;
+                       if(substring(file_read, 0, 1) == "#")
+                               continue;
+
+                       entity e;
+                       e = sandbox_ObjectPort_Load(file_read, true);
+
+                       if(e.material)
+                       {
+                               // since objects are being loaded for the first time, precache material sounds for each
+                               for (i = 1; i <= 5; i++) // 5 sounds in total
+                                       precache_sound(strcat("object/impact_", e.material, "_", ftos(i), ".wav"));
+                       }
+               }
+               if(autocvar_g_sandbox_info > 0)
+                       LOG_INFO(strcat("^3SANDBOX - SERVER: ^7successfully loaded storage file ^3", file_name, "\n"));
+       }
+       fclose(file_get);
+}
+
+MUTATOR_HOOKFUNCTION(sandbox, SV_ParseClientCommand)
+{SELFPARAM();
+       if(MUTATOR_RETURNVALUE) // command was already handled?
+               return false;
+       if(cmd_name == "g_sandbox")
+       {
+               if(autocvar_g_sandbox_readonly)
+               {
+                       print_to(self, "^2SANDBOX - INFO: ^7Sandbox mode is active, but in read-only mode. Sandbox commands cannot be used");
+                       return true;
+               }
+               if(cmd_argc < 2)
+               {
+                       print_to(self, "^2SANDBOX - INFO: ^7Sandbox mode is active. For usage information, type 'sandbox help'");
+                       return true;
+               }
+
+               switch(argv(1))
+               {
+                       entity e;
+                       float i;
+                       string s;
+
+                       // ---------------- COMMAND: HELP ----------------
+                       case "help":
+                               print_to(self, "You can use the following sandbox commands:");
+                               print_to(self, "^7\"^2object_spawn ^3models/foo/bar.md3^7\" spawns a new object in front of the player, and gives it the specified model");
+                               print_to(self, "^7\"^2object_remove^7\" removes the object the player is looking at. Players can only remove their own objects");
+                               print_to(self, "^7\"^2object_duplicate ^3value^7\" duplicates the object, if the player has copying rights over the original");
+                               print_to(self, "^3copy value ^7- copies the properties of the object to the specified client cvar");
+                               print_to(self, "^3paste value ^7- spawns an object with the given properties. Properties or cvars must be specified as follows; eg1: \"0 1 2 ...\", eg2: \"$cl_cvar\"");
+                               print_to(self, "^7\"^2object_attach ^3property value^7\" attaches one object to another. Players can only attach their own objects");
+                               print_to(self, "^3get ^7- selects the object you are facing as the object to be attached");
+                               print_to(self, "^3set value ^7- attaches the previously selected object to the object you are facing, on the specified bone");
+                               print_to(self, "^3remove ^7- detaches all objects from the object you are facing");
+                               print_to(self, "^7\"^2object_edit ^3property value^7\" edits the given property of the object. Players can only edit their own objects");
+                               print_to(self, "^3skin value ^7- changes the skin of the object");
+                               print_to(self, "^3alpha value ^7- sets object transparency");
+                               print_to(self, "^3colormod \"value_x value_y value_z\" ^7- main object color");
+                               print_to(self, "^3glowmod \"value_x value_y value_z\" ^7- glow object color");
+                               print_to(self, "^3frame value ^7- object animation frame, for self-animated models");
+                               print_to(self, "^3scale value ^7- changes object scale. 0.5 is half size and 2 is double size");
+                               print_to(self, "^3solidity value ^7- object collisions, 0 = non-solid, 1 = solid");
+                               print_to(self, "^3physics value ^7- object physics, 0 = static, 1 = movable, 2 = physical");
+                               print_to(self, "^3force value ^7- amount of force applied to objects that are shot");
+                               print_to(self, "^3material value ^7- sets the material of the object. Default materials are: metal, stone, wood, flesh");
+                               print_to(self, "^7\"^2object_claim^7\" sets the player as the owner of the object, if he has the right to edit it");
+                               print_to(self, "^7\"^2object_info ^3value^7\" shows public information about the object");
+                               print_to(self, "^3object ^7- prints general information about the object, such as owner and creation / editing date");
+                               print_to(self, "^3mesh ^7- prints information about the object's mesh, including skeletal bones");
+                               print_to(self, "^3attachments ^7- prints information about the object's attachments");
+                               print_to(self, "^7The ^1drag object ^7key can be used to grab and carry objects. Players can only grab their own objects");
+                               return true;
+
+                       // ---------------- COMMAND: OBJECT, SPAWN ----------------
+                       case "object_spawn":
+                               if(time < self.object_flood)
+                               {
+                                       print_to(self, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(self.object_flood - time), " ^7seconds beofore spawning another object"));
+                                       return true;
+                               }
+                               self.object_flood = time + autocvar_g_sandbox_editor_flood;
+                               if(object_count >= autocvar_g_sandbox_editor_maxobjects)
+                               {
+                                       print_to(self, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time"));
+                                       return true;
+                               }
+                               if(cmd_argc < 3)
+                               {
+                                       print_to(self, "^1SANDBOX - WARNING: ^7Attempted to spawn an object without specifying a model. Please specify the path to your model file after the 'object_spawn' command");
+                                       return true;
+                               }
+                               if (!(fexists(argv(2))))
+                               {
+                                       print_to(self, "^1SANDBOX - WARNING: ^7Attempted to spawn an object with a non-existent model. Make sure the path to your model file is correct");
+                                       return true;
+                               }
+
+                               e = sandbox_ObjectSpawn(false);
+                               _setmodel(e, argv(2));
+
+                               if(autocvar_g_sandbox_info > 0)
+                                       LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " spawned an object at origin ^3", vtos(e.origin), "\n"));
+                               return true;
+
+                       // ---------------- COMMAND: OBJECT, REMOVE ----------------
+                       case "object_remove":
+                               e = sandbox_ObjectEdit_Get(true);
+                               if(e != world)
+                               {
+                                       if(autocvar_g_sandbox_info > 0)
+                                               LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " removed an object at origin ^3", vtos(e.origin), "\n"));
+                                       sandbox_ObjectRemove(e);
+                                       return true;
+                               }
+
+                               print_to(self, "^1SANDBOX - WARNING: ^7Object could not be removed. Make sure you are facing an object that you have edit rights over");
+                               return true;
+
+                       // ---------------- COMMAND: OBJECT, DUPLICATE ----------------
+                       case "object_duplicate":
+                               switch(argv(2))
+                               {
+                                       case "copy":
+                                               // copies customizable properties of the selected object to the clipboard cvar
+                                               e = sandbox_ObjectEdit_Get(autocvar_g_sandbox_editor_free); // can we copy objects we can't edit?
+                                               if(e != world)
+                                               {
+                                                       s = sandbox_ObjectPort_Save(e, false);
+                                                       s = strreplace("\"", "\\\"", s);
+                                                       stuffcmd(self, strcat("set ", argv(3), " \"", s, "\""));
+
+                                                       print_to(self, "^2SANDBOX - INFO: ^7Object copied to clipboard");
+                                                       return true;
+                                               }
+                                               print_to(self, "^1SANDBOX - WARNING: ^7Object could not be copied. Make sure you are facing an object that you have copy rights over");
+                                               return true;
+
+                                       case "paste":
+                                               // spawns a new object using the properties in the player's clipboard cvar
+                                               if(time < self.object_flood)
+                                               {
+                                                       print_to(self, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(self.object_flood - time), " ^7seconds beofore spawning another object"));
+                                                       return true;
+                                               }
+                                               self.object_flood = time + autocvar_g_sandbox_editor_flood;
+                                               if(argv(3) == "") // no object in clipboard
+                                               {
+                                                       print_to(self, "^1SANDBOX - WARNING: ^7No object in clipboard. You must copy an object before you can paste it");
+                                                       return true;
+                                               }
+                                               if(object_count >= autocvar_g_sandbox_editor_maxobjects)
+                                               {
+                                                       print_to(self, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time"));
+                                                       return true;
+                                               }
+                                               e = sandbox_ObjectPort_Load(argv(3), false);
+
+                                               print_to(self, "^2SANDBOX - INFO: ^7Object pasted successfully");
+                                               if(autocvar_g_sandbox_info > 0)
+                                                       LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " pasted an object at origin ^3", vtos(e.origin), "\n"));
+                                               return true;
+                               }
+                               return true;
+
+                       // ---------------- COMMAND: OBJECT, ATTACH ----------------
+                       case "object_attach":
+                               switch(argv(2))
+                               {
+                                       case "get":
+                                               // select e as the object as meant to be attached
+                                               e = sandbox_ObjectEdit_Get(true);
+                                               if(e != world)
+                                               {
+                                                       self.object_attach = e;
+                                                       print_to(self, "^2SANDBOX - INFO: ^7Object selected for attachment");
+                                                       return true;
+                                               }
+                                               print_to(self, "^1SANDBOX - WARNING: ^7Object could not be selected for attachment. Make sure you are facing an object that you have edit rights over");
+                                               return true;
+                                       case "set":
+                                               if(self.object_attach == world)
+                                               {
+                                                       print_to(self, "^1SANDBOX - WARNING: ^7No object selected for attachment. Please select an object to be attached first.");
+                                                       return true;
+                                               }
+
+                                               // attaches the previously selected object to e
+                                               e = sandbox_ObjectEdit_Get(true);
+                                               if(e != world)
+                                               {
+                                                       sandbox_ObjectAttach_Set(self.object_attach, e, argv(3));
+                                                       self.object_attach = world; // object was attached, no longer keep it scheduled for attachment
+                                                       print_to(self, "^2SANDBOX - INFO: ^7Object attached successfully");
+                                                       if(autocvar_g_sandbox_info > 1)
+                                                               LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " attached objects at origin ^3", vtos(e.origin), "\n"));
+                                                       return true;
+                                               }
+                                               print_to(self, "^1SANDBOX - WARNING: ^7Object could not be attached to the parent. Make sure you are facing an object that you have edit rights over");
+                                               return true;
+                                       case "remove":
+                                               // removes e if it was attached
+                                               e = sandbox_ObjectEdit_Get(true);
+                                               if(e != world)
+                                               {
+                                                       sandbox_ObjectAttach_Remove(e);
+                                                       print_to(self, "^2SANDBOX - INFO: ^7Child objects detached successfully");
+                                                       if(autocvar_g_sandbox_info > 1)
+                                                               LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " detached objects at origin ^3", vtos(e.origin), "\n"));
+                                                       return true;
+                                               }
+                                               print_to(self, "^1SANDBOX - WARNING: ^7Child objects could not be detached. Make sure you are facing an object that you have edit rights over");
+                                               return true;
+                               }
+                               return true;
+
+                       // ---------------- COMMAND: OBJECT, EDIT ----------------
+                       case "object_edit":
+                               if(argv(2) == "")
+                               {
+                                       print_to(self, "^1SANDBOX - WARNING: ^7Too few parameters. You must specify a property to edit");
+                                       return true;
+                               }
+
+                               e = sandbox_ObjectEdit_Get(true);
+                               if(e != world)
+                               {
+                                       switch(argv(2))
+                                       {
+                                               case "skin":
+                                                       e.skin = stof(argv(3));
+                                                       break;
+                                               case "alpha":
+                                                       e.alpha = stof(argv(3));
+                                                       break;
+                                               case "color_main":
+                                                       e.colormod = stov(argv(3));
+                                                       break;
+                                               case "color_glow":
+                                                       e.glowmod = stov(argv(3));
+                                                       break;
+                                               case "frame":
+                                                       e.frame = stof(argv(3));
+                                                       break;
+                                               case "scale":
+                                                       sandbox_ObjectEdit_Scale(e, stof(argv(3)));
+                                                       break;
+                                               case "solidity":
+                                                       switch(argv(3))
+                                                       {
+                                                               case "0": // non-solid
+                                                                       e.solid = SOLID_TRIGGER;
+                                                                       break;
+                                                               case "1": // solid
+                                                                       e.solid = SOLID_BBOX;
+                                                                       break;
+                                                               default:
+                                                                       break;
+                                                       }
+                                               case "physics":
+                                                       switch(argv(3))
+                                                       {
+                                                               case "0": // static
+                                                                       e.movetype = MOVETYPE_NONE;
+                                                                       break;
+                                                               case "1": // movable
+                                                                       e.movetype = MOVETYPE_TOSS;
+                                                                       break;
+                                                               case "2": // physical
+                                                                       e.movetype = MOVETYPE_PHYSICS;
+                                                                       break;
+                                                               default:
+                                                                       break;
+                                                       }
+                                                       break;
+                                               case "force":
+                                                       e.damageforcescale = stof(argv(3));
+                                                       break;
+                                               case "material":
+                                                       if(e.material)  strunzone(e.material);
+                                                       if(argv(3))
+                                                       {
+                                                               for (i = 1; i <= 5; i++) // precache material sounds, 5 in total
+                                                                       precache_sound(strcat("object/impact_", argv(3), "_", ftos(i), ".wav"));
+                                                               e.material = strzone(argv(3));
+                                                       }
+                                                       else
+                                                               e.material = string_null; // no material
+                                                       break;
+                                               default:
+                                                       print_to(self, "^1SANDBOX - WARNING: ^7Invalid object property. For usage information, type 'sandbox help'");
+                                                       return true;
+                                       }
+
+                                       // update last editing time
+                                       if(e.message2)  strunzone(e.message2);
+                                       e.message2 = strzone(strftime(true, "%d-%m-%Y %H:%M:%S"));
+
+                                       if(autocvar_g_sandbox_info > 1)
+                                               LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " edited property ^3", argv(2), " ^7of an object at origin ^3", vtos(e.origin), "\n"));
+                                       return true;
+                               }
+
+                               print_to(self, "^1SANDBOX - WARNING: ^7Object could not be edited. Make sure you are facing an object that you have edit rights over");
+                               return true;
+
+                       // ---------------- COMMAND: OBJECT, CLAIM ----------------
+                       case "object_claim":
+                               // if the player can edit an object but is not its owner, this can be used to claim that object
+                               if(self.crypto_idfp == "")
+                               {
+                                       print_to(self, "^1SANDBOX - WARNING: ^7You do not have a player UID, and cannot claim objects");
+                                       return true;
+                               }
+                               e = sandbox_ObjectEdit_Get(true);
+                               if(e != world)
+                               {
+                                       // update the owner's name
+                                       // Do this before checking if you're already the owner and skipping if such, so we
+                                       // also update the player's nickname if he changed it (but has the same player UID)
+                                       if(e.netname != self.netname)
+                                       {
+                                               if(e.netname)   strunzone(e.netname);
+                                               e.netname = strzone(self.netname);
+                                               print_to(self, "^2SANDBOX - INFO: ^7Object owner name updated");
+                                       }
+
+                                       if(e.crypto_idfp == self.crypto_idfp)
+                                       {
+                                               print_to(self, "^2SANDBOX - INFO: ^7Object is already yours, nothing to claim");
+                                               return true;
+                                       }
+
+                                       if(e.crypto_idfp)       strunzone(e.crypto_idfp);
+                                       e.crypto_idfp = strzone(self.crypto_idfp);
+
+                                       print_to(self, "^2SANDBOX - INFO: ^7Object claimed successfully");
+                               }
+                               print_to(self, "^1SANDBOX - WARNING: ^7Object could not be claimed. Make sure you are facing an object that you have edit rights over");
+                               return true;
+
+                       // ---------------- COMMAND: OBJECT, INFO ----------------
+                       case "object_info":
+                               // prints public information about the object to the player
+                               e = sandbox_ObjectEdit_Get(false);
+                               if(e != world)
+                               {
+                                       switch(argv(2))
+                                       {
+                                               case "object":
+                                                       print_to(self, strcat("^2SANDBOX - INFO: ^7Object is owned by \"^7", e.netname, "^7\", created \"^3", e.message, "^7\", last edited \"^3", e.message2, "^7\""));
+                                                       return true;
+                                               case "mesh":
+                                                       s = "";
+                                                       FOR_EACH_TAG(e)
+                                                               s = strcat(s, "^7\"^5", gettaginfo_name, "^7\", ");
+                                                       print_to(self, strcat("^2SANDBOX - INFO: ^7Object mesh is \"^3", e.model, "^7\" at animation frame ^3", ftos(e.frame), " ^7containing the following tags: ", s));
+                                                       return true;
+                                               case "attachments":
+                                                       // this should show the same info as 'mesh' but for attachments
+                                                       s = "";
+                                                       entity head;
+                                                       i = 0;
+                                                       for(head = world; (head = find(head, classname, "object")); )
+                                                       {
+                                                               if(head.owner == e)
+                                                               {
+                                                                       ++i; // start from 1
+                                                                       gettaginfo(e, head.tag_index);
+                                                                       s = strcat(s, "^1attachment ", ftos(i), "^7 has mesh \"^3", head.model, "^7\" at animation frame ^3", ftos(head.frame));
+                                                                       s = strcat(s, "^7 and is attached to bone \"^5", gettaginfo_name, "^7\", ");
+                                                               }
+                                                       }
+                                                       if(i) // object contains attachments
+                                                               print_to(self, strcat("^2SANDBOX - INFO: ^7Object contains the following ^1", ftos(i), "^7 attachment(s): ", s));
+                                                       else
+                                                               print_to(self, "^2SANDBOX - INFO: ^7Object contains no attachments");
+                                                       return true;
+                                       }
+                               }
+                               print_to(self, "^1SANDBOX - WARNING: ^7No information could be found. Make sure you are facing an object");
+                               return true;
+
+                       // ---------------- COMMAND: DEFAULT ----------------
+                       default:
+                               print_to(self, "Invalid command. For usage information, type 'sandbox help'");
+                               return true;
+               }
+       }
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(sandbox, SV_StartFrame)
+{
+       if(!autocvar_g_sandbox_storage_autosave)
+               return false;
+       if(time < autosave_time)
+               return false;
+       autosave_time = time + autocvar_g_sandbox_storage_autosave;
+
+       sandbox_Database_Save();
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(sandbox, SetModname)
+{
+       modname = "Sandbox";
+       return true;
+}
+#endif
diff --git a/qcsrc/server/mutators/mutator_bloodloss.qc b/qcsrc/server/mutators/mutator_bloodloss.qc
deleted file mode 100644 (file)
index 5db1f98..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-
-#include "mutator.qh"
-
-REGISTER_MUTATOR(bloodloss, cvar("g_bloodloss"));
-
-.float bloodloss_timer;
-
-MUTATOR_HOOKFUNCTION(bloodloss, PlayerPreThink)
-{SELFPARAM();
-       if(IS_PLAYER(self))
-       if(self.health <= autocvar_g_bloodloss && self.deadflag == DEAD_NO)
-       {
-               self.BUTTON_CROUCH = true;
-
-               if(time >= self.bloodloss_timer)
-               {
-                       if(self.vehicle)
-                               vehicles_exit(VHEF_RELEASE);
-                       if(self.event_damage)
-                               self.event_damage(self, self, 1, DEATH_ROT.m_id, self.origin, '0 0 0');
-                       self.bloodloss_timer = time + 0.5 + random() * 0.5;
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(bloodloss, PlayerJump)
-{SELFPARAM();
-       if(self.health <= autocvar_g_bloodloss)
-               return true;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(bloodloss, BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":bloodloss");
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(bloodloss, BuildMutatorsPrettyString)
-{
-       ret_string = strcat(ret_string, ", Blood loss");
-       return false;
-}
diff --git a/qcsrc/server/mutators/mutator_breakablehook.qc b/qcsrc/server/mutators/mutator_breakablehook.qc
deleted file mode 100644 (file)
index 257937e..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-#include "../../common/deathtypes/all.qh"
-#include "../g_hook.qh"
-
-REGISTER_MUTATOR(breakablehook, cvar("g_breakablehook"));
-
-bool autocvar_g_breakablehook; // allow toggling mid match?
-bool autocvar_g_breakablehook_owner;
-
-MUTATOR_HOOKFUNCTION(breakablehook, PlayerDamage_Calculate)
-{
-       if(frag_target.classname == "grapplinghook")
-       {
-               if((!autocvar_g_breakablehook)
-               || (!autocvar_g_breakablehook_owner && frag_attacker == frag_target.realowner)
-                       ) { frag_damage = 0; }
-
-               // hurt the owner of the hook
-               if(DIFF_TEAM(frag_attacker, frag_target.realowner))
-               {
-                       Damage (frag_target.realowner, frag_attacker, frag_attacker, 5, WEP_HOOK.m_id | HITTYPE_SPLASH, frag_target.realowner.origin, '0 0 0');
-                       RemoveGrapplingHook(frag_target.realowner);
-                       return false; // dead
-               }
-       }
-
-       return false;
-}
diff --git a/qcsrc/server/mutators/mutator_buffs.qc b/qcsrc/server/mutators/mutator_buffs.qc
deleted file mode 100644 (file)
index 80de63c..0000000
+++ /dev/null
@@ -1,970 +0,0 @@
-#include "../../common/triggers/target/music.qh"
-#include "mutator_buffs.qh"
-
-#include "mutator.qh"
-
-#include "../../common/gamemodes/all.qh"
-#include "../../common/buffs/all.qh"
-
-.float buff_time;
-void buffs_DelayedInit();
-
-REGISTER_MUTATOR(buffs, cvar("g_buffs"))
-{
-       MUTATOR_ONADD
-       {
-               addstat(STAT_BUFFS, AS_INT, buffs);
-               addstat(STAT_BUFF_TIME, AS_FLOAT, buff_time);
-
-               InitializeEntity(world, buffs_DelayedInit, INITPRIO_FINDTARGET);
-       }
-}
-
-entity buff_FirstFromFlags(int _buffs)
-{
-       if (flags)
-       {
-               FOREACH(Buffs, it.m_itemid & _buffs, LAMBDA(return it));
-       }
-       return BUFF_Null;
-}
-
-bool buffs_BuffModel_Customize()
-{SELFPARAM();
-       entity player, myowner;
-       bool 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(MUTATOR_CALLHOOK(BuffModel_Customize, self, player))
-               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;
-}
-
-void buffs_BuffModel_Spawn(entity player)
-{
-       player.buff_model = spawn();
-       setmodel(player.buff_model, MDL_BUFF);
-       setsize(player.buff_model, '0 0 -40', '0 0 40');
-       setattachment(player.buff_model, player, "");
-       setorigin(player.buff_model, '0 0 1' * (player.buff_model.maxs.z * 1));
-       player.buff_model.owner = player;
-       player.buff_model.scale = 0.7;
-       player.buff_model.pflags = PFLAGS_FULLDYNAMIC;
-       player.buff_model.light_lev = 200;
-       player.buff_model.customizeentityforclient = buffs_BuffModel_Customize;
-}
-
-vector buff_GlowColor(entity buff)
-{
-       //if(buff.team) { return Team_ColorRGB(buff.team); }
-       return buff.m_color;
-}
-
-void buff_Effect(entity player, string eff)
-{SELFPARAM();
-       if(!autocvar_g_buffs_effects) { return; }
-
-       if(time >= self.buff_effect_delay)
-       {
-               Send_Effect_(eff, player.origin + ((player.mins + player.maxs) * 0.5), '0 0 0', 1);
-               self.buff_effect_delay = time + 0.05; // prevent spam
-       }
-}
-
-// buff item
-float buff_Waypoint_visible_for_player(entity plr)
-{SELFPARAM();
-       if(!self.owner.buff_active && !self.owner.buff_activetime)
-               return false;
-
-       if (plr.buffs)
-       {
-               return plr.cvar_cl_buffs_autoreplace == false || plr.buffs != self.owner.buffs;
-       }
-
-       return WaypointSprite_visible_for_player(plr);
-}
-
-void buff_Waypoint_Spawn(entity e)
-{
-       entity buff = buff_FirstFromFlags(e.buffs);
-       entity wp = WaypointSprite_Spawn(WP_Buff, 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs.z, world, e.team, e, buff_waypoint, true, RADARICON_Buff);
-       wp.wp_extra = buff.m_id;
-       WaypointSprite_UpdateTeamRadar(e.buff_waypoint, RADARICON_Buff, e.glowmod);
-       e.buff_waypoint.waypointsprite_visible_for_player = buff_Waypoint_visible_for_player;
-}
-
-void buff_SetCooldown(float cd)
-{SELFPARAM();
-       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)
-{SELFPARAM();
-       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;
-
-       Send_Effect(EFFECT_ELECTRO_COMBO, oldbufforigin + ((ent.mins + ent.maxs) * 0.5), '0 0 0', 1);
-       Send_Effect(EFFECT_ELECTRO_COMBO, CENTER_OR_VIEWOFS(ent), '0 0 0', 1);
-
-       WaypointSprite_Ping(ent.buff_waypoint);
-
-       sound(ent, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-}
-
-void buff_Touch()
-{SELFPARAM();
-       if(gameover) { return; }
-
-       if(ITEM_TOUCH_NEEDKILL())
-       {
-               buff_Respawn(self);
-               return;
-       }
-
-       if((self.team && DIFF_TEAM(other, self))
-       || (other.frozen)
-       || (other.vehicle)
-       || (!self.buff_active)
-       )
-       {
-               // can't touch this
-               return;
-       }
-
-       if(MUTATOR_CALLHOOK(BuffTouch, self, other))
-               return;
-
-       if(!IS_PLAYER(other))
-               return; // incase mutator changed other
-
-       if (other.buffs)
-       {
-               if (other.cvar_cl_buffs_autoreplace && other.buffs != self.buffs)
-               {
-                       int buffid = buff_FirstFromFlags(other.buffs).m_id;
-                       //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, buffid);
-
-                       other.buffs = 0;
-                       //sound(other, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
-               }
-               else { return; } // do nothing
-       }
-
-       self.owner = other;
-       self.buff_active = false;
-       self.lifetime = 0;
-       int buffid = buff_FirstFromFlags(self.buffs).m_id;
-       Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_GOT, buffid);
-       Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_INFO, INFO_ITEM_BUFF, other.netname, buffid);
-
-       Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(self), '0 0 0', 1);
-       sound(other, CH_TRIGGER, SND_SHIELD_RESPAWN, VOL_BASE, ATTN_NORM);
-       other.buffs |= (self.buffs);
-}
-
-float buff_Available(entity buff)
-{
-       if (buff == BUFF_Null)
-               return false;
-       if (buff == BUFF_AMMO && ((start_items & IT_UNLIMITED_WEAPON_AMMO) || (start_items & IT_UNLIMITED_AMMO) || (cvar("g_melee_only"))))
-               return false;
-       if (buff == BUFF_VAMPIRE && cvar("g_vampire"))
-               return false;
-       return cvar(strcat("g_buffs_", buff.m_name));
-}
-
-.int buff_seencount;
-
-void buff_NewType(entity ent, float cb)
-{
-       RandomSelection_Init();
-       FOREACH(Buffs, buff_Available(it), LAMBDA(
-               it.buff_seencount += 1;
-               // if it's already been chosen, give it a lower priority
-               RandomSelection_Add(world, it.m_itemid, string_null, 1, max(0.2, 1 / it.buff_seencount));
-       ));
-       ent.buffs = RandomSelection_chosen_float;
-}
-
-void buff_Think()
-{SELFPARAM();
-       if(self.buffs != self.oldbuffs)
-       {
-               entity buff = buff_FirstFromFlags(self.buffs);
-               self.color = buff.m_color;
-               self.glowmod = buff_GlowColor(buff);
-               self.skin = buff.m_skin;
-
-               setmodel(self, MDL_BUFF);
-
-               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(!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 & 64))
-                       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, SND_STRENGTH_RESPAWN, VOL_BASE, ATTN_NORM);
-                       Send_Effect(EFFECT_ITEM_RESPAWN, CENTER_OR_VIEWOFS(self), '0 0 0', 1);
-               }
-       }
-
-       if(self.buff_active)
-       {
-               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()
-{SELFPARAM();
-       WaypointSprite_Kill(self.buff_waypoint);
-
-       if(self.buff_activetime) { buff_Waypoint_Spawn(self); }
-}
-
-void buff_Reset()
-{SELFPARAM();
-       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 & 64))
-               buff_Respawn(self);
-}
-
-float buff_Customize()
-{SELFPARAM();
-       entity player = WaypointSprite_getviewentity(other);
-       if(!self.buff_active || (self.team && DIFF_TEAM(player, self)))
-       {
-               self.alpha = 0.3;
-               if(self.effects & EF_FULLBRIGHT) { self.effects &= ~(EF_FULLBRIGHT); }
-               self.pflags = 0;
-       }
-       else
-       {
-               self.alpha = 1;
-               if(!(self.effects & EF_FULLBRIGHT)) { self.effects |= EF_FULLBRIGHT; }
-               self.light_lev = 220 + 36 * sin(time);
-               self.pflags = PFLAGS_FULLDYNAMIC;
-       }
-       return true;
-}
-
-void buff_Init(entity ent)
-{SELFPARAM();
-       if(!cvar("g_buffs")) { remove(ent); return; }
-
-       if(!teamplay && ent.team) { ent.team = 0; }
-
-       entity buff = buff_FirstFromFlags(self.buffs);
-
-       setself(ent);
-       if(!self.buffs || buff_Available(buff))
-               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.m_skin;
-       self.effects = EF_FULLBRIGHT | EF_STARDUST | EF_NOSHADOW;
-       self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY;
-       self.customizeentityforclient = buff_Customize;
-       //self.gravity = 100;
-       self.color = buff.m_color;
-       self.glowmod = buff_GlowColor(self);
-       buff_SetCooldown(autocvar_g_buffs_cooldown_activate + game_starttime);
-       self.buff_active = !self.buff_activetime;
-       self.pflags = PFLAGS_FULLDYNAMIC;
-
-       if(self.spawnflags & 1)
-               self.noalign = true;
-
-       if(self.noalign)
-               self.movetype = MOVETYPE_NONE; // reset by random location
-
-       setmodel(self, MDL_BUFF);
-       setsize(self, BUFF_MIN, BUFF_MAX);
-
-       if(cvar("g_buffs_random_location") || (self.spawnflags & 64))
-               buff_Respawn(self);
-
-       setself(this);
-}
-
-void buff_Init_Compat(entity ent, entity replacement)
-{
-       if (ent.spawnflags & 2)
-               ent.team = NUM_TEAM_1;
-       else if (ent.spawnflags & 4)
-               ent.team = NUM_TEAM_2;
-
-       ent.buffs = replacement.m_itemid;
-
-       buff_Init(ent);
-}
-
-void buff_SpawnReplacement(entity ent, entity old)
-{
-       setorigin(ent, old.origin);
-       ent.angles = old.angles;
-       ent.noalign = (old.noalign || (old.spawnflags & 1));
-
-       buff_Init(ent);
-}
-
-void buff_Vengeance_DelayedDamage()
-{SELFPARAM();
-       if(self.enemy)
-               Damage(self.enemy, self.owner, self.owner, self.dmg, DEATH_BUFF.m_id, self.enemy.origin, '0 0 0');
-
-       remove(self);
-       return;
-}
-
-float buff_Inferno_CalculateTime(float x, float offset_x, float offset_y, float intersect_x, float intersect_y, float base)
-{
-       return offset_y + (intersect_y - offset_y) * logn(((x - offset_x) * ((base - 1) / intersect_x)) + 1, base);
-}
-
-// mutator hooks
-MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_SplitHealthArmor)
-{
-       if(frag_deathtype == DEATH_BUFF.m_id) { return false; }
-
-       if(frag_target.buffs & BUFF_RESISTANCE.m_itemid)
-       {
-               vector v = healtharmor_applydamage(50, autocvar_g_buffs_resistance_blockpercent, frag_deathtype, frag_damage);
-               damage_take = v.x;
-               damage_save = v.y;
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_Calculate)
-{
-       if(frag_deathtype == DEATH_BUFF.m_id) { return false; }
-
-       if(frag_target.buffs & BUFF_SPEED.m_itemid)
-       if(frag_target != frag_attacker)
-               frag_damage *= autocvar_g_buffs_speed_damage_take;
-
-       if(frag_target.buffs & BUFF_MEDIC.m_itemid)
-       if((frag_target.health - frag_damage) <= 0)
-       if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
-       if(frag_attacker)
-       if(random() <= autocvar_g_buffs_medic_survive_chance)
-               frag_damage = max(5, frag_target.health - autocvar_g_buffs_medic_survive_health);
-
-       if(frag_target.buffs & BUFF_JUMP.m_itemid)
-       if(frag_deathtype == DEATH_FALL.m_id)
-               frag_damage = 0;
-
-       if(frag_target.buffs & BUFF_VENGEANCE.m_itemid)
-       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.m_itemid)
-       if(frag_attacker != frag_target)
-       if(vlen(frag_force))
-               frag_force = '0 0 0';
-
-       if(frag_attacker.buffs & BUFF_BASH.m_itemid)
-       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.m_itemid)
-       if(frag_target != frag_attacker)
-               frag_target.buff_disability_time = time + autocvar_g_buffs_disability_slowtime;
-
-       if(frag_attacker.buffs & BUFF_MEDIC.m_itemid)
-       if(DEATH_WEAPONOF(frag_deathtype) != WEP_ARC)
-       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;
-       }
-
-       if(frag_attacker.buffs & BUFF_INFERNO.m_itemid)
-       if(frag_target != frag_attacker) {
-               float time = buff_Inferno_CalculateTime(
-                       frag_damage,
-                       0,
-                       autocvar_g_buffs_inferno_burntime_min_time,
-                       autocvar_g_buffs_inferno_burntime_target_damage,
-                       autocvar_g_buffs_inferno_burntime_target_time,
-                       autocvar_g_buffs_inferno_burntime_factor
-               );
-               Fire_AddDamage(frag_target, frag_attacker, (frag_damage * autocvar_g_buffs_inferno_damagemultiplier) * time, time, DEATH_BUFF.m_id);
-       }
-
-       // this... is ridiculous (TODO: fix!)
-       if(frag_attacker.buffs & BUFF_VAMPIRE.m_itemid)
-       if(!frag_target.vehicle)
-       if(DEATH_WEAPONOF(frag_deathtype) != WEP_ARC)
-       if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
-       if(frag_target.deadflag == DEAD_NO)
-       if(IS_PLAYER(frag_target) || IS_MONSTER(frag_target))
-       if(frag_attacker != frag_target)
-       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);
-               if(frag_target.armorvalue)
-                       frag_attacker.armorvalue = bound(0, frag_attacker.armorvalue + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.armorvalue), g_pickup_armorsmall_max);
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(buffs,PlayerSpawn)
-{SELFPARAM();
-       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;
-}
-
-.float stat_sv_maxspeed;
-.float stat_sv_airspeedlimit_nonqw;
-.float stat_sv_jumpvelocity;
-
-MUTATOR_HOOKFUNCTION(buffs, PlayerPhysics)
-{SELFPARAM();
-       if(self.buffs & BUFF_SPEED.m_itemid)
-       {
-               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;
-       }
-
-       if(self.buffs & BUFF_JUMP.m_itemid)
-       {
-               // automatically reset, no need to worry
-               self.stat_sv_jumpvelocity = autocvar_g_buffs_jump_height;
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(buffs, PlayerJump)
-{SELFPARAM();
-       if(self.buffs & BUFF_JUMP.m_itemid)
-               player_jumpheight = autocvar_g_buffs_jump_height;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(buffs, MonsterMove)
-{SELFPARAM();
-       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)
-{SELFPARAM();
-       if(self.buffs)
-       {
-               int buffid = buff_FirstFromFlags(self.buffs).m_id;
-               Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, buffid);
-               self.buffs = 0;
-
-               if(self.buff_model)
-               {
-                       remove(self.buff_model);
-                       self.buff_model = world;
-               }
-       }
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(buffs, PlayerUseKey, CBC_ORDER_FIRST)
-{SELFPARAM();
-       if(MUTATOR_RETURNVALUE || gameover) { return false; }
-       if(self.buffs)
-       {
-               int buffid = buff_FirstFromFlags(self.buffs).m_id;
-               Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_BUFF_DROP, buffid);
-               Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, buffid);
-
-               self.buffs = 0;
-               sound(self, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
-               return true;
-       }
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(buffs, ForbidThrowCurrentWeapon)
-{SELFPARAM();
-       if(MUTATOR_RETURNVALUE || gameover) { return false; }
-
-       if(self.buffs & BUFF_SWAPPER.m_itemid)
-       {
-               float best_distance = autocvar_g_buffs_swapper_range;
-               entity closest = world;
-               entity player;
-               FOR_EACH_PLAYER(player)
-               if(DIFF_TEAM(self, player))
-               if(player.deadflag == DEAD_NO && !player.frozen && !player.vehicle)
-               if(vlen(self.origin - player.origin) <= best_distance)
-               {
-                       best_distance = vlen(self.origin - player.origin);
-                       closest = player;
-               }
-
-               if(closest)
-               {
-                       vector my_org, my_vel, my_ang, their_org, their_vel, their_ang;
-
-                       my_org = self.origin;
-                       my_vel = self.velocity;
-                       my_ang = self.angles;
-                       their_org = closest.origin;
-                       their_vel = closest.velocity;
-                       their_ang = closest.angles;
-
-                       Drop_Special_Items(closest);
-
-                       MUTATOR_CALLHOOK(PortalTeleport, self); // initiate flag dropper
-
-                       setorigin(self, their_org);
-                       setorigin(closest, my_org);
-
-                       closest.velocity = my_vel;
-                       closest.angles = my_ang;
-                       closest.fixangle = true;
-                       closest.oldorigin = my_org;
-                       closest.oldvelocity = my_vel;
-                       self.velocity = their_vel;
-                       self.angles = their_ang;
-                       self.fixangle = true;
-                       self.oldorigin = their_org;
-                       self.oldvelocity = their_vel;
-
-                       // set pusher so self gets the kill if they fall into void
-                       closest.pusher = self;
-                       closest.pushltime = time + autocvar_g_maxpushtime;
-                       closest.istypefrag = closest.BUTTON_CHAT;
-
-                       Send_Effect(EFFECT_ELECTRO_COMBO, their_org, '0 0 0', 1);
-                       Send_Effect(EFFECT_ELECTRO_COMBO, my_org, '0 0 0', 1);
-
-                       sound(self, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM);
-                       sound(closest, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM);
-
-                       // TODO: add a counter to handle how many times one can teleport, and a delay to prevent spam
-                       self.buffs = 0;
-                       return true;
-               }
-       }
-       return false;
-}
-
-bool buffs_RemovePlayer(entity player)
-{
-       if(player.buff_model)
-       {
-               remove(player.buff_model);
-               player.buff_model = world;
-       }
-
-       // also reset timers here to prevent them continuing after spectating
-       player.buff_disability_time = 0;
-       player.buff_disability_effect_time = 0;
-
-       return false;
-}
-MUTATOR_HOOKFUNCTION(buffs, MakePlayerObserver) { return buffs_RemovePlayer(self); }
-MUTATOR_HOOKFUNCTION(buffs, ClientDisconnect) { return buffs_RemovePlayer(self); }
-
-MUTATOR_HOOKFUNCTION(buffs, CustomizeWaypoint)
-{SELFPARAM();
-       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.m_itemid) && (e == other))
-       if(DIFF_TEAM(self.owner, e))
-               return true;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(buffs, OnEntityPreSpawn, CBC_ORDER_LAST)
-{SELFPARAM();
-       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, WeaponRateFactor)
-{SELFPARAM();
-       if(self.buffs & BUFF_SPEED.m_itemid)
-               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, WeaponSpeedFactor)
-{SELFPARAM();
-       if(self.buffs & BUFF_SPEED.m_itemid)
-               ret_float *= autocvar_g_buffs_speed_weaponspeed;
-
-       if(time < self.buff_disability_time)
-               ret_float *= autocvar_g_buffs_disability_weaponspeed;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink)
-{SELFPARAM();
-       if(gameover || self.deadflag != DEAD_NO) { return false; }
-
-       if(time < self.buff_disability_time)
-       if(time >= self.buff_disability_effect_time)
-       {
-               Send_Effect(EFFECT_SMOKING, self.origin + ((self.mins + self.maxs) * 0.5), '0 0 0', 1);
-               self.buff_disability_effect_time = time + 0.5;
-       }
-
-       // handle buff lost status
-       // 1: notify everyone else
-       // 2: notify carrier as well
-       int buff_lost = 0;
-
-       if(self.buff_time)
-       if(time >= self.buff_time)
-               buff_lost = 2;
-
-       if(self.frozen) { buff_lost = 1; }
-
-       if(buff_lost)
-       {
-               if(self.buffs)
-               {
-                       int buffid = buff_FirstFromFlags(self.buffs).m_id;
-                       Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, buffid);
-                       if(buff_lost >= 2)
-                       {
-                               Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_BUFF_DROP, buffid); // TODO: special timeout message?
-                               sound(self, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
-                       }
-                       self.buffs = 0;
-               }
-       }
-
-       if(self.buffs & BUFF_MAGNET.m_itemid)
-       {
-               vector pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_item;
-               for(other = world; (other = findflags(other, flags, FL_ITEM)); )
-               if(boxesoverlap(self.absmin - pickup_size, self.absmax + pickup_size, other.absmin, other.absmax))
-               {
-                       setself(other);
-                       other = this;
-                       if(self.touch)
-                               self.touch();
-                       other = self;
-                       setself(this);
-               }
-       }
-
-       if(self.buffs & BUFF_AMMO.m_itemid)
-       if(self.clip_size)
-               self.clip_load = self.(weapon_load[self.switchweapon]) = self.clip_size;
-
-       if((self.buffs & BUFF_INVISIBLE.m_itemid) && (self.oldbuffs & BUFF_INVISIBLE.m_itemid))
-       if(self.alpha != autocvar_g_buffs_invisible_alpha)
-               self.alpha = autocvar_g_buffs_invisible_alpha; // powerups reset alpha, so we must enforce this (TODO)
-
-#define BUFF_ONADD(b) if ( (self.buffs & (b).m_itemid) && !(self.oldbuffs & (b).m_itemid))
-#define BUFF_ONREM(b) if (!(self.buffs & (b).m_itemid) &&  (self.oldbuffs & (b).m_itemid))
-
-       if(self.buffs != self.oldbuffs)
-       {
-               entity buff = buff_FirstFromFlags(self.buffs);
-               float bufftime = buff != BUFF_Null ? buff.m_time(buff) : 0;
-               self.buff_time = (bufftime) ? time + bufftime : 0;
-
-               BUFF_ONADD(BUFF_AMMO)
-               {
-                       self.buff_ammo_prev_infitems = (self.items & IT_UNLIMITED_WEAPON_AMMO);
-                       self.items |= IT_UNLIMITED_WEAPON_AMMO;
-
-                       if(self.clip_load)
-                               self.buff_ammo_prev_clipload = self.clip_load;
-                       self.clip_load = self.(weapon_load[self.switchweapon]) = self.clip_size;
-               }
-
-               BUFF_ONREM(BUFF_AMMO)
-               {
-                       if(self.buff_ammo_prev_infitems)
-                               self.items |= IT_UNLIMITED_WEAPON_AMMO;
-                       else
-                               self.items &= ~IT_UNLIMITED_WEAPON_AMMO;
-
-                       if(self.buff_ammo_prev_clipload)
-                               self.clip_load = self.buff_ammo_prev_clipload;
-               }
-
-               BUFF_ONADD(BUFF_INVISIBLE)
-               {
-                       if(time < self.strength_finished && g_instagib)
-                               self.alpha = autocvar_g_instagib_invis_alpha;
-                       else
-                               self.alpha = self.buff_invisible_prev_alpha;
-                       self.alpha = autocvar_g_buffs_invisible_alpha;
-               }
-
-               BUFF_ONREM(BUFF_INVISIBLE)
-                       self.alpha = self.buff_invisible_prev_alpha;
-
-               BUFF_ONADD(BUFF_FLIGHT)
-               {
-                       self.buff_flight_prev_gravity = self.gravity;
-                       self.gravity = autocvar_g_buffs_flight_gravity;
-               }
-
-               BUFF_ONREM(BUFF_FLIGHT)
-                       self.gravity = self.buff_flight_prev_gravity;
-
-               self.oldbuffs = self.buffs;
-               if(self.buffs)
-               {
-                       if(!self.buff_model)
-                               buffs_BuffModel_Spawn(self);
-
-                       self.buff_model.color = buff.m_color;
-                       self.buff_model.glowmod = buff_GlowColor(self.buff_model);
-                       self.buff_model.skin = buff.m_skin;
-
-                       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;
-       }
-
-#undef BUFF_ONADD
-#undef BUFF_ONREM
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(buffs, SpectateCopy)
-{SELFPARAM();
-       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)
-{SELFPARAM();
-       if(self.buffs & BUFF_MEDIC.m_itemid)
-       {
-               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.m_itemid)
-               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 |= 64; // always randomize
-                       e.velocity = randomvec() * 250; // this gets reset anyway if random location works
-                       buff_Init(e);
-               }
-       }
-}
diff --git a/qcsrc/server/mutators/mutator_buffs.qh b/qcsrc/server/mutators/mutator_buffs.qh
deleted file mode 100644 (file)
index 10d84ef..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-#ifndef MUTATOR_BUFFS_H
-#define MUTATOR_BUFFS_H
-
-// buff specific variables \\
-//
-// ammo
-.float buff_ammo_prev_infitems;
-.int buff_ammo_prev_clipload;
-// invisible
-.float buff_invisible_prev_alpha;
-// flight
-.float buff_flight_prev_gravity;
-// disability
-.float buff_disability_time;
-.float buff_disability_effect_time;
-// common buff variables
-.float buff_effect_delay;
-
-// buff definitions
-.float buff_active;
-.float buff_activetime;
-.float buff_activetime_updated;
-.entity buff_waypoint;
-.int oldbuffs; // for updating effects
-.entity buff_model; // controls effects (TODO: make csqc)
-
-const vector BUFF_MIN = ('-16 -16 -20');
-const vector BUFF_MAX = ('16 16 20');
-
-// client side options
-.float cvar_cl_buffs_autoreplace;
-#endif
diff --git a/qcsrc/server/mutators/mutator_campcheck.qc b/qcsrc/server/mutators/mutator_campcheck.qc
deleted file mode 100644 (file)
index 04598ed..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-
-#include "mutator.qh"
-
-#include "../campaign.qh"
-
-REGISTER_MUTATOR(campcheck, cvar("g_campcheck"));
-
-.float campcheck_nextcheck;
-.float campcheck_traveled_distance;
-
-MUTATOR_HOOKFUNCTION(campcheck, PlayerDies)
-{SELFPARAM();
-       Kill_Notification(NOTIF_ONE, self, MSG_CENTER_CPID, CPID_CAMPCHECK);
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(campcheck, PlayerDamage_Calculate)
-{
-       if(IS_PLAYER(frag_target))
-       if(IS_PLAYER(frag_attacker))
-       if(frag_attacker != frag_target)
-       {
-               frag_target.campcheck_traveled_distance = autocvar_g_campcheck_distance;
-               frag_attacker.campcheck_traveled_distance = autocvar_g_campcheck_distance;
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(campcheck, PlayerPreThink)
-{SELFPARAM();
-       if(!gameover)
-       if(!warmup_stage) // don't consider it camping during warmup?
-       if(time >= game_starttime)
-       if(IS_PLAYER(self))
-       if(IS_REAL_CLIENT(self)) // bots may camp, but that's no reason to constantly kill them
-       if(self.deadflag == DEAD_NO)
-       if(!self.frozen)
-       if(!self.BUTTON_CHAT)
-       if(autocvar_g_campcheck_interval)
-       {
-               vector dist;
-
-               // calculate player movement (in 2 dimensions only, so jumping on one spot doesn't count as movement)
-               dist = self.prevorigin - self.origin;
-               dist.z = 0;
-               self.campcheck_traveled_distance += fabs(vlen(dist));
-
-               if((autocvar_g_campaign && !campaign_bots_may_start) || (time < game_starttime) || (round_handler_IsActive() && !round_handler_IsRoundStarted()))
-               {
-                       self.campcheck_nextcheck = time + autocvar_g_campcheck_interval * 2;
-                       self.campcheck_traveled_distance = 0;
-               }
-
-               if(time > self.campcheck_nextcheck)
-               {
-                       if(self.campcheck_traveled_distance < autocvar_g_campcheck_distance)
-                       {
-                               Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_CAMPCHECK);
-                               if(self.vehicle)
-                                       Damage(self.vehicle, self, self, autocvar_g_campcheck_damage * 2, DEATH_CAMP.m_id, self.vehicle.origin, '0 0 0');
-                               else
-                                       Damage(self, self, self, bound(0, autocvar_g_campcheck_damage, self.health + self.armorvalue * autocvar_g_balance_armor_blockpercent + 5), DEATH_CAMP.m_id, self.origin, '0 0 0');
-                       }
-                       self.campcheck_nextcheck = time + autocvar_g_campcheck_interval;
-                       self.campcheck_traveled_distance = 0;
-               }
-
-               return false;
-       }
-
-       self.campcheck_nextcheck = time + autocvar_g_campcheck_interval; // one of the above checks failed, so keep the timer up to date
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(campcheck, PlayerSpawn)
-{SELFPARAM();
-       self.campcheck_nextcheck = time + autocvar_g_campcheck_interval * 2;
-       self.campcheck_traveled_distance = 0;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(campcheck, BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":CampCheck");
-       return false;
-}
diff --git a/qcsrc/server/mutators/mutator_dodging.qc b/qcsrc/server/mutators/mutator_dodging.qc
deleted file mode 100644 (file)
index e41456a..0000000
+++ /dev/null
@@ -1,314 +0,0 @@
-#ifdef CSQC
-       #define PHYS_DODGING_FRAMETIME                          (1 / (frametime <= 0 ? 60 : frametime))
-       #define PHYS_DODGING                                            getstati(STAT_DODGING)
-       #define PHYS_DODGING_DELAY                                      getstatf(STAT_DODGING_DELAY)
-       #define PHYS_DODGING_TIMEOUT(s)                         getstatf(STAT_DODGING_TIMEOUT)
-       #define PHYS_DODGING_HORIZ_SPEED_FROZEN         getstatf(STAT_DODGING_HORIZ_SPEED_FROZEN)
-       #define PHYS_DODGING_FROZEN_NODOUBLETAP         getstati(STAT_DODGING_FROZEN_NO_DOUBLETAP)
-       #define PHYS_DODGING_HORIZ_SPEED                        getstatf(STAT_DODGING_HORIZ_SPEED)
-       #define PHYS_DODGING_PRESSED_KEYS(s)            s.pressedkeys
-       #define PHYS_DODGING_HEIGHT_THRESHOLD           getstatf(STAT_DODGING_HEIGHT_THRESHOLD)
-       #define PHYS_DODGING_DISTANCE_THRESHOLD         getstatf(STAT_DODGING_DISTANCE_THRESHOLD)
-       #define PHYS_DODGING_RAMP_TIME                          getstatf(STAT_DODGING_RAMP_TIME)
-       #define PHYS_DODGING_UP_SPEED                           getstatf(STAT_DODGING_UP_SPEED)
-       #define PHYS_DODGING_WALL                                       getstatf(STAT_DODGING_WALL)
-#elif defined(SVQC)
-       #define PHYS_DODGING_FRAMETIME                          sys_frametime
-       #define PHYS_DODGING                                            g_dodging
-       #define PHYS_DODGING_DELAY                                      autocvar_sv_dodging_delay
-       #define PHYS_DODGING_TIMEOUT(s)                         s.cvar_cl_dodging_timeout
-       #define PHYS_DODGING_HORIZ_SPEED_FROZEN         autocvar_sv_dodging_horiz_speed_frozen
-       #define PHYS_DODGING_FROZEN_NODOUBLETAP         autocvar_sv_dodging_frozen_doubletap
-       #define PHYS_DODGING_HORIZ_SPEED                        autocvar_sv_dodging_horiz_speed
-       #define PHYS_DODGING_PRESSED_KEYS(s)            s.pressedkeys
-       #define PHYS_DODGING_HEIGHT_THRESHOLD           autocvar_sv_dodging_height_threshold
-       #define PHYS_DODGING_DISTANCE_THRESHOLD         autocvar_sv_dodging_wall_distance_threshold
-       #define PHYS_DODGING_RAMP_TIME                          autocvar_sv_dodging_ramp_time
-       #define PHYS_DODGING_UP_SPEED                           autocvar_sv_dodging_up_speed
-       #define PHYS_DODGING_WALL                                       autocvar_sv_dodging_wall_dodging
-#endif
-
-#ifdef SVQC
-#include "mutator_dodging.qh"
-
-#include "mutator.qh"
-
-#include "../../common/animdecide.qh"
-#include "../../common/physics.qh"
-
-.float cvar_cl_dodging_timeout;
-
-.float stat_dodging;
-.float stat_dodging_delay;
-.float stat_dodging_horiz_speed_frozen;
-.float stat_dodging_frozen_nodoubletap;
-.float stat_dodging_frozen;
-.float stat_dodging_horiz_speed;
-.float stat_dodging_height_threshold;
-.float stat_dodging_distance_threshold;
-.float stat_dodging_ramp_time;
-.float stat_dodging_up_speed;
-.float stat_dodging_wall;
-
-REGISTER_MUTATOR(dodging, cvar("g_dodging"))
-{
-       // this just turns on the cvar.
-       MUTATOR_ONADD
-       {
-               g_dodging = cvar("g_dodging");
-               addstat(STAT_DODGING, AS_INT, stat_dodging);
-               addstat(STAT_DODGING_DELAY, AS_FLOAT, stat_dodging_delay);
-               addstat(STAT_DODGING_TIMEOUT, AS_FLOAT, cvar_cl_dodging_timeout); // we stat this, so it is updated on the client when updated on server (otherwise, chaos)
-               addstat(STAT_DODGING_FROZEN_NO_DOUBLETAP, AS_INT, stat_dodging_frozen_nodoubletap);
-               addstat(STAT_DODGING_HORIZ_SPEED_FROZEN, AS_FLOAT, stat_dodging_horiz_speed_frozen);
-               addstat(STAT_DODGING_FROZEN, AS_INT, stat_dodging_frozen);
-               addstat(STAT_DODGING_HORIZ_SPEED, AS_FLOAT, stat_dodging_horiz_speed);
-               addstat(STAT_DODGING_HEIGHT_THRESHOLD, AS_FLOAT, stat_dodging_height_threshold);
-               addstat(STAT_DODGING_DISTANCE_THRESHOLD, AS_FLOAT, stat_dodging_distance_threshold);
-               addstat(STAT_DODGING_RAMP_TIME, AS_FLOAT, stat_dodging_ramp_time);
-               addstat(STAT_DODGING_UP_SPEED, AS_FLOAT, stat_dodging_up_speed);
-               addstat(STAT_DODGING_WALL, AS_FLOAT, stat_dodging_wall);
-       }
-
-       // this just turns off the cvar.
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               g_dodging = 0;
-       }
-
-       return false;
-}
-
-#endif
-
-// set to 1 to indicate dodging has started.. reset by physics hook after dodge has been done..
-.float dodging_action;
-
-// the jump part of the dodge cannot be ramped
-.float dodging_single_action;
-
-
-// these are used to store the last key press time for each of the keys..
-.float last_FORWARD_KEY_time;
-.float last_BACKWARD_KEY_time;
-.float last_LEFT_KEY_time;
-.float last_RIGHT_KEY_time;
-
-// these store the movement direction at the time of the dodge action happening.
-.vector dodging_direction;
-
-// this indicates the last time a dodge was executed. used to check if another one is allowed
-// and to ramp up the dodge acceleration in the physics hook.
-.float last_dodging_time;
-
-// This is the velocity gain to be added over the ramp time.
-// It will decrease from frame to frame during dodging_action = 1
-// until it's 0.
-.float dodging_velocity_gain;
-
-#ifdef CSQC
-.int pressedkeys;
-
-#elif defined(SVQC)
-
-void dodging_UpdateStats()
-{SELFPARAM();
-       self.stat_dodging = PHYS_DODGING;
-       self.stat_dodging_delay = PHYS_DODGING_DELAY;
-       self.stat_dodging_horiz_speed_frozen = PHYS_DODGING_HORIZ_SPEED_FROZEN;
-       self.stat_dodging_frozen = PHYS_DODGING_FROZEN;
-       self.stat_dodging_frozen_nodoubletap = PHYS_DODGING_FROZEN_NODOUBLETAP;
-       self.stat_dodging_height_threshold = PHYS_DODGING_HEIGHT_THRESHOLD;
-       self.stat_dodging_distance_threshold = PHYS_DODGING_DISTANCE_THRESHOLD;
-       self.stat_dodging_ramp_time = PHYS_DODGING_RAMP_TIME;
-       self.stat_dodging_up_speed = PHYS_DODGING_UP_SPEED;
-       self.stat_dodging_wall = PHYS_DODGING_WALL;
-}
-
-#endif
-
-// returns 1 if the player is close to a wall
-bool check_close_to_wall(float threshold)
-{SELFPARAM();
-       if (PHYS_DODGING_WALL == 0) { return false; }
-
-       #define X(OFFSET)                                                                                                                               \
-       tracebox(self.origin, self.mins, self.maxs, self.origin + OFFSET, true, self);  \
-       if (trace_fraction < 1 && vlen (self.origin - trace_endpos) < threshold)                \
-               return true;
-       X(1000*v_right);
-       X(-1000*v_right);
-       X(1000*v_forward);
-       X(-1000*v_forward);
-       #undef X
-
-       return false;
-}
-
-bool check_close_to_ground(float threshold)
-{SELFPARAM();
-       return IS_ONGROUND(self) ? true : false;
-}
-
-float PM_dodging_checkpressedkeys()
-{SELFPARAM();
-       if(!PHYS_DODGING)
-               return false;
-
-       float frozen_dodging = (PHYS_FROZEN(self) && PHYS_DODGING_FROZEN);
-       float frozen_no_doubletap = (frozen_dodging && !PHYS_DODGING_FROZEN_NODOUBLETAP);
-
-       // first check if the last dodge is far enough back in time so we can dodge again
-       if ((time - self.last_dodging_time) < PHYS_DODGING_DELAY)
-               return false;
-
-       makevectors(self.angles);
-
-       if (check_close_to_ground(PHYS_DODGING_HEIGHT_THRESHOLD) != 1
-               && check_close_to_wall(PHYS_DODGING_DISTANCE_THRESHOLD) != 1)
-               return true;
-
-       float tap_direction_x = 0;
-       float tap_direction_y = 0;
-       float dodge_detected = 0;
-
-       #define X(COND,BTN,RESULT)                                                                                                                      \
-       if (self.movement_##COND)                                                                                               \
-               /* is this a state change? */                                                                                                   \
-               if(!(PHYS_DODGING_PRESSED_KEYS(self) & KEY_##BTN) || frozen_no_doubletap) {             \
-                               tap_direction_##RESULT;                                                                                                 \
-                               if ((time - self.last_##BTN##_KEY_time) < PHYS_DODGING_TIMEOUT(self))   \
-                                       dodge_detected = 1;                                                                                                     \
-                               self.last_##BTN##_KEY_time = time;                                                                              \
-               }
-       X(x < 0, BACKWARD,      x--);
-       X(x > 0, FORWARD,       x++);
-       X(y < 0, LEFT,          y--);
-       X(y > 0, RIGHT,         y++);
-       #undef X
-
-       if (dodge_detected == 1)
-       {
-               self.last_dodging_time = time;
-
-               self.dodging_action = 1;
-               self.dodging_single_action = 1;
-
-               self.dodging_velocity_gain = PHYS_DODGING_HORIZ_SPEED;
-
-               self.dodging_direction_x = tap_direction_x;
-               self.dodging_direction_y = tap_direction_y;
-
-               // normalize the dodging_direction vector.. (unlike UT99) XD
-               float length = self.dodging_direction_x * self.dodging_direction_x
-                                       + self.dodging_direction_y * self.dodging_direction_y;
-               length = sqrt(length);
-
-               self.dodging_direction_x = self.dodging_direction_x * 1.0 / length;
-               self.dodging_direction_y = self.dodging_direction_y * 1.0 / length;
-               return true;
-       }
-       return false;
-}
-
-void PM_dodging()
-{SELFPARAM();
-       if (!PHYS_DODGING)
-               return;
-
-#ifdef SVQC
-       dodging_UpdateStats();
-#endif
-
-    if (PHYS_DEAD(self))
-        return;
-
-       // when swimming, no dodging allowed..
-       if (self.waterlevel >= WATERLEVEL_SWIMMING)
-       {
-               self.dodging_action = 0;
-               self.dodging_direction_x = 0;
-               self.dodging_direction_y = 0;
-               return;
-       }
-
-       // make sure v_up, v_right and v_forward are sane
-       makevectors(self.angles);
-
-       // if we have e.g. 0.5 sec ramptime and a frametime of 0.25, then the ramp code
-       // will be called ramp_time/frametime times = 2 times. so, we need to
-       // add 0.5 * the total speed each frame until the dodge action is done..
-       float common_factor = PHYS_DODGING_FRAMETIME / PHYS_DODGING_RAMP_TIME;
-
-       // if ramp time is smaller than frametime we get problems ;D
-       common_factor = min(common_factor, 1);
-
-       float horiz_speed = PHYS_FROZEN(self) ? PHYS_DODGING_HORIZ_SPEED_FROZEN : PHYS_DODGING_HORIZ_SPEED;
-       float new_velocity_gain = self.dodging_velocity_gain - (common_factor * horiz_speed);
-       new_velocity_gain = max(0, new_velocity_gain);
-
-       float velocity_difference = self.dodging_velocity_gain - new_velocity_gain;
-
-       // ramp up dodging speed by adding some velocity each frame.. TODO: do it! :D
-       if (self.dodging_action == 1)
-       {
-               //disable jump key during dodge accel phase
-               if(self.movement_z > 0) { self.movement_z = 0; }
-
-               self.velocity += ((self.dodging_direction_y * velocity_difference) * v_right)
-                                       + ((self.dodging_direction_x * velocity_difference) * v_forward);
-
-               self.dodging_velocity_gain = self.dodging_velocity_gain - velocity_difference;
-       }
-
-       // the up part of the dodge is a single shot action
-       if (self.dodging_single_action == 1)
-       {
-               UNSET_ONGROUND(self);
-
-               self.velocity += PHYS_DODGING_UP_SPEED * v_up;
-
-#ifdef SVQC
-               if (autocvar_sv_dodging_sound)
-                       PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND);
-
-               animdecide_setaction(self, ANIMACTION_JUMP, true);
-#endif
-
-               self.dodging_single_action = 0;
-       }
-
-       // are we done with the dodging ramp yet?
-       if((self.dodging_action == 1) && ((time - self.last_dodging_time) > PHYS_DODGING_RAMP_TIME))
-       {
-               // reset state so next dodge can be done correctly
-               self.dodging_action = 0;
-               self.dodging_direction_x = 0;
-               self.dodging_direction_y = 0;
-       }
-}
-
-#ifdef SVQC
-
-MUTATOR_HOOKFUNCTION(dodging, GetCvars)
-{
-       GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_dodging_timeout, "cl_dodging_timeout");
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(dodging, PlayerPhysics)
-{
-       // print("dodging_PlayerPhysics\n");
-       PM_dodging();
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(dodging, GetPressedKeys)
-{
-       PM_dodging_checkpressedkeys();
-
-       return false;
-}
-
-#endif
diff --git a/qcsrc/server/mutators/mutator_dodging.qh b/qcsrc/server/mutators/mutator_dodging.qh
deleted file mode 100644 (file)
index a8fd665..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-#ifndef MUTATOR_DODGING_H
-#define MUTATOR_DODGING_H
-
-float g_dodging;
-
-// set to 1 to indicate dodging has started.. reset by physics hook after dodge has been done..
-.float dodging_action;
-
-// the jump part of the dodge cannot be ramped
-.float dodging_single_action;
-#endif
diff --git a/qcsrc/server/mutators/mutator_hook.qc b/qcsrc/server/mutators/mutator_hook.qc
deleted file mode 100644 (file)
index e43848b..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-AUTOCVAR(g_grappling_hook, bool, false, _("let players spawn with the grappling hook which allows them to pull themselves up"));
-#ifdef SVQC
-REGISTER_MUTATOR(hook, autocvar_g_grappling_hook) {
-    MUTATOR_ONADD {
-        g_grappling_hook = true;
-        WEP_HOOK.ammo_factor = 0;
-    }
-    MUTATOR_ONROLLBACK_OR_REMOVE {
-        g_grappling_hook = false;
-        WEP_HOOK.ammo_factor = 1;
-    }
-}
-
-MUTATOR_HOOKFUNCTION(hook, BuildMutatorsString)
-{
-    ret_string = strcat(ret_string, ":grappling_hook");
-}
-
-MUTATOR_HOOKFUNCTION(hook, BuildMutatorsPrettyString)
-{
-    ret_string = strcat(ret_string, ", Hook");
-}
-
-MUTATOR_HOOKFUNCTION(hook, BuildGameplayTipsString)
-{
-    ret_string = strcat(ret_string, "\n\n^3grappling hook^8 is enabled, press 'e' to use it\n");
-}
-
-MUTATOR_HOOKFUNCTION(hook, PlayerSpawn)
-{
-    SELFPARAM();
-    self.offhand = OFFHAND_HOOK;
-}
-
-MUTATOR_HOOKFUNCTION(hook, FilterItem)
-{
-    return self.weapon == WEP_HOOK.m_id;
-}
-
-#endif
diff --git a/qcsrc/server/mutators/mutator_invincibleproj.qc b/qcsrc/server/mutators/mutator_invincibleproj.qc
deleted file mode 100644 (file)
index 3f76bf7..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-
-#include "mutator.qh"
-
-REGISTER_MUTATOR(invincibleprojectiles, cvar("g_invincible_projectiles"));
-
-MUTATOR_HOOKFUNCTION(invincibleprojectiles, EditProjectile)
-{
-       if(other.health)
-       {
-               // disable health which in effect disables damage calculations
-               other.health = 0;
-       }
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(invincibleprojectiles, BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":InvincibleProjectiles");
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(invincibleprojectiles, BuildMutatorsPrettyString)
-{
-       ret_string = strcat(ret_string, ", Invincible Projectiles");
-       return 0;
-}
diff --git a/qcsrc/server/mutators/mutator_melee_only.qc b/qcsrc/server/mutators/mutator_melee_only.qc
deleted file mode 100644 (file)
index f85260d..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-
-#include "mutator.qh"
-
-REGISTER_MUTATOR(melee_only, cvar("g_melee_only") && !cvar("g_instagib") && !g_nexball);
-
-MUTATOR_HOOKFUNCTION(melee_only, SetStartItems)
-{
-       start_ammo_shells = warmup_start_ammo_shells = 0;
-       start_weapons = warmup_start_weapons = WEPSET(SHOTGUN);
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(melee_only, ForbidThrowCurrentWeapon)
-{
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(melee_only, FilterItem)
-{SELFPARAM();
-       switch (self.items)
-       {
-               case ITEM_HealthSmall.m_itemid:
-               case ITEM_ArmorSmall.m_itemid:
-                       return false;
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(melee_only, BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":MeleeOnly");
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(melee_only, BuildMutatorsPrettyString)
-{
-       ret_string = strcat(ret_string, ", Melee Only Arena");
-       return false;
-}
diff --git a/qcsrc/server/mutators/mutator_midair.qc b/qcsrc/server/mutators/mutator_midair.qc
deleted file mode 100644 (file)
index 4dfb7d5..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-
-#include "mutator.qh"
-
-REGISTER_MUTATOR(midair, cvar("g_midair"));
-
-.float midair_shieldtime;
-
-MUTATOR_HOOKFUNCTION(midair, PlayerDamage_Calculate)
-{SELFPARAM();
-       if(IS_PLAYER(frag_attacker))
-       if(IS_PLAYER(frag_target))
-       if(time < self.midair_shieldtime)
-               frag_damage = false;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(midair, PlayerPowerups)
-{SELFPARAM();
-       if(time >= game_starttime)
-       if(self.flags & FL_ONGROUND)
-       {
-               self.effects |= (EF_ADDITIVE | EF_FULLBRIGHT);
-               self.midair_shieldtime = max(self.midair_shieldtime, time + autocvar_g_midair_shieldtime);
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(midair, PlayerSpawn)
-{SELFPARAM();
-       if(IS_BOT_CLIENT(self))
-               self.bot_moveskill = 0; // disable bunnyhopping
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(midair, BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":midair");
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(midair, BuildMutatorsPrettyString)
-{
-       ret_string = strcat(ret_string, ", Midair");
-       return false;
-}
diff --git a/qcsrc/server/mutators/mutator_multijump.qc b/qcsrc/server/mutators/mutator_multijump.qc
deleted file mode 100644 (file)
index d36f281..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-#ifdef SVQC
-       #include "mutator.qh"
-       #include "../antilag.qh"
-#endif
-#include "../../common/physics.qh"
-
-.int multijump_count;
-.bool multijump_ready;
-.bool cvar_cl_multijump;
-
-#ifdef CSQC
-
-#define PHYS_MULTIJUMP                                 getstati(STAT_MULTIJUMP)
-#define PHYS_MULTIJUMP_SPEED           getstatf(STAT_MULTIJUMP_SPEED)
-#define PHYS_MULTIJUMP_ADD                     getstati(STAT_MULTIJUMP_ADD)
-#define PHYS_MULTIJUMP_MAXSPEED        getstatf(STAT_MULTIJUMP_MAXSPEED)
-#define PHYS_MULTIJUMP_DODGING                 getstati(STAT_MULTIJUMP_DODGING)
-
-#elif defined(SVQC)
-
-#define PHYS_MULTIJUMP                                 autocvar_g_multijump
-#define PHYS_MULTIJUMP_SPEED           autocvar_g_multijump_speed
-#define PHYS_MULTIJUMP_ADD                     autocvar_g_multijump_add
-#define PHYS_MULTIJUMP_MAXSPEED        autocvar_g_multijump_maxspeed
-#define PHYS_MULTIJUMP_DODGING                 autocvar_g_multijump_dodging
-
-
-.float stat_multijump;
-.float stat_multijump_speed;
-.float stat_multijump_add;
-.float stat_multijump_maxspeed;
-.float stat_multijump_dodging;
-
-void multijump_UpdateStats()
-{SELFPARAM();
-       self.stat_multijump = PHYS_MULTIJUMP;
-       self.stat_multijump_speed = PHYS_MULTIJUMP_SPEED;
-       self.stat_multijump_add = PHYS_MULTIJUMP_ADD;
-       self.stat_multijump_maxspeed = PHYS_MULTIJUMP_MAXSPEED;
-       self.stat_multijump_dodging = PHYS_MULTIJUMP_DODGING;
-}
-
-void multijump_AddStats()
-{
-       addstat(STAT_MULTIJUMP, AS_INT, stat_multijump);
-       addstat(STAT_MULTIJUMP_SPEED, AS_FLOAT, stat_multijump_speed);
-       addstat(STAT_MULTIJUMP_ADD, AS_INT, stat_multijump_add);
-       addstat(STAT_MULTIJUMP_MAXSPEED, AS_FLOAT, stat_multijump_maxspeed);
-       addstat(STAT_MULTIJUMP_DODGING, AS_INT, stat_multijump_dodging);
-}
-
-#endif
-
-void PM_multijump()
-{SELFPARAM();
-       if(!PHYS_MULTIJUMP) { return; }
-
-       if(IS_ONGROUND(self))
-       {
-               self.multijump_count = 0;
-       }
-}
-
-bool PM_multijump_checkjump()
-{SELFPARAM();
-       if(!PHYS_MULTIJUMP) { return false; }
-
-#ifdef SVQC
-       bool client_multijump = self.cvar_cl_multijump;
-#elif defined(CSQC)
-       bool client_multijump = cvar("cl_multijump");
-
-       if(cvar("cl_multijump") > 1)
-               return false; // nope
-#endif
-
-       if (!IS_JUMP_HELD(self) && !IS_ONGROUND(self) && client_multijump) // jump button pressed this frame and we are in midair
-               self.multijump_ready = true;  // this is necessary to check that we released the jump button and pressed it again
-       else
-               self.multijump_ready = false;
-
-       int phys_multijump = PHYS_MULTIJUMP;
-
-#ifdef CSQC
-       phys_multijump = (PHYS_MULTIJUMP) ? -1 : 0;
-#endif
-
-       if(!player_multijump && self.multijump_ready && (self.multijump_count < phys_multijump || phys_multijump == -1) && self.velocity_z > PHYS_MULTIJUMP_SPEED && (!PHYS_MULTIJUMP_MAXSPEED || vlen(self.velocity) <= PHYS_MULTIJUMP_MAXSPEED))
-       {
-               if (PHYS_MULTIJUMP)
-               {
-                       if (!PHYS_MULTIJUMP_ADD) // in this case we make the z velocity == jumpvelocity
-                       {
-                               if (self.velocity_z < PHYS_JUMPVELOCITY)
-                               {
-                                       player_multijump = true;
-                                       self.velocity_z = 0;
-                               }
-                       }
-                       else
-                               player_multijump = true;
-
-                       if(player_multijump)
-                       {
-                               if(PHYS_MULTIJUMP_DODGING)
-                               if(self.movement_x != 0 || self.movement_y != 0) // don't remove all speed if player isnt pressing any movement keys
-                               {
-                                       float curspeed;
-                                       vector wishvel, wishdir;
-
-/*#ifdef SVQC
-                                       curspeed = max(
-                                               vlen(vec2(self.velocity)), // current xy speed
-                                               vlen(vec2(antilag_takebackavgvelocity(self, max(self.lastteleporttime + sys_frametime, time - 0.25), time))) // average xy topspeed over the last 0.25 secs
-                                       );
-#elif defined(CSQC)*/
-                                       curspeed = vlen(vec2(self.velocity));
-//#endif
-
-                                       makevectors(self.v_angle_y * '0 1 0');
-                                       wishvel = v_forward * self.movement_x + v_right * self.movement_y;
-                                       wishdir = normalize(wishvel);
-
-                                       self.velocity_x = wishdir_x * curspeed; // allow "dodging" at a multijump
-                                       self.velocity_y = wishdir_y * curspeed;
-                                       // keep velocity_z unchanged!
-                               }
-                               if (PHYS_MULTIJUMP > 0)
-                               {
-                                       self.multijump_count += 1;
-                               }
-                       }
-               }
-               self.multijump_ready = false; // require releasing and pressing the jump button again for the next jump
-       }
-
-       return false;
-}
-
-#ifdef SVQC
-REGISTER_MUTATOR(multijump, cvar("g_multijump"))
-{
-       MUTATOR_ONADD
-       {
-               multijump_AddStats();
-       }
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(multijump, PlayerPhysics)
-{
-       multijump_UpdateStats();
-       PM_multijump();
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(multijump, PlayerJump)
-{
-       return PM_multijump_checkjump();
-}
-
-MUTATOR_HOOKFUNCTION(multijump, GetCvars)
-{
-       GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_multijump, "cl_multijump");
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(multijump, BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":multijump");
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(multijump, BuildMutatorsPrettyString)
-{
-       ret_string = strcat(ret_string, ", Multi jump");
-       return false;
-}
-
-#endif
diff --git a/qcsrc/server/mutators/mutator_nades.qc b/qcsrc/server/mutators/mutator_nades.qc
deleted file mode 100644 (file)
index 45cc4f9..0000000
+++ /dev/null
@@ -1,1207 +0,0 @@
-#include "mutator_nades.qh"
-
-#include "mutator.qh"
-
-#include "gamemode_keyhunt.qh"
-#include "gamemode_freezetag.qh"
-#include "../../common/nades/all.qh"
-#include "../../common/gamemodes/all.qh"
-#include "../../common/monsters/spawn.qh"
-#include "../../common/monsters/sv_monsters.qh"
-#include "../g_subs.qh"
-
-REGISTER_MUTATOR(nades, cvar("g_nades"))
-{
-       MUTATOR_ONADD
-       {
-               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);
-       }
-
-       return false;
-}
-
-.float nade_time_primed;
-
-.entity nade_spawnloc;
-
-void nade_timer_think()
-{SELFPARAM();
-       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)
-{
-       CSQCProjectile(_nade, true, Nades[_nade.nade_type].m_projectile[true], true);
-}
-
-void nade_spawn(entity _nade)
-{
-       entity timer = spawn();
-       setmodel(timer, MDL_NADE_TIMER);
-       setattachment(timer, _nade, "");
-       timer.classname = "nade_timer";
-       timer.colormap = _nade.colormap;
-       timer.glowmod = _nade.glowmod;
-       timer.think = nade_timer_think;
-       timer.nextthink = time;
-       timer.wait = _nade.wait;
-       timer.owner = _nade;
-       timer.skin = 10;
-
-       _nade.effects |= EF_LOWPRECISION;
-
-       CSQCProjectile(_nade, true, Nades[_nade.nade_type].m_projectile[false], true);
-}
-
-void napalm_damage(float dist, float damage, float edgedamage, float burntime)
-{SELFPARAM();
-       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)
-       {
-               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(EFFECT_FIREBALL_LASER), self.origin, RandomSelection_chosen_ent.fireball_impactvec);
-               Send_Effect(EFFECT_FIREBALL_LASER, self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1);
-       }
-}
-
-
-void napalm_ball_think()
-{SELFPARAM();
-       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()
-{SELFPARAM();
-       entity proj;
-       vector kick;
-
-       spamsound(self, CH_SHOTS, SND(FIREBALL_FIRE), 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.m_id;
-       PROJECTILE_MAKETRIGGER(proj);
-       setmodel(proj, MDL_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()
-{SELFPARAM();
-
-       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()
-{SELFPARAM();
-       entity fountain;
-       int 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.m_id;
-       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;
-       Send_Effect(EFFECT_ELECTRO_IMPACT, frost_target.origin, '0 0 0', 1);
-       Freeze(frost_target, 1/freeze_time, 3, false);
-
-       Drop_Special_Items(frost_target);
-}
-
-void nade_ice_think()
-{SELFPARAM();
-
-       if(round_handler_IsActive())
-       if(!round_handler_IsRoundStarted())
-       {
-               remove(self);
-               return;
-       }
-
-       if(time >= self.ltime)
-       {
-               if ( autocvar_g_nades_ice_explode )
-               {
-                       entity expef = EFFECT_NADE_EXPLODE(self.realowner.team);
-                       Send_Effect(expef, self.origin + '0 0 1', '0 0 0', 1);
-                       sound(self, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
-
-                       RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
-                               autocvar_g_nades_nade_radius, self, world, 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;
-       Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, self.origin + randomp, '0 0 0', 1);
-
-       if(time >= self.nade_special_time)
-       {
-               self.nade_special_time = time+0.7;
-
-               Send_Effect(EFFECT_ELECTRO_IMPACT, self.origin, '0 0 0', 1);
-               Send_Effect(EFFECT_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()
-{SELFPARAM();
-       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.m_id;
-       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, MDL_PROJECTILE_GRENADE);
-               entity timer = spawn();
-               setmodel(timer, MDL_NADE_TIMER);
-               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, MDL_Null);
-}
-
-void nade_translocate_boom()
-{SELFPARAM();
-       if(self.realowner.vehicle)
-               return;
-
-       vector locout = self.origin + '0 0 1' * (1 - self.realowner.mins.z - 24);
-       tracebox(locout, self.realowner.mins, self.realowner.maxs, locout, MOVE_NOMONSTERS, self.realowner);
-       locout = trace_endpos;
-
-       makevectors(self.realowner.angles);
-
-       MUTATOR_CALLHOOK(PortalTeleport, self.realowner);
-
-       TeleportPlayer(self, self.realowner, locout, self.realowner.angles, v_forward * vlen(self.realowner.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER);
-}
-
-void nade_spawn_boom()
-{SELFPARAM();
-       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()
-{SELFPARAM();
-       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()
-{SELFPARAM();
-       float maxhealth;
-       float health_factor;
-       if(IS_PLAYER(other) || IS_MONSTER(other))
-       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 = (IS_MONSTER(other)) ? other.max_health : g_pickup_healthmega_max;
-                       if ( other.health < maxhealth )
-                       {
-                               if ( self.nade_show_particles )
-                                       Send_Effect(EFFECT_HEALING, 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.m_id,other.origin,'0 0 0');
-               }
-
-       }
-
-       if ( IS_REAL_CLIENT(other) || IS_VEHICLE(other) )
-       {
-               entity show_red = (IS_VEHICLE(other)) ? 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()
-{SELFPARAM();
-       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, MDL_NADE_HEAL);
-       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()
-{SELFPARAM();
-       entity e = spawnmonster(self.pokenade_type, 0, self.realowner, self.realowner, self.origin, false, false, 1);
-
-       if(autocvar_g_nades_pokenade_monster_lifetime > 0)
-               e.monster_lifetime = time + autocvar_g_nades_pokenade_monster_lifetime;
-       e.monster_skill = MONSTER_SKILL_INSANE;
-}
-
-void nade_boom()
-{SELFPARAM();
-       entity expef = NULL;
-       bool nade_blast = true;
-
-       switch ( Nades[self.nade_type] )
-       {
-               case NADE_TYPE_NAPALM:
-                       nade_blast = autocvar_g_nades_napalm_blast;
-                       expef = EFFECT_EXPLOSION_MEDIUM;
-                       break;
-               case NADE_TYPE_ICE:
-                       nade_blast = false;
-                       expef = EFFECT_ELECTRO_COMBO; // hookbomb_explode electro_combo bigplasma_impact
-                       break;
-               case NADE_TYPE_TRANSLOCATE:
-                       nade_blast = false;
-                       break;
-               case NADE_TYPE_MONSTER:
-               case NADE_TYPE_SPAWN:
-                       nade_blast = false;
-                       switch(self.realowner.team)
-                       {
-                               case NUM_TEAM_1: expef = EFFECT_SPAWN_RED; break;
-                               case NUM_TEAM_2: expef = EFFECT_SPAWN_BLUE; break;
-                               case NUM_TEAM_3: expef = EFFECT_SPAWN_YELLOW; break;
-                               case NUM_TEAM_4: expef = EFFECT_SPAWN_PINK; break;
-                               default: expef = EFFECT_SPAWN_NEUTRAL; break;
-                       }
-                       break;
-               case NADE_TYPE_HEAL:
-                       nade_blast = false;
-                       expef = EFFECT_SPAWN_RED;
-                       break;
-
-               default:
-               case NADE_TYPE_NORMAL:
-                       expef = EFFECT_NADE_EXPLODE(self.realowner.team);
-                       break;
-       }
-
-       if(expef)
-               Send_Effect(expef, findbetterlocation(self.origin, 8), '0 0 0', 1);
-
-       sound(self, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM);
-       sound(self, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
-
-       self.event_damage = func_null; // prevent somehow calling damage in the next call
-
-       if(nade_blast)
-       {
-               RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
-                                autocvar_g_nades_nade_radius, self, world, 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);
-       }
-
-       if(self.takedamage)
-       switch ( Nades[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;
-       }
-
-       entity head;
-       for(head = world; (head = find(head, classname, "grapplinghook")); )
-       if(head.aiment == self)
-               RemoveGrapplingHook(head.realowner);
-
-       remove(self);
-}
-
-void nade_touch()
-{SELFPARAM();
-       /*float is_weapclip = 0;
-       if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
-       if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID))
-       if (!(trace_dphitcontents & DPCONTENTS_OPAQUE))
-               is_weapclip = 1;*/
-       if(ITEM_TOUCH_NEEDKILL()) // || is_weapclip)
-       {
-               entity head;
-               for(head = world; (head = find(head, classname, "grapplinghook")); )
-               if(head.aiment == self)
-                       RemoveGrapplingHook(head.realowner);
-               remove(self);
-               return;
-       }
-
-       PROJECTILE_TOUCH;
-
-       //setsize(self, '-2 -2 -2', '2 2 2');
-       //UpdateCSQCProjectile(self);
-       if(self.health == self.max_health)
-       {
-               spamsound(self, CH_SHOTS, SND(GRENADE_BOUNCE_RANDOM()), VOL_BASE, ATTEN_NORM);
-               return;
-       }
-
-       self.enemy = other;
-       nade_boom();
-}
-
-void nade_beep()
-{SELFPARAM();
-       sound(self, CH_SHOTS_SINGLE, SND_NADE_BEEP, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX));
-       self.think = nade_boom;
-       self.nextthink = max(self.wait, time);
-}
-
-void nade_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{SELFPARAM();
-       if(ITEM_DAMAGE_NEEDKILL(deathtype))
-       {
-               self.takedamage = DAMAGE_NO;
-               nade_boom();
-               return;
-       }
-
-       if(self.nade_type == NADE_TYPE_TRANSLOCATE.m_id || self.nade_type == NADE_TYPE_SPAWN.m_id)
-               return;
-
-       if(DEATH_ISWEAPON(deathtype, WEP_BLASTER))
-       {
-               force *= 1.5;
-               damage = 0;
-       }
-
-       if(DEATH_ISWEAPON(deathtype, WEP_VAPORIZER) && (deathtype & HITTYPE_SECONDARY))
-       {
-               force *= 0.5; // too much
-               frag_damage = 0;
-       }
-
-       if(DEATH_ISWEAPON(deathtype, WEP_VORTEX) || DEATH_ISWEAPON(deathtype, WEP_VAPORIZER))
-       {
-               force *= 6;
-               damage = self.max_health * 0.55;
-       }
-
-       if(DEATH_ISWEAPON(deathtype, WEP_MACHINEGUN) || DEATH_ISWEAPON(deathtype, WEP_HMG))
-               damage = self.max_health * 0.1;
-
-       if(DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN)) // WEAPONTODO
-       if(deathtype & HITTYPE_SECONDARY)
-       {
-               damage = self.max_health * 0.1;
-               force *= 10;
-       }
-       else
-               damage = self.max_health * 1.15;
-
-       self.velocity += force;
-       UpdateCSQCProjectile(self);
-
-       if(damage <= 0 || ((self.flags & FL_ONGROUND) && IS_PLAYER(attacker)))
-               return;
-
-       if(self.health == self.max_health)
-       {
-               sound(self, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX));
-               self.nextthink = max(time + autocvar_g_nades_nade_lifetime, time);
-               self.think = nade_beep;
-       }
-
-       self.health -= damage;
-
-       if ( self.nade_type != NADE_TYPE_HEAL.m_id || IS_PLAYER(attacker) )
-               self.realowner = attacker;
-
-       if(self.health <= 0)
-               W_PrepareExplosionByDamage(attacker, nade_boom);
-       else
-               nade_burn_spawn(self);
-}
-
-void toss_nade(entity e, vector _velocity, float _time)
-{SELFPARAM();
-       if(e.nade == world)
-               return;
-
-       entity _nade = e.nade;
-       e.nade = world;
-
-       remove(e.fake_nade);
-       e.fake_nade = world;
-
-       makevectors(e.v_angle);
-
-       W_SetupShot(e, false, false, "", CH_WEAPON_A, 0);
-
-       Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER_CPID, CPID_NADES);
-
-       vector offset = (v_forward * autocvar_g_nades_throw_offset.x)
-                                 + (v_right * autocvar_g_nades_throw_offset.y)
-                                 + (v_up * autocvar_g_nades_throw_offset.z);
-       if(autocvar_g_nades_throw_offset == '0 0 0')
-               offset = '0 0 0';
-
-       setorigin(_nade, w_shotorg + offset + (v_right * 25) * -1);
-       //setmodel(_nade, MDL_PROJECTILE_NADE);
-       //setattachment(_nade, world, "");
-       PROJECTILE_MAKETRIGGER(_nade);
-       setsize(_nade, '-16 -16 -16', '16 16 16');
-       _nade.movetype = MOVETYPE_BOUNCE;
-
-       tracebox(_nade.origin, _nade.mins, _nade.maxs, _nade.origin, false, _nade);
-       if (trace_startsolid)
-               setorigin(_nade, e.origin);
-
-       if(self.v_angle.x >= 70 && self.v_angle.x <= 110 && self.BUTTON_CROUCH)
-               _nade.velocity = '0 0 100';
-       else if(autocvar_g_nades_nade_newton_style == 1)
-               _nade.velocity = e.velocity + _velocity;
-       else if(autocvar_g_nades_nade_newton_style == 2)
-               _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.missile_flags = MIF_SPLASH | MIF_ARC;
-       _nade.damagedbycontents = true;
-       _nade.angles = vectoangles(_nade.velocity);
-       _nade.flags = FL_PROJECTILE;
-       _nade.projectiledeathtype = DEATH_NADE.m_id;
-       _nade.toss_time = time;
-       _nade.solid = SOLID_CORPSE; //((_nade.nade_type == NADE_TYPE_TRANSLOCATE) ? SOLID_CORPSE : SOLID_BBOX);
-
-       if(_nade.nade_type == NADE_TYPE_TRANSLOCATE.m_id || _nade.nade_type == NADE_TYPE_SPAWN.m_id)
-               _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
-       else
-               _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY;
-
-       nade_spawn(_nade);
-
-       if(_time)
-       {
-               _nade.think = nade_boom;
-               _nade.nextthink = _time;
-       }
-
-       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, SND(KH_ALARM));
-                       player.bonus_nades++;
-                       player.bonus_nade_score -= 1;
-               }
-       }
-}
-
-void nades_RemoveBonus(entity player)
-{
-       player.bonus_nades = player.bonus_nade_score = 0;
-}
-
-float nade_customize()
-{SELFPARAM();
-       //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(Nades[self.nade_type].m_projectile[false], self.team).eent_eff_name);
-               self.alpha = 1;
-       }
-
-       return true;
-}
-
-void nade_prime()
-{SELFPARAM();
-       if(autocvar_g_nades_bonus_only)
-       if(!self.bonus_nades)
-               return; // only allow bonus nades
-
-       if(self.nade)
-               remove(self.nade);
-
-       if(self.fake_nade)
-               remove(self.fake_nade);
-
-       entity n = spawn(), fn = spawn();
-
-       n.classname = "nade";
-       fn.classname = "fake_nade";
-
-       if(self.items & ITEM_Strength.m_itemid && 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, Nades_COUNT);
-
-       setmodel(n, MDL_PROJECTILE_NADE);
-       //setattachment(n, self, "bip01 l hand");
-       n.exteriormodeltoclient = self;
-       n.customizeentityforclient = nade_customize;
-       n.traileffectnum = _particleeffectnum(Nade_TrailEffect(Nades[n.nade_type].m_projectile[false], self.team).eent_eff_name);
-       n.colormod = Nades[n.nade_type].m_color;
-       n.realowner = self;
-       n.colormap = self.colormap;
-       n.glowmod = self.glowmod;
-       n.wait = time + autocvar_g_nades_nade_lifetime;
-       n.nade_time_primed = time;
-       n.think = nade_beep;
-       n.nextthink = max(n.wait - 3, time);
-       n.projectiledeathtype = DEATH_NADE.m_id;
-
-       setmodel(fn, MDL_NADE_VIEW);
-       setattachment(fn, self.weaponentity, "");
-       fn.realowner = fn.owner = self;
-       fn.colormod = Nades[n.nade_type].m_color;
-       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()
-{SELFPARAM();
-       if(self.vehicle)
-               return false;
-
-       if(gameover)
-               return false;
-
-       if(self.deadflag != DEAD_NO)
-               return false;
-
-       if (!autocvar_g_nades)
-               return false; // allow turning them off mid match
-
-       if(forbidWeaponUse(self))
-               return false;
-
-       if (!IS_PLAYER(self))
-               return false;
-
-       return true;
-}
-
-.bool nade_altbutton;
-
-void nades_CheckThrow()
-{SELFPARAM();
-       if(!CanThrowNade())
-               return;
-
-       entity held_nade = self.nade;
-       if (!held_nade)
-       {
-               self.nade_altbutton = true;
-               if(time > self.nade_refire)
-               {
-                       Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_NADE_THROW);
-                       nade_prime();
-                       self.nade_refire = time + autocvar_g_nades_nade_refire;
-               }
-       }
-       else
-       {
-               self.nade_altbutton = false;
-               if (time >= held_nade.nade_time_primed + 1) {
-                       makevectors(self.v_angle);
-                       float _force = time - held_nade.nade_time_primed;
-                       _force /= autocvar_g_nades_nade_lifetime;
-                       _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce));
-                       toss_nade(self, (v_forward * 0.75 + v_up * 0.2 + v_right * 0.05) * _force, 0);
-               }
-       }
-}
-
-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, VehicleEnter)
-{
-       if(vh_player.nade)
-               toss_nade(vh_player, '0 0 100', max(vh_player.nade.wait, time + 0.05));
-
-       return false;
-}
-
-CLASS(NadeOffhand, OffhandWeapon)
-    METHOD(NadeOffhand, offhand_think, void(NadeOffhand this, entity player, bool key_pressed))
-    {
-       entity held_nade = player.nade;
-               if (held_nade)
-               {
-                       player.nade_timer = bound(0, (time - held_nade.nade_time_primed) / autocvar_g_nades_nade_lifetime, 1);
-                       // LOG_TRACEF("%d %d\n", player.nade_timer, time - held_nade.nade_time_primed);
-                       makevectors(player.angles);
-                       held_nade.velocity = player.velocity;
-                       setorigin(held_nade, player.origin + player.view_ofs + v_forward * 8 + v_right * -8 + v_up * 0);
-                       held_nade.angles_y = player.angles.y;
-
-                       if (time + 0.1 >= held_nade.wait)
-                               toss_nade(player, '0 0 0', time + 0.05);
-               }
-
-        if (!CanThrowNade()) return;
-        if (!(time > player.nade_refire)) return;
-               if (key_pressed) {
-                       if (!held_nade) {
-                               nade_prime();
-                               held_nade = player.nade;
-                       }
-               } else if (time >= held_nade.nade_time_primed + 1) {
-                       if (held_nade) {
-                               makevectors(player.v_angle);
-                               float _force = time - held_nade.nade_time_primed;
-                               _force /= autocvar_g_nades_nade_lifetime;
-                               _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce));
-                               toss_nade(player, (v_forward * 0.7 + v_up * 0.2 + v_right * 0.1) * _force, 0);
-                       }
-               }
-    }
-ENDCLASS(NadeOffhand)
-NadeOffhand OFFHAND_NADE; STATIC_INIT(OFFHAND_NADE) { OFFHAND_NADE = NEW(NadeOffhand); }
-
-MUTATOR_HOOKFUNCTION(nades, ForbidThrowCurrentWeapon, CBC_ORDER_LAST)
-{
-       if (self.offhand != OFFHAND_NADE || (self.weapons & WEPSET(HOOK)) || autocvar_g_nades_override_dropweapon) {
-               nades_CheckThrow();
-               return true;
-       }
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(nades, PlayerPreThink)
-{SELFPARAM();
-       if (!IS_PLAYER(self)) { return false; }
-
-       if (self.nade && (self.offhand != OFFHAND_NADE || (self.weapons & WEPSET(HOOK)))) OFFHAND_NADE.offhand_think(OFFHAND_NADE, self, self.nade_altbutton);
-
-       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; }
-
-                       float time_score;
-                       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, Nades_COUNT);
-
-                       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;
-}
-
-MUTATOR_HOOKFUNCTION(nades, PlayerSpawn)
-{SELFPARAM();
-       if(autocvar_g_nades_spawn)
-               self.nade_refire = time + autocvar_g_spawnshieldtime;
-       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.offhand) self.offhand = OFFHAND_NADE;
-
-       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, CBC_ORDER_LAST)
-{
-       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_Calculate)
-{
-       if(frag_target.frozen)
-       if(autocvar_g_freezetag_revive_nade)
-       if(frag_attacker == frag_target)
-       if(frag_deathtype == DEATH_NADE.m_id)
-       if(time - frag_inflictor.toss_time <= 0.1)
-       {
-               Unfreeze(frag_target);
-               frag_target.health = autocvar_g_freezetag_revive_nade_health;
-               Send_Effect(EFFECT_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)
-{SELFPARAM();
-       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, DropSpecialItems)
-{
-       if(frag_target.nade)
-               toss_nade(frag_target, '0 0 0', time + 0.05);
-
-       return false;
-}
-
-bool nades_RemovePlayer()
-{SELFPARAM();
-       nades_Clear(self);
-       nades_RemoveBonus(self);
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(nades, MakePlayerObserver) { nades_RemovePlayer(); }
-MUTATOR_HOOKFUNCTION(nades, ClientDisconnect) { nades_RemovePlayer(); }
-MUTATOR_HOOKFUNCTION(nades, reset_map_global) { nades_RemovePlayer(); }
-
-MUTATOR_HOOKFUNCTION(nades, SpectateCopy)
-{SELFPARAM();
-       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;
-}
-
-MUTATOR_HOOKFUNCTION(nades, BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":Nades");
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(nades, BuildMutatorsPrettyString)
-{
-       ret_string = strcat(ret_string, ", Nades");
-       return false;
-}
diff --git a/qcsrc/server/mutators/mutator_nades.qh b/qcsrc/server/mutators/mutator_nades.qh
deleted file mode 100644 (file)
index 8f571b8..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-#ifndef MUTATOR_NADES_H
-#define MUTATOR_NADES_H
-
-.entity nade;
-.entity fake_nade;
-.float nade_timer;
-.float nade_refire;
-.float bonus_nades;
-.float nade_special_time;
-.float bonus_nade_score;
-.float nade_type;
-.string pokenade_type;
-.entity nade_damage_target;
-.float cvar_cl_nade_type;
-.string cvar_cl_pokenade_type;
-.float toss_time;
-.float stat_healing_orb;
-.float stat_healing_orb_alpha;
-.float nade_show_particles;
-
-// Remove nades that are being thrown
-void(entity player) nades_Clear;
-
-// Give a bonus grenade to a player
-void(entity player, float score) nades_GiveBonus;
-// Remove all bonus nades from a player
-void(entity player) nades_RemoveBonus;
-#endif
diff --git a/qcsrc/server/mutators/mutator_new_toys.qc b/qcsrc/server/mutators/mutator_new_toys.qc
deleted file mode 100644 (file)
index d7fc525..0000000
+++ /dev/null
@@ -1,228 +0,0 @@
-
-#include "mutator.qh"
-
-/*
-
-CORE    laser   vortex     lg      rl      cry     gl      elec    hagar   fireb   hook
-                                                                       vaporizer  porto
-                                                                       tuba
-
-NEW             rifle   hlac    minel                           seeker
-IDEAS                                   OPEN    flak    OPEN            FUN FUN FUN FUN
-
-
-
-How this mutator works:
- =======================
-
-When a gun tries to spawn, this mutator is called. It will provide alternate
-weaponreplace lists.
-
-Entity:
-
-{
-"classname" "weapon_vortex"
-"new_toys" "rifle"
-}
--> This will spawn as Rifle in this mutator ONLY, and as Vortex otherwise.
-
-{
-"classname" "weapon_vortext"
-"new_toys" "vortex rifle"
-}
--> This will spawn as either Vortex or Rifle in this mutator ONLY, and as Vortex otherwise.
-
-{
-"classname" "weapon_vortex"
-"new_toys" "vortex"
-}
--> This is always a Vortex.
-
-If the map specifies no "new_toys" argument
-
-There will be two default replacements selectable: "replace all" and "replace random".
-In "replace all" mode, e.g. Vortex will have the default replacement "rifle".
-In "replace random" mode, Vortex will have the default replacement "vortex rifle".
-
-This mutator's replacements run BEFORE regular weaponreplace!
-
-The New Toys guns do NOT get a spawn function, so they can only ever be spawned
-when this mutator is active.
-
-Likewise, warmup, give all, give ALL and impulse 99 will not give them unless
-this mutator is active.
-
-Outside this mutator, they still can be spawned by:
-- setting their start weapon cvar to 1
-- give weaponname
-- weaponreplace
-- weaponarena (but all and most weapons arena again won't include them)
-
-This mutator performs the default replacements on the DEFAULTS of the
-start weapon selection.
-
-These weapons appear in the menu's priority list, BUT get a suffix
-"(Mutator weapon)".
-
-Picking up a "new toys" weapon will not play standard weapon pickup sound, but
-roflsound "New toys, new toys!" sound.
-
-*/
-
-bool nt_IsNewToy(int w);
-
-REGISTER_MUTATOR(nt, cvar("g_new_toys") && !cvar("g_instagib") && !cvar("g_overkill"))
-{
-       MUTATOR_ONADD
-       {
-               if(time > 1) // game loads at time 1
-                       error("This cannot be added at runtime\n");
-
-               // mark the guns as ok to use by e.g. impulse 99
-               for(int i = WEP_FIRST; i <= WEP_LAST; ++i)
-                       if(nt_IsNewToy(i))
-                               get_weaponinfo(i).spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               for(int i = WEP_FIRST; i <= WEP_LAST; ++i)
-                       if(nt_IsNewToy(i))
-                               get_weaponinfo(i).spawnflags |= WEP_FLAG_MUTATORBLOCKED;
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This cannot be removed at runtime\n");
-               return -1;
-       }
-
-       return 0;
-}
-
-.string new_toys;
-
-float autocvar_g_new_toys_autoreplace;
-bool autocvar_g_new_toys_use_pickupsound = true;
-const float NT_AUTOREPLACE_NEVER = 0;
-const float NT_AUTOREPLACE_ALWAYS = 1;
-const float NT_AUTOREPLACE_RANDOM = 2;
-
-MUTATOR_HOOKFUNCTION(nt, SetModname)
-{
-       modname = "NewToys";
-       return 0;
-}
-
-bool nt_IsNewToy(int w)
-{
-       switch(w)
-       {
-               case WEP_SEEKER.m_id:
-               case WEP_MINE_LAYER.m_id:
-               case WEP_HLAC.m_id:
-               case WEP_RIFLE.m_id:
-               case WEP_SHOCKWAVE.m_id:
-                       return true;
-               default:
-                       return false;
-       }
-}
-
-string nt_GetFullReplacement(string w)
-{
-       switch(w)
-       {
-               case "hagar": return "seeker";
-               case "devastator": return "minelayer";
-               case "machinegun": return "hlac";
-               case "vortex": return "rifle";
-               //case "shotgun": return "shockwave";
-               default: return string_null;
-       }
-}
-
-string nt_GetReplacement(string w, float m)
-{
-       if(m == NT_AUTOREPLACE_NEVER)
-               return w;
-       string s = nt_GetFullReplacement(w);
-       if (!s)
-               return w;
-       if(m == NT_AUTOREPLACE_RANDOM)
-               s = strcat(w, " ", s);
-       return s;
-}
-
-MUTATOR_HOOKFUNCTION(nt, SetStartItems)
-{
-       // rearrange start_weapon_default
-       // apply those bits that are set by start_weapon_defaultmask
-       // same for warmup
-
-       float i, j, k, n;
-
-       WepSet newdefault;
-       WepSet warmup_newdefault;
-
-       newdefault = '0 0 0';
-       warmup_newdefault = '0 0 0';
-
-       for(i = WEP_FIRST; i <= WEP_LAST; ++i)
-       {
-               entity e = get_weaponinfo(i);
-               if(!e.weapon)
-                       continue;
-
-               n = tokenize_console(nt_GetReplacement(e.netname, autocvar_g_new_toys_autoreplace));
-
-               for(j = 0; j < n; ++j)
-                       for(k = WEP_FIRST; k <= WEP_LAST; ++k)
-                               if(get_weaponinfo(k).netname == argv(j))
-                               {
-                                       if(start_weapons & WepSet_FromWeapon(i))
-                                               newdefault |= WepSet_FromWeapon(k);
-                                       if(warmup_start_weapons & WepSet_FromWeapon(i))
-                                               warmup_newdefault |= WepSet_FromWeapon(k);
-                               }
-       }
-
-       newdefault &= start_weapons_defaultmask;
-       start_weapons &= ~start_weapons_defaultmask;
-       start_weapons |= newdefault;
-
-       warmup_newdefault &= warmup_start_weapons_defaultmask;
-       warmup_start_weapons &= ~warmup_start_weapons_defaultmask;
-       warmup_start_weapons |= warmup_newdefault;
-
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(nt, SetWeaponreplace)
-{SELFPARAM();
-       // otherwise, we do replace
-       if(self.new_toys)
-       {
-               // map defined replacement:
-               ret_string = self.new_toys;
-       }
-       else
-       {
-               // auto replacement:
-               ret_string = nt_GetReplacement(other.netname, autocvar_g_new_toys_autoreplace);
-       }
-
-       // apply regular weaponreplace
-       ret_string = W_Apply_Weaponreplace(ret_string);
-
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(nt, FilterItem)
-{SELFPARAM();
-       if(nt_IsNewToy(self.weapon) && autocvar_g_new_toys_use_pickupsound) {
-               self.item_pickupsound = string_null;
-               self.item_pickupsound_ent = SND_WEAPONPICKUP_NEW_TOYS;
-       }
-       return 0;
-}
diff --git a/qcsrc/server/mutators/mutator_nix.qc b/qcsrc/server/mutators/mutator_nix.qc
deleted file mode 100644 (file)
index 96e8ca9..0000000
+++ /dev/null
@@ -1,268 +0,0 @@
-
-#include "mutator.qh"
-
-float g_nix_with_blaster;
-// WEAPONTODO
-int nix_weapon;
-float nix_nextchange;
-float nix_nextweapon;
-.float nix_lastchange_id;
-.float nix_lastinfotime;
-.float nix_nextincr;
-
-bool NIX_CanChooseWeapon(int wpn);
-
-REGISTER_MUTATOR(nix, cvar("g_nix") && !cvar("g_instagib") && !cvar("g_overkill"))
-{
-       MUTATOR_ONADD
-       {
-               g_nix_with_blaster = autocvar_g_nix_with_blaster;
-
-               nix_nextchange = 0;
-               nix_nextweapon = 0;
-
-               for (int i = WEP_FIRST; i <= WEP_LAST; ++i)
-                       if (NIX_CanChooseWeapon(i)) {
-                               Weapon w = get_weaponinfo(i);
-                               w.wr_init(w);
-                       }
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // nothing to roll back
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               // as the PlayerSpawn hook will no longer run, NIX is turned off by this!
-               entity e;
-               FOR_EACH_PLAYER(e) if(e.deadflag == DEAD_NO)
-               {
-                       e.ammo_cells = start_ammo_cells;
-                       e.ammo_plasma = start_ammo_plasma;
-                       e.ammo_shells = start_ammo_shells;
-                       e.ammo_nails = start_ammo_nails;
-                       e.ammo_rockets = start_ammo_rockets;
-                       e.ammo_fuel = start_ammo_fuel;
-                       e.weapons = start_weapons;
-                       if(!client_hasweapon(e, e.weapon, true, false))
-                               e.switchweapon = w_getbestweapon(self);
-               }
-       }
-
-       return 0;
-}
-
-bool NIX_CanChooseWeapon(int wpn)
-{
-       entity e = get_weaponinfo(wpn);
-       if(!e.weapon) // skip dummies
-               return false;
-       if(g_weaponarena)
-       {
-               if(!(g_weaponarena_weapons & WepSet_FromWeapon(wpn)))
-                       return false;
-       }
-       else
-       {
-               if(wpn == WEP_BLASTER.m_id && g_nix_with_blaster)
-                       return false;
-               if(e.spawnflags & WEP_FLAG_MUTATORBLOCKED)
-                       return false;
-               if (!(e.spawnflags & WEP_FLAG_NORMAL))
-                       return false;
-       }
-       return true;
-}
-void NIX_ChooseNextWeapon()
-{
-       float j;
-       RandomSelection_Init();
-       for(j = WEP_FIRST; j <= WEP_LAST; ++j)
-               if(NIX_CanChooseWeapon(j))
-                       RandomSelection_Add(world, j, string_null, 1, (j != nix_weapon));
-       nix_nextweapon = RandomSelection_chosen_float;
-}
-
-void NIX_GiveCurrentWeapon()
-{SELFPARAM();
-       float dt;
-
-       if(!nix_nextweapon)
-               NIX_ChooseNextWeapon();
-
-       dt = ceil(nix_nextchange - time);
-
-       if(dt <= 0)
-       {
-               nix_weapon = nix_nextweapon;
-               nix_nextweapon = 0;
-               if (!nix_nextchange) // no round played yet?
-                       nix_nextchange = time; // start the first round now!
-               else
-                       nix_nextchange = time + autocvar_g_balance_nix_roundtime;
-               // Weapon w = get_weaponinfo(nix_weapon);
-               // w.wr_init(w); // forget it, too slow
-       }
-
-       // get weapon info
-       entity e = get_weaponinfo(nix_weapon);
-
-       if(nix_nextchange != self.nix_lastchange_id) // this shall only be called once per round!
-       {
-               self.ammo_shells = self.ammo_nails = self.ammo_rockets = self.ammo_cells = self.ammo_plasma = self.ammo_fuel = 0;
-
-               if(self.items & IT_UNLIMITED_WEAPON_AMMO)
-               {
-                       switch(e.ammo_field)
-                       {
-                               case ammo_shells:  self.ammo_shells  = autocvar_g_pickup_shells_max;  break;
-                               case ammo_nails:   self.ammo_nails   = autocvar_g_pickup_nails_max;   break;
-                               case ammo_rockets: self.ammo_rockets = autocvar_g_pickup_rockets_max; break;
-                               case ammo_cells:   self.ammo_cells   = autocvar_g_pickup_cells_max;   break;
-                               case ammo_plasma:  self.ammo_plasma  = autocvar_g_pickup_plasma_max;   break;
-                               case ammo_fuel:    self.ammo_fuel    = autocvar_g_pickup_fuel_max;    break;
-                       }
-               }
-               else
-               {
-                       switch(e.ammo_field)
-                       {
-                               case ammo_shells:  self.ammo_shells  = autocvar_g_balance_nix_ammo_shells;  break;
-                               case ammo_nails:   self.ammo_nails   = autocvar_g_balance_nix_ammo_nails;   break;
-                               case ammo_rockets: self.ammo_rockets = autocvar_g_balance_nix_ammo_rockets; break;
-                               case ammo_cells:   self.ammo_cells   = autocvar_g_balance_nix_ammo_cells;   break;
-                               case ammo_plasma:  self.ammo_plasma  = autocvar_g_balance_nix_ammo_plasma;   break;
-                               case ammo_fuel:    self.ammo_fuel    = autocvar_g_balance_nix_ammo_fuel;    break;
-                       }
-               }
-
-               self.nix_nextincr = time + autocvar_g_balance_nix_incrtime;
-               if(dt >= 1 && dt <= 5)
-                       self.nix_lastinfotime = -42;
-               else
-                       Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_NIX_NEWWEAPON, nix_weapon);
-
-               Weapon w = get_weaponinfo(nix_weapon);
-               w.wr_resetplayer(w);
-
-               // all weapons must be fully loaded when we spawn
-               if(e.spawnflags & WEP_FLAG_RELOADABLE) // prevent accessing undefined cvars
-                       self.(weapon_load[nix_weapon]) = e.reloading_ammo;
-
-               // vortex too
-               if(WEP_CVAR(vortex, charge))
-               {
-                       if(WEP_CVAR_SEC(vortex, chargepool))
-                               self.vortex_chargepool_ammo = 1;
-                       self.vortex_charge = WEP_CVAR(vortex, charge_start);
-               }
-
-               // set last change info
-               self.nix_lastchange_id = nix_nextchange;
-       }
-       if(self.nix_lastinfotime != dt)
-       {
-               self.nix_lastinfotime = dt; // initial value 0 should count as "not seen"
-               if(dt >= 1 && dt <= 5)
-                       Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_NIX_COUNTDOWN, nix_nextweapon, dt);
-       }
-
-       if(!(self.items & IT_UNLIMITED_WEAPON_AMMO) && time > self.nix_nextincr)
-       {
-               switch(e.ammo_field)
-               {
-                       case ammo_shells:  self.ammo_shells  += autocvar_g_balance_nix_ammoincr_shells;  break;
-                       case ammo_nails:   self.ammo_nails   += autocvar_g_balance_nix_ammoincr_nails;   break;
-                       case ammo_rockets: self.ammo_rockets += autocvar_g_balance_nix_ammoincr_rockets; break;
-                       case ammo_cells:   self.ammo_cells   += autocvar_g_balance_nix_ammoincr_cells;   break;
-                       case ammo_plasma:  self.ammo_plasma  += autocvar_g_balance_nix_ammoincr_plasma;   break;
-                       case ammo_fuel:    self.ammo_fuel    += autocvar_g_balance_nix_ammoincr_fuel;    break;
-               }
-
-               self.nix_nextincr = time + autocvar_g_balance_nix_incrtime;
-       }
-
-       self.weapons = '0 0 0';
-       if(g_nix_with_blaster)
-               self.weapons |= WEPSET(BLASTER);
-       self.weapons |= WepSet_FromWeapon(nix_weapon);
-
-       if(self.switchweapon != nix_weapon)
-               if(!client_hasweapon(self, self.switchweapon, true, false))
-                       if(client_hasweapon(self, nix_weapon, true, false))
-                               W_SwitchWeapon(nix_weapon);
-}
-
-MUTATOR_HOOKFUNCTION(nix, ForbidThrowCurrentWeapon)
-{
-       return 1; // no throwing in NIX
-}
-
-MUTATOR_HOOKFUNCTION(nix, BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":NIX");
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(nix, BuildMutatorsPrettyString)
-{
-       ret_string = strcat(ret_string, ", NIX");
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(nix, FilterItem)
-{SELFPARAM();
-       switch (self.items)
-       {
-               case ITEM_HealthSmall.m_itemid:
-               case ITEM_HealthMedium.m_itemid:
-               case ITEM_HealthLarge.m_itemid:
-               case ITEM_HealthMega.m_itemid:
-               case ITEM_ArmorSmall.m_itemid:
-               case ITEM_ArmorMedium.m_itemid:
-               case ITEM_ArmorLarge.m_itemid:
-               case ITEM_ArmorMega.m_itemid:
-                       if (autocvar_g_nix_with_healtharmor)
-                               return 0;
-                       break;
-               case ITEM_Strength.m_itemid:
-               case ITEM_Shield.m_itemid:
-                       if (autocvar_g_nix_with_powerups)
-                               return 0;
-                       break;
-       }
-
-       return 1; // delete all other items
-}
-
-MUTATOR_HOOKFUNCTION(nix, OnEntityPreSpawn)
-{SELFPARAM();
-       if(self.classname == "target_items") // items triggers cannot work in nix (as they change weapons/ammo)
-               return 1;
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(nix, PlayerPreThink)
-{SELFPARAM();
-       if(!intermission_running)
-       if(self.deadflag == DEAD_NO)
-       if(IS_PLAYER(self))
-               NIX_GiveCurrentWeapon();
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(nix, PlayerSpawn)
-{SELFPARAM();
-       self.nix_lastchange_id = -1;
-       NIX_GiveCurrentWeapon(); // overrides the weapons you got when spawning
-       self.items |= IT_UNLIMITED_SUPERWEAPONS;
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(nix, SetModname, CBC_ORDER_LAST)
-{
-       modname = "NIX";
-       return 0;
-}
diff --git a/qcsrc/server/mutators/mutator_overkill.qc b/qcsrc/server/mutators/mutator_overkill.qc
deleted file mode 100644 (file)
index 79607a1..0000000
+++ /dev/null
@@ -1,351 +0,0 @@
-#include "mutator_overkill.qh"
-
-#include "mutator.qh"
-
-void ok_Initialize();
-
-REGISTER_MUTATOR(ok, cvar("g_overkill") && !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill")
-{
-       MUTATOR_ONADD
-       {
-               ok_Initialize();
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               WEP_RPC.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
-               WEP_HMG.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
-       }
-
-       return false;
-}
-
-void W_Blaster_Attack(entity, float, float, float, float, float, float, float, float, float, float);
-spawnfunc(weapon_hmg);
-spawnfunc(weapon_rpc);
-
-void ok_DecreaseCharge(entity ent, int wep)
-{
-       if(!ent.ok_use_ammocharge) return;
-
-       entity wepent = get_weaponinfo(wep);
-
-       if(wepent.weapon == 0)
-               return; // dummy
-
-       ent.ammo_charge[wep] -= max(0, cvar(sprintf("g_overkill_ammo_decharge_%s", wepent.netname)));
-}
-
-void ok_IncreaseCharge(entity ent, int wep)
-{
-       entity wepent = get_weaponinfo(wep);
-
-       if(wepent.weapon == 0)
-               return; // dummy
-
-       if(ent.ok_use_ammocharge)
-       if(!ent.BUTTON_ATCK) // not while attacking?
-               ent.ammo_charge[wep] = min(autocvar_g_overkill_ammo_charge_limit, ent.ammo_charge[wep] + cvar(sprintf("g_overkill_ammo_charge_rate_%s", wepent.netname)) * frametime / W_TICSPERFRAME);
-}
-
-float ok_CheckWeaponCharge(entity ent, int wep)
-{
-       if(!ent.ok_use_ammocharge) return true;
-
-       entity wepent = get_weaponinfo(wep);
-
-       if(wepent.weapon == 0)
-               return 0; // dummy
-
-       return (ent.ammo_charge[wep] >= cvar(sprintf("g_overkill_ammo_decharge_%s", wepent.netname)));
-}
-
-MUTATOR_HOOKFUNCTION(ok, PlayerDamage_Calculate, CBC_ORDER_LAST)
-{
-       if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target))
-       if(DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER))
-       {
-               frag_damage = 0;
-
-               if(frag_attacker != frag_target)
-               if(frag_target.health > 0)
-               if(frag_target.frozen == 0)
-               if(frag_target.deadflag == DEAD_NO)
-               {
-                       Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE);
-                       frag_force = '0 0 0';
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ok, PlayerDamage_SplitHealthArmor)
-{SELFPARAM();
-       if(damage_take)
-               self.ok_pauseregen_finished = max(self.ok_pauseregen_finished, time + 2);
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ok, PlayerDies)
-{SELFPARAM();
-       entity targ = ((frag_attacker) ? frag_attacker : frag_target);
-
-       if(IS_MONSTER(self))
-       {
-               remove(other); // remove default item
-               other = world;
-       }
-
-       setself(spawn());
-       self.ok_item = true;
-       self.noalign = true;
-       self.pickup_anyway = true;
-       spawnfunc_item_armor_small(this);
-       self.movetype = MOVETYPE_TOSS;
-       self.gravity = 1;
-       self.reset = SUB_Remove;
-       setorigin(self, frag_target.origin + '0 0 32');
-       self.velocity = '0 0 200' + normalize(targ.origin - self.origin) * 500;
-       self.classname = "droppedweapon"; // hax
-       SUB_SetFade(self, time + 5, 1);
-       setself(this);
-
-       self.ok_lastwep = self.switchweapon;
-
-       return false;
-}
-MUTATOR_HOOKFUNCTION(ok, MonsterDropItem) { ok_PlayerDies(); }
-
-MUTATOR_HOOKFUNCTION(ok, PlayerRegen)
-{SELFPARAM();
-       // overkill's values are different, so use custom regen
-       if(!self.frozen)
-       {
-               self.armorvalue = CalcRotRegen(self.armorvalue, autocvar_g_balance_armor_regenstable, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, 1 * frametime * (time > self.ok_pauseregen_finished), 0, 0, 1, 1 * frametime * (time > self.pauserotarmor_finished), autocvar_g_balance_armor_limit);
-               self.health = CalcRotRegen(self.health, autocvar_g_balance_health_regenstable, 0, 100, 1 * frametime * (time > self.ok_pauseregen_finished), 200, 0, autocvar_g_balance_health_rotlinear, 1 * frametime * (time > self.pauserothealth_finished), autocvar_g_balance_health_limit);
-
-               float minf, maxf, limitf;
-
-               maxf = autocvar_g_balance_fuel_rotstable;
-               minf = autocvar_g_balance_fuel_regenstable;
-               limitf = autocvar_g_balance_fuel_limit;
-
-               self.ammo_fuel = CalcRotRegen(self.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > self.pauseregen_finished) * ((self.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > self.pauserotfuel_finished), limitf);
-       }
-       return true; // return true anyway, as frozen uses no regen
-}
-
-MUTATOR_HOOKFUNCTION(ok, ForbidThrowCurrentWeapon)
-{
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ok, PlayerPreThink)
-{SELFPARAM();
-       if(intermission_running || gameover)
-               return false;
-
-       if(self.deadflag != DEAD_NO || !IS_PLAYER(self) || self.frozen)
-               return false;
-
-       if(self.ok_lastwep)
-       {
-               self.switchweapon = self.ok_lastwep;
-               self.ok_lastwep = 0;
-       }
-
-       ok_IncreaseCharge(self, self.weapon);
-
-       if(self.BUTTON_ATCK2)
-       if(!forbidWeaponUse(self) || self.weapon_blocked) // allow if weapon is blocked
-       if(time >= self.jump_interval)
-       {
-               self.jump_interval = time + WEP_CVAR_PRI(blaster, refire) * W_WeaponRateFactor();
-               makevectors(self.v_angle);
-
-               int oldwep = self.weapon;
-               self.weapon = WEP_BLASTER.m_id;
-               W_Blaster_Attack(
-                       self,
-                       WEP_BLASTER.m_id | HITTYPE_SECONDARY,
-                       WEP_CVAR_SEC(vaporizer, shotangle),
-                       WEP_CVAR_SEC(vaporizer, damage),
-                       WEP_CVAR_SEC(vaporizer, edgedamage),
-                       WEP_CVAR_SEC(vaporizer, radius),
-                       WEP_CVAR_SEC(vaporizer, force),
-                       WEP_CVAR_SEC(vaporizer, speed),
-                       WEP_CVAR_SEC(vaporizer, spread),
-                       WEP_CVAR_SEC(vaporizer, delay),
-                       WEP_CVAR_SEC(vaporizer, lifetime)
-               );
-               self.weapon = oldwep;
-       }
-
-       self.weapon_blocked = false;
-
-       self.ok_ammo_charge = self.ammo_charge[self.weapon];
-
-       if(self.ok_use_ammocharge)
-       if(!ok_CheckWeaponCharge(self, self.weapon))
-       {
-               if(autocvar_g_overkill_ammo_charge_notice && time > self.ok_notice_time && self.BUTTON_ATCK && IS_REAL_CLIENT(self) && self.weapon == self.switchweapon)
-               {
-                       //Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_OVERKILL_CHARGE);
-                       self.ok_notice_time = time + 2;
-                       play2(self, SND(DRYFIRE));
-               }
-               Weapon wpn = get_weaponinfo(self.weapon);
-               if(self.weaponentity.state != WS_CLEAR)
-                       w_ready(wpn, self, self.BUTTON_ATCK, self.BUTTON_ATCK2);
-
-               self.weapon_blocked = true;
-       }
-
-       self.BUTTON_ATCK2 = 0;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ok, PlayerSpawn)
-{SELFPARAM();
-       if(autocvar_g_overkill_ammo_charge)
-       {
-               for(int i = WEP_FIRST; i <= WEP_LAST; ++i)
-                       self.ammo_charge[i] = autocvar_g_overkill_ammo_charge_limit;
-
-               self.ok_use_ammocharge = 1;
-               self.ok_notice_time = time;
-       }
-       else
-               self.ok_use_ammocharge = 0;
-
-       self.ok_pauseregen_finished = time + 2;
-
-       return false;
-}
-
-void _spawnfunc_weapon_hmg() { SELFPARAM(); spawnfunc_weapon_hmg(this); }
-void _spawnfunc_weapon_rpc() { SELFPARAM(); spawnfunc_weapon_rpc(this); }
-
-MUTATOR_HOOKFUNCTION(ok, OnEntityPreSpawn)
-{SELFPARAM();
-       if(autocvar_g_powerups)
-       if(autocvar_g_overkill_powerups_replace)
-       {
-               if(self.classname == "item_strength")
-               {
-                       entity wep = spawn();
-                       setorigin(wep, self.origin);
-                       setmodel(wep, MDL_OK_HMG);
-                       wep.classname = "weapon_hmg";
-                       wep.ok_item = true;
-                       wep.noalign = self.noalign;
-                       wep.cnt = self.cnt;
-                       wep.team = self.team;
-                       wep.respawntime = autocvar_g_overkill_superguns_respawn_time;
-                       wep.pickup_anyway = true;
-                       wep.think = _spawnfunc_weapon_hmg;
-                       wep.nextthink = time + 0.1;
-                       return true;
-               }
-
-               if(self.classname == "item_invincible")
-               {
-                       entity wep = spawn();
-                       setorigin(wep, self.origin);
-                       setmodel(wep, MDL_OK_RPC);
-                       wep.classname = "weapon_rpc";
-                       wep.ok_item = true;
-                       wep.noalign = self.noalign;
-                       wep.cnt = self.cnt;
-                       wep.team = self.team;
-                       wep.respawntime = autocvar_g_overkill_superguns_respawn_time;
-                       wep.pickup_anyway = true;
-                       wep.think = _spawnfunc_weapon_rpc;
-                       wep.nextthink = time + 0.1;
-                       return true;
-               }
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ok, FilterItem)
-{SELFPARAM();
-       if(self.ok_item)
-               return false;
-
-       switch(self.items)
-       {
-               case ITEM_HealthMega.m_itemid: return !(autocvar_g_overkill_100h_anyway);
-               case ITEM_ArmorMega.m_itemid: return !(autocvar_g_overkill_100a_anyway);
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ok, SpectateCopy)
-{SELFPARAM();
-       self.ammo_charge[self.weapon] = other.ammo_charge[other.weapon];
-       self.ok_use_ammocharge = other.ok_use_ammocharge;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ok, SetStartItems)
-{
-       WepSet ok_start_items = (WEPSET(MACHINEGUN) | WEPSET(VORTEX) | WEPSET(SHOTGUN));
-
-       if(WEP_RPC.weaponstart > 0) { ok_start_items |= WEPSET(RPC); }
-       if(WEP_HMG.weaponstart > 0) { ok_start_items |= WEPSET(HMG); }
-
-       start_items |= IT_UNLIMITED_WEAPON_AMMO;
-       start_weapons = warmup_start_weapons = ok_start_items;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ok, BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":OK");
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ok, BuildMutatorsPrettyString)
-{
-       ret_string = strcat(ret_string, ", Overkill");
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ok, SetModname)
-{
-       modname = "Overkill";
-       return true;
-}
-
-void ok_SetCvars()
-{
-       // hack to force overkill playermodels
-       cvar_settemp("sv_defaultcharacter", "1");
-       cvar_settemp("sv_defaultplayermodel", "models/ok_player/okrobot1.dpm models/ok_player/okrobot2.dpm models/ok_player/okrobot3.dpm models/ok_player/okrobot4.dpm models/ok_player/okmale1.dpm models/ok_player/okmale2.dpm models/ok_player/okmale3.dpm models/ok_player/okmale4.dpm");
-       cvar_settemp("sv_defaultplayermodel_red", "models/ok_player/okrobot1.dpm models/ok_player/okrobot2.dpm models/ok_player/okrobot3.dpm models/ok_player/okrobot4.dpm");
-       cvar_settemp("sv_defaultplayermodel_blue", "models/ok_player/okmale1.dpm models/ok_player/okmale2.dpm models/ok_player/okmale3.dpm models/ok_player/okmale4.dpm");
-}
-
-void ok_Initialize()
-{
-       ok_SetCvars();
-
-       precache_all_playermodels("models/ok_player/*.dpm");
-
-       addstat(STAT_OK_AMMO_CHARGE, AS_FLOAT, ok_use_ammocharge);
-       addstat(STAT_OK_AMMO_CHARGEPOOL, AS_FLOAT, ok_ammo_charge);
-
-       WEP_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
-       WEP_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
-
-       WEP_SHOTGUN.mdl = "ok_shotgun";
-       WEP_MACHINEGUN.mdl = "ok_mg";
-       WEP_VORTEX.mdl = "ok_sniper";
-}
diff --git a/qcsrc/server/mutators/mutator_overkill.qh b/qcsrc/server/mutators/mutator_overkill.qh
deleted file mode 100644 (file)
index 10f89c0..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef MUTATOR_OVERKILL_H
-#define MUTATOR_OVERKILL_H
-
-.vector ok_deathloc;
-.float ok_spawnsys_timer;
-.float ok_lastwep;
-.float ok_item;
-
-.float ok_notice_time;
-.float ammo_charge[Weapons_MAX];
-.float ok_use_ammocharge;
-.float ok_ammo_charge;
-
-.float ok_pauseregen_finished;
-
-void(entity ent, float wep) ok_DecreaseCharge;
-
-#endif
diff --git a/qcsrc/server/mutators/mutator_physical_items.qc b/qcsrc/server/mutators/mutator_physical_items.qc
deleted file mode 100644 (file)
index 4bcf495..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-
-#include "mutator.qh"
-
-REGISTER_MUTATOR(physical_items, cvar("g_physical_items"))
-{
-       // check if we have a physics engine
-       MUTATOR_ONADD
-       {
-               if (!(autocvar_physics_ode && checkextension("DP_PHYSICS_ODE")))
-               {
-                       LOG_TRACE("Warning: Physical items are enabled but no physics engine can be used. Reverting to old items.\n");
-                       return -1;
-               }
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // nothing to roll back
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               LOG_INFO("This cannot be removed at runtime\n");
-               return -1;
-       }
-
-       return 0;
-}
-
-.vector spawn_origin, spawn_angles;
-
-void physical_item_think()
-{SELFPARAM();
-       self.nextthink = time;
-
-       self.alpha = self.owner.alpha; // apply fading and ghosting
-
-       if(!self.cnt) // map item, not dropped
-       {
-               // copy ghost item properties
-               self.colormap = self.owner.colormap;
-               self.colormod = self.owner.colormod;
-               self.glowmod = self.owner.glowmod;
-
-               // if the item is not spawned, make sure the invisible / ghost item returns to its origin and stays there
-               if(autocvar_g_physical_items_reset)
-               {
-                       if(self.owner.wait > time) // awaiting respawn
-                       {
-                               setorigin(self, self.spawn_origin);
-                               self.angles = self.spawn_angles;
-                               self.solid = SOLID_NOT;
-                               self.alpha = -1;
-                               self.movetype = MOVETYPE_NONE;
-                       }
-                       else
-                       {
-                               self.alpha = 1;
-                               self.solid = SOLID_CORPSE;
-                               self.movetype = MOVETYPE_PHYSICS;
-                       }
-               }
-       }
-
-       if(!self.owner.modelindex)
-               remove(self); // the real item is gone, remove this
-}
-
-void physical_item_touch()
-{SELFPARAM();
-       if(!self.cnt) // not for dropped items
-       if (ITEM_TOUCH_NEEDKILL())
-       {
-               setorigin(self, self.spawn_origin);
-               self.angles = self.spawn_angles;
-       }
-}
-
-void physical_item_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{SELFPARAM();
-       if(!self.cnt) // not for dropped items
-       if(ITEM_DAMAGE_NEEDKILL(deathtype))
-       {
-               setorigin(self, self.spawn_origin);
-               self.angles = self.spawn_angles;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(physical_items, Item_Spawn)
-{SELFPARAM();
-       if(self.owner == world && autocvar_g_physical_items <= 1)
-               return false;
-       if (self.spawnflags & 1) // floating item
-               return false;
-
-       // The actual item can't be physical and trigger at the same time, so make it invisible and use a second entity for physics.
-       // Ugly hack, but unless SOLID_TRIGGER is gotten to work with MOVETYPE_PHYSICS in the engine it can't be fixed.
-       entity wep;
-       wep = spawn();
-       _setmodel(wep, self.model);
-       setsize(wep, self.mins, self.maxs);
-       setorigin(wep, self.origin);
-       wep.angles = self.angles;
-       wep.velocity = self.velocity;
-
-       wep.owner = self;
-       wep.solid = SOLID_CORPSE;
-       wep.movetype = MOVETYPE_PHYSICS;
-       wep.takedamage = DAMAGE_AIM;
-       wep.effects |= EF_NOMODELFLAGS; // disable the spinning
-       wep.colormap = self.owner.colormap;
-       wep.glowmod = self.owner.glowmod;
-       wep.damageforcescale = autocvar_g_physical_items_damageforcescale;
-       wep.dphitcontentsmask = self.dphitcontentsmask;
-       wep.cnt = (self.owner != world);
-
-       wep.think = physical_item_think;
-       wep.nextthink = time;
-       wep.touch = physical_item_touch;
-       wep.event_damage = physical_item_damage;
-
-       if(!wep.cnt)
-       {
-               // fix the spawn origin
-               setorigin(wep, wep.origin + '0 0 1');
-               entity oldself;
-               oldself = self;
-               WITH(entity, self, wep, builtin_droptofloor());
-       }
-
-       wep.spawn_origin = wep.origin;
-       wep.spawn_angles = self.angles;
-
-       self.effects |= EF_NODRAW; // hide the original weapon
-       self.movetype = MOVETYPE_FOLLOW;
-       self.aiment = wep; // attach the original weapon
-       self.SendEntity = func_null;
-
-       return false;
-}
diff --git a/qcsrc/server/mutators/mutator_pinata.qc b/qcsrc/server/mutators/mutator_pinata.qc
deleted file mode 100644 (file)
index a531d6b..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-
-#include "mutator.qh"
-
-REGISTER_MUTATOR(pinata, cvar("g_pinata") && !cvar("g_instagib") && !cvar("g_overkill"));
-
-MUTATOR_HOOKFUNCTION(pinata, PlayerDies)
-{SELFPARAM();
-       for(int j = WEP_FIRST; j <= WEP_LAST; ++j)
-       if(self.weapons & WepSet_FromWeapon(j))
-       if(self.switchweapon != j)
-       if(W_IsWeaponThrowable(j))
-               W_ThrowNewWeapon(self, j, false, self.origin + (self.mins + self.maxs) * 0.5, randomvec() * 175 + '0 0 325');
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(pinata, BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":Pinata");
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(pinata, BuildMutatorsPrettyString)
-{
-       ret_string = strcat(ret_string, ", Piñata");
-       return false;
-}
-
diff --git a/qcsrc/server/mutators/mutator_random_gravity.qc b/qcsrc/server/mutators/mutator_random_gravity.qc
deleted file mode 100644 (file)
index 1a92764..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-
-#include "mutator.qh"
-
-// Random Gravity
-//
-// Mutator by Mario
-// Inspired by Player 2
-
-REGISTER_MUTATOR(random_gravity, cvar("g_random_gravity"))
-{
-       MUTATOR_ONADD
-       {
-               cvar_settemp("sv_gravity", cvar_string("sv_gravity")); // settemp current gravity so it's restored on match end
-       }
-
-       return false;
-}
-
-float gravity_delay;
-
-MUTATOR_HOOKFUNCTION(random_gravity, SV_StartFrame)
-{
-       if(gameover || !cvar("g_random_gravity")) return false;
-       if(time < gravity_delay) return false;
-       if(time < game_starttime) return false;
-       if(round_handler_IsActive() && !round_handler_IsRoundStarted()) return false;
-
-    if(random() >= autocvar_g_random_gravity_negative_chance)
-        cvar_set("sv_gravity", ftos(bound(autocvar_g_random_gravity_min, random() - random() * -autocvar_g_random_gravity_negative, autocvar_g_random_gravity_max)));
-    else
-        cvar_set("sv_gravity", ftos(bound(autocvar_g_random_gravity_min, random() * autocvar_g_random_gravity_positive, autocvar_g_random_gravity_max)));
-
-       gravity_delay = time + autocvar_g_random_gravity_delay;
-
-       LOG_TRACE("Gravity is now: ", ftos(autocvar_sv_gravity), "\n");
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(random_gravity, BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":RandomGravity");
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(random_gravity, BuildMutatorsPrettyString)
-{
-       ret_string = strcat(ret_string, ", Random gravity");
-       return 0;
-}
diff --git a/qcsrc/server/mutators/mutator_rocketflying.qc b/qcsrc/server/mutators/mutator_rocketflying.qc
deleted file mode 100644 (file)
index 44ceeaa..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-
-#include "mutator.qh"
-
-REGISTER_MUTATOR(rocketflying, cvar("g_rocket_flying"));
-
-MUTATOR_HOOKFUNCTION(rocketflying, EditProjectile)
-{
-       if(other.classname == "rocket" || other.classname == "mine")
-       {
-               // kill detonate delay of rockets
-               other.spawnshieldtime = time;
-       }
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(rocketflying, BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":RocketFlying");
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(rocketflying, BuildMutatorsPrettyString)
-{
-       ret_string = strcat(ret_string, ", Rocket Flying");
-       return 0;
-}
diff --git a/qcsrc/server/mutators/mutator_rocketminsta.qc b/qcsrc/server/mutators/mutator_rocketminsta.qc
deleted file mode 100644 (file)
index b7319cf..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-#include "../../common/deathtypes/all.qh"
-#include "../round_handler.qh"
-
-REGISTER_MUTATOR(rm, cvar("g_instagib"));
-
-MUTATOR_HOOKFUNCTION(rm, PlayerDamage_Calculate)
-{
-       // we do it this way, so rm can be toggled during the match
-       if(!autocvar_g_rm) { return false; }
-
-       if(DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR))
-       if(frag_attacker == frag_target || frag_target.classname == "nade")
-               frag_damage = 0;
-
-       if(autocvar_g_rm_laser)
-       if(DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO))
-       if(frag_attacker == frag_target || (round_handler_IsActive() && !round_handler_IsRoundStarted()))
-               frag_damage = 0;
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(rm, PlayerDies)
-{
-       // we do it this way, so rm can be toggled during the match
-       if(!autocvar_g_rm) { return false; }
-
-       if(DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR) || DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO))
-               frag_damage = 1000; // always gib if it was a vaporizer death
-
-       return false;
-}
-
diff --git a/qcsrc/server/mutators/mutator_spawn_near_teammate.qc b/qcsrc/server/mutators/mutator_spawn_near_teammate.qc
deleted file mode 100644 (file)
index 3bc1f7b..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-
-#include "mutator.qh"
-
-REGISTER_MUTATOR(spawn_near_teammate, cvar("g_spawn_near_teammate") && teamplay);
-
-.entity msnt_lookat;
-
-.float msnt_timer;
-.vector msnt_deathloc;
-
-.float cvar_cl_spawn_near_teammate;
-
-MUTATOR_HOOKFUNCTION(spawn_near_teammate, Spawn_Score)
-{SELFPARAM();
-       if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && self.cvar_cl_spawn_near_teammate))
-               return 0;
-
-       entity p;
-
-       spawn_spot.msnt_lookat = world;
-
-       if(!teamplay)
-               return 0;
-
-       RandomSelection_Init();
-       FOR_EACH_PLAYER(p) if(p != self) if(p.team == self.team) if(!p.deadflag)
-       {
-               float l = vlen(spawn_spot.origin - p.origin);
-               if(l > autocvar_g_spawn_near_teammate_distance)
-                       continue;
-               if(l < 48)
-                       continue;
-               if(!checkpvs(spawn_spot.origin, p))
-                       continue;
-               RandomSelection_Add(p, 0, string_null, 1, 1);
-       }
-
-       if(RandomSelection_chosen_ent)
-       {
-               spawn_spot.msnt_lookat = RandomSelection_chosen_ent;
-               spawn_score.x += SPAWN_PRIO_NEAR_TEAMMATE_FOUND;
-       }
-       else if(self.team == spawn_spot.team)
-               spawn_score.x += SPAWN_PRIO_NEAR_TEAMMATE_SAMETEAM; // prefer same team, if we can't find a spawn near teammate
-
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn)
-{SELFPARAM();
-       // Note: when entering this, fixangle is already set.
-       if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && self.cvar_cl_spawn_near_teammate))
-       {
-               if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death)
-                       self.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death;
-
-               entity team_mate, best_mate = world;
-               vector best_spot = '0 0 0';
-               float pc = 0, best_dist = 0, dist = 0;
-               FOR_EACH_PLAYER(team_mate)
-               {
-                       if((autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health >= 0 && team_mate.health >= autocvar_g_balance_health_regenstable) || autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health == 0)
-                       if(team_mate.deadflag == DEAD_NO)
-                       if(team_mate.msnt_timer < time)
-                       if(SAME_TEAM(self, team_mate))
-                       if(time > team_mate.spawnshieldtime) // spawn shielding
-                       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);
-                               if(trace_fraction != 1.0)
-                               if(!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY))
-                               {
-                                       pc = pointcontents(trace_endpos + '0 0 1');
-                                       if(pc == CONTENT_EMPTY)
-                                       {
-                                               if(vlen(team_mate.velocity) > 5)
-                                                       fixedmakevectors(vectoangles(team_mate.velocity));
-                                               else
-                                                       fixedmakevectors(team_mate.angles);
-
-                                               for(pc = 0; pc != 5; ++pc) // test 5 diffrent spots close to mate
-                                               {
-                                                       switch(pc)
-                                                       {
-                                                               case 0:
-                                                                       tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin + v_right * 128, MOVE_NORMAL, team_mate);
-                                                                       break;
-                                                               case 1:
-                                                                       tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin - v_right * 128 , MOVE_NORMAL, team_mate);
-                                                                       break;
-                                                               case 2:
-                                                                       tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin + v_right * 64 - v_forward * 64, MOVE_NORMAL, team_mate);
-                                                                       break;
-                                                               case 3:
-                                                                       tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin - v_right * 64 - v_forward * 64, MOVE_NORMAL, team_mate);
-                                                                       break;
-                                                               case 4:
-                                                                       tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin - v_forward * 128, MOVE_NORMAL, team_mate);
-                                                                       break;
-                                                       }
-
-                                                       if(trace_fraction == 1.0)
-                                                       {
-                                                               traceline(trace_endpos + '0 0 4', trace_endpos - '0 0 100', MOVE_NORMAL, team_mate);
-                                                               if(trace_fraction != 1.0)
-                                                               {
-                                                                       if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath)
-                                                                       {
-                                                                               dist = vlen(trace_endpos - self.msnt_deathloc);
-                                                                               if(dist < best_dist || best_dist == 0)
-                                                                               {
-                                                                                       best_dist = dist;
-                                                                                       best_spot = trace_endpos;
-                                                                                       best_mate = team_mate;
-                                                                               }
-                                                                       }
-                                                                       else
-                                                                       {
-                                                                               setorigin(self, trace_endpos);
-                                                                               self.angles = team_mate.angles;
-                                                                               self.angles_z = 0; // never spawn tilted even if the spot says to
-                                                                               team_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
-                                                                               return 0;
-                                                                       }
-                                                               }
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-               }
-
-               if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath)
-               if(best_dist)
-               {
-                       setorigin(self, best_spot);
-                       self.angles = best_mate.angles;
-                       self.angles_z = 0; // never spawn tilted even if the spot says to
-                       best_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
-               }
-       }
-       else if(spawn_spot.msnt_lookat)
-       {
-               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
-               /*
-               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");
-               sprint(self, "angles: ", vtos(self.angles), "\n");
-               */
-       }
-
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerDies)
-{SELFPARAM();
-       self.msnt_deathloc = self.origin;
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(spawn_near_teammate, GetCvars)
-{
-       GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_spawn_near_teammate, "cl_spawn_near_teammate");
-       return false;
-}
diff --git a/qcsrc/server/mutators/mutator_superspec.qc b/qcsrc/server/mutators/mutator_superspec.qc
deleted file mode 100644 (file)
index 8f10603..0000000
+++ /dev/null
@@ -1,480 +0,0 @@
-#include "mutator.qh"
-
-REGISTER_MUTATOR(superspec, cvar("g_superspectate"));
-
-#define _SSMAGIX "SUPERSPEC_OPTIONSFILE_V1"
-#define _ISLOCAL ((edict_num(1) == self) ? true : false)
-
-const float ASF_STRENGTH               = 1;
-const float ASF_SHIELD                         = 2;
-const float ASF_MEGA_AR                = 4;
-const float ASF_MEGA_HP                = 8;
-const float ASF_FLAG_GRAB              = 16;
-const float ASF_OBSERVER_ONLY  = 32;
-const float ASF_SHOWWHAT               = 64;
-const float ASF_SSIM                   = 128;
-const float ASF_FOLLOWKILLER   = 256;
-const float ASF_ALL                    = 0xFFFFFF;
-.float autospec_flags;
-
-const float SSF_SILENT = 1;
-const float SSF_VERBOSE = 2;
-const float SSF_ITEMMSG = 4;
-.float superspec_flags;
-
-.string superspec_itemfilter; //"classname1 classname2 ..."
-
-bool superspec_Spectate(entity _player)
-{SELFPARAM();
-       if(Spectate(_player) == 1)
-               self.classname = "spectator";
-
-       return true;
-}
-
-void superspec_save_client_conf()
-{SELFPARAM();
-       string fn = "superspec-local.options";
-       float fh;
-
-       if (!_ISLOCAL)
-       {
-               if(self.crypto_idfp == "")
-                       return;
-
-               fn = sprintf("superspec-%s.options", uri_escape(self.crypto_idfp));
-       }
-
-       fh = fopen(fn, FILE_WRITE);
-       if(fh < 0)
-       {
-               LOG_TRACE("^1ERROR: ^7 superspec can not open ", fn, " for writing.\n");
-       }
-       else
-       {
-               fputs(fh, _SSMAGIX);
-               fputs(fh, "\n");
-               fputs(fh, ftos(self.autospec_flags));
-               fputs(fh, "\n");
-               fputs(fh, ftos(self.superspec_flags));
-               fputs(fh, "\n");
-               fputs(fh, self.superspec_itemfilter);
-               fputs(fh, "\n");
-               fclose(fh);
-       }
-}
-
-void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel)
-{
-       sprint(_to, strcat(_con_title, _msg));
-
-       if(_to.superspec_flags & SSF_SILENT)
-               return;
-
-       if(_spamlevel > 1)
-               if (!(_to.superspec_flags & SSF_VERBOSE))
-                       return;
-
-       centerprint(_to, strcat(_center_title, _msg));
-}
-
-float superspec_filteritem(entity _for, entity _item)
-{
-       float i;
-
-       if(_for.superspec_itemfilter == "")
-               return true;
-
-       if(_for.superspec_itemfilter == "")
-               return true;
-
-       float l = tokenize_console(_for.superspec_itemfilter);
-       for(i = 0; i < l; ++i)
-       {
-               if(argv(i) == _item.classname)
-                       return true;
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(superspec, ItemTouch)
-{SELFPARAM();
-       entity _item = self;
-
-       entity e;
-       FOR_EACH_SPEC(e)
-       {
-               setself(e);
-               if(self.superspec_flags & SSF_ITEMMSG)
-                       if(superspec_filteritem(self, _item))
-                       {
-                               if(self.superspec_flags & SSF_VERBOSE)
-                                       superspec_msg("", "", self, sprintf("Player %s^7 just picked up ^3%s\n", other.netname, _item.netname), 1);
-                               else
-                                       superspec_msg("", "", self, sprintf("Player %s^7 just picked up ^3%s\n^8(%s^8)\n", other.netname, _item.netname, _item.classname), 1);
-                               if((self.autospec_flags & ASF_SSIM) && self.enemy != other)
-                               {
-                                       superspec_Spectate(other);
-
-                                       setself(this);
-                                       return MUT_ITEMTOUCH_CONTINUE;
-                               }
-                       }
-
-               if((self.autospec_flags & ASF_SHIELD && _item.invincible_finished) ||
-                       (self.autospec_flags & ASF_STRENGTH && _item.strength_finished) ||
-                       (self.autospec_flags & ASF_MEGA_AR && _item.itemdef == ITEM_ArmorMega) ||
-                       (self.autospec_flags & ASF_MEGA_HP && _item.itemdef == ITEM_HealthMega) ||
-                       (self.autospec_flags & ASF_FLAG_GRAB && _item.classname == "item_flag_team"))
-               {
-
-                       if((self.enemy != other) || IS_OBSERVER(self))
-                       {
-                               if(self.autospec_flags & ASF_OBSERVER_ONLY && !IS_OBSERVER(self))
-                               {
-                                       if(self.superspec_flags & SSF_VERBOSE)
-                                               superspec_msg("", "", self, sprintf("^8Ignored that ^7%s^8 grabbed %s^8 since the observer_only option is ON\n", other.netname, _item.netname), 2);
-                               }
-                               else
-                               {
-                                       if(self.autospec_flags & ASF_SHOWWHAT)
-                                               superspec_msg("", "", self, sprintf("^7Following %s^7 due to picking up %s\n", other.netname, _item.netname), 2);
-
-                                       superspec_Spectate(other);
-                               }
-                       }
-               }
-       }
-
-       setself(this);
-
-       return MUT_ITEMTOUCH_CONTINUE;
-}
-
-MUTATOR_HOOKFUNCTION(superspec, SV_ParseClientCommand)
-{SELFPARAM();
-#define OPTIONINFO(flag,var,test,text,long,short) \
-    var = strcat(var, ((flag & test) ? "^2[ON]  ^7" : "^1[OFF] ^7")); \
-    var = strcat(var, text," ^7(^3 ", long, "^7 | ^3", short, " ^7)\n")
-
-       if(MUTATOR_RETURNVALUE) // command was already handled?
-               return false;
-
-       if(IS_PLAYER(self))
-               return false;
-
-       if(cmd_name == "superspec_itemfilter")
-       {
-               if(argv(1) == "help")
-               {
-                       string _aspeco;
-                       _aspeco = "^7 superspec_itemfilter ^3\"item_classname1 item_classname2\"^7 only show thise items when ^2superspec ^3item_message^7 is on\n";
-                       _aspeco = strcat(_aspeco, "^3 clear^7 Remove the filter (show all pickups)\n");
-                       _aspeco = strcat(_aspeco, "^3 show ^7 Display current filter\n");
-                       superspec_msg("^3superspec_itemfilter help:\n\n\n", "\n^3superspec_itemfilter help:\n", self, _aspeco, 1);
-               }
-               else if(argv(1) == "clear")
-               {
-                       if(self.superspec_itemfilter != "")
-                               strunzone(self.superspec_itemfilter);
-
-                       self.superspec_itemfilter = "";
-               }
-               else if(argv(1) == "show" || argv(1) == "")
-               {
-                       if(self.superspec_itemfilter == "")
-                       {
-                               superspec_msg("^3superspec_itemfilter^7 is ^1not^7 set", "\n^3superspec_itemfilter^7 is ^1not^7 set\n", self, "", 1);
-                               return true;
-                       }
-                       float i;
-                       float l = tokenize_console(self.superspec_itemfilter);
-                       string _msg = "";
-                       for(i = 0; i < l; ++i)
-                               _msg = strcat(_msg, "^3#", ftos(i), " ^7", argv(i), "\n");
-                               //_msg = sprintf("^3#%d^7 %s\n%s", i, _msg, argv(i));
-
-                       _msg = strcat(_msg,"\n");
-
-                       superspec_msg("^3superspec_itemfilter is:\n\n\n", "\n^3superspec_itemfilter is:\n", self, _msg, 1);
-               }
-               else
-               {
-                       if(self.superspec_itemfilter != "")
-                               strunzone(self.superspec_itemfilter);
-
-                       self.superspec_itemfilter = strzone(argv(1));
-               }
-
-               return true;
-       }
-
-       if(cmd_name == "superspec")
-       {
-               string _aspeco;
-
-               if(cmd_argc > 1)
-               {
-                       float i, _bits = 0, _start = 1;
-                       if(argv(1) == "help")
-                       {
-                               _aspeco = "use cmd superspec [option] [on|off] to set options\n\n";
-                               _aspeco = strcat(_aspeco, "^3 silent ^7(short^5 si^7) supresses ALL messages from superspectate.\n");
-                               _aspeco = strcat(_aspeco, "^3 verbose ^7(short^5 ve^7) makes superspectate print some additional information.\n");
-                               _aspeco = strcat(_aspeco, "^3 item_message ^7(short^5 im^7) makes superspectate print items that were picked up.\n");
-                               _aspeco = strcat(_aspeco, "^7    Use cmd superspec_itemfilter \"item_class1 item_class2\" to set up a filter of what to show with ^3item_message.\n");
-                               superspec_msg("^2Available Super Spectate ^3options:\n\n\n", "\n^2Available Super Spectate ^3options:\n", self, _aspeco, 1);
-                               return true;
-                       }
-
-                       if(argv(1) == "clear")
-                       {
-                               self.superspec_flags = 0;
-                               _start = 2;
-                       }
-
-                       for(i = _start; i < cmd_argc; ++i)
-                       {
-                               if(argv(i) == "on" || argv(i) == "1")
-                               {
-                                       self.superspec_flags |= _bits;
-                                       _bits = 0;
-                               }
-                               else if(argv(i) == "off" || argv(i) == "0")
-                               {
-                                       if(_start == 1)
-                                               self.superspec_flags &= ~_bits;
-
-                                       _bits = 0;
-                               }
-                               else
-                               {
-                                       if((argv(i) == "silent") || (argv(i) == "si")) _bits |= SSF_SILENT ;
-                                       if((argv(i) == "verbose") || (argv(i) == "ve")) _bits |= SSF_VERBOSE;
-                                       if((argv(i) == "item_message") || (argv(i) == "im")) _bits |= SSF_ITEMMSG;
-                               }
-                       }
-               }
-
-               _aspeco = "";
-               OPTIONINFO(self.superspec_flags, _aspeco, SSF_SILENT, "Silent", "silent", "si");
-               OPTIONINFO(self.superspec_flags, _aspeco, SSF_VERBOSE, "Verbose", "verbose", "ve");
-               OPTIONINFO(self.superspec_flags, _aspeco, SSF_ITEMMSG, "Item pickup messages", "item_message", "im");
-
-               superspec_msg("^3Current Super Spectate options are:\n\n\n\n\n", "\n^3Current Super Spectate options are:\n", self, _aspeco, 1);
-
-               return true;
-       }
-
-/////////////////////
-
-       if(cmd_name == "autospec")
-       {
-               string _aspeco;
-               if(cmd_argc > 1)
-               {
-                       if(argv(1) == "help")
-                       {
-                               _aspeco = "use cmd autospec [option] [on|off] to set options\n\n";
-                               _aspeco = strcat(_aspeco, "^3 strength ^7(short^5 st^7) for automatic spectate on strength powerup\n");
-                               _aspeco = strcat(_aspeco, "^3 shield ^7(short^5 sh^7) for automatic spectate on shield powerup\n");
-                               _aspeco = strcat(_aspeco, "^3 mega_health ^7(short^5 mh^7) for automatic spectate on mega health\n");
-                               _aspeco = strcat(_aspeco, "^3 mega_armor ^7(short^5 ma^7) for automatic spectate on mega armor\n");
-                               _aspeco = strcat(_aspeco, "^3 flag_grab ^7(short^5 fg^7) for automatic spectate on CTF flag grab\n");
-                               _aspeco = strcat(_aspeco, "^3 observer_only ^7(short^5 oo^7) for automatic spectate only if in observer mode\n");
-                               _aspeco = strcat(_aspeco, "^3 show_what ^7(short^5 sw^7) to display what event triggered autospectate\n");
-                               _aspeco = strcat(_aspeco, "^3 item_msg ^7(short^5 im^7) to autospec when item_message in superspectate is triggered\n");
-                               _aspeco = strcat(_aspeco, "^3 followkiller ^7(short ^5fk^7) to autospec the killer/off\n");
-                               _aspeco = strcat(_aspeco, "^3 all ^7(short ^5aa^7) to turn everything on/off\n");
-                               superspec_msg("^2Available Auto Spectate ^3options:\n\n\n", "\n^2Available Auto Spectate ^3options:\n", self, _aspeco, 1);
-                               return true;
-                       }
-
-                       float i, _bits = 0, _start = 1;
-                       if(argv(1) == "clear")
-                       {
-                               self.autospec_flags = 0;
-                               _start = 2;
-                       }
-
-                       for(i = _start; i < cmd_argc; ++i)
-                       {
-                               if(argv(i) == "on" || argv(i) == "1")
-                               {
-                                       self.autospec_flags |= _bits;
-                                       _bits = 0;
-                               }
-                               else if(argv(i) == "off" || argv(i) == "0")
-                               {
-                                       if(_start == 1)
-                                               self.autospec_flags &= ~_bits;
-
-                                       _bits = 0;
-                               }
-                               else
-                               {
-                                       if((argv(i) == "strength") || (argv(i) == "st")) _bits |= ASF_STRENGTH;
-                                       if((argv(i) == "shield") || (argv(i) == "sh")) _bits |= ASF_SHIELD;
-                                       if((argv(i) == "mega_health") || (argv(i) == "mh")) _bits |= ASF_MEGA_HP;
-                                       if((argv(i) == "mega_armor") || (argv(i) == "ma")) _bits |= ASF_MEGA_AR;
-                                       if((argv(i) == "flag_grab") || (argv(i) == "fg")) _bits |= ASF_FLAG_GRAB;
-                                       if((argv(i) == "observer_only") || (argv(i) == "oo")) _bits |= ASF_OBSERVER_ONLY;
-                                       if((argv(i) == "show_what") || (argv(i) == "sw")) _bits |= ASF_SHOWWHAT;
-                                       if((argv(i) == "item_msg") || (argv(i) == "im")) _bits |= ASF_SSIM;
-                                       if((argv(i) == "followkiller") || (argv(i) == "fk")) _bits |= ASF_FOLLOWKILLER;
-                                       if((argv(i) == "all") || (argv(i) == "aa")) _bits |= ASF_ALL;
-                               }
-                       }
-               }
-
-               _aspeco = "";
-               OPTIONINFO(self.autospec_flags, _aspeco, ASF_STRENGTH, "Strength", "strength", "st");
-               OPTIONINFO(self.autospec_flags, _aspeco, ASF_SHIELD, "Shield", "shield", "sh");
-               OPTIONINFO(self.autospec_flags, _aspeco, ASF_MEGA_HP, "Mega Health", "mega_health", "mh");
-               OPTIONINFO(self.autospec_flags, _aspeco, ASF_MEGA_AR, "Mega Armor", "mega_armor", "ma");
-               OPTIONINFO(self.autospec_flags, _aspeco, ASF_FLAG_GRAB, "Flag grab", "flag_grab","fg");
-               OPTIONINFO(self.autospec_flags, _aspeco, ASF_OBSERVER_ONLY, "Only switch if observer", "observer_only", "oo");
-               OPTIONINFO(self.autospec_flags, _aspeco, ASF_SHOWWHAT, "Show what item triggered spectate", "show_what", "sw");
-               OPTIONINFO(self.autospec_flags, _aspeco, ASF_SSIM, "Switch on superspec item message", "item_msg", "im");
-               OPTIONINFO(self.autospec_flags, _aspeco, ASF_FOLLOWKILLER, "Followkiller", "followkiller", "fk");
-
-               superspec_msg("^3Current auto spectate options are:\n\n\n\n\n", "\n^3Current auto spectate options are:\n", self, _aspeco, 1);
-               return true;
-       }
-
-       if(cmd_name == "followpowerup")
-       {
-               entity _player;
-               FOR_EACH_PLAYER(_player)
-               {
-                       if(_player.strength_finished > time || _player.invincible_finished > time)
-                               return superspec_Spectate(_player);
-               }
-
-               superspec_msg("", "", self, "No active powerup\n", 1);
-               return true;
-       }
-
-       if(cmd_name == "followstrength")
-       {
-               entity _player;
-               FOR_EACH_PLAYER(_player)
-               {
-                       if(_player.strength_finished > time)
-                               return superspec_Spectate(_player);
-               }
-
-               superspec_msg("", "", self, "No active Strength\n", 1);
-               return true;
-       }
-
-       if(cmd_name == "followshield")
-       {
-               entity _player;
-               FOR_EACH_PLAYER(_player)
-               {
-                       if(_player.invincible_finished > time)
-                               return superspec_Spectate(_player);
-               }
-
-               superspec_msg("", "", self, "No active Shield\n", 1);
-               return true;
-       }
-
-       return false;
-#undef OPTIONINFO
-}
-
-MUTATOR_HOOKFUNCTION(superspec, BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":SS");
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(superspec, BuildMutatorsPrettyString)
-{
-       ret_string = strcat(ret_string, ", Super Spectators");
-       return 0;
-}
-
-void superspec_hello()
-{SELFPARAM();
-       if(self.enemy.crypto_idfp == "")
-               Send_Notification(NOTIF_ONE_ONLY, self.enemy, MSG_INFO, INFO_SUPERSPEC_MISSING_UID);
-
-       remove(self);
-}
-
-MUTATOR_HOOKFUNCTION(superspec, ClientConnect)
-{SELFPARAM();
-       if(!IS_REAL_CLIENT(self))
-               return false;
-
-       string fn = "superspec-local.options";
-       float fh;
-
-       self.superspec_flags = SSF_VERBOSE;
-       self.superspec_itemfilter = "";
-
-       entity _hello = spawn();
-       _hello.enemy = self;
-       _hello.think = superspec_hello;
-       _hello.nextthink = time + 5;
-
-       if (!_ISLOCAL)
-       {
-               if(self.crypto_idfp == "")
-                       return false;
-
-               fn = sprintf("superspec-%s.options", uri_escape(self.crypto_idfp));
-       }
-
-       fh = fopen(fn, FILE_READ);
-       if(fh < 0)
-       {
-               LOG_TRACE("^1ERROR: ^7 superspec can not open ", fn, " for reading.\n");
-       }
-       else
-       {
-               string _magic = fgets(fh);
-               if(_magic != _SSMAGIX)
-               {
-                       LOG_TRACE("^1ERROR^7 While reading superspec options file: unknown magic\n");
-               }
-               else
-               {
-                       self.autospec_flags = stof(fgets(fh));
-                       self.superspec_flags = stof(fgets(fh));
-                       self.superspec_itemfilter = strzone(fgets(fh));
-               }
-               fclose(fh);
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(superspec, PlayerDies)
-{SELFPARAM();
-       entity e;
-       FOR_EACH_SPEC(e)
-       {
-               setself(e);
-               if(self.autospec_flags & ASF_FOLLOWKILLER && IS_PLAYER(frag_attacker) && self.enemy == this)
-               {
-                       if(self.autospec_flags & ASF_SHOWWHAT)
-                               superspec_msg("", "", self, sprintf("^7Following %s^7 due to followkiller\n", frag_attacker.netname), 2);
-
-                       superspec_Spectate(frag_attacker);
-               }
-       }
-
-       setself(this);
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(superspec, ClientDisconnect)
-{
-       superspec_save_client_conf();
-       return false;
-}
diff --git a/qcsrc/server/mutators/mutator_touchexplode.qc b/qcsrc/server/mutators/mutator_touchexplode.qc
deleted file mode 100644 (file)
index ba90bed..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-#include "mutator.qh"
-
-REGISTER_MUTATOR(touchexplode, cvar("g_touchexplode"));
-
-.float touchexplode_time;
-
-void PlayerTouchExplode(entity p1, entity p2)
-{SELFPARAM();
-       vector org = (p1.origin + p2.origin) * 0.5;
-       org.z += (p1.mins.z + p2.mins.z) * 0.5;
-
-       sound(self, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM);
-       Send_Effect(EFFECT_EXPLOSION_SMALL, org, '0 0 0', 1);
-
-       entity e = spawn();
-       setorigin(e, org);
-       RadiusDamage(e, world, autocvar_g_touchexplode_damage, autocvar_g_touchexplode_edgedamage, autocvar_g_touchexplode_radius, world, world, autocvar_g_touchexplode_force, DEATH_TOUCHEXPLODE.m_id, world);
-       remove(e);
-}
-
-MUTATOR_HOOKFUNCTION(touchexplode, PlayerPreThink)
-{SELFPARAM();
-       if(time > self.touchexplode_time)
-       if(!gameover)
-       if(!self.frozen)
-       if(IS_PLAYER(self))
-       if(self.deadflag == DEAD_NO)
-       if (!IS_INDEPENDENT_PLAYER(self))
-       FOR_EACH_PLAYER(other) if(self != other)
-       {
-               if(time > other.touchexplode_time)
-               if(!other.frozen)
-               if(other.deadflag == DEAD_NO)
-               if (!IS_INDEPENDENT_PLAYER(other))
-               if(boxesoverlap(self.absmin, self.absmax, other.absmin, other.absmax))
-               {
-                       PlayerTouchExplode(self, other);
-                       self.touchexplode_time = other.touchexplode_time = time + 0.2;
-               }
-       }
-
-       return false;
-}
diff --git a/qcsrc/server/mutators/mutator_vampire.qc b/qcsrc/server/mutators/mutator_vampire.qc
deleted file mode 100644 (file)
index d06b0ee..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "mutator.qh"
-
-REGISTER_MUTATOR(vampire, cvar("g_vampire") && !cvar("g_instagib"));
-
-MUTATOR_HOOKFUNCTION(vampire, PlayerDamage_SplitHealthArmor)
-{
-       if(time >= frag_target.spawnshieldtime)
-       if(frag_target != frag_attacker)
-       if(frag_target.deadflag == DEAD_NO)
-       {
-               frag_attacker.health += bound(0, damage_take, frag_target.health);
-               frag_attacker.health = bound(0, frag_attacker.health, autocvar_g_balance_health_limit);
-       }
-
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(vampire, BuildMutatorsString)
-{
-       ret_string = strcat(ret_string, ":Vampire");
-       return 0;
-}
-
-MUTATOR_HOOKFUNCTION(vampire, BuildMutatorsPrettyString)
-{
-       ret_string = strcat(ret_string, ", Vampire");
-       return 0;
-}
diff --git a/qcsrc/server/mutators/mutator_vampirehook.qc b/qcsrc/server/mutators/mutator_vampirehook.qc
deleted file mode 100644 (file)
index 3d88a90..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-REGISTER_MUTATOR(vh, cvar("g_vampirehook"));
-
-bool autocvar_g_vampirehook_teamheal;
-float autocvar_g_vampirehook_damage;
-float autocvar_g_vampirehook_damagerate;
-float autocvar_g_vampirehook_health_steal;
-
-.float last_dmg;
-
-MUTATOR_HOOKFUNCTION(vh, GrappleHookThink)
-{SELFPARAM();
-       entity dmgent = ((SAME_TEAM(self.owner, self.aiment) && autocvar_g_vampirehook_teamheal) ? self.owner : self.aiment);
-
-       if(IS_PLAYER(self.aiment))
-       if(self.last_dmg < time)
-       if(!self.aiment.frozen)
-       if(time >= game_starttime)
-       if(DIFF_TEAM(self.owner, self.aiment) || autocvar_g_vampirehook_teamheal)
-       if(self.aiment.health > 0)
-       if(autocvar_g_vampirehook_damage)
-       {
-               self.last_dmg = time + autocvar_g_vampirehook_damagerate;
-               self.owner.damage_dealt += autocvar_g_vampirehook_damage;
-               Damage(dmgent, self, self.owner, autocvar_g_vampirehook_damage, WEP_HOOK.m_id, self.origin, '0 0 0');
-               if(SAME_TEAM(self.owner, self.aiment))
-                       self.aiment.health = min(self.aiment.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max);
-               else
-                       self.owner.health = min(self.owner.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max);
-
-               if(dmgent == self.owner)
-                       dmgent.health -= autocvar_g_vampirehook_damage; // FIXME: friendly fire?!
-       }
-
-       return false;
-}
-
diff --git a/qcsrc/server/mutators/mutators_include.qc b/qcsrc/server/mutators/mutators_include.qc
deleted file mode 100644 (file)
index d2ea468..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
-    #include "../../lib/warpzone/anglestransform.qh"
-    #include "../../lib/warpzone/common.qh"
-    #include "../../lib/warpzone/util_server.qh"
-    #include "../../lib/warpzone/server.qh"
-    #include "../../common/constants.qh"
-    #include "../../common/stats.qh"
-    #include "../../common/teams.qh"
-    #include "../../common/util.qh"
-    #include "../../common/nades/all.qh"
-    #include "../../common/buffs/all.qh"
-    #include "../../common/command/markup.qh"
-    #include "../../common/command/rpn.qh"
-    #include "../../common/command/generic.qh"
-    #include "../../common/command/command.qh"
-    #include "../../common/net_notice.qh"
-    #include "../../common/animdecide.qh"
-    #include "../../common/monsters/all.qh"
-    #include "../../common/monsters/sv_monsters.qh"
-    #include "../../common/monsters/spawn.qh"
-    #include "../../common/weapons/config.qh"
-    #include "../../common/weapons/all.qh"
-    #include "../weapons/accuracy.qh"
-    #include "../weapons/common.qh"
-    #include "../weapons/csqcprojectile.qh"
-    #include "../weapons/hitplot.qh"
-    #include "../weapons/selection.qh"
-    #include "../weapons/spawning.qh"
-    #include "../weapons/throwing.qh"
-    #include "../weapons/tracing.qh"
-    #include "../weapons/weaponstats.qh"
-    #include "../weapons/weaponsystem.qh"
-    #include "../t_items.qh"
-    #include "../autocvars.qh"
-    #include "../constants.qh"
-    #include "../defs.qh"
-    #include "../../common/notifications.qh"
-    #include "../../common/deathtypes/all.qh"
-    #include "mutators_include.qh"
-    #include "../../common/turrets/sv_turrets.qh"
-    #include "../../common/vehicles/all.qh"
-    #include "../campaign.qh"
-    #include "../../common/campaign_common.qh"
-    #include "../../common/mapinfo.qh"
-    #include "../command/common.qh"
-    #include "../command/banning.qh"
-    #include "../command/radarmap.qh"
-    #include "../command/vote.qh"
-    #include "../command/getreplies.qh"
-    #include "../command/cmd.qh"
-    #include "../command/sv_cmd.qh"
-    #include "../../common/csqcmodel_settings.qh"
-    #include "../../lib/csqcmodel/common.qh"
-    #include "../../lib/csqcmodel/sv_model.qh"
-    #include "../anticheat.qh"
-    #include "../cheats.qh"
-    #include "../../common/playerstats.qh"
-    #include "../portals.qh"
-    #include "../g_hook.qh"
-    #include "../scores.qh"
-    #include "../spawnpoints.qh"
-    #include "../mapvoting.qh"
-    #include "../ipban.qh"
-    #include "../race.qh"
-    #include "../antilag.qh"
-    #include "../playerdemo.qh"
-    #include "../round_handler.qh"
-    #include "../item_key.qh"
-    #include "../pathlib/pathlib.qh"
-    #include "../../common/vehicles/all.qh"
-#endif
-
-#include "../../common/mutators/base.qh"
-
-#include "gamemode_assault.qc"
-#include "gamemode_ca.qc"
-#include "gamemode_ctf.qc"
-#include "gamemode_cts.qc"
-#include "gamemode_deathmatch.qc"
-#include "gamemode_domination.qc"
-#include "gamemode_freezetag.qc"
-#include "gamemode_invasion.qc"
-#include "gamemode_keepaway.qc"
-#include "gamemode_keyhunt.qc"
-#include "gamemode_lms.qc"
-#include "gamemode_onslaught.qc"
-#include "gamemode_race.qc"
-#include "gamemode_tdm.qc"
-
-#include "mutator_bloodloss.qc"
-#include "mutator_breakablehook.qc"
-#include "mutator_buffs.qc"
-#include "mutator_campcheck.qc"
-#include "mutator_dodging.qc"
-#include "mutator_hook.qc"
-#include "mutator_invincibleproj.qc"
-#include "mutator_melee_only.qc"
-#include "mutator_midair.qc"
-#include "mutator_multijump.qc"
-#include "mutator_nades.qc"
-#include "mutator_new_toys.qc"
-#include "mutator_nix.qc"
-#include "mutator_overkill.qc"
-#include "mutator_physical_items.qc"
-#include "mutator_pinata.qc"
-#include "mutator_random_gravity.qc"
-#include "mutator_rocketflying.qc"
-#include "mutator_rocketminsta.qc"
-#include "mutator_spawn_near_teammate.qc"
-#include "mutator_superspec.qc"
-#include "mutator_touchexplode.qc"
-#include "mutator_vampirehook.qc"
-#include "mutator_vampire.qc"
-
-#include "sandbox.qc"
diff --git a/qcsrc/server/mutators/mutators_include.qh b/qcsrc/server/mutators/mutators_include.qh
deleted file mode 100644 (file)
index fa91a83..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-#ifndef MUTATORS_INCLUDE_H
-#define MUTATORS_INCLUDE_H
-
-#include "../../common/mutators/base.qh"
-
-#include "gamemode_assault.qh"
-#include "gamemode_ca.qh"
-#include "gamemode_ctf.qh"
-#include "gamemode_cts.qh"
-#include "gamemode_domination.qh"
-#include "gamemode_invasion.qh"
-#include "gamemode_keepaway.qh"
-#include "gamemode_keyhunt.qh"
-#include "gamemode_lms.qh"
-#include "gamemode_onslaught.qh"
-#include "gamemode_race.qh"
-
-#include "mutator_buffs.qh"
-#include "mutator_dodging.qh"
-#include "mutator_nades.qh"
-#include "mutator_overkill.qh"
-#endif
diff --git a/qcsrc/server/mutators/sandbox.qc b/qcsrc/server/mutators/sandbox.qc
deleted file mode 100644 (file)
index c611995..0000000
+++ /dev/null
@@ -1,834 +0,0 @@
-#include "mutator.qh"
-
-float autosave_time;
-void sandbox_Database_Load();
-
-REGISTER_MUTATOR(sandbox, cvar("g_sandbox"))
-{
-       MUTATOR_ONADD
-       {
-               autosave_time = time + autocvar_g_sandbox_storage_autosave; // don't save the first server frame
-               if(autocvar_g_sandbox_storage_autoload)
-                       sandbox_Database_Load();
-       }
-
-       MUTATOR_ONROLLBACK_OR_REMOVE
-       {
-               // nothing to roll back
-       }
-
-       MUTATOR_ONREMOVE
-       {
-               // nothing to remove
-       }
-
-       return false;
-}
-
-const float MAX_STORAGE_ATTACHMENTS = 16;
-float object_count;
-.float object_flood;
-.entity object_attach;
-.string material;
-
-.float touch_timer;
-void sandbox_ObjectFunction_Touch()
-{SELFPARAM();
-       // apply material impact effects
-
-       if(!self.material)
-               return;
-       if(self.touch_timer > time)
-               return; // don't execute each frame
-       self.touch_timer = time + 0.1;
-
-       // make particle count and sound volume depend on impact speed
-       float intensity;
-       intensity = vlen(self.velocity) + vlen(other.velocity);
-       if(intensity) // avoid divisions by 0
-               intensity /= 2; // average the two velocities
-       if (!(intensity >= autocvar_g_sandbox_object_material_velocity_min))
-               return; // impact not strong enough to do anything
-       // now offset intensity and apply it to the effects
-       intensity -= autocvar_g_sandbox_object_material_velocity_min; // start from minimum velocity, not actual velocity
-       intensity = bound(0, intensity * autocvar_g_sandbox_object_material_velocity_factor, 1);
-
-       _sound(self, CH_TRIGGER, strcat("object/impact_", self.material, "_", ftos(ceil(random() * 5)) , ".wav"), VOL_BASE * intensity, ATTEN_NORM);
-       Send_Effect_(strcat("impact_", self.material), self.origin, '0 0 0', ceil(intensity * 10)); // allow a count from 1 to 10
-}
-
-void sandbox_ObjectFunction_Think()
-{SELFPARAM();
-       entity e;
-
-       // decide if and how this object can be grabbed
-       if(autocvar_g_sandbox_readonly)
-               self.grab = 0; // no grabbing
-       else if(autocvar_g_sandbox_editor_free < 2 && self.crypto_idfp)
-               self.grab = 1; // owner only
-       else
-               self.grab = 3; // anyone
-
-       // Object owner is stored via player UID, but we also need the owner as an entity (if the player is available on the server).
-       // Therefore, scan for all players, and update the owner as long as the player is present. We must always do this,
-       // since if the owning player disconnects, the object's owner should also be reset.
-       FOR_EACH_REALPLAYER(e) // bots can't have objects
-       {
-               if(self.crypto_idfp == e.crypto_idfp)
-               {
-                       self.realowner = e;
-                       break;
-               }
-               self.realowner = world;
-       }
-
-       self.nextthink = time;
-
-       CSQCMODEL_AUTOUPDATE(self);
-}
-
-.float old_solid, old_movetype;
-entity sandbox_ObjectEdit_Get(float permissions)
-{SELFPARAM();
-       // Returns the traced entity if the player can edit it, and world if not.
-       // If permissions if false, the object is returned regardless of editing rights.
-       // Attached objects are SOLID_NOT and do not get traced.
-
-       crosshair_trace_plusvisibletriggers(self);
-       if(vlen(self.origin - trace_ent.origin) > autocvar_g_sandbox_editor_distance_edit)
-               return world; // out of trace range
-       if(trace_ent.classname != "object")
-               return world; // entity is not an object
-       if(!permissions)
-               return trace_ent; // don't check permissions, anyone can edit this object
-       if(trace_ent.crypto_idfp == "")
-               return trace_ent; // the player who spawned this object did not have an UID, so anyone can edit it
-       if (!(trace_ent.realowner != self && autocvar_g_sandbox_editor_free < 2))
-               return trace_ent; // object does not belong to the player, and players can only edit their own objects on this server
-       return world;
-}
-
-void sandbox_ObjectEdit_Scale(entity e, float f)
-{
-       e.scale = f;
-       if(e.scale)
-       {
-               e.scale = bound(autocvar_g_sandbox_object_scale_min, e.scale, autocvar_g_sandbox_object_scale_max);
-               _setmodel(e, e.model); // reset mins and maxs based on mesh
-               setsize(e, e.mins * e.scale, e.maxs * e.scale); // adapt bounding box size to model size
-       }
-}
-
-void sandbox_ObjectAttach_Remove(entity e);
-void sandbox_ObjectAttach_Set(entity e, entity parent, string s)
-{
-       // attaches e to parent on string s
-
-       // we can't attach to an attachment, for obvious reasons
-       sandbox_ObjectAttach_Remove(e);
-
-       e.old_solid = e.solid; // persist solidity
-       e.old_movetype = e.movetype; // persist physics
-       e.movetype = MOVETYPE_FOLLOW;
-       e.solid = SOLID_NOT;
-       e.takedamage = DAMAGE_NO;
-
-       setattachment(e, parent, s);
-       e.owner = parent;
-}
-
-void sandbox_ObjectAttach_Remove(entity e)
-{
-       // detaches any object attached to e
-
-       entity head;
-       for(head = world; (head = find(head, classname, "object")); )
-       {
-               if(head.owner == e)
-               {
-                       vector org;
-                       org = gettaginfo(head, 0);
-                       setattachment(head, world, "");
-                       head.owner = world;
-
-                       // objects change origin and angles when detached, so apply previous position
-                       setorigin(head, org);
-                       head.angles = e.angles; // don't allow detached objects to spin or roll
-
-                       head.solid = head.old_solid; // restore persisted solidity
-                       head.movetype = head.old_movetype; // restore persisted physics
-                       head.takedamage = DAMAGE_AIM;
-               }
-       }
-}
-
-entity sandbox_ObjectSpawn(float database)
-{SELFPARAM();
-       // spawn a new object with default properties
-
-       entity e = spawn();
-       e.classname = "object";
-       e.takedamage = DAMAGE_AIM;
-       e.damageforcescale = 1;
-       e.solid = SOLID_BBOX; // SOLID_BSP would be best, but can lag the server badly
-       e.movetype = MOVETYPE_TOSS;
-       e.frame = 0;
-       e.skin = 0;
-       e.material = string_null;
-       e.touch = sandbox_ObjectFunction_Touch;
-       e.think = sandbox_ObjectFunction_Think;
-       e.nextthink = time;
-       //e.effects |= EF_SELECTABLE; // don't do this all the time, maybe just when editing objects?
-
-       if(!database)
-       {
-               // set the object's owner via player UID
-               // if the player does not have an UID, the owner cannot be stored and his objects may be edited by anyone
-               if(self.crypto_idfp != "")
-                       e.crypto_idfp = strzone(self.crypto_idfp);
-               else
-                       print_to(self, "^1SANDBOX - WARNING: ^7You spawned an object, but lack a player UID. ^1Your objects are not secured and can be edited by any player!");
-
-               // set public object information
-               e.netname = strzone(self.netname); // name of the owner
-               e.message = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); // creation time
-               e.message2 = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); // last editing time
-
-               // set origin and direction based on player position and view angle
-               makevectors(self.v_angle);
-               WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * autocvar_g_sandbox_editor_distance_spawn, MOVE_NORMAL, self);
-               setorigin(e, trace_endpos);
-               e.angles_y = self.v_angle.y;
-       }
-
-       WITH(entity, self, e, CSQCMODEL_AUTOINIT(e));
-
-       object_count += 1;
-       return e;
-}
-
-void sandbox_ObjectRemove(entity e)
-{
-       sandbox_ObjectAttach_Remove(e); // detach child objects
-
-       // if the object being removed has been selected for attachment by a player, unset it
-       entity head;
-       FOR_EACH_REALPLAYER(head) // bots can't have objects
-       {
-               if(head.object_attach == e)
-                       head.object_attach = world;
-       }
-
-       if(e.material)  {       strunzone(e.material);  e.material = string_null;       }
-       if(e.crypto_idfp)       {       strunzone(e.crypto_idfp);       e.crypto_idfp = string_null;    }
-       if(e.netname)   {       strunzone(e.netname);   e.netname = string_null;        }
-       if(e.message)   {       strunzone(e.message);   e.message = string_null;        }
-       if(e.message2)  {       strunzone(e.message2);  e.message2 = string_null;       }
-       remove(e);
-       e = world;
-
-       object_count -= 1;
-}
-
-string port_string[MAX_STORAGE_ATTACHMENTS]; // fteqcc crashes if this isn't defined as a global
-
-string sandbox_ObjectPort_Save(entity e, float database)
-{
-       // save object properties, and return them as a string
-       float i = 0;
-       string s;
-       entity head;
-
-       for(head = world; (head = find(head, classname, "object")); )
-       {
-               // the main object needs to be first in the array [0] with attached objects following
-               float slot, physics, solidity;
-               if(head == e) // this is the main object, place it first
-               {
-                       slot = 0;
-                       solidity = head.solid; // applied solidity is normal solidity for children
-                       physics = head.movetype; // applied physics are normal physics for parents
-               }
-               else if(head.owner == e) // child object, list them in order
-               {
-                       i += 1; // children start from 1
-                       slot = i;
-                       solidity = head.old_solid; // persisted solidity is normal solidity for children
-                       physics = head.old_movetype; // persisted physics are normal physics for children
-                       gettaginfo(head.owner, head.tag_index); // get the name of the tag our object is attached to, used further below
-               }
-               else
-                       continue;
-
-               // ---------------- OBJECT PROPERTY STORAGE: SAVE ----------------
-               if(slot)
-               {
-                       // properties stored only for child objects
-                       if(gettaginfo_name)     port_string[slot] = strcat(port_string[slot], "\"", gettaginfo_name, "\" ");    else    port_string[slot] = strcat(port_string[slot], "\"\" "); // none
-               }
-               else
-               {
-                       // properties stored only for parent objects
-                       if(database)
-                       {
-                               port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.origin), " ");
-                               port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.angles), " ");
-                       }
-               }
-               // properties stored for all objects
-               port_string[slot] = strcat(port_string[slot], "\"", head.model, "\" ");
-               port_string[slot] = strcat(port_string[slot], ftos(head.skin), " ");
-               port_string[slot] = strcat(port_string[slot], ftos(head.alpha), " ");
-               port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.colormod), " ");
-               port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.glowmod), " ");
-               port_string[slot] = strcat(port_string[slot], ftos(head.frame), " ");
-               port_string[slot] = strcat(port_string[slot], ftos(head.scale), " ");
-               port_string[slot] = strcat(port_string[slot], ftos(solidity), " ");
-               port_string[slot] = strcat(port_string[slot], ftos(physics), " ");
-               port_string[slot] = strcat(port_string[slot], ftos(head.damageforcescale), " ");
-               if(head.material)       port_string[slot] = strcat(port_string[slot], "\"", head.material, "\" ");      else    port_string[slot] = strcat(port_string[slot], "\"\" "); // none
-               if(database)
-               {
-                       // properties stored only for the database
-                       if(head.crypto_idfp)    port_string[slot] = strcat(port_string[slot], "\"", head.crypto_idfp, "\" ");   else    port_string[slot] = strcat(port_string[slot], "\"\" "); // none
-                       port_string[slot] = strcat(port_string[slot], "\"", e.netname, "\" ");
-                       port_string[slot] = strcat(port_string[slot], "\"", e.message, "\" ");
-                       port_string[slot] = strcat(port_string[slot], "\"", e.message2, "\" ");
-               }
-       }
-
-       // now apply the array to a simple string, with the ; symbol separating objects
-       s = "";
-       for(i = 0; i <= MAX_STORAGE_ATTACHMENTS; ++i)
-       {
-               if(port_string[i])
-                       s = strcat(s, port_string[i], "; ");
-               port_string[i] = string_null; // fully clear the string
-       }
-
-       return s;
-}
-
-entity sandbox_ObjectPort_Load(string s, float database)
-{
-       // load object properties, and spawn a new object with them
-       float n, i;
-       entity e = world, parent = world;
-
-       // separate objects between the ; symbols
-       n = tokenizebyseparator(s, "; ");
-       for(i = 0; i < n; ++i)
-               port_string[i] = argv(i);
-
-       // now separate and apply the properties of each object
-       for(i = 0; i < n; ++i)
-       {
-               float argv_num;
-               string tagname = string_null;
-               argv_num = 0;
-               tokenize_console(port_string[i]);
-               e = sandbox_ObjectSpawn(database);
-
-               // ---------------- OBJECT PROPERTY STORAGE: LOAD ----------------
-               if(i)
-               {
-                       // properties stored only for child objects
-                       if(argv(argv_num) != "")        tagname = argv(argv_num);       else tagname = string_null;     ++argv_num;
-               }
-               else
-               {
-                       // properties stored only for parent objects
-                       if(database)
-                       {
-                               setorigin(e, stov(argv(argv_num)));     ++argv_num;
-                               e.angles = stov(argv(argv_num));        ++argv_num;
-                       }
-                       parent = e; // mark parent objects as such
-               }
-               // properties stored for all objects
-               _setmodel(e, argv(argv_num));   ++argv_num;
-               e.skin = stof(argv(argv_num));  ++argv_num;
-               e.alpha = stof(argv(argv_num)); ++argv_num;
-               e.colormod = stov(argv(argv_num));      ++argv_num;
-               e.glowmod = stov(argv(argv_num));       ++argv_num;
-               e.frame = stof(argv(argv_num)); ++argv_num;
-               sandbox_ObjectEdit_Scale(e, stof(argv(argv_num)));      ++argv_num;
-               e.solid = e.old_solid = stof(argv(argv_num));   ++argv_num;
-               e.movetype = e.old_movetype = stof(argv(argv_num));     ++argv_num;
-               e.damageforcescale = stof(argv(argv_num));      ++argv_num;
-               if(e.material)  strunzone(e.material);  if(argv(argv_num) != "")        e.material = strzone(argv(argv_num));   else    e.material = string_null;       ++argv_num;
-               if(database)
-               {
-                       // properties stored only for the database
-                       if(e.crypto_idfp)       strunzone(e.crypto_idfp);       if(argv(argv_num) != "")        e.crypto_idfp = strzone(argv(argv_num));        else    e.crypto_idfp = string_null;    ++argv_num;
-                       if(e.netname)   strunzone(e.netname);   e.netname = strzone(argv(argv_num));    ++argv_num;
-                       if(e.message)   strunzone(e.message);   e.message = strzone(argv(argv_num));    ++argv_num;
-                       if(e.message2)  strunzone(e.message2);  e.message2 = strzone(argv(argv_num));   ++argv_num;
-               }
-
-               // attach last
-               if(i)
-                       sandbox_ObjectAttach_Set(e, parent, tagname);
-       }
-
-       for(i = 0; i <= MAX_STORAGE_ATTACHMENTS; ++i)
-               port_string[i] = string_null; // fully clear the string
-
-       return e;
-}
-
-void sandbox_Database_Save()
-{
-       // saves all objects to the database file
-       entity head;
-       string file_name;
-       float file_get;
-
-       file_name = strcat("sandbox/storage_", autocvar_g_sandbox_storage_name, "_", GetMapname(), ".txt");
-       file_get = fopen(file_name, FILE_WRITE);
-       fputs(file_get, strcat("// sandbox storage \"", autocvar_g_sandbox_storage_name, "\" for map \"", GetMapname(), "\" last updated ", strftime(true, "%d-%m-%Y %H:%M:%S")));
-       fputs(file_get, strcat(" containing ", ftos(object_count), " objects\n"));
-
-       for(head = world; (head = find(head, classname, "object")); )
-       {
-               // attached objects are persisted separately, ignore them here
-               if(head.owner != world)
-                       continue;
-
-               // use a line of text for each object, listing all properties
-               fputs(file_get, strcat(sandbox_ObjectPort_Save(head, true), "\n"));
-       }
-       fclose(file_get);
-}
-
-void sandbox_Database_Load()
-{
-       // loads all objects from the database file
-       string file_read, file_name;
-       float file_get, i;
-
-       file_name = strcat("sandbox/storage_", autocvar_g_sandbox_storage_name, "_", GetMapname(), ".txt");
-       file_get = fopen(file_name, FILE_READ);
-       if(file_get < 0)
-       {
-               if(autocvar_g_sandbox_info > 0)
-                       LOG_INFO(strcat("^3SANDBOX - SERVER: ^7could not find storage file ^3", file_name, "^7, no objects were loaded\n"));
-       }
-       else
-       {
-               for (;;)
-               {
-                       file_read = fgets(file_get);
-                       if(file_read == "")
-                               break;
-                       if(substring(file_read, 0, 2) == "//")
-                               continue;
-                       if(substring(file_read, 0, 1) == "#")
-                               continue;
-
-                       entity e;
-                       e = sandbox_ObjectPort_Load(file_read, true);
-
-                       if(e.material)
-                       {
-                               // since objects are being loaded for the first time, precache material sounds for each
-                               for (i = 1; i <= 5; i++) // 5 sounds in total
-                                       precache_sound(strcat("object/impact_", e.material, "_", ftos(i), ".wav"));
-                       }
-               }
-               if(autocvar_g_sandbox_info > 0)
-                       LOG_INFO(strcat("^3SANDBOX - SERVER: ^7successfully loaded storage file ^3", file_name, "\n"));
-       }
-       fclose(file_get);
-}
-
-MUTATOR_HOOKFUNCTION(sandbox, SV_ParseClientCommand)
-{SELFPARAM();
-       if(MUTATOR_RETURNVALUE) // command was already handled?
-               return false;
-       if(cmd_name == "g_sandbox")
-       {
-               if(autocvar_g_sandbox_readonly)
-               {
-                       print_to(self, "^2SANDBOX - INFO: ^7Sandbox mode is active, but in read-only mode. Sandbox commands cannot be used");
-                       return true;
-               }
-               if(cmd_argc < 2)
-               {
-                       print_to(self, "^2SANDBOX - INFO: ^7Sandbox mode is active. For usage information, type 'sandbox help'");
-                       return true;
-               }
-
-               switch(argv(1))
-               {
-                       entity e;
-                       float i;
-                       string s;
-
-                       // ---------------- COMMAND: HELP ----------------
-                       case "help":
-                               print_to(self, "You can use the following sandbox commands:");
-                               print_to(self, "^7\"^2object_spawn ^3models/foo/bar.md3^7\" spawns a new object in front of the player, and gives it the specified model");
-                               print_to(self, "^7\"^2object_remove^7\" removes the object the player is looking at. Players can only remove their own objects");
-                               print_to(self, "^7\"^2object_duplicate ^3value^7\" duplicates the object, if the player has copying rights over the original");
-                               print_to(self, "^3copy value ^7- copies the properties of the object to the specified client cvar");
-                               print_to(self, "^3paste value ^7- spawns an object with the given properties. Properties or cvars must be specified as follows; eg1: \"0 1 2 ...\", eg2: \"$cl_cvar\"");
-                               print_to(self, "^7\"^2object_attach ^3property value^7\" attaches one object to another. Players can only attach their own objects");
-                               print_to(self, "^3get ^7- selects the object you are facing as the object to be attached");
-                               print_to(self, "^3set value ^7- attaches the previously selected object to the object you are facing, on the specified bone");
-                               print_to(self, "^3remove ^7- detaches all objects from the object you are facing");
-                               print_to(self, "^7\"^2object_edit ^3property value^7\" edits the given property of the object. Players can only edit their own objects");
-                               print_to(self, "^3skin value ^7- changes the skin of the object");
-                               print_to(self, "^3alpha value ^7- sets object transparency");
-                               print_to(self, "^3colormod \"value_x value_y value_z\" ^7- main object color");
-                               print_to(self, "^3glowmod \"value_x value_y value_z\" ^7- glow object color");
-                               print_to(self, "^3frame value ^7- object animation frame, for self-animated models");
-                               print_to(self, "^3scale value ^7- changes object scale. 0.5 is half size and 2 is double size");
-                               print_to(self, "^3solidity value ^7- object collisions, 0 = non-solid, 1 = solid");
-                               print_to(self, "^3physics value ^7- object physics, 0 = static, 1 = movable, 2 = physical");
-                               print_to(self, "^3force value ^7- amount of force applied to objects that are shot");
-                               print_to(self, "^3material value ^7- sets the material of the object. Default materials are: metal, stone, wood, flesh");
-                               print_to(self, "^7\"^2object_claim^7\" sets the player as the owner of the object, if he has the right to edit it");
-                               print_to(self, "^7\"^2object_info ^3value^7\" shows public information about the object");
-                               print_to(self, "^3object ^7- prints general information about the object, such as owner and creation / editing date");
-                               print_to(self, "^3mesh ^7- prints information about the object's mesh, including skeletal bones");
-                               print_to(self, "^3attachments ^7- prints information about the object's attachments");
-                               print_to(self, "^7The ^1drag object ^7key can be used to grab and carry objects. Players can only grab their own objects");
-                               return true;
-
-                       // ---------------- COMMAND: OBJECT, SPAWN ----------------
-                       case "object_spawn":
-                               if(time < self.object_flood)
-                               {
-                                       print_to(self, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(self.object_flood - time), " ^7seconds beofore spawning another object"));
-                                       return true;
-                               }
-                               self.object_flood = time + autocvar_g_sandbox_editor_flood;
-                               if(object_count >= autocvar_g_sandbox_editor_maxobjects)
-                               {
-                                       print_to(self, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time"));
-                                       return true;
-                               }
-                               if(cmd_argc < 3)
-                               {
-                                       print_to(self, "^1SANDBOX - WARNING: ^7Attempted to spawn an object without specifying a model. Please specify the path to your model file after the 'object_spawn' command");
-                                       return true;
-                               }
-                               if (!(fexists(argv(2))))
-                               {
-                                       print_to(self, "^1SANDBOX - WARNING: ^7Attempted to spawn an object with a non-existent model. Make sure the path to your model file is correct");
-                                       return true;
-                               }
-
-                               e = sandbox_ObjectSpawn(false);
-                               _setmodel(e, argv(2));
-
-                               if(autocvar_g_sandbox_info > 0)
-                                       LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " spawned an object at origin ^3", vtos(e.origin), "\n"));
-                               return true;
-
-                       // ---------------- COMMAND: OBJECT, REMOVE ----------------
-                       case "object_remove":
-                               e = sandbox_ObjectEdit_Get(true);
-                               if(e != world)
-                               {
-                                       if(autocvar_g_sandbox_info > 0)
-                                               LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " removed an object at origin ^3", vtos(e.origin), "\n"));
-                                       sandbox_ObjectRemove(e);
-                                       return true;
-                               }
-
-                               print_to(self, "^1SANDBOX - WARNING: ^7Object could not be removed. Make sure you are facing an object that you have edit rights over");
-                               return true;
-
-                       // ---------------- COMMAND: OBJECT, DUPLICATE ----------------
-                       case "object_duplicate":
-                               switch(argv(2))
-                               {
-                                       case "copy":
-                                               // copies customizable properties of the selected object to the clipboard cvar
-                                               e = sandbox_ObjectEdit_Get(autocvar_g_sandbox_editor_free); // can we copy objects we can't edit?
-                                               if(e != world)
-                                               {
-                                                       s = sandbox_ObjectPort_Save(e, false);
-                                                       s = strreplace("\"", "\\\"", s);
-                                                       stuffcmd(self, strcat("set ", argv(3), " \"", s, "\""));
-
-                                                       print_to(self, "^2SANDBOX - INFO: ^7Object copied to clipboard");
-                                                       return true;
-                                               }
-                                               print_to(self, "^1SANDBOX - WARNING: ^7Object could not be copied. Make sure you are facing an object that you have copy rights over");
-                                               return true;
-
-                                       case "paste":
-                                               // spawns a new object using the properties in the player's clipboard cvar
-                                               if(time < self.object_flood)
-                                               {
-                                                       print_to(self, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(self.object_flood - time), " ^7seconds beofore spawning another object"));
-                                                       return true;
-                                               }
-                                               self.object_flood = time + autocvar_g_sandbox_editor_flood;
-                                               if(argv(3) == "") // no object in clipboard
-                                               {
-                                                       print_to(self, "^1SANDBOX - WARNING: ^7No object in clipboard. You must copy an object before you can paste it");
-                                                       return true;
-                                               }
-                                               if(object_count >= autocvar_g_sandbox_editor_maxobjects)
-                                               {
-                                                       print_to(self, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time"));
-                                                       return true;
-                                               }
-                                               e = sandbox_ObjectPort_Load(argv(3), false);
-
-                                               print_to(self, "^2SANDBOX - INFO: ^7Object pasted successfully");
-                                               if(autocvar_g_sandbox_info > 0)
-                                                       LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " pasted an object at origin ^3", vtos(e.origin), "\n"));
-                                               return true;
-                               }
-                               return true;
-
-                       // ---------------- COMMAND: OBJECT, ATTACH ----------------
-                       case "object_attach":
-                               switch(argv(2))
-                               {
-                                       case "get":
-                                               // select e as the object as meant to be attached
-                                               e = sandbox_ObjectEdit_Get(true);
-                                               if(e != world)
-                                               {
-                                                       self.object_attach = e;
-                                                       print_to(self, "^2SANDBOX - INFO: ^7Object selected for attachment");
-                                                       return true;
-                                               }
-                                               print_to(self, "^1SANDBOX - WARNING: ^7Object could not be selected for attachment. Make sure you are facing an object that you have edit rights over");
-                                               return true;
-                                       case "set":
-                                               if(self.object_attach == world)
-                                               {
-                                                       print_to(self, "^1SANDBOX - WARNING: ^7No object selected for attachment. Please select an object to be attached first.");
-                                                       return true;
-                                               }
-
-                                               // attaches the previously selected object to e
-                                               e = sandbox_ObjectEdit_Get(true);
-                                               if(e != world)
-                                               {
-                                                       sandbox_ObjectAttach_Set(self.object_attach, e, argv(3));
-                                                       self.object_attach = world; // object was attached, no longer keep it scheduled for attachment
-                                                       print_to(self, "^2SANDBOX - INFO: ^7Object attached successfully");
-                                                       if(autocvar_g_sandbox_info > 1)
-                                                               LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " attached objects at origin ^3", vtos(e.origin), "\n"));
-                                                       return true;
-                                               }
-                                               print_to(self, "^1SANDBOX - WARNING: ^7Object could not be attached to the parent. Make sure you are facing an object that you have edit rights over");
-                                               return true;
-                                       case "remove":
-                                               // removes e if it was attached
-                                               e = sandbox_ObjectEdit_Get(true);
-                                               if(e != world)
-                                               {
-                                                       sandbox_ObjectAttach_Remove(e);
-                                                       print_to(self, "^2SANDBOX - INFO: ^7Child objects detached successfully");
-                                                       if(autocvar_g_sandbox_info > 1)
-                                                               LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " detached objects at origin ^3", vtos(e.origin), "\n"));
-                                                       return true;
-                                               }
-                                               print_to(self, "^1SANDBOX - WARNING: ^7Child objects could not be detached. Make sure you are facing an object that you have edit rights over");
-                                               return true;
-                               }
-                               return true;
-
-                       // ---------------- COMMAND: OBJECT, EDIT ----------------
-                       case "object_edit":
-                               if(argv(2) == "")
-                               {
-                                       print_to(self, "^1SANDBOX - WARNING: ^7Too few parameters. You must specify a property to edit");
-                                       return true;
-                               }
-
-                               e = sandbox_ObjectEdit_Get(true);
-                               if(e != world)
-                               {
-                                       switch(argv(2))
-                                       {
-                                               case "skin":
-                                                       e.skin = stof(argv(3));
-                                                       break;
-                                               case "alpha":
-                                                       e.alpha = stof(argv(3));
-                                                       break;
-                                               case "color_main":
-                                                       e.colormod = stov(argv(3));
-                                                       break;
-                                               case "color_glow":
-                                                       e.glowmod = stov(argv(3));
-                                                       break;
-                                               case "frame":
-                                                       e.frame = stof(argv(3));
-                                                       break;
-                                               case "scale":
-                                                       sandbox_ObjectEdit_Scale(e, stof(argv(3)));
-                                                       break;
-                                               case "solidity":
-                                                       switch(argv(3))
-                                                       {
-                                                               case "0": // non-solid
-                                                                       e.solid = SOLID_TRIGGER;
-                                                                       break;
-                                                               case "1": // solid
-                                                                       e.solid = SOLID_BBOX;
-                                                                       break;
-                                                               default:
-                                                                       break;
-                                                       }
-                                               case "physics":
-                                                       switch(argv(3))
-                                                       {
-                                                               case "0": // static
-                                                                       e.movetype = MOVETYPE_NONE;
-                                                                       break;
-                                                               case "1": // movable
-                                                                       e.movetype = MOVETYPE_TOSS;
-                                                                       break;
-                                                               case "2": // physical
-                                                                       e.movetype = MOVETYPE_PHYSICS;
-                                                                       break;
-                                                               default:
-                                                                       break;
-                                                       }
-                                                       break;
-                                               case "force":
-                                                       e.damageforcescale = stof(argv(3));
-                                                       break;
-                                               case "material":
-                                                       if(e.material)  strunzone(e.material);
-                                                       if(argv(3))
-                                                       {
-                                                               for (i = 1; i <= 5; i++) // precache material sounds, 5 in total
-                                                                       precache_sound(strcat("object/impact_", argv(3), "_", ftos(i), ".wav"));
-                                                               e.material = strzone(argv(3));
-                                                       }
-                                                       else
-                                                               e.material = string_null; // no material
-                                                       break;
-                                               default:
-                                                       print_to(self, "^1SANDBOX - WARNING: ^7Invalid object property. For usage information, type 'sandbox help'");
-                                                       return true;
-                                       }
-
-                                       // update last editing time
-                                       if(e.message2)  strunzone(e.message2);
-                                       e.message2 = strzone(strftime(true, "%d-%m-%Y %H:%M:%S"));
-
-                                       if(autocvar_g_sandbox_info > 1)
-                                               LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " edited property ^3", argv(2), " ^7of an object at origin ^3", vtos(e.origin), "\n"));
-                                       return true;
-                               }
-
-                               print_to(self, "^1SANDBOX - WARNING: ^7Object could not be edited. Make sure you are facing an object that you have edit rights over");
-                               return true;
-
-                       // ---------------- COMMAND: OBJECT, CLAIM ----------------
-                       case "object_claim":
-                               // if the player can edit an object but is not its owner, this can be used to claim that object
-                               if(self.crypto_idfp == "")
-                               {
-                                       print_to(self, "^1SANDBOX - WARNING: ^7You do not have a player UID, and cannot claim objects");
-                                       return true;
-                               }
-                               e = sandbox_ObjectEdit_Get(true);
-                               if(e != world)
-                               {
-                                       // update the owner's name
-                                       // Do this before checking if you're already the owner and skipping if such, so we
-                                       // also update the player's nickname if he changed it (but has the same player UID)
-                                       if(e.netname != self.netname)
-                                       {
-                                               if(e.netname)   strunzone(e.netname);
-                                               e.netname = strzone(self.netname);
-                                               print_to(self, "^2SANDBOX - INFO: ^7Object owner name updated");
-                                       }
-
-                                       if(e.crypto_idfp == self.crypto_idfp)
-                                       {
-                                               print_to(self, "^2SANDBOX - INFO: ^7Object is already yours, nothing to claim");
-                                               return true;
-                                       }
-
-                                       if(e.crypto_idfp)       strunzone(e.crypto_idfp);
-                                       e.crypto_idfp = strzone(self.crypto_idfp);
-
-                                       print_to(self, "^2SANDBOX - INFO: ^7Object claimed successfully");
-                               }
-                               print_to(self, "^1SANDBOX - WARNING: ^7Object could not be claimed. Make sure you are facing an object that you have edit rights over");
-                               return true;
-
-                       // ---------------- COMMAND: OBJECT, INFO ----------------
-                       case "object_info":
-                               // prints public information about the object to the player
-                               e = sandbox_ObjectEdit_Get(false);
-                               if(e != world)
-                               {
-                                       switch(argv(2))
-                                       {
-                                               case "object":
-                                                       print_to(self, strcat("^2SANDBOX - INFO: ^7Object is owned by \"^7", e.netname, "^7\", created \"^3", e.message, "^7\", last edited \"^3", e.message2, "^7\""));
-                                                       return true;
-                                               case "mesh":
-                                                       s = "";
-                                                       FOR_EACH_TAG(e)
-                                                               s = strcat(s, "^7\"^5", gettaginfo_name, "^7\", ");
-                                                       print_to(self, strcat("^2SANDBOX - INFO: ^7Object mesh is \"^3", e.model, "^7\" at animation frame ^3", ftos(e.frame), " ^7containing the following tags: ", s));
-                                                       return true;
-                                               case "attachments":
-                                                       // this should show the same info as 'mesh' but for attachments
-                                                       s = "";
-                                                       entity head;
-                                                       i = 0;
-                                                       for(head = world; (head = find(head, classname, "object")); )
-                                                       {
-                                                               if(head.owner == e)
-                                                               {
-                                                                       ++i; // start from 1
-                                                                       gettaginfo(e, head.tag_index);
-                                                                       s = strcat(s, "^1attachment ", ftos(i), "^7 has mesh \"^3", head.model, "^7\" at animation frame ^3", ftos(head.frame));
-                                                                       s = strcat(s, "^7 and is attached to bone \"^5", gettaginfo_name, "^7\", ");
-                                                               }
-                                                       }
-                                                       if(i) // object contains attachments
-                                                               print_to(self, strcat("^2SANDBOX - INFO: ^7Object contains the following ^1", ftos(i), "^7 attachment(s): ", s));
-                                                       else
-                                                               print_to(self, "^2SANDBOX - INFO: ^7Object contains no attachments");
-                                                       return true;
-                                       }
-                               }
-                               print_to(self, "^1SANDBOX - WARNING: ^7No information could be found. Make sure you are facing an object");
-                               return true;
-
-                       // ---------------- COMMAND: DEFAULT ----------------
-                       default:
-                               print_to(self, "Invalid command. For usage information, type 'sandbox help'");
-                               return true;
-               }
-       }
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(sandbox, SV_StartFrame)
-{
-       if(!autocvar_g_sandbox_storage_autosave)
-               return false;
-       if(time < autosave_time)
-               return false;
-       autosave_time = time + autocvar_g_sandbox_storage_autosave;
-
-       sandbox_Database_Save();
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(sandbox, SetModname)
-{
-       modname = "Sandbox";
-       return true;
-}
index ca6442f979dff6515ea38f24018fb89afbdc917f..9b1d7194f9555587bb42fcda6e0d01d589c168fc 100644 (file)
@@ -1,7 +1,7 @@
 #include "portals.qh"
 
 #include "g_hook.qh"
-#include "mutators/mutators_include.qh"
+#include "mutators/all.qh"
 #include "../common/constants.qh"
 #include "../common/deathtypes/all.qh"
 #include "../common/notifications.qh"
index 37946e38cb62fd893ce240b1f1ed4db436c04ef5..ab94c42bdd4d6215daef510455181d1ac5a8116f 100644 (file)
@@ -43,7 +43,7 @@
 
 #include "command/all.qc"
 
-#include "mutators/mutators_include.qc"
+#include "mutators/all.qc"
 
 #include "pathlib/_all.inc"
 
index 2279e649dd8ac07f7f1a01c0eeb24389f415b27d..678baec958aa26d313a297959947fde5cbd5cac3 100644 (file)
@@ -1,7 +1,7 @@
 #include "scores.qh"
 
 #include "command/common.qh"
-#include "mutators/mutators_include.qh"
+#include "mutators/all.qh"
 #include "../common/playerstats.qh"
 #include "../common/teams.qh"
 
index 5ad02707b301e6d87b6a6d1b83bfbc87055e4460..a1abfa58fc2f02e926d6e0de50c264b8aa4fa08c 100644 (file)
@@ -1,6 +1,6 @@
 #include "spawnpoints.qh"
 
-#include "mutators/mutators_include.qh"
+#include "mutators/all.qh"
 #include "g_world.qh"
 #include "race.qh"
 #include "../common/constants.qh"
index 0a9d8f21014dab6571af9b9deb818ae0d7ed1702..5fc3f771aa87aeb449074d16c4e4edba96d9078f 100644 (file)
@@ -8,7 +8,7 @@
 
 #include "command/common.qh"
 
-#include "mutators/mutators_include.qh"
+#include "mutators/all.qh"
 #include "weapons/csqcprojectile.qh"
 
 #include "../common/constants.qh"
index a640cce105a46f39b95d18b90fd6adedc0f860e5..8cde49cbd5bff4e3176db1dab2f7d279ad398e0e 100644 (file)
@@ -7,7 +7,7 @@
     #include "bot/bot.qh"
     #include "bot/waypoints.qh"
 
-    #include "mutators/mutators_include.qh"
+    #include "mutators/all.qh"
 
     #include "weapons/common.qh"
     #include "weapons/selection.qh"
index 253697905951f55f784a12ba7067c2311919c2aa..2f2bf545455a60af2b6a1c4185bd25ce43bda98f 100644 (file)
@@ -9,7 +9,7 @@
 
 #include "command/vote.qh"
 
-#include "mutators/mutators_include.qh"
+#include "mutators/all.qh"
 
 #include "../common/deathtypes/all.qh"
 #include "../common/gamemodes/all.qh"
index baa95c64952ffd34b0fcc56d5da4e0bb6062cb21..7d1633f7f34124e6de945eb2fd17657c3b47daec 100644 (file)
@@ -1,6 +1,6 @@
 #include "accuracy.qh"
 
-#include "../mutators/mutators_include.qh"
+#include "../mutators/all.qh"
 #include "../../common/constants.qh"
 #include "../../common/teams.qh"
 #include "../../common/util.qh"
index be2ca6b50f0c6d734d1903aa9470c6d19f1bb291..995154509c11243d148f0f0c90f7aef0287d8892 100644 (file)
@@ -1,7 +1,7 @@
 #include "spawning.qh"
 
 #include "weaponsystem.qh"
-#include "../mutators/mutators_include.qh"
+#include "../mutators/all.qh"
 #include "../t_items.qh"
 #include "../../common/weapons/all.qh"
 
index 4c6b99a5013358fe2bd0a65960aa44e007604754..9e1b3e4dc987de6c4de6472a454e9e73006b2c00 100644 (file)
@@ -1,7 +1,7 @@
 #include "throwing.qh"
 
 #include "weaponsystem.qh"
-#include "../mutators/mutators_include.qh"
+#include "../mutators/all.qh"
 #include "../t_items.qh"
 #include "../g_damage.qh"
 #include "../../common/items/item.qh"
index dd7e2b59935ceed86ffdc7b24f3d2ac039e774fa..8b0997bec52564435ffe67c067f06211c0c95e91 100644 (file)
@@ -3,7 +3,7 @@
 #include "selection.qh"
 
 #include "../command/common.qh"
-#include "../mutators/mutators_include.qh"
+#include "../mutators/all.qh"
 #include "../round_handler.qh"
 #include "../t_items.qh"
 #include "../../common/animdecide.qh"