]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'Mario/playerdemo_removal'
authorMario <mario@smbclan.net>
Tue, 19 Jun 2018 01:34:10 +0000 (11:34 +1000)
committerMario <mario@smbclan.net>
Tue, 19 Jun 2018 01:34:10 +0000 (11:34 +1000)
319 files changed:
.gitlab-ci.yml
bal-wep-mario.cfg
balance-mario.cfg
commands.cfg
gamemodes-server.cfg
hud_luma.cfg
mutators.cfg
qcsrc/client/_mod.inc
qcsrc/client/_mod.qh
qcsrc/client/announcer.qc
qcsrc/client/commands/cl_cmd.qc
qcsrc/client/commands/cl_cmd.qh
qcsrc/client/csqcmodel_hooks.qc
qcsrc/client/csqcmodel_hooks.qh
qcsrc/client/hud/hud.qc
qcsrc/client/hud/hud.qh
qcsrc/client/hud/hud_config.qc
qcsrc/client/hud/hud_config.qh
qcsrc/client/hud/panel.qh
qcsrc/client/hud/panel/ammo.qc
qcsrc/client/hud/panel/infomessages.qc
qcsrc/client/hud/panel/modicons.qc
qcsrc/client/hud/panel/quickmenu.qc
qcsrc/client/hud/panel/quickmenu.qh
qcsrc/client/hud/panel/radar.qc
qcsrc/client/hud/panel/radar.qh
qcsrc/client/hud/panel/scoreboard.qc
qcsrc/client/main.qc
qcsrc/client/main.qh
qcsrc/client/mapvoting.qc
qcsrc/client/miscfunctions.qh
qcsrc/client/player_skeleton.qc
qcsrc/client/resources.qc [new file with mode: 0644]
qcsrc/client/resources.qh [new file with mode: 0644]
qcsrc/client/shownames.qc
qcsrc/client/view.qc
qcsrc/client/view.qh
qcsrc/client/wall.qc [deleted file]
qcsrc/client/wall.qh [deleted file]
qcsrc/client/weapons/projectile.qc
qcsrc/common/debug.qh
qcsrc/common/effects/qc/damageeffects.qh
qcsrc/common/ent_cs.qc
qcsrc/common/gamemodes/gamemode/_mod.inc
qcsrc/common/gamemodes/gamemode/_mod.qh
qcsrc/common/gamemodes/gamemode/assault/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/assault/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/assault/assault.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/assault/assault.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/clanarena/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/clanarena/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/ctf/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/ctf/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/ctf/ctf.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/ctf/ctf.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/cts/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/cts/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/cts/cts.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/cts/cts.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/deathmatch/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/deathmatch/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/domination/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/domination/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/domination/domination.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/domination/domination.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/freezetag/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/freezetag/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/invasion/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/invasion/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/invasion/invasion.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/invasion/invasion.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/keepaway/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/keepaway/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/keyhunt/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/keyhunt/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/lms/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/lms/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/lms/lms.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/lms/lms.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/nexball/nexball.qc
qcsrc/common/gamemodes/gamemode/onslaught/cl_controlpoint.qc
qcsrc/common/gamemodes/gamemode/onslaught/cl_generator.qc
qcsrc/common/gamemodes/gamemode/onslaught/onslaught.qc
qcsrc/common/gamemodes/gamemode/onslaught/sv_controlpoint.qc
qcsrc/common/gamemodes/gamemode/onslaught/sv_generator.qc
qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc
qcsrc/common/gamemodes/gamemode/race/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/race/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/race/race.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/race/race.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/tdm/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/tdm/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/tdm/tdm.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/tdm/tdm.qh [new file with mode: 0644]
qcsrc/common/gamemodes/sv_rules.qh
qcsrc/common/items/inventory.qh
qcsrc/common/items/item.qh
qcsrc/common/items/item/ammo.qh
qcsrc/common/items/item/armor.qh
qcsrc/common/items/item/health.qh
qcsrc/common/items/item/jetpack.qh
qcsrc/common/items/item/pickup.qh
qcsrc/common/items/item/powerup.qh
qcsrc/common/mapinfo.qh
qcsrc/common/mapobjects/func/breakable.qc
qcsrc/common/mapobjects/func/button.qc
qcsrc/common/mapobjects/func/door.qc
qcsrc/common/mapobjects/func/door_rotating.qc
qcsrc/common/mapobjects/func/door_secret.qc
qcsrc/common/mapobjects/models.qc
qcsrc/common/mapobjects/models.qh
qcsrc/common/mapobjects/platforms.qc
qcsrc/common/mapobjects/subs.qc
qcsrc/common/mapobjects/subs.qh
qcsrc/common/mapobjects/target/music.qh
qcsrc/common/mapobjects/teleporters.qc
qcsrc/common/mapobjects/trigger/heal.qc
qcsrc/common/mapobjects/trigger/multi.qc
qcsrc/common/mapobjects/trigger/secret.qc
qcsrc/common/mapobjects/trigger/swamp.qc
qcsrc/common/minigames/cl_minigames.qh
qcsrc/common/minigames/cl_minigames_hud.qh
qcsrc/common/monsters/monster/mage.qc
qcsrc/common/monsters/monster/shambler.qc
qcsrc/common/monsters/monster/spider.qc
qcsrc/common/monsters/monster/wyvern.qc
qcsrc/common/monsters/monster/zombie.qc
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/monsters/sv_spawner.qh
qcsrc/common/mutators/mutator/buffs/buffs.qh
qcsrc/common/mutators/mutator/buffs/sv_buffs.qc
qcsrc/common/mutators/mutator/instagib/items.qh
qcsrc/common/mutators/mutator/instagib/sv_instagib.qc
qcsrc/common/mutators/mutator/instagib/sv_items.qc
qcsrc/common/mutators/mutator/invincibleproj/sv_invincibleproj.qc
qcsrc/common/mutators/mutator/nades/effects.inc
qcsrc/common/mutators/mutator/nades/nades.qc
qcsrc/common/mutators/mutator/nades/nades.qh
qcsrc/common/mutators/mutator/nades/net.qc
qcsrc/common/mutators/mutator/new_toys/sv_new_toys.qc
qcsrc/common/mutators/mutator/overkill/_mod.inc
qcsrc/common/mutators/mutator/overkill/okmachinegun.qh
qcsrc/common/mutators/mutator/overkill/oknex.qh
qcsrc/common/mutators/mutator/overkill/okrpc.qc
qcsrc/common/mutators/mutator/overkill/okshotgun.qh
qcsrc/common/mutators/mutator/overkill/sv_overkill.qc
qcsrc/common/mutators/mutator/overkill/sv_overkill.qh
qcsrc/common/mutators/mutator/overkill/sv_weapons.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/random_items/sv_random_items.qc
qcsrc/common/mutators/mutator/random_items/sv_random_items.qh
qcsrc/common/mutators/mutator/spawn_near_teammate/sv_spawn_near_teammate.qc
qcsrc/common/mutators/mutator/vampire/sv_vampire.qc
qcsrc/common/mutators/mutator/vampirehook/sv_vampirehook.qc
qcsrc/common/mutators/mutator/waypoints/waypointsprites.qc
qcsrc/common/net_notice.qh
qcsrc/common/notifications/all.inc
qcsrc/common/notifications/all.qh
qcsrc/common/physics/player.qc
qcsrc/common/resources.qh
qcsrc/common/t_items.qc
qcsrc/common/t_items.qh
qcsrc/common/turrets/cl_turrets.qc
qcsrc/common/turrets/sv_turrets.qc
qcsrc/common/turrets/sv_turrets.qh
qcsrc/common/turrets/turret/ewheel.qc
qcsrc/common/turrets/turret/hk_weapon.qc
qcsrc/common/turrets/turret/walker.qc
qcsrc/common/turrets/util.qh
qcsrc/common/util.qc
qcsrc/common/util.qh
qcsrc/common/vehicles/sv_vehicles.qc
qcsrc/common/vehicles/sv_vehicles.qh
qcsrc/common/vehicles/vehicle/bumblebee.qc
qcsrc/common/vehicles/vehicle/racer.qc
qcsrc/common/vehicles/vehicle/raptor.qc
qcsrc/common/vehicles/vehicle/raptor_weapons.qc
qcsrc/common/vehicles/vehicle/spiderbot.qc
qcsrc/common/weapons/all.qh
qcsrc/common/weapons/weapon/arc.qc
qcsrc/common/weapons/weapon/crylink.qh
qcsrc/common/weapons/weapon/devastator.qc
qcsrc/common/weapons/weapon/electro.qc
qcsrc/common/weapons/weapon/fireball.qc
qcsrc/common/weapons/weapon/hagar.qc
qcsrc/common/weapons/weapon/hook.qc
qcsrc/common/weapons/weapon/machinegun.qc
qcsrc/common/weapons/weapon/minelayer.qc
qcsrc/common/weapons/weapon/mortar.qc
qcsrc/common/weapons/weapon/porto.qc
qcsrc/common/weapons/weapon/porto.qh
qcsrc/common/weapons/weapon/seeker.qc
qcsrc/common/weapons/weapon/vaporizer.qc
qcsrc/lib/csqcmodel/cl_player.qc
qcsrc/menu/command/menu_cmd.qc
qcsrc/menu/mutators/_mod.inc
qcsrc/menu/mutators/_mod.qh
qcsrc/menu/mutators/events.qc [new file with mode: 0644]
qcsrc/menu/xonotic/dialog_multiplayer_create_mutators.qc
qcsrc/menu/xonotic/keybinder.qc
qcsrc/menu/xonotic/mainwindow.qc
qcsrc/server/_mod.inc
qcsrc/server/_mod.qh
qcsrc/server/antilag.qc
qcsrc/server/antilag.qh
qcsrc/server/autocvars.qh
qcsrc/server/bot/api.qh
qcsrc/server/bot/default/aim.qc
qcsrc/server/bot/default/bot.qc
qcsrc/server/bot/default/havocbot/havocbot.qc
qcsrc/server/bot/default/havocbot/roles.qc
qcsrc/server/bot/default/navigation.qc
qcsrc/server/bot/default/scripting.qc
qcsrc/server/bot/default/waypoints.qc
qcsrc/server/cheats.qc
qcsrc/server/cheats.qh
qcsrc/server/client.qc
qcsrc/server/client.qh
qcsrc/server/command/banning.qh
qcsrc/server/command/cmd.qc
qcsrc/server/command/common.qc
qcsrc/server/command/radarmap.qc
qcsrc/server/command/sv_cmd.qc
qcsrc/server/command/vote.qc
qcsrc/server/command/vote.qh
qcsrc/server/compat/quake3.qc
qcsrc/server/compat/quake3.qh
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/g_damage.qh
qcsrc/server/g_hook.qc
qcsrc/server/g_hook.qh
qcsrc/server/g_subs.qc [deleted file]
qcsrc/server/g_subs.qh [deleted file]
qcsrc/server/g_world.qc
qcsrc/server/g_world.qh
qcsrc/server/ipban.qc
qcsrc/server/ipban.qh
qcsrc/server/item_key.qc
qcsrc/server/items.qc
qcsrc/server/mapvoting.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/miscfunctions.qh
qcsrc/server/mutators/_mod.inc
qcsrc/server/mutators/_mod.qh
qcsrc/server/mutators/events.qc [new file with mode: 0644]
qcsrc/server/mutators/events.qh
qcsrc/server/mutators/gamemode.qh [deleted file]
qcsrc/server/mutators/mutator.qh [deleted file]
qcsrc/server/mutators/mutator/_mod.inc [deleted file]
qcsrc/server/mutators/mutator/_mod.qh [deleted file]
qcsrc/server/mutators/mutator/gamemode_assault.qc [deleted file]
qcsrc/server/mutators/mutator/gamemode_assault.qh [deleted file]
qcsrc/server/mutators/mutator/gamemode_ca.qc [deleted file]
qcsrc/server/mutators/mutator/gamemode_ca.qh [deleted file]
qcsrc/server/mutators/mutator/gamemode_ctf.qc [deleted file]
qcsrc/server/mutators/mutator/gamemode_ctf.qh [deleted file]
qcsrc/server/mutators/mutator/gamemode_cts.qc [deleted file]
qcsrc/server/mutators/mutator/gamemode_cts.qh [deleted file]
qcsrc/server/mutators/mutator/gamemode_deathmatch.qc [deleted file]
qcsrc/server/mutators/mutator/gamemode_deathmatch.qh [deleted file]
qcsrc/server/mutators/mutator/gamemode_domination.qc [deleted file]
qcsrc/server/mutators/mutator/gamemode_domination.qh [deleted file]
qcsrc/server/mutators/mutator/gamemode_freezetag.qc [deleted file]
qcsrc/server/mutators/mutator/gamemode_freezetag.qh [deleted file]
qcsrc/server/mutators/mutator/gamemode_invasion.qc [deleted file]
qcsrc/server/mutators/mutator/gamemode_invasion.qh [deleted file]
qcsrc/server/mutators/mutator/gamemode_keepaway.qc [deleted file]
qcsrc/server/mutators/mutator/gamemode_keepaway.qh [deleted file]
qcsrc/server/mutators/mutator/gamemode_keyhunt.qc [deleted file]
qcsrc/server/mutators/mutator/gamemode_keyhunt.qh [deleted file]
qcsrc/server/mutators/mutator/gamemode_lms.qc [deleted file]
qcsrc/server/mutators/mutator/gamemode_lms.qh [deleted file]
qcsrc/server/mutators/mutator/gamemode_race.qc [deleted file]
qcsrc/server/mutators/mutator/gamemode_race.qh [deleted file]
qcsrc/server/mutators/mutator/gamemode_tdm.qc [deleted file]
qcsrc/server/mutators/mutator/gamemode_tdm.qh [deleted file]
qcsrc/server/pathlib/debug.qc
qcsrc/server/pathlib/debug.qh
qcsrc/server/pathlib/main.qc
qcsrc/server/pathlib/pathlib.qh
qcsrc/server/player.qc
qcsrc/server/player.qh
qcsrc/server/portals.qc
qcsrc/server/portals.qh
qcsrc/server/race.qc
qcsrc/server/race.qh
qcsrc/server/resources.qc
qcsrc/server/resources.qh
qcsrc/server/scores.qc
qcsrc/server/scores_rules.qc
qcsrc/server/spawnpoints.qc
qcsrc/server/steerlib.qc
qcsrc/server/sv_main.qc
qcsrc/server/sv_main.qh
qcsrc/server/teamplay.qc
qcsrc/server/tests.qc
qcsrc/server/weapons/accuracy.qc
qcsrc/server/weapons/hitplot.qc
qcsrc/server/weapons/spawning.qc
qcsrc/server/weapons/throwing.qc
qcsrc/server/weapons/tracing.qc
qcsrc/server/weapons/weaponsystem.qc
randomitems-overkill.cfg [new file with mode: 0644]
randomitems-xonotic.cfg
ruleset-XDF.cfg
ruleset-XPM.cfg
ruleset-overkill.cfg
xonotic-client.cfg
xonotic-server.cfg

index e50392ca6a8e1c49df20f3465838b7a6052041c0..8243f0d9ba5af67ff036301b93dfb84e9510ca87 100644 (file)
@@ -29,7 +29,7 @@ test_sv_game:
     - wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
     - wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
     - make
-    - EXPECT=033546d32426e6409458fb39d0130f56
+    - EXPECT=de6a7d95ce65fb6c66558a93a9fb994f
     - HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
       | tee /dev/stderr
       | grep '^:'
index a4c4bedda1e60924fad98ed710e54804bb34054a..d2ff12f6bc81cf2e13051600315fd4fb78f9c2ca 100644 (file)
@@ -1,6 +1,6 @@
 // {{{ #1: Blaster
 set g_balance_blaster_primary_animtime 0.2
-set g_balance_blaster_primary_damage 25
+set g_balance_blaster_primary_damage 20
 set g_balance_blaster_primary_delay 0
 set g_balance_blaster_primary_edgedamage 12.5
 set g_balance_blaster_primary_force 300
@@ -60,8 +60,8 @@ set g_balance_shotgun_secondary_alt_animtime 0.2
 set g_balance_shotgun_secondary_alt_refire 1.2
 set g_balance_shotgun_switchdelay_drop 0.2
 set g_balance_shotgun_switchdelay_raise 0.2
-set g_balance_shotgun_weaponreplace ""
-set g_balance_shotgun_weaponstart 1
+set g_balance_shotgun_weaponreplace "shockwave"
+set g_balance_shotgun_weaponstart 0
 set g_balance_shotgun_weaponstartoverride -1
 set g_balance_shotgun_weaponthrowable 1
 // }}}
@@ -75,7 +75,7 @@ set g_balance_machinegun_burst_speed 0
 set g_balance_machinegun_first 1
 set g_balance_machinegun_first_ammo 1
 set g_balance_machinegun_first_damage 14
-set g_balance_machinegun_first_force 5
+set g_balance_machinegun_first_force 3
 set g_balance_machinegun_first_refire 0.125
 set g_balance_machinegun_first_spread 0.03
 set g_balance_machinegun_mode 1
@@ -87,12 +87,12 @@ set g_balance_machinegun_spread_max 0.05
 set g_balance_machinegun_spread_min 0.02
 set g_balance_machinegun_sustained_ammo 1
 set g_balance_machinegun_sustained_damage 10
-set g_balance_machinegun_sustained_force 5
+set g_balance_machinegun_sustained_force 3
 set g_balance_machinegun_sustained_refire 0.1
 set g_balance_machinegun_sustained_spread 0.03
 set g_balance_machinegun_switchdelay_drop 0.2
 set g_balance_machinegun_switchdelay_raise 0.2
-set g_balance_machinegun_weaponreplace ""
+set g_balance_machinegun_weaponreplace "arc"
 set g_balance_machinegun_weaponstart 0
 set g_balance_machinegun_weaponstartoverride -1
 set g_balance_machinegun_weaponthrowable 1
@@ -219,7 +219,7 @@ set g_balance_electro_secondary_speed 1000
 set g_balance_electro_secondary_speed_up 200
 set g_balance_electro_secondary_speed_z 0
 set g_balance_electro_secondary_spread 0
-set g_balance_electro_secondary_stick 1
+set g_balance_electro_secondary_stick 0
 set g_balance_electro_secondary_touchexplode 1
 set g_balance_electro_switchdelay_drop 0.2
 set g_balance_electro_switchdelay_raise 0.2
@@ -251,18 +251,18 @@ set g_balance_crylink_primary_other_lifetime 5
 set g_balance_crylink_primary_radius 80
 set g_balance_crylink_primary_refire 0.7
 set g_balance_crylink_primary_shots 6
-set g_balance_crylink_primary_speed 2000
+set g_balance_crylink_primary_speed 4000
 set g_balance_crylink_primary_spread 0.08
 set g_balance_crylink_reload_ammo 0
 set g_balance_crylink_reload_time 2
 set g_balance_crylink_secondary 1
-set g_balance_crylink_secondary_ammo 2
+set g_balance_crylink_secondary_ammo 3
 set g_balance_crylink_secondary_animtime 0.2
 set g_balance_crylink_secondary_bouncedamagefactor 0.5
 set g_balance_crylink_secondary_bounces 0
-set g_balance_crylink_secondary_damage 10
-set g_balance_crylink_secondary_edgedamage 5
-set g_balance_crylink_secondary_force -250
+set g_balance_crylink_secondary_damage 50
+set g_balance_crylink_secondary_edgedamage 15
+set g_balance_crylink_secondary_force -400
 set g_balance_crylink_secondary_joindelay 0
 set g_balance_crylink_secondary_joinexplode 0
 set g_balance_crylink_secondary_joinexplode_damage 0
@@ -275,11 +275,11 @@ set g_balance_crylink_secondary_middle_fadetime 5
 set g_balance_crylink_secondary_middle_lifetime 5
 set g_balance_crylink_secondary_other_fadetime 5
 set g_balance_crylink_secondary_other_lifetime 5
-set g_balance_crylink_secondary_radius 100
-set g_balance_crylink_secondary_refire 0.7
-set g_balance_crylink_secondary_shots 5
+set g_balance_crylink_secondary_radius 70
+set g_balance_crylink_secondary_refire 0.8
+set g_balance_crylink_secondary_shots 1
 set g_balance_crylink_secondary_speed 3000
-set g_balance_crylink_secondary_spread 0.01
+set g_balance_crylink_secondary_spread 0
 set g_balance_crylink_secondary_spreadtype 1
 set g_balance_crylink_switchdelay_drop 0.2
 set g_balance_crylink_switchdelay_raise 0.2
@@ -304,7 +304,7 @@ set g_balance_vortex_charge_velocity_rate 0
 set g_balance_vortex_primary_ammo 6
 set g_balance_vortex_primary_animtime 0.4
 set g_balance_vortex_primary_armorpierce 0
-set g_balance_vortex_primary_damage 70
+set g_balance_vortex_primary_damage 65
 set g_balance_vortex_primary_damagefalloff_forcehalflife 0
 set g_balance_vortex_primary_damagefalloff_halflife 0
 set g_balance_vortex_primary_damagefalloff_maxdist 0
@@ -373,7 +373,7 @@ set g_balance_hagar_secondary_speed 2000
 set g_balance_hagar_secondary_spread 0
 set g_balance_hagar_switchdelay_drop 0.2
 set g_balance_hagar_switchdelay_raise 0.2
-set g_balance_hagar_weaponreplace ""
+set g_balance_hagar_weaponreplace "0"
 set g_balance_hagar_weaponstart 0
 set g_balance_hagar_weaponstartoverride -1
 set g_balance_hagar_weaponthrowable 1
@@ -459,7 +459,7 @@ set g_balance_vaporizer_switchdelay_raise 0.2
 set g_balance_vaporizer_weaponreplace ""
 set g_balance_vaporizer_weaponstart 0
 set g_balance_vaporizer_weaponstartoverride -1
-set g_balance_vaporizer_weaponthrowable 0
+set g_balance_vaporizer_weaponthrowable 1
 // }}}
 // {{{ #13: Grappling Hook
 set g_balance_hook_primary_ammo 5
@@ -725,7 +725,7 @@ set g_balance_shockwave_melee_traces 10
 set g_balance_shockwave_switchdelay_drop 0.2
 set g_balance_shockwave_switchdelay_raise 0.2
 set g_balance_shockwave_weaponreplace ""
-set g_balance_shockwave_weaponstart 0
+set g_balance_shockwave_weaponstart 1
 set g_balance_shockwave_weaponstartoverride -1
 set g_balance_shockwave_weaponthrowable 0
 // }}}
@@ -753,7 +753,7 @@ set g_balance_arc_beam_heat 0
 set g_balance_arc_burst_heat 5
 set g_balance_arc_beam_maxangle 10
 set g_balance_arc_beam_nonplayerdamage 80
-set g_balance_arc_beam_range 1000
+set g_balance_arc_beam_range 1250
 set g_balance_arc_beam_refire 0.25
 set g_balance_arc_beam_returnspeed 8
 set g_balance_arc_beam_tightness 0.5
@@ -847,3 +847,116 @@ set g_balance_okrpc_weaponstart 0
 set g_balance_okrpc_weaponstartoverride 0
 set g_balance_okrpc_weaponthrowable 0
 // }}}
+// {{{ Overkill Shotgun
+set g_balance_okshotgun_primary_ammo 3
+set g_balance_okshotgun_primary_animtime 0.65
+set g_balance_okshotgun_primary_bot_range 512
+set g_balance_okshotgun_primary_bullets 10
+set g_balance_okshotgun_primary_damage 17
+set g_balance_okshotgun_primary_force 80
+set g_balance_okshotgun_primary_refire 0.75
+set g_balance_okshotgun_primary_solidpenetration 3.8
+set g_balance_okshotgun_primary_spread 0.07
+set g_balance_okshotgun_reload_ammo 24
+set g_balance_okshotgun_reload_time 2
+set g_balance_okshotgun_secondary_animtime 0.2
+set g_balance_okshotgun_secondary_damage 25
+set g_balance_okshotgun_secondary_delay 0
+set g_balance_okshotgun_secondary_edgedamage 12.5
+set g_balance_okshotgun_secondary_force 300
+set g_balance_okshotgun_secondary_lifetime 5
+set g_balance_okshotgun_secondary_radius 70
+set g_balance_okshotgun_secondary_refire 0.7
+set g_balance_okshotgun_secondary_refire_type 1
+set g_balance_okshotgun_secondary_shotangle 0
+set g_balance_okshotgun_secondary_speed 6000
+set g_balance_okshotgun_secondary_spread 0
+set g_balance_okshotgun_switchdelay_drop 0.2
+set g_balance_okshotgun_switchdelay_raise 0.2
+set g_balance_okshotgun_weaponreplace ""
+set g_balance_okshotgun_weaponstart 0
+set g_balance_okshotgun_weaponstartoverride -1
+set g_balance_okshotgun_weaponthrowable 1
+// }}}
+// {{{ Overkill Machine Gun
+set g_balance_okmachinegun_primary_ammo 1
+set g_balance_okmachinegun_primary_damage 25
+set g_balance_okmachinegun_primary_force 5
+set g_balance_okmachinegun_primary_refire 0.1
+set g_balance_okmachinegun_primary_solidpenetration 13.1
+set g_balance_okmachinegun_primary_spread_add 0.012
+set g_balance_okmachinegun_primary_spread_max 0.05
+set g_balance_okmachinegun_primary_spread_min 0
+set g_balance_okmachinegun_reload_ammo 30
+set g_balance_okmachinegun_reload_time 1.5
+set g_balance_okmachinegun_secondary_animtime 0.2
+set g_balance_okmachinegun_secondary_damage 25
+set g_balance_okmachinegun_secondary_delay 0
+set g_balance_okmachinegun_secondary_edgedamage 12.5
+set g_balance_okmachinegun_secondary_force 300
+set g_balance_okmachinegun_secondary_lifetime 5
+set g_balance_okmachinegun_secondary_radius 70
+set g_balance_okmachinegun_secondary_refire 0.7
+set g_balance_okmachinegun_secondary_refire_type 1
+set g_balance_okmachinegun_secondary_shotangle 0
+set g_balance_okmachinegun_secondary_speed 6000
+set g_balance_okmachinegun_secondary_spread 0
+set g_balance_okmachinegun_switchdelay_drop 0.2
+set g_balance_okmachinegun_switchdelay_raise 0.2
+set g_balance_okmachinegun_weaponreplace ""
+set g_balance_okmachinegun_weaponstart 0
+set g_balance_okmachinegun_weaponstartoverride -1
+set g_balance_okmachinegun_weaponthrowable 1
+// }}}
+// {{{ Overkill Nex
+set g_balance_oknex_charge 0
+set g_balance_oknex_charge_animlimit 0.5
+set g_balance_oknex_charge_limit 1
+set g_balance_oknex_charge_maxspeed 800
+set g_balance_oknex_charge_mindmg 40
+set g_balance_oknex_charge_minspeed 400
+set g_balance_oknex_charge_rate 0.6
+set g_balance_oknex_charge_rot_pause 0
+set g_balance_oknex_charge_rot_rate 0
+set g_balance_oknex_charge_shot_multiplier 0
+set g_balance_oknex_charge_start 0.5
+set g_balance_oknex_charge_velocity_rate 0
+set g_balance_oknex_primary_ammo 10
+set g_balance_oknex_primary_animtime 0.65
+set g_balance_oknex_primary_damage 100
+set g_balance_oknex_primary_damagefalloff_forcehalflife 0
+set g_balance_oknex_primary_damagefalloff_halflife 0
+set g_balance_oknex_primary_damagefalloff_maxdist 0
+set g_balance_oknex_primary_damagefalloff_mindist 0
+set g_balance_oknex_primary_force 500
+set g_balance_oknex_primary_refire 1
+set g_balance_oknex_reload_ammo 50
+set g_balance_oknex_reload_time 2
+set g_balance_oknex_secondary 2
+set g_balance_oknex_secondary_ammo 0
+set g_balance_oknex_secondary_animtime 0.2
+set g_balance_oknex_secondary_chargepool 0
+set g_balance_oknex_secondary_chargepool_pause_regen 1
+set g_balance_oknex_secondary_chargepool_regen 0.15
+set g_balance_oknex_secondary_damage 25
+set g_balance_oknex_secondary_damagefalloff_forcehalflife 0
+set g_balance_oknex_secondary_damagefalloff_halflife 0
+set g_balance_oknex_secondary_damagefalloff_maxdist 0
+set g_balance_oknex_secondary_damagefalloff_mindist 0
+set g_balance_oknex_secondary_force 300
+set g_balance_oknex_secondary_refire 0.7
+set g_balance_oknex_secondary_refire_type 1
+set g_balance_oknex_secondary_delay 0
+set g_balance_oknex_secondary_edgedamage 12.5
+set g_balance_oknex_secondary_lifetime 5
+set g_balance_oknex_secondary_radius 70
+set g_balance_oknex_secondary_shotangle 0
+set g_balance_oknex_secondary_speed 6000
+set g_balance_oknex_secondary_spread 0
+set g_balance_oknex_switchdelay_drop 0.2
+set g_balance_oknex_switchdelay_raise 0.2
+set g_balance_oknex_weaponreplace ""
+set g_balance_oknex_weaponstart 0
+set g_balance_oknex_weaponstartoverride -1
+set g_balance_oknex_weaponthrowable 1
+// }}}
index 4dda8bdc8842318701f1dcc39e95db6a87f8f7c3..1974ad6c811d130e2ad129424d24e5824b5135c9 100644 (file)
@@ -101,7 +101,7 @@ set g_pickup_respawntime_powerup 120
 set g_pickup_respawntime_weapon 10
 set g_pickup_respawntime_superweapon 120
 set g_pickup_respawntime_ammo 10
-set g_pickup_respawntime_initial_random 2
+set g_pickup_respawntime_initial_random 1
 set g_pickup_respawntimejitter_short 0
 set g_pickup_respawntimejitter_medium 0
 set g_pickup_respawntimejitter_long 0
@@ -132,7 +132,7 @@ set g_balance_pause_armor_rot_spawn 5
 set g_balance_armor_regenstable 100
 set g_balance_armor_rotstable 100
 set g_balance_armor_limit 200
-set g_balance_armor_blockpercent 0.7
+set g_balance_armor_blockpercent 0.55
 set g_balance_fuel_regen 0.1 "fuel regeneration (only applies if the player owns IT_FUEL_REGEN)"
 set g_balance_fuel_regenlinear 0
 set g_balance_pause_fuel_regen 2 // other than this, fuel uses the health regen counter
@@ -199,7 +199,7 @@ set g_maxpushtime 8.0 "timeout for kill credit when your damage knocks someone i
 
 // {{{ powerups
 set g_balance_powerup_invincible_takedamage 0.33 // only 1/3rd damage is taken
-set g_balance_powerup_invincible_takeforce 1
+set g_balance_powerup_invincible_takeforce 0.33
 set g_balance_powerup_invincible_time 30
 set g_balance_powerup_strength_damage 3
 set g_balance_powerup_strength_force 3
index e36e41e227850696970cec56ba62121f43a2c09b..b2edf84788332b7da31d681a318c19d6fd2e807d 100644 (file)
@@ -321,7 +321,7 @@ set sv_vote_master_password "" "when set, users can use \"vlogin PASSWORD\" to l
 set sv_vote_master_playerlimit 2 "Minimum number of players needed for a player to be allowed to vote for master"
 set sv_vote_no_stops_vote 1 "Allow the vote caller to stop his own vote simply by voting no"
 set sv_vote_singlecount 0      "set to 1 to count votes once after timeout or to 0 to count with every vote"
-set sv_vote_timeout 30 "a vote will timeout after this many seconds"
+set sv_vote_timeout 24 "a vote will timeout after this many seconds"
 set sv_vote_wait 120   "a player can not call a vote again for this many seconds when his vote was not accepted"
 set sv_vote_stop 15    "a player can not call a vote again for this many seconds when he stopped this vote (e.g. to correct it)"
 set sv_vote_majority_factor 0.5        "What percentage of the PLAYERS constitute a majority? (Must be at least 0.5, recommended: 0.5)"
index 7319cba4e8d1d1d286261036a5da4b215e26364c..41b5fc5dbe745dd85c02b81e6ad2b3ac6d2a574f 100644 (file)
@@ -478,7 +478,7 @@ seta g_nexball_tackling 1 "Allow ball theft?"
 set g_onslaught 0 "Onslaught: take control points towards the enemy generator and then destroy it"
 set g_onslaught_point_limit 1 "Onslaught point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 set g_onslaught_warmup 5
-set g_onslaught_round_timelimit 280
+set g_onslaught_round_timelimit 500
 set g_onslaught_teleport_radius 200 "Allows teleporting from a control point to another"
 set g_onslaught_teleport_wait 5 "Time before player can teleport again"
 set g_onslaught_spawn_choose 1 "Allow players to choose the control point to be spawned at"
index 95726571679ae6e985e0391b10f43c8a8af6a63e..9dcfe0629a03a1e249d4b9f2e37f8e65f673b40e 100644 (file)
@@ -44,7 +44,7 @@ seta hud_panel_weapons_bg_alpha ""
 seta hud_panel_weapons_bg_border ""
 seta hud_panel_weapons_bg_padding "0"
 seta hud_panel_weapons_accuracy "0"
-seta hud_panel_weapons_label "1"
+seta hud_panel_weapons_label "2"
 seta hud_panel_weapons_label_scale "0.3"
 seta hud_panel_weapons_complainbubble "1"
 seta hud_panel_weapons_complainbubble_padding "0"
index c997a807f459574846fc3de6b11af14ddbe7447b..afa17824980e02ddc6be90760b998ec8bfd8d1c5 100644 (file)
@@ -55,6 +55,7 @@ set g_instagib_friendlypush 1 "allow pushing teammates with the vaporizer primar
 //  overkill
 // ==========
 set g_overkill 0 "internal cvar, to enable overkill, use  `exec ruleset-overkill.cfg`"
+set g_overkill_weapons 0 "Whether to enable overkill weapons outside of overkill ruleset."
 
 set g_overkill_powerups_replace 1
 set g_overkill_itemwaypoints 1
@@ -293,7 +294,7 @@ set g_campcheck_distance 1800
 // ==========
 set g_new_toys 0 "Mutator 'New Toys': enable extra fun guns"
 set g_new_toys_autoreplace 2 "0: never replace, 1: always auto replace guns by available new toys, 2: randomly auto replace guns by available new toys"
-set g_new_toys_use_pickupsound 1 "play the 'new toys, new toys!' roflsound when picking up a new toys weapon"
+set g_new_toys_use_pickupsound 0 "play the 'new toys, new toys!' roflsound when picking up a new toys weapon"
 
 
 // =======
index 240e07a4aed707f674c447d01bd2239a65af117c..ab9184b9b9efccf44caaaa393a6bace1bf0f6e8e 100644 (file)
@@ -6,10 +6,10 @@
 #include <client/mapvoting.qc>
 #include <client/miscfunctions.qc>
 #include <client/player_skeleton.qc>
+#include <client/resources.qc>
 #include <client/shownames.qc>
 #include <client/teamradar.qc>
 #include <client/view.qc>
-#include <client/wall.qc>
 
 #include <client/commands/_mod.inc>
 #include <client/hud/_mod.inc>
index 10482caaa4b5081b492f925976c5a3fbbd2cc9fe..971cc01de6afaf6e3a002942cfac832a15b9d165 100644 (file)
@@ -6,10 +6,10 @@
 #include <client/mapvoting.qh>
 #include <client/miscfunctions.qh>
 #include <client/player_skeleton.qh>
+#include <client/resources.qh>
 #include <client/shownames.qh>
 #include <client/teamradar.qh>
 #include <client/view.qh>
-#include <client/wall.qh>
 
 #include <client/commands/_mod.qh>
 #include <client/hud/_mod.qh>
index 62b732bec25034e773801113d86aa9f268bfcbf4..0195db43a432e17e1e472155995451a8f703160d 100644 (file)
@@ -1,6 +1,6 @@
 #include "announcer.qh"
 
-#include "mutators/events.qh"
+#include <client/mutators/_mod.qh>
 
 #include <common/notifications/all.qh>
 #include <common/stats.qh>
@@ -129,6 +129,9 @@ void Announcer_Gamestart()
 
 void Announcer_Time()
 {
+       if(intermission)
+               return;
+
        float timeleft;
        if(warmup_stage)
        {
index 8eea240d40f4da8ebdd79bb9dee4a30b364e19b2..034bb6336680e7ead16421fcaf5fba0c8f78d69c 100644 (file)
 #include "../autocvars.qh"
 #include "../defs.qh"
 #include <client/hud/_mod.qh>
+#include <client/hud/panel/quickmenu.qh>
+#include <client/hud/panel/radar.qh>
 #include "../main.qh"
 #include "../mapvoting.qh"
 #include "../miscfunctions.qh"
 
-#include "../mutators/events.qh"
+#include <client/mutators/_mod.qh>
+
+#include <common/minigames/cl_minigames_hud.qh>
 
 #include <common/mapinfo.qh>
 
@@ -249,16 +253,6 @@ void LocalCommand_handlevote(int request, int argc)
        }
 }
 
-bool QuickMenu_IsOpened();
-void QuickMenu_Close();
-bool QuickMenu_Open(string mode, string submenu, string file);
-
-bool HUD_MinigameMenu_IsOpened();
-void HUD_MinigameMenu_Close(entity this, entity actor, entity trigger);
-void HUD_MinigameMenu_Open();
-
-void HUD_Radar_Show_Maximized(bool doshow, bool clickable);
-
 void LocalCommand_hud(int request, int argc)
 {
     TC(int, request); TC(int, argc);
index f1be4315fe5df1caeaaca2b6e29b62031bae2001..f6f96501aee2abb928a8de29ca923f602f80ea6b 100644 (file)
@@ -2,6 +2,7 @@
 
 void Cmd_Scoreboard_SetFields(int);
 void Cmd_Scoreboard_Help();
+void ConsoleCommand_macro_init();
 
 // used by common/command/generic.qc:GenericCommand_dumpcommands to list all commands into a .txt file
 void LocalCommand_macro_write_aliases(int fh);
index 6499a683e8f4c2cf198be39cb0bee2c9eb9e9a3f..d8f6d26a33895e91fd40fb6a8d1b42e723de8575 100644 (file)
@@ -2,7 +2,7 @@
 #include "autocvars.qh"
 #include "csqcmodel_hooks.qh"
 #include "miscfunctions.qh"
-#include "mutators/events.qh"
+#include <client/mutators/_mod.qh>
 #include "player_skeleton.qh"
 #include "weapons/projectile.qh"
 #include <common/animdecide.qh>
@@ -18,8 +18,6 @@
 .float death_time;
 .int modelflags;
 
-void CSQCModel_Hook_PreDraw(entity this, bool isplayer);
-
 .bool isplayermodel;
 
 // FEATURE: LOD
index 56a3fb4a549097a12295e15ce5c409b7b7e12cfe..8ed256379ff2e6d60b6bbf5a8598472f185999b4 100644 (file)
@@ -24,3 +24,5 @@ const int MF_TRACER3 =  BIT(7);  // purple trail
 .int csqcmodel_traileffect;
 
 void CSQCModel_Effects_Apply(entity this);
+
+void CSQCModel_Hook_PreDraw(entity this, bool isplayer);
index 01799caccc7d307e5c99daabde9263750e24b232..2b8eed95f7705578fc5ae34a083e8187c0f45a0b 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <client/defs.qh>
 #include <client/miscfunctions.qh>
+#include <client/view.qh>
 #include "panel/scoreboard.qh"
 #include "hud_config.qh"
 #include "../mapvoting.qh"
 #include <common/items/_mod.qh>
 #include <common/mapinfo.qh>
 #include <common/vehicles/all.qh>
+#include <common/vehicles/vehicle/bumblebee.qh>
 #include <common/mutators/mutator/waypoints/all.qh>
 #include <common/stats.qh>
 #include <lib/csqcmodel/cl_player.qh>
-#include <server/mutators/mutator/gamemode_ctf.qh> // TODO: remove
+#include <lib/csqcmodel/cl_model.qh>
+#include <common/gamemodes/_mod.qh>
 
 
 /*
@@ -395,8 +398,6 @@ Main HUD system
 ==================
 */
 
-void CSQC_BUMBLE_GUN_HUD();
-
 void HUD_Vehicle()
 {
        if(autocvar__hud_configure) return;
@@ -485,8 +486,6 @@ bool Hud_Shake_Update()
        return true;
 }
 
-entity CSQCModel_server2csqc(int i);
-void calc_followmodel_ofs(entity view);
 void Hud_Dynamic_Frame()
 {
        vector ofs = '0 0 0';
index 496d775efe4f3ad39c0a1a26196753bfb089b82a..950dee17ad5ec77799cab431c5bc3229f13d58af 100644 (file)
@@ -175,8 +175,6 @@ vector hud_shift;
 vector hud_shift_current = '0 0 0';
 vector hud_scale_center;
 
-float stringwidth_colors(string s, vector theSize);
-float stringwidth_nocolors(string s, vector theSize);
 void HUD_Panel_DrawProgressBar(vector theOrigin, vector theSize, string pic, float length_ratio, bool vertical, float baralign, vector theColor, float theAlpha, int drawflag);
 
 .int panel_showflags;
index 3c93a6d64ca1b5723817c94eaa4287a16c28e745..e8ec807be5bc4f9c4e0f59e9410b748da1520757 100644 (file)
@@ -660,12 +660,10 @@ void HUD_Panel_Arrow_Action(float nPrimary)
        }
 }
 
-void HUD_Panel_EnableMenu();
 entity tab_panels[hud_panels_MAX];
 entity tab_panel;
 vector tab_panel_pos;
 float tab_backward;
-void HUD_Panel_FirstInDrawQ(float id);
 void reset_tab_panels()
 {
        for (int i = 0; i < hud_panels_COUNT; ++i)
index 6ab64f6aed2a7548f5b51dc36bbb012713c8c9e7..d91fe370e1d997cd213e661dfeb69adb63ab6312 100644 (file)
@@ -23,3 +23,7 @@ void HUD_Configure_Frame();
 void HUD_Configure_PostDraw();
 
 float HUD_Panel_InputEvent(float bInputType, float nPrimary, float nSecondary);
+
+void HUD_Panel_EnableMenu();
+
+void HUD_Panel_FirstInDrawQ(float id);
index 66ca0772b602933b25d56b0af4a628616863f985..1ea23ae2e758531f19ce24d40843062252a3e277 100644 (file)
@@ -2,4 +2,4 @@
 
 #include "hud.qh"
 #include "hud_config.qh"
-#include <client/mutators/events.qh>
+#include <client/mutators/_mod.qh>
index 0636f3f2e972cfc551eb991b4631732d90aa79ab..01ce50cdbb6f87e03291c9f57232dc683aff76c9 100644 (file)
@@ -6,6 +6,7 @@
 #include <client/view.qh>
 #include <common/t_items.qh>
 #include <common/wepent.qh>
+#include <common/mutators/mutator/nades/nades.qh>
 
 // Ammo (#1)
 
@@ -19,8 +20,6 @@ void DrawNadeProgressBar(vector myPos, vector mySize, float progress, vector col
                autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
 }
 
-void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expand_time); // TODO: mutator
-
 void DrawAmmoItem(vector myPos, vector mySize, int ammoType, bool isCurrent, bool isInfinite)
 {
     TC(bool, isCurrent); TC(bool, isInfinite);
index 1e5a0c9f2f293403c18408e3ff0cdae1fc72d649..7b7d82b4445c376ea46a019daf14ee2488b0c5dc 100644 (file)
@@ -33,7 +33,6 @@ int img_select(int group_id)
        return img_cur_msg[group_id];
 }
 
-float stringwidth_colors(string s, vector theSize);
 vector InfoMessages_drawstring(string s, vector pos, vector sz, float a, vector fontsize)
 {
        getWrappedLine_remaining = s;
index 0bbcb41484a85db250f8655e03ec4f16149c1b10..89b8a8c188b4a981774ed1c173b98e8215c62a78 100644 (file)
@@ -5,7 +5,7 @@
 #include <common/mapinfo.qh>
 #include <common/ent_cs.qh>
 #include <common/scores.qh>
-#include <server/mutators/mutator/gamemode_ctf.qh> // TODO: remove
+#include <common/gamemodes/_mod.qh>
 
 // Mod icons (#10)
 
index b84066b84f4de86723d9944b6de302d2711e23f3..29f69b3d4d9c506efc1676f48055af6c13011c4e 100644 (file)
@@ -57,8 +57,6 @@ void QuickMenu_Page_ClearEntry(int i)
        QuickMenu_Page_Command_Type[i] = 0;
 }
 
-float QuickMenu_Page_Load(string target_submenu, float new_page);
-void QuickMenu_Default(string submenu);
 bool QuickMenu_Open(string mode, string submenu, string file)
 {
        int fh = -1;
@@ -225,7 +223,6 @@ bool QuickMenu_IsOpened()
        return (QuickMenu_Page_Entries > 0);
 }
 
-void HUD_Quickmenu_PlayerListEntries(string cmd, int teamplayers, bool without_me);
 bool HUD_Quickmenu_PlayerListEntries_Create(string cmd, int teamplayers, bool without_me)
 {
     TC(int, teamplayers); TC(bool, without_me);
index aad86e6bd689ed786280f6c1b8210e96b69ec6a3..694f0d1d7e0c439cedea395241f62d248e445473 100644 (file)
@@ -4,3 +4,8 @@
 bool QuickMenu_InputEvent(float bInputType, float nPrimary, float nSecondary);
 bool QuickMenu_IsOpened();
 void QuickMenu_Mouse();
+float QuickMenu_Page_Load(string target_submenu, float new_page);
+void QuickMenu_Default(string submenu);
+void HUD_Quickmenu_PlayerListEntries(string cmd, int teamplayers, bool without_me);
+void QuickMenu_Close();
+bool QuickMenu_Open(string mode, string submenu, string file);
index f0ec01c9e7b1847e68734896348da82b7658ad0f..bd94520d4e4292972e51277c0f27d6fc7fd7cd75 100644 (file)
@@ -6,6 +6,7 @@
 #include <common/ent_cs.qh>
 #include <common/mapinfo.qh>
 #include <client/mapvoting.qh>
+#include <client/resources.qh>
 #include <client/teamradar.qh>
 #include <common/mutators/mutator/waypoints/all.qh>
 
@@ -352,7 +353,7 @@ void HUD_Radar()
 
        IL_EACH(g_radaricons, it.teamradar_icon, {
                if ( hud_panel_radar_mouse )
-               if ( it.health >= 0 )
+               if ( GetResourceAmount(it, RESOURCE_HEALTH) >= 0 )
                if ( it.team == myteam + 1 || gametype == MAPINFO_TYPE_RACE || !teamplay )
                {
                        vector coord = teamradar_texcoord_to_2dcoord(teamradar_3dcoord_to_texcoord(it.origin));
index 6db88c68b39ee50fce1f8acce9c778465a3223ae..d2fbc8f6b25ce5bb6a5afe05bb2637dfe53bd08c 100644 (file)
@@ -1,2 +1,4 @@
 #pragma once
 #include "../panel.qh"
+
+void HUD_Radar_Show_Maximized(bool doshow, bool clickable);
index 96d30aa52ce8559e6c4cc7dc234d74a44f2e9a99..5b8964d0d3962a913033d672c7ad4366d9605b06 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <client/autocvars.qh>
 #include <client/defs.qh>
+#include <client/main.qh>
 #include <client/miscfunctions.qh>
 #include "quickmenu.qh"
 #include <common/ent_cs.qh>
@@ -70,10 +71,6 @@ bool autocvar_hud_panel_scoreboard_spectators_showping = true;
 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
 
-
-void drawstringright(vector, string, vector, vector, float, float);
-void drawstringcenter(vector, string, vector, vector, float, float);
-
 // wrapper to put all possible scores titles through gettext
 string TranslateScoresLabel(string l)
 {
@@ -150,7 +147,6 @@ void Scoreboard_InitScores()
        Cmd_Scoreboard_SetFields(0);
 }
 
-float SetTeam(entity pl, float Team);
 //float lastpnum;
 void Scoreboard_UpdatePlayerTeams()
 {
index c221125418748c434b08432131b1ed1a907f357f..3de8cefeb661084515be0ebe365d88f73888ede5 100644 (file)
@@ -8,13 +8,14 @@
 #include <common/effects/all.qh>
 #include <common/effects/all.inc>
 #include "hud/_mod.qh"
+#include "commands/cl_cmd.qh"
 #include "mapvoting.qh"
-#include "mutators/events.qh"
+#include <client/mutators/_mod.qh>
 #include "hud/panel/scoreboard.qh"
 #include "hud/panel/quickmenu.qh"
 #include "shownames.qh"
+#include "view.qh"
 #include <common/t_items.qh>
-#include "wall.qh"
 #include "weapons/projectile.qh"
 #include <common/deathtypes/all.qh>
 #include <common/items/_mod.qh>
@@ -91,7 +92,6 @@ void LoadMenuSkinValues()
 // CSQC_Init : Called every time the CSQC code is initialized (essentially at map load)
 // Useful for precaching things
 
-void ConsoleCommand_macro_init();
 void CSQC_Init()
 {
        prvm_language = strzone(cvar_string("prvm_language"));
@@ -343,7 +343,6 @@ void Playerchecker_Think(entity this)
        this.nextthink = time + 0.2;
 }
 
-void TrueAim_Init();
 void PostInit()
 {
        entity playerchecker = new_pure(playerchecker);
@@ -389,8 +388,6 @@ float CSQC_InputEvent(int bInputType, float nPrimary, float nSecondary)
 // --------------------------------------------------------------------------
 // BEGIN OPTIONAL CSQC FUNCTIONS
 
-void Ent_Remove(entity this);
-
 void Ent_RemovePlayerScore(entity this)
 {
        if(this.owner) {
@@ -947,7 +944,6 @@ void Fog_Force()
                localcmd(sprintf("\nfog %s\nr_fog_exp2 0\nr_drawfog 1\n", forcefog));
 }
 
-void Gamemode_Init();
 NET_HANDLE(ENT_CLIENT_SCORES_INFO, bool isnew)
 {
        make_pure(this);
index 70189b3a23b2a36099f342cfd7f410017f4930c3..a95acd5739672b5fe096b1e2891220ab92fe3f40 100644 (file)
@@ -21,6 +21,14 @@ void draw_cursor(vector pos, vector ofs, string img, vector col, float a);
 void draw_cursor_normal(vector pos, vector col, float a);
 void LoadMenuSkinValues();
 
+void PostInit();
+
+void Ent_Remove(entity this);
+
+void Gamemode_Init();
+
+float SetTeam(entity pl, float Team);
+
 vector hud_fontsize;
 
 float RANKINGS_RECEIVED_CNT;
index 87b6d585b38c769cd8d20d65d0be24ad3303d52d..8412bd757964ffdec471381121b8a923af5a152c 100644 (file)
@@ -321,7 +321,6 @@ float MapVote_Selection(vector topleft, vector cellsize, float rows, float colum
        return mv_mouse_selection;
 }
 
-vector HUD_GetTableSize_BestItemAR(int item_count, vector psize, float item_aspect);
 void MapVote_Draw()
 {
        string map;
index f23a3976b55c6fcaba36a0ec2d5301f80ab0ba8f..0143d1a0134ac1720a49273a0bb87b408156ab54 100644 (file)
@@ -34,7 +34,7 @@ float PreviewExists(string name);
 vector Rotate(vector v, float a);
 
 
-#define IS_DEAD(s) (((s).classname == "csqcmodel") ? (s).csqcmodel_isdead : ((s).health <= 0))
+#define IS_DEAD(s) (((s).classname == "csqcmodel") ? (s).csqcmodel_isdead : (GetResourceAmount((s), RESOURCE_HEALTH) <= 0))
 
 
 // decolorizes and team colors the player name when needed
index 220b9c6127ef958dd0b07c6312b44842240ac6e5..bb1b6f919b840c78a2bca6d339245e5b057acbc1 100644 (file)
@@ -2,7 +2,7 @@
 
 #include <common/physics/movetypes/movetypes.qh>
 #include <common/physics/player.qh>
-#include "mutators/events.qh"
+#include <client/mutators/_mod.qh>
 #include "../lib/csqcmodel/cl_player.qh"
 #include "../lib/warpzone/anglestransform.qh"
 
diff --git a/qcsrc/client/resources.qc b/qcsrc/client/resources.qc
new file mode 100644 (file)
index 0000000..29a9cae
--- /dev/null
@@ -0,0 +1,86 @@
+#include "resources.qh"
+
+/// \file
+/// \brief Source file that contains implementation of the resource system.
+/// \copyright GNU GPLv2 or any later version.
+
+float GetResourceAmount(entity e, int resource_type)
+{
+       .float resource_field = GetResourceField(resource_type);
+       return e.(resource_field);
+}
+
+bool SetResourceAmountExplicit(entity e, int resource_type, float amount)
+{
+       .float resource_field = GetResourceField(resource_type);
+       if (e.(resource_field) != amount)
+       {
+               e.(resource_field) = amount;
+               return true;
+       }
+       return false;
+}
+
+void SetResourceAmount(entity e, int resource_type, float amount)
+{
+       SetResourceAmountExplicit(e, resource_type, amount);
+}
+
+void TakeResource(entity receiver, int resource_type, float amount)
+{
+       if (amount == 0)
+       {
+               return;
+       }
+       SetResourceAmount(receiver, resource_type,
+               GetResourceAmount(receiver, resource_type) - amount);
+}
+
+void TakeResourceWithLimit(entity receiver, int resource_type, float amount,
+       float limit)
+{
+       if (amount == 0)
+       {
+               return;
+       }
+       float current_amount = GetResourceAmount(receiver, resource_type);
+       if (current_amount - amount < limit)
+       {
+               amount = limit + current_amount;
+       }
+       TakeResource(receiver, resource_type, amount);
+}
+
+int GetResourceType(.float resource_field)
+{
+       switch (resource_field)
+       {
+               case health: { return RESOURCE_HEALTH; }
+               case armorvalue: { return RESOURCE_ARMOR; }
+               case ammo_shells: { return RESOURCE_SHELLS; }
+               case ammo_nails: { return RESOURCE_BULLETS; }
+               case ammo_rockets: { return RESOURCE_ROCKETS; }
+               case ammo_cells: { return RESOURCE_CELLS; }
+               case ammo_plasma: { return RESOURCE_PLASMA; }
+               case ammo_fuel: { return RESOURCE_FUEL; }
+       }
+       error("GetResourceType: Invalid field.");
+       return 0;
+}
+
+.float GetResourceField(int resource_type)
+{
+       switch (resource_type)
+       {
+               case RESOURCE_HEALTH: { return health; }
+               case RESOURCE_ARMOR: { return armorvalue; }
+               case RESOURCE_SHELLS: { return ammo_shells; }
+               case RESOURCE_BULLETS: { return ammo_nails; }
+               case RESOURCE_ROCKETS: { return ammo_rockets; }
+               case RESOURCE_CELLS: { return ammo_cells; }
+               case RESOURCE_PLASMA: { return ammo_plasma; }
+               case RESOURCE_FUEL: { return ammo_fuel; }
+       }
+       error("GetResourceField: Invalid resource type.");
+       return health;
+}
diff --git a/qcsrc/client/resources.qh b/qcsrc/client/resources.qh
new file mode 100644 (file)
index 0000000..6d4f46e
--- /dev/null
@@ -0,0 +1,57 @@
+#pragma once
+
+/// \file
+/// \brief Header file that describes the resource system.
+/// \copyright GNU GPLv2 or any later version.
+
+#include <common/resources.qh>
+
+// ============================ Public API ====================================
+
+/// \brief Returns the current amount of resource the given entity has.
+/// \param[in] e Entity to check.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \return Current amount of resource the given entity has.
+float GetResourceAmount(entity e, int resource_type);
+
+/// \brief Sets the resource amount of an entity without calling any hooks.
+/// \param[in,out] e Entity to adjust.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to set.
+/// \return Boolean for whether the ammo amount was changed
+bool SetResourceAmountExplicit(entity e, int resource_type, float amount);
+
+/// \brief Sets the current amount of resource the given entity will have.
+/// \param[in,out] e Entity to adjust.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to set.
+/// \return No return.
+void SetResourceAmount(entity e, int resource_type, float amount);
+
+/// \brief Takes an entity some resource.
+/// \param[in,out] receiver Entity to take resource from.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to take.
+/// \return No return.
+void TakeResource(entity receiver, int resource_type, float amount);
+
+/// \brief Takes an entity some resource but not less than a limit.
+/// \param[in,out] receiver Entity to take resource from.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to take.
+/// \param[in] limit Limit of resources to take.
+/// \return No return.
+void TakeResourceWithLimit(entity receiver, int resource_type, float amount,
+       float limit);
+
+// ===================== Legacy and/or internal API ===========================
+
+/// \brief Converts an entity field to resource type.
+/// \param[in] resource_field Entity field to convert.
+/// \return Resource type (a RESOURCE_* constant).
+int GetResourceType(.float resource_field);
+
+/// \brief Converts resource type (a RESOURCE_* constant) to entity field.
+/// \param[in] resource_type Type of the resource.
+/// \return Entity field for that resource.
+.float GetResourceField(int resource_type);
index 8a7d225bff7bdc319260e5e0648a2aaff9b7aa08..2dbd8efa2760a437cd58bef57b9f7273a8a7b576 100644 (file)
@@ -157,10 +157,10 @@ void Draw_ShowNames(entity this)
                                        this.healthvalue / autocvar_hud_panel_healtharmor_maxhealth, false, 1, '1 0 0', a,
                                        DRAWFLAG_NORMAL);
                        }
-                       if (this.armorvalue > 0)
+                       if (GetResourceAmount(this, RESOURCE_ARMOR) > 0)
                        {
                                HUD_Panel_DrawProgressBar(pos + eX * 0.5 * mySize.x, sz, "nametag_statusbar",
-                                       this.armorvalue / autocvar_hud_panel_healtharmor_maxarmor, false, 0, '0 1 0', a,
+                                       GetResourceAmount(this, RESOURCE_ARMOR) / autocvar_hud_panel_healtharmor_maxarmor, false, 0, '0 1 0', a,
                                        DRAWFLAG_NORMAL);
                        }
                }
@@ -193,13 +193,13 @@ void Draw_ShowNames_All()
                if (entcs.m_entcs_private)
                {
                        it.healthvalue = entcs.healthvalue;
-                       it.armorvalue = entcs.armorvalue;
+                       SetResourceAmountExplicit(it, RESOURCE_ARMOR, GetResourceAmount(entcs, RESOURCE_ARMOR));
                        it.sameteam = true;
                }
                else
                {
                        it.healthvalue = 0;
-                       it.armorvalue = 0;
+                       SetResourceAmountExplicit(it, RESOURCE_ARMOR, 0);
                        it.sameteam = false;
                }
                bool dead = entcs_IsDead(i) || entcs_IsSpectating(i);
index fa5a79207e10519a7a3b1e1c106889d3d7e8ebe6..4d355fb2a177fd5fe102c19ff5b7783381abf227 100644 (file)
@@ -9,7 +9,7 @@
 #include "hud/panel/scoreboard.qh"
 #include "hud/panel/quickmenu.qh"
 
-#include "mutators/events.qh"
+#include <client/mutators/_mod.qh>
 
 #include <common/animdecide.qh>
 #include <common/deathtypes/all.qh>
@@ -17,6 +17,7 @@
 #include <common/anim.qh>
 #include <common/constants.qh>
 #include <common/net_linked.qh>
+#include <common/net_notice.qh>
 #include <common/debug.qh>
 #include <common/mapinfo.qh>
 #include <common/gamemodes/_mod.qh>
@@ -31,6 +32,7 @@
 #include <common/vehicles/all.qh>
 #include <common/weapons/_all.qh>
 #include <common/mutators/mutator/overkill/oknex.qh>
+#include <common/mutators/mutator/waypoints/all.qh>
 #include <common/viewloc.qh>
 #include <common/mapobjects/trigger/viewloc.qh>
 #include <common/minigames/cl_minigames.qh>
@@ -400,7 +402,6 @@ STATIC_INIT(fpscounter_init)
        showfps_prevfps_time = currentTime; // we must initialize it to avoid an instant low frame sending
 }
 
-void Porto_Draw(entity this);
 STATIC_INIT(Porto)
 {
        entity e = new_pure(porto);
@@ -768,8 +769,6 @@ float TrueAimCheck(entity wepent)
        return SHOTTYPE_HITWORLD;
 }
 
-void PostInit();
-void CSQC_Demo_Camera();
 float camera_mode;
 const float CAMERA_FREE = 1;
 const float CAMERA_CHASE = 2;
@@ -1561,12 +1560,9 @@ float oldr_novis;
 float oldr_useportalculling;
 float oldr_useinfinitefarclip;
 
-void cl_notice_run();
-
 float prev_myteam;
 int lasthud;
 float vh_notice_time;
-void WaypointSprite_Load();
 void CSQC_UpdateView(entity this, float w, float h)
 {
     TC(int, w); TC(int, h);
@@ -2061,7 +2057,7 @@ void CSQC_UpdateView(entity this, float w, float h)
 
        IL_EACH(g_drawables, it.draw, it.draw(it));
 
-       addentities(MASK_NORMAL | MASK_ENGINE | MASK_ENGINEVIEWMODELS);
+       addentities(MASK_NORMAL | MASK_ENGINE | MASK_ENGINEVIEWMODELS); // TODO: .health is used in cl_deathfade (a feature we have turned off currently)
        renderscene();
 
        // now switch to 2D drawing mode by calling a 2D drawing function
index 0a2c5c0c7066263f5a893538c5c26c152490f342..93af4255e2b95570cd5ab278b22af71110b8368e 100644 (file)
@@ -4,6 +4,14 @@
 
 vector crosshair_getcolor(entity this, float health_stat);
 
+void calc_followmodel_ofs(entity view);
+
+void Porto_Draw(entity this);
+
+void CSQC_Demo_Camera();
+
+void TrueAim_Init();
+
 entity viewmodels[MAX_WEAPONSLOTS];
 
 vector viewloc_mousepos;
diff --git a/qcsrc/client/wall.qc b/qcsrc/client/wall.qc
deleted file mode 100644 (file)
index 600bf5f..0000000
+++ /dev/null
@@ -1,226 +0,0 @@
-#include "wall.qh"
-
-#include "autocvars.qh"
-#include "main.qh"
-#include "bgmscript.qh"
-
-
-#include "../lib/csqcmodel/interpolate.qh"
-
-.float alpha;
-.float scale;
-.vector movedir;
-
-void Ent_Wall_PreDraw(entity this)
-{
-       if (this.inactive)
-       {
-               this.alpha = 0;
-       }
-       else
-       {
-               vector org = getpropertyvec(VF_ORIGIN);
-               if(!checkpvs(org, this))
-                       this.alpha = 0;
-               else if(this.fade_start || this.fade_end) {
-                       vector offset = '0 0 0';
-                       offset_z = this.fade_vertical_offset;
-                       float player_dist = vlen(org - this.origin - 0.5 * (this.mins + this.maxs) + offset);
-                       if (this.fade_end == this.fade_start)
-                       {
-                               if (player_dist >= this.fade_start)
-                                       this.alpha = 0;
-                               else
-                                       this.alpha = 1;
-                       }
-                       else
-                       {
-                               this.alpha = (this.alpha_min + this.alpha_max * bound(0,
-                                                          (this.fade_end - player_dist)
-                                                          / (this.fade_end - this.fade_start), 1)) / 100.0;
-                       }
-               }
-               else
-               {
-                       this.alpha = 1;
-               }
-       }
-       if(this.alpha <= 0)
-               this.drawmask = 0;
-       else
-               this.drawmask = MASK_NORMAL;
-}
-
-void Ent_Wall_Draw(entity this)
-{
-       float f;
-       var .vector fld;
-
-       if(this.bgmscriptangular)
-               fld = angles;
-       else
-               fld = origin;
-       this.(fld) = this.saved;
-
-       if(this.lodmodelindex1)
-       {
-               if(autocvar_cl_modeldetailreduction <= 0)
-               {
-                       if(this.lodmodelindex2 && autocvar_cl_modeldetailreduction <= -2)
-                               this.modelindex = this.lodmodelindex2;
-                       else if(autocvar_cl_modeldetailreduction <= -1)
-                               this.modelindex = this.lodmodelindex1;
-                       else
-                               this.modelindex = this.lodmodelindex0;
-               }
-               else
-               {
-                       float distance = vlen(NearestPointOnBox(this, view_origin) - view_origin);
-                       f = (distance * current_viewzoom + 100.0) * autocvar_cl_modeldetailreduction;
-                       f *= 1.0 / bound(0.01, view_quality, 1);
-                       if(this.lodmodelindex2 && f > this.loddistance2)
-                               this.modelindex = this.lodmodelindex2;
-                       else if(f > this.loddistance1)
-                               this.modelindex = this.lodmodelindex1;
-                       else
-                               this.modelindex = this.lodmodelindex0;
-               }
-       }
-
-       InterpolateOrigin_Do(this);
-
-       this.saved = this.(fld);
-
-       f = doBGMScript(this);
-       if(f >= 0)
-       {
-               if(this.lip < 0) // < 0: alpha goes from 1 to 1-|lip| when toggled (toggling subtracts lip)
-                       this.alpha = 1 + this.lip * f;
-               else // > 0: alpha goes from 1-|lip| to 1 when toggled (toggling adds lip)
-                       this.alpha = 1 - this.lip * (1 - f);
-               this.(fld) = this.(fld) + this.movedir * f;
-       }
-       else
-               this.alpha = 1;
-
-       if(this.alpha >= ALPHA_MIN_VISIBLE)
-               this.drawmask = MASK_NORMAL;
-       else
-               this.drawmask = 0;
-}
-
-void Ent_Wall_Remove(entity this)
-{
-       strfree(this.bgmscript);
-}
-
-NET_HANDLE(ENT_CLIENT_WALL, bool isnew)
-{
-       int f;
-       var .vector fld;
-
-       InterpolateOrigin_Undo(this);
-       this.iflags = IFLAG_ANGLES | IFLAG_ORIGIN;
-
-       if(this.bgmscriptangular)
-               fld = angles;
-       else
-               fld = origin;
-       this.(fld) = this.saved;
-
-       f = ReadByte();
-
-       if(f & 1)
-       {
-               if(f & 0x40)
-                       this.colormap = ReadShort();
-               else
-                       this.colormap = 0;
-               this.skin = ReadByte();
-       }
-
-       if(f & 2)
-       {
-               this.origin = ReadVector();
-               setorigin(this, this.origin);
-       }
-
-       if(f & 4)
-       {
-               if(f & 0x10)
-               {
-                       this.angles_x = ReadAngle();
-                       this.angles_y = ReadAngle();
-                       this.angles_z = ReadAngle();
-               }
-               else
-                       this.angles = '0 0 0';
-       }
-
-       if(f & 8)
-       {
-               if(f & 0x80)
-               {
-                       this.lodmodelindex0 = ReadShort();
-                       this.loddistance1 = ReadShort();
-                       this.lodmodelindex1 = ReadShort();
-                       this.loddistance2 = ReadShort();
-                       this.lodmodelindex2 = ReadShort();
-               }
-               else
-               {
-                       this.modelindex = ReadShort();
-                       this.loddistance1 = 0;
-                       this.loddistance2 = 0;
-               }
-               this.solid = ReadByte();
-               this.scale = ReadShort() / 256.0;
-               if(f & 0x20)
-               {
-                       this.mins = ReadVector();
-                       this.maxs = ReadVector();
-               }
-               else
-                       this.mins = this.maxs = '0 0 0';
-               setsize(this, this.mins, this.maxs);
-
-               string s = ReadString();
-               if(substring(s, 0, 1) == "<")
-               {
-                       strcpy(this.bgmscript, substring(s, 1, -1));
-                       this.bgmscriptangular = 1;
-               }
-               else
-               {
-                       strcpy(this.bgmscript, s);
-                       this.bgmscriptangular = 0;
-               }
-               if(this.bgmscript != "")
-               {
-                       this.bgmscriptattack = ReadByte() / 64.0;
-                       this.bgmscriptdecay = ReadByte() / 64.0;
-                       this.bgmscriptsustain = ReadByte() / 255.0;
-                       this.bgmscriptrelease = ReadByte() / 64.0;
-                       this.movedir = ReadVector();
-                       this.lip = ReadByte() / 255.0;
-               }
-               this.fade_start = ReadByte();
-               this.fade_end = ReadByte();
-               this.alpha_max = ReadByte();
-               this.alpha_min = ReadByte();
-               this.inactive = ReadByte();
-               this.fade_vertical_offset = ReadShort();
-               BGMScript_InitEntity(this);
-       }
-
-       return = true;
-
-       InterpolateOrigin_Note(this);
-
-       this.saved = this.(fld);
-
-       this.entremove = Ent_Wall_Remove;
-       this.draw = Ent_Wall_Draw;
-       if (isnew) IL_PUSH(g_drawables, this);
-       setpredraw(this, Ent_Wall_PreDraw);
-}
diff --git a/qcsrc/client/wall.qh b/qcsrc/client/wall.qh
deleted file mode 100644 (file)
index e55bc48..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma once
-
-entityclass(Wall);
-classfield(Wall) .float lip;
-classfield(Wall) .float bgmscriptangular;
-classfield(Wall) .int lodmodelindex0, lodmodelindex1, lodmodelindex2;
-classfield(Wall) .float loddistance1, loddistance2;
-classfield(Wall) .vector saved;
-
-// Needed for interactive clientwalls
-.float inactive; // Clientwall disappears when inactive
-.float alpha_max, alpha_min;
-// If fade_start > fade_end, fadeout will be inverted
-// fade_vertical_offset is a vertival offset for player position
-.float fade_start, fade_end, fade_vertical_offset;
-.float default_solid;
-
-void Ent_Wall_Draw(entity this);
-
-void Ent_Wall_Remove(entity this);
index 089c7df11fd326f0598f0243c59933a50a8a03a5..41b97694496616fa4de17b8f73aa2c163cb2fa6d 100644 (file)
@@ -3,7 +3,7 @@
 #include "../autocvars.qh"
 #include "../defs.qh"
 #include "../main.qh"
-#include "../mutators/events.qh"
+#include <client/mutators/_mod.qh>
 
 #include <common/constants.qh>
 #include <common/effects/effect.qh>
@@ -11,6 +11,8 @@
 #include <common/net_linked.qh>
 #include <common/physics/movetypes/movetypes.qh>
 
+#include <common/mutators/mutator/nades/nades.qh>
+
 #include <lib/csqcmodel/interpolate.qh>
 
 #include <lib/warpzone/anglestransform.qh>
@@ -50,8 +52,6 @@ void Projectile_DrawTrail(entity this, vector to)
        }
 }
 
-bool Projectile_isnade(int proj); // TODO: remove
-
 void Projectile_Draw(entity this)
 {
        vector rot;
index 983b073b406c8cfb93de92a31002d5dd1359fa56..ab4e6f2e950fffc5e5ce489d27e26acf004f61c1 100644 (file)
@@ -403,7 +403,7 @@ CLASS(DebugText3d, Object)
                CONSTRUCT(DebugText3d);
                this.origin = pos;
                this.message = strzone(msg);
-               this.health = align;
+               SetResourceAmount(this, RESOURCE_HEALTH, align);
                this.hit_time = time;
                this.fade_rate = fade_rate_;
                this.velocity = vel;
@@ -425,7 +425,7 @@ CLASS(DebugText3d, Object)
 
                int size = 8;
                vector screen_pos = project_3d_to_2d(this.origin) + since_created * this.velocity;
-               float align = this.health;
+               float align = GetResourceAmount(this, RESOURCE_HEALTH);
                if (align > 0)
                        screen_pos.x -= stringwidth(this.message, true, size * '1 1 0') * min(1, align);
                if (screen_pos.z < 0) return; // behind camera
index 2a1d587ca497d3ee57680684ad5c88b547568149..68b43b17608094c8cdddcd2725a3f607765940e4 100644 (file)
@@ -3,7 +3,7 @@
 #ifdef CSQC
 #include <common/deathtypes/all.qh>
 #include <common/physics/movetypes/movetypes.qh>
-#include <client/mutators/events.qh>
+#include <client/mutators/_mod.qh>
 #include <common/vehicles/all.qh>
 #include <common/weapons/_all.qh>
 #endif
index 0eeddc349bce72d747f55d2b1f3b8b3251ca7d50..86acdc154064ac6d0bc80258e7fca234d369740c 100644 (file)
@@ -1,4 +1,9 @@
 #include "ent_cs.qh"
+#include <common/gamemodes/_mod.qh>
+#include <common/resources.qh>
+#ifdef SVQC
+#include <server/resources.qh>
+#endif
 
 REGISTRY(EntCSProps, BITS(16) - 1)
 #define EntCSProps_from(i) _EntCSProps_from(i, NULL)
@@ -33,6 +38,26 @@ STATIC_INIT(RegisterEntCSProps_renumber) { FOREACH(EntCSProps, true, it.m_id = i
        }
 #endif
 
+#ifdef SVQC
+#define ENTCS_PROP_RESOURCE(id, ispublic, checkprop, setprop, svsend, clreceive) \
+       bool id##_check(entity ent, entity player) { return (GetResourceAmount(ent, checkprop) != GetResourceAmount(player, checkprop)); } \
+       void id##_set(entity ent, entity player) { SetResourceAmountExplicit(ent, checkprop, GetResourceAmount(player, checkprop)); } \
+       void id##_send(int chan, entity ent) { LAMBDA(svsend); } \
+       REGISTER(EntCSProps, ENTCS_PROP, id, m_id, new_pure(entcs_prop)) { \
+               this.m_public = ispublic; \
+               this.m_check = id##_check; \
+               this.m_set = id##_set; \
+               this.m_send = id##_send; \
+       }
+#elif defined(CSQC)
+#define ENTCS_PROP_RESOURCE(id, ispublic, checkprop, setprop, svsend, clreceive) \
+       void id##_receive(entity ent) { LAMBDA(clreceive); } \
+       REGISTER(EntCSProps, ENTCS_PROP, id, m_id, new_pure(entcs_prop)) { \
+               this.m_public = ispublic; \
+               this.m_receive = id##_receive; \
+       }
+#endif
+
 #define ENTCS_SET_NORMAL(var, x) MACRO_BEGIN \
        var = x; \
 MACRO_END
@@ -52,13 +77,13 @@ ENTCS_PROP(ANGLES, false, angles_y, ENTCS_SET_NORMAL,
        { WriteByte(chan, ent.angles.y / 360 * 256); },
        { vector v = '0 0 0'; v.y = ReadByte() / 256 * 360; ent.angles = v; })
 
-ENTCS_PROP(HEALTH, false, health, ENTCS_SET_NORMAL,
-       { WriteByte(chan, bound(0, ent.health / 10, 255));  /* FIXME: use a better scale? */ },
+ENTCS_PROP_RESOURCE(HEALTH, false, RESOURCE_HEALTH, ENTCS_SET_NORMAL,
+       { WriteByte(chan, bound(0, GetResourceAmount(ent, RESOURCE_HEALTH) / 10, 255));  /* FIXME: use a better scale? */ },
        { ent.healthvalue = ReadByte() * 10; })
 
-ENTCS_PROP(ARMOR, false, armorvalue, ENTCS_SET_NORMAL,
-       { WriteByte(chan, bound(0, ent.armorvalue / 10, 255));  /* FIXME: use a better scale? */ },
-       { ent.armorvalue = ReadByte() * 10; })
+ENTCS_PROP_RESOURCE(ARMOR, false, RESOURCE_ARMOR, ENTCS_SET_NORMAL,
+       { WriteByte(chan, bound(0, GetResourceAmount(ent, RESOURCE_ARMOR) / 10, 255));  /* FIXME: use a better scale? */ },
+       { SetResourceAmountExplicit(ent, RESOURCE_ARMOR, ReadByte() * 10); })
 
 ENTCS_PROP(NAME, true, netname, ENTCS_SET_MUTABLE_STRING,
        { WriteString(chan, ent.netname); },
index 2fc2c404678883117dcd9205365e36317f5e997e..c4cd002c782221fe420d38415c25fed570909a4d 100644 (file)
@@ -1,4 +1,17 @@
 // generated file; do not modify
 
+#include <common/gamemodes/gamemode/assault/_mod.inc>
+#include <common/gamemodes/gamemode/clanarena/_mod.inc>
+#include <common/gamemodes/gamemode/ctf/_mod.inc>
+#include <common/gamemodes/gamemode/cts/_mod.inc>
+#include <common/gamemodes/gamemode/deathmatch/_mod.inc>
+#include <common/gamemodes/gamemode/domination/_mod.inc>
+#include <common/gamemodes/gamemode/freezetag/_mod.inc>
+#include <common/gamemodes/gamemode/invasion/_mod.inc>
+#include <common/gamemodes/gamemode/keepaway/_mod.inc>
+#include <common/gamemodes/gamemode/keyhunt/_mod.inc>
+#include <common/gamemodes/gamemode/lms/_mod.inc>
 #include <common/gamemodes/gamemode/nexball/_mod.inc>
 #include <common/gamemodes/gamemode/onslaught/_mod.inc>
+#include <common/gamemodes/gamemode/race/_mod.inc>
+#include <common/gamemodes/gamemode/tdm/_mod.inc>
index d79957012609493478bdf9e0a03ea2116fec63c3..d7c1aa66cc35f6f3f8143031123542abf134a33c 100644 (file)
@@ -1,4 +1,17 @@
 // generated file; do not modify
 
+#include <common/gamemodes/gamemode/assault/_mod.qh>
+#include <common/gamemodes/gamemode/clanarena/_mod.qh>
+#include <common/gamemodes/gamemode/ctf/_mod.qh>
+#include <common/gamemodes/gamemode/cts/_mod.qh>
+#include <common/gamemodes/gamemode/deathmatch/_mod.qh>
+#include <common/gamemodes/gamemode/domination/_mod.qh>
+#include <common/gamemodes/gamemode/freezetag/_mod.qh>
+#include <common/gamemodes/gamemode/invasion/_mod.qh>
+#include <common/gamemodes/gamemode/keepaway/_mod.qh>
+#include <common/gamemodes/gamemode/keyhunt/_mod.qh>
+#include <common/gamemodes/gamemode/lms/_mod.qh>
 #include <common/gamemodes/gamemode/nexball/_mod.qh>
 #include <common/gamemodes/gamemode/onslaught/_mod.qh>
+#include <common/gamemodes/gamemode/race/_mod.qh>
+#include <common/gamemodes/gamemode/tdm/_mod.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/assault/_mod.inc b/qcsrc/common/gamemodes/gamemode/assault/_mod.inc
new file mode 100644 (file)
index 0000000..1deb031
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/assault/assault.qc>
diff --git a/qcsrc/common/gamemodes/gamemode/assault/_mod.qh b/qcsrc/common/gamemodes/gamemode/assault/_mod.qh
new file mode 100644 (file)
index 0000000..38b426d
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/assault/assault.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/assault/assault.qc b/qcsrc/common/gamemodes/gamemode/assault/assault.qc
new file mode 100644 (file)
index 0000000..9a2d6af
--- /dev/null
@@ -0,0 +1,644 @@
+#include "assault.qh"
+
+// TODO: split into sv_assault
+#ifdef SVQC
+.entity sprite;
+#define AS_ROUND_DELAY 5
+
+IntrusiveList g_assault_destructibles;
+IntrusiveList g_assault_objectivedecreasers;
+IntrusiveList g_assault_objectives;
+STATIC_INIT(g_assault)
+{
+       g_assault_destructibles = IL_NEW();
+       g_assault_objectivedecreasers = IL_NEW();
+       g_assault_objectives = IL_NEW();
+}
+
+// random functions
+void assault_objective_use(entity this, entity actor, entity trigger)
+{
+       // activate objective
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 100);
+       //print("^2Activated objective ", this.targetname, "=", etos(this), "\n");
+       //print("Activator is ", actor.classname, "\n");
+
+       IL_EACH(g_assault_objectivedecreasers, it.target == this.targetname,
+       {
+               target_objective_decrease_activate(it);
+       });
+}
+
+vector target_objective_spawn_evalfunc(entity this, entity player, entity spot, vector current)
+{
+       if(GetResourceAmount(this, RESOURCE_HEALTH) < 0 || GetResourceAmount(this, RESOURCE_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(entity this)
+{
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE);
+}
+
+// decrease the health of targeted objectives
+void assault_objective_decrease_use(entity this, entity actor, entity trigger)
+{
+       if(actor.team != assault_attacker_team)
+       {
+               // wrong team triggered decrease
+               return;
+       }
+
+       if(trigger.assault_sprite)
+       {
+               WaypointSprite_Disown(trigger.assault_sprite, waypointsprite_deadlifetime);
+               if(trigger.classname == "func_assault_destructible")
+                       trigger.sprite = NULL; // TODO: just unsetting it?!
+       }
+       else
+               return; // already activated! cannot activate again!
+
+       if(GetResourceAmount(this.enemy, RESOURCE_HEALTH) < ASSAULT_VALUE_INACTIVE)
+       {
+               if(GetResourceAmount(this.enemy, RESOURCE_HEALTH) - this.dmg > 0.5)
+               {
+                       GameRules_scoring_add_team(actor, SCORE, this.dmg);
+                       TakeResource(this.enemy, RESOURCE_HEALTH, this.dmg);
+               }
+               else
+               {
+                       GameRules_scoring_add_team(actor, SCORE, GetResourceAmount(this.enemy, RESOURCE_HEALTH));
+                       GameRules_scoring_add_team(actor, ASSAULT_OBJECTIVES, 1);
+                       SetResourceAmountExplicit(this.enemy, RESOURCE_HEALTH, -1);
+
+                       if(this.enemy.message)
+                               FOREACH_CLIENT(IS_PLAYER(it), { centerprint(it, this.enemy.message); });
+
+                       SUB_UseTargets(this.enemy, this, trigger);
+               }
+       }
+}
+
+void assault_setenemytoobjective(entity this)
+{
+       IL_EACH(g_assault_objectives, it.targetname == this.target,
+       {
+               if(this.enemy == NULL)
+                       this.enemy = it;
+               else
+                       objerror(this, "more than one objective as target - fix the map!");
+               break;
+       });
+
+       if(this.enemy == NULL)
+               objerror(this, "no objective as target - fix the map!");
+}
+
+bool assault_decreaser_sprite_visible(entity this, entity player, entity view)
+{
+       if(GetResourceAmount(this.assault_decreaser.enemy, RESOURCE_HEALTH) >= ASSAULT_VALUE_INACTIVE)
+               return false;
+
+       return true;
+}
+
+void target_objective_decrease_activate(entity this)
+{
+       entity spr;
+       this.owner = NULL;
+       FOREACH_ENTITY_STRING(target, this.targetname,
+       {
+               if(it.assault_sprite != NULL)
+               {
+                       WaypointSprite_Disown(it.assault_sprite, waypointsprite_deadlifetime);
+                       if(it.classname == "func_assault_destructible")
+                               it.sprite = NULL; // TODO: just unsetting it?!
+               }
+
+               spr = WaypointSprite_SpawnFixed(WP_AssaultDefend, 0.5 * (it.absmin + it.absmax), it, assault_sprite, RADARICON_OBJECTIVE);
+               spr.assault_decreaser = this;
+               spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
+               spr.classname = "sprite_waypoint";
+               WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
+               if(it.classname == "func_assault_destructible")
+               {
+                       WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
+                       WaypointSprite_UpdateMaxHealth(spr, it.max_health);
+                       WaypointSprite_UpdateHealth(spr, GetResourceAmount(it, RESOURCE_HEALTH));
+                       it.sprite = spr;
+               }
+               else
+                       WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
+       });
+}
+
+void target_objective_decrease_findtarget(entity this)
+{
+       assault_setenemytoobjective(this);
+}
+
+void target_assault_roundend_reset(entity this)
+{
+       //print("round end reset\n");
+       ++this.cnt; // up round counter
+       this.winning = false; // up round
+}
+
+void target_assault_roundend_use(entity this, entity actor, entity trigger)
+{
+       this.winning = 1; // round has been won by attackers
+}
+
+void assault_roundstart_use(entity this, entity actor, entity trigger)
+{
+       SUB_UseTargets(this, this, trigger);
+
+       //(Re)spawn all turrets
+       IL_EACH(g_turrets, true,
+       {
+               // Swap turret teams
+               if(it.team == NUM_TEAM_1)
+                       it.team = NUM_TEAM_2;
+               else
+                       it.team = NUM_TEAM_1;
+
+               // Doubles as teamchange
+               turret_respawn(it);
+       });
+}
+void assault_roundstart_use_this(entity this)
+{
+       assault_roundstart_use(this, NULL, NULL);
+}
+
+void assault_wall_think(entity this)
+{
+       if(GetResourceAmount(this.enemy, RESOURCE_HEALTH) < 0)
+       {
+               this.model = "";
+               this.solid = SOLID_NOT;
+       }
+       else
+       {
+               this.model = this.mdl;
+               this.solid = SOLID_BSP;
+       }
+
+       this.nextthink = time + 0.2;
+}
+
+// trigger new round
+// reset objectives, toggle spawnpoints, reset triggers, ...
+void assault_new_round(entity this)
+{
+       //bprint("ASSAULT: new round\n");
+
+       // up round counter
+       this.winning = this.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;
+
+       IL_EACH(g_saved_team, !IS_CLIENT(it),
+       {
+               if(it.team_saved == NUM_TEAM_1)
+                       it.team_saved = NUM_TEAM_2;
+               else if(it.team_saved == NUM_TEAM_2)
+                       it.team_saved = NUM_TEAM_1;
+       });
+
+       // reset the level with a countdown
+       cvar_set("timelimit", ftos(ceil(time - AS_ROUND_DELAY - game_starttime) / 60));
+       ReadyRestart_force(); // sets game_starttime
+}
+
+entity as_round;
+.entity ent_winning;
+void as_round_think()
+{
+       game_stopped = false;
+       assault_new_round(as_round.ent_winning);
+       delete(as_round);
+       as_round = NULL;
+}
+
+// Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives)
+// they win. Otherwise the defending team wins once the timelimit passes.
+int WinningCondition_Assault()
+{
+       if(as_round)
+               return WINNING_NO;
+
+       WinningConditionHelper(NULL); // set worldstatus
+
+       int status = WINNING_NO;
+       // as the timelimit has not yet passed just assume the defending team will win
+       if(assault_attacker_team == NUM_TEAM_1)
+       {
+               SetWinners(team, NUM_TEAM_2);
+       }
+       else
+       {
+               SetWinners(team, NUM_TEAM_1);
+       }
+
+       entity ent;
+       ent = find(NULL, classname, "target_assault_roundend");
+       if(ent)
+       {
+               if(ent.winning) // round end has been triggered by attacking team
+               {
+                       bprint("Assault: round completed.\n");
+                       SetWinners(team, assault_attacker_team);
+
+                       TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 666 - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 0));
+
+                       if(ent.cnt == 1 || autocvar_g_campaign) // this was the second round
+                       {
+                               status = WINNING_YES;
+                       }
+                       else
+                       {
+                               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ASSAULT_OBJ_DESTROYED, ceil(time - game_starttime));
+                               as_round = new(as_round);
+                               as_round.think = as_round_think;
+                               as_round.ent_winning = ent;
+                               as_round.nextthink = time + AS_ROUND_DELAY;
+                               game_stopped = true;
+
+                               // make sure timelimit isn't hit while the game is blocked
+                               if(autocvar_timelimit > 0)
+                               if(time + AS_ROUND_DELAY >= game_starttime + autocvar_timelimit * 60)
+                                       cvar_set("timelimit", ftos(autocvar_timelimit + AS_ROUND_DELAY / 60));
+                       }
+               }
+       }
+
+       return status;
+}
+
+// spawnfuncs
+spawnfunc(info_player_attacker)
+{
+       if (!g_assault) { delete(this); return; }
+
+       this.team = NUM_TEAM_1; // red, gets swapped every round
+       spawnfunc_info_player_deathmatch(this);
+}
+
+spawnfunc(info_player_defender)
+{
+       if (!g_assault) { delete(this); return; }
+
+       this.team = NUM_TEAM_2; // blue, gets swapped every round
+       spawnfunc_info_player_deathmatch(this);
+}
+
+spawnfunc(target_objective)
+{
+       if (!g_assault) { delete(this); return; }
+
+       this.classname = "target_objective";
+       IL_PUSH(g_assault_objectives, this);
+       this.use = assault_objective_use;
+       this.reset = assault_objective_reset;
+       this.reset(this);
+       this.spawn_evalfunc = target_objective_spawn_evalfunc;
+}
+
+spawnfunc(target_objective_decrease)
+{
+       if (!g_assault) { delete(this); return; }
+
+       this.classname = "target_objective_decrease";
+       IL_PUSH(g_assault_objectivedecreasers, this);
+
+       if(!this.dmg)
+               this.dmg = 101;
+
+       this.use = assault_objective_decrease_use;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE);
+       this.max_health = ASSAULT_VALUE_INACTIVE;
+       this.enemy = NULL;
+
+       InitializeEntity(this, target_objective_decrease_findtarget, INITPRIO_FINDTARGET);
+}
+
+// destructible walls that can be used to trigger target_objective_decrease
+bool destructible_heal(entity targ, entity inflictor, float amount, float limit)
+{
+       float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
+       if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
+               return false;
+
+       GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+       if(targ.sprite)
+       {
+               WaypointSprite_UpdateHealth(targ.sprite, GetResourceAmount(targ, RESOURCE_HEALTH));
+       }
+       func_breakable_colormod(targ);
+       return true;
+}
+
+spawnfunc(func_breakable);
+spawnfunc(func_assault_destructible)
+{
+       if (!g_assault) { delete(this); return; }
+
+       this.spawnflags = 3;
+       this.classname = "func_assault_destructible";
+       this.event_heal = destructible_heal;
+       IL_PUSH(g_assault_destructibles, this);
+
+       if(assault_attacker_team == NUM_TEAM_1)
+               this.team = NUM_TEAM_2;
+       else
+               this.team = NUM_TEAM_1;
+
+       spawnfunc_func_breakable(this);
+}
+
+spawnfunc(func_assault_wall)
+{
+       if (!g_assault) { delete(this); return; }
+
+       this.classname = "func_assault_wall";
+       this.mdl = this.model;
+       _setmodel(this, this.mdl);
+       this.solid = SOLID_BSP;
+       setthink(this, assault_wall_think);
+       this.nextthink = time;
+       InitializeEntity(this, assault_setenemytoobjective, INITPRIO_FINDTARGET);
+}
+
+spawnfunc(target_assault_roundend)
+{
+       if (!g_assault) { delete(this); return; }
+
+       this.winning = 0; // round not yet won by attackers
+       this.classname = "target_assault_roundend";
+       this.use = target_assault_roundend_use;
+       this.cnt = 0; // first round
+       this.reset = target_assault_roundend_reset;
+}
+
+spawnfunc(target_assault_roundstart)
+{
+       if (!g_assault) { delete(this); return; }
+
+       assault_attacker_team = NUM_TEAM_1;
+       this.classname = "target_assault_roundstart";
+       this.use = assault_roundstart_use;
+       this.reset2 = assault_roundstart_use_this;
+       InitializeEntity(this, assault_roundstart_use_this, INITPRIO_FINDTARGET);
+}
+
+// legacy bot code
+void havocbot_goalrating_ast_targets(entity this, float ratingscale)
+{
+       IL_EACH(g_assault_destructibles, it.bot_attack,
+       {
+               if (it.target == "")
+                       continue;
+
+               bool found = false;
+               entity destr = it;
+               IL_EACH(g_assault_objectivedecreasers, it.targetname == destr.target,
+               {
+                       if(GetResourceAmount(it.enemy, RESOURCE_HEALTH) > 0 && GetResourceAmount(it.enemy, RESOURCE_HEALTH) < ASSAULT_VALUE_INACTIVE)
+                       {
+                               found = true;
+                               break;
+                       }
+               });
+
+               if(!found)
+                       continue;
+
+               vector p = 0.5 * (it.absmin + it.absmax);
+
+               // Find and rate waypoints around it
+               found = false;
+               entity best = NULL;
+               float bestvalue = 99999999999;
+               entity des = it;
+               for(float radius = 0; radius < 1500 && !found; radius += 500)
+               {
+                       FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
+                       {
+                               if(checkpvs(it.origin, des))
+                               {
+                                       found = true;
+                                       if(it.cnt < bestvalue)
+                                       {
+                                               best = it;
+                                               bestvalue = it.cnt;
+                                       }
+                               }
+                       });
+               }
+
+               if(best)
+               {
+               ///     dprint("waypoints around target were found\n");
+               //      te_lightning2(NULL, '0 0 0', best.origin);
+               //      te_knightspike(best.origin);
+
+                       navigation_routerating(this, best, ratingscale, 4000);
+                       best.cnt += 1;
+
+                       this.havocbot_attack_time = 0;
+
+                       if(checkpvs(this.origin + this.view_ofs, it))
+                       if(checkpvs(this.origin + this.view_ofs, best))
+                       {
+                       //      dprint("increasing attack time for this target\n");
+                               this.havocbot_attack_time = time + 2;
+                       }
+               }
+       });
+}
+
+void havocbot_role_ast_offense(entity this)
+{
+       if(IS_DEAD(this))
+       {
+               this.havocbot_attack_time = 0;
+               havocbot_ast_reset_role(this);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + 120;
+
+       if (time > this.havocbot_role_timeout)
+       {
+               havocbot_ast_reset_role(this);
+               return;
+       }
+
+       if(this.havocbot_attack_time>time)
+               return;
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
+               havocbot_goalrating_ast_targets(this, 20000);
+               havocbot_goalrating_items(this, 15000, this.origin, 10000);
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_ast_defense(entity this)
+{
+       if(IS_DEAD(this))
+       {
+               this.havocbot_attack_time = 0;
+               havocbot_ast_reset_role(this);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + 120;
+
+       if (time > this.havocbot_role_timeout)
+       {
+               havocbot_ast_reset_role(this);
+               return;
+       }
+
+       if(this.havocbot_attack_time>time)
+               return;
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 3000);
+               havocbot_goalrating_ast_targets(this, 20000);
+               havocbot_goalrating_items(this, 15000, this.origin, 10000);
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_ast_setrole(entity this, float role)
+{
+       switch(role)
+       {
+               case HAVOCBOT_AST_ROLE_DEFENSE:
+                       this.havocbot_role = havocbot_role_ast_defense;
+                       this.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE;
+                       this.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_AST_ROLE_OFFENSE:
+                       this.havocbot_role = havocbot_role_ast_offense;
+                       this.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE;
+                       this.havocbot_role_timeout = 0;
+                       break;
+       }
+}
+
+void havocbot_ast_reset_role(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if(this.team == assault_attacker_team)
+               havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_OFFENSE);
+       else
+               havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_DEFENSE);
+}
+
+// mutator hooks
+MUTATOR_HOOKFUNCTION(as, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.team == assault_attacker_team)
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
+       else
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
+}
+
+MUTATOR_HOOKFUNCTION(as, TurretSpawn)
+{
+       entity turret = M_ARGV(0, entity);
+
+       if(!turret.team || turret.team == FLOAT_MAX)
+               turret.team = 5; // this gets reversed when match starts?
+}
+
+MUTATOR_HOOKFUNCTION(as, VehicleInit)
+{
+       entity veh = M_ARGV(0, entity);
+
+       veh.nextthink = time + 0.5;
+}
+
+MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole)
+{
+       entity bot = M_ARGV(0, entity);
+
+       havocbot_ast_reset_role(bot);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, PlayHitsound)
+{
+       entity frag_victim = M_ARGV(0, entity);
+
+       return (frag_victim.classname == "func_assault_destructible");
+}
+
+MUTATOR_HOOKFUNCTION(as, CheckAllowedTeams)
+{
+       // assault always has 2 teams
+       c1 = c2 = 0;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, CheckRules_World)
+{
+       M_ARGV(0, float) = WinningCondition_Assault();
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, ReadLevelCvars)
+{
+       // incompatible
+       warmup_stage = 0;
+       sv_ready_restart_after_countdown = 0;
+}
+
+MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn)
+{
+    entity ent = M_ARGV(0, entity);
+
+       switch(ent.classname)
+       {
+               case "info_player_team1":
+               case "info_player_team2":
+               case "info_player_team3":
+               case "info_player_team4":
+                       return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(as, ReadyRestart_Deny)
+{
+       // readyrestart not supported (yet)
+       return true;
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/assault/assault.qh b/qcsrc/common/gamemodes/gamemode/assault/assault.qh
new file mode 100644 (file)
index 0000000..d949f18
--- /dev/null
@@ -0,0 +1,48 @@
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+
+const int ASSAULT_VALUE_INACTIVE = 1000;
+
+const int ST_ASSAULT_OBJECTIVES = 1;
+
+REGISTER_MUTATOR(as, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+        GameRules_teams(true);
+        int teams = BITS(2); // always red vs blue
+        GameRules_scoring(teams, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, {
+            field_team(ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
+            field(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
+        });
+       }
+       return 0;
+}
+
+// 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(entity this) havocbot_role_ast_defense;
+void(entity this) havocbot_role_ast_offense;
+
+void(entity bot) havocbot_ast_reset_role;
+
+void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_items;
+void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
+
+// predefined spawnfuncs
+void target_objective_decrease_activate(entity this);
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/clanarena/_mod.inc b/qcsrc/common/gamemodes/gamemode/clanarena/_mod.inc
new file mode 100644 (file)
index 0000000..57dc9b3
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/clanarena/clanarena.qc>
diff --git a/qcsrc/common/gamemodes/gamemode/clanarena/_mod.qh b/qcsrc/common/gamemodes/gamemode/clanarena/_mod.qh
new file mode 100644 (file)
index 0000000..66f2374
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/clanarena/clanarena.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qc b/qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qc
new file mode 100644 (file)
index 0000000..561129c
--- /dev/null
@@ -0,0 +1,491 @@
+#include "clanarena.qh"
+
+// TODO: split into sv_clanarena
+#ifdef SVQC
+float autocvar_g_ca_damage2score_multiplier;
+bool autocvar_g_ca_spectate_enemies;
+
+void CA_count_alive_players()
+{
+       total_players = redalive = bluealive = yellowalive = pinkalive = 0;
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               switch(it.team)
+               {
+                       case NUM_TEAM_1: ++total_players; if(!IS_DEAD(it)) ++redalive; break;
+                       case NUM_TEAM_2: ++total_players; if(!IS_DEAD(it)) ++bluealive; break;
+                       case NUM_TEAM_3: ++total_players; if(!IS_DEAD(it)) ++yellowalive; break;
+                       case NUM_TEAM_4: ++total_players; if(!IS_DEAD(it)) ++pinkalive; break;
+               }
+       });
+       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+               STAT(REDALIVE, it) = redalive;
+               STAT(BLUEALIVE, it) = bluealive;
+               STAT(YELLOWALIVE, it) = yellowalive;
+               STAT(PINKALIVE, it) = 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
+}
+
+void nades_Clear(entity player);
+
+#define CA_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
+#define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == NumTeams(ca_teams))
+float CA_CheckWinner()
+{
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+               FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
+
+               allowed_to_spawn = false;
+               game_stopped = true;
+               round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+               return 1;
+       }
+
+       CA_count_alive_players();
+       if(CA_ALIVE_TEAMS() > 1)
+               return 0;
+
+       int winner_team = CA_GetWinnerTeam();
+       if(winner_team > 0)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+               TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1);
+       }
+       else if(winner_team == -1)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+       }
+
+       allowed_to_spawn = false;
+       game_stopped = true;
+       round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+
+       FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
+
+       return 1;
+}
+
+void CA_RoundStart()
+{
+    allowed_to_spawn = boolean(warmup_stage);
+}
+
+bool CA_CheckTeams()
+{
+       static int 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, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               return true;
+       }
+       if(total_players == 0)
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               return false;
+       }
+       int missing_teams_mask = 0;
+       if(ca_teams & BIT(0))
+               missing_teams_mask += (!redalive) * 1;
+       if(ca_teams & BIT(1))
+               missing_teams_mask += (!bluealive) * 2;
+       if(ca_teams & BIT(2))
+               missing_teams_mask += (!yellowalive) * 4;
+       if(ca_teams & BIT(3))
+               missing_teams_mask += (!pinkalive) * 8;
+       if(prev_missing_teams_mask != missing_teams_mask)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
+               prev_missing_teams_mask = missing_teams_mask;
+       }
+       return false;
+}
+
+bool ca_isEliminated(entity e)
+{
+       if(e.caplayer == 1 && (IS_DEAD(e) || 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;
+       // continue from current player
+       for (entity e = start; (e = find(e, classname, STR_PLAYER)); )
+       {
+               if (SAME_TEAM(player, e)) return e;
+       }
+       // restart from begining
+       for (entity e = NULL; (e = find(e, classname, STR_PLAYER)); )
+       {
+               if (SAME_TEAM(player, e)) return e;
+       }
+       return start;
+}
+
+
+MUTATOR_HOOKFUNCTION(ca, PlayerSpawn)
+{
+    entity player = M_ARGV(0, entity);
+
+       player.caplayer = 1;
+       if (!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ForbidSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       // spectators / observers that weren't playing can join; they are
+       // immediately forced to observe in the PutClientInServer hook
+       // this way they are put in a team and can play in the next round
+       if (!allowed_to_spawn && player.caplayer)
+               return true;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
+       {
+               TRANSMUTE(Observer, player);
+               if (CS(player).jointime != time && !player.caplayer) // not when connecting
+               {
+                       player.caplayer = 0.5;
+                       Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ca, reset_map_players)
+{
+       FOREACH_CLIENT(true, {
+               CS(it).killcount = 0;
+               if (!it.caplayer && IS_BOT_CLIENT(it))
+               {
+                       it.team = -1;
+                       it.caplayer = 1;
+               }
+               if (it.caplayer)
+               {
+                       TRANSMUTE(Player, it);
+                       it.caplayer = 1;
+                       PutClientInServer(it);
+               }
+       });
+       bot_relinkplayerlist();
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientConnect)
+{
+    entity player = M_ARGV(0, entity);
+
+       TRANSMUTE(Observer, player);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, reset_map_global)
+{
+       allowed_to_spawn = true;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       M_ARGV(0, float) = ca_teams;
+}
+
+entity ca_LastPlayerForTeam(entity this)
+{
+       entity last_pl = NULL;
+       FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
+               if (!IS_DEAD(it))
+               if (SAME_TEAM(this, it))
+               if (!last_pl)
+                       last_pl = it;
+               else
+                       return NULL;
+       });
+       return last_pl;
+}
+
+void ca_LastPlayerForTeam_Notify(entity this)
+{
+       if (round_handler_IsActive())
+       if (round_handler_IsRoundStarted())
+       {
+               entity pl = ca_LastPlayerForTeam(this);
+               if (pl)
+                       Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerDies)
+{
+       entity frag_target = M_ARGV(2, entity);
+
+       ca_LastPlayerForTeam_Notify(frag_target);
+       if (!allowed_to_spawn)
+       {
+               frag_target.respawn_flags = RESPAWN_SILENT;
+               // prevent unwanted sudden rejoin as spectator and movement of spectator camera
+               frag_target.respawn_time = time + 2;
+       }
+       frag_target.respawn_flags |= RESPAWN_FORCE;
+       if (!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
+       if(IS_BOT_CLIENT(frag_target))
+               bot_clear(frag_target);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
+{
+    entity player = M_ARGV(0, entity);
+
+       if (player.caplayer == 1)
+               ca_LastPlayerForTeam_Notify(player);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
+{
+    entity player = M_ARGV(0, entity);
+
+       if (!IS_DEAD(player))
+               ca_LastPlayerForTeam_Notify(player);
+       if (player.killindicator_teamchange == -2) // player wants to spectate
+               player.caplayer = 0;
+       if (player.caplayer)
+               player.frags = FRAGS_LMS_LOSER;
+       if (!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
+       if (!player.caplayer)
+               return false;  // allow team reset
+       return true;  // prevent team reset
+}
+
+MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon)
+{
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+       M_ARGV(2, float) = 0; // score will be given to the winner team when the round ends
+       return true;
+}
+
+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");
+}
+
+MUTATOR_HOOKFUNCTION(ca, Damage_Calculate)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_deathtype = M_ARGV(3, float);
+       float frag_damage = M_ARGV(4, float);
+       float frag_mirrordamage = M_ARGV(5, float);
+
+       if (IS_PLAYER(frag_target))
+       if (!IS_DEAD(frag_target))
+       if (frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id)
+               frag_damage = 0;
+
+       frag_mirrordamage = 0;
+
+       M_ARGV(4, float) = frag_damage;
+       M_ARGV(5, float) = frag_mirrordamage;
+}
+
+MUTATOR_HOOKFUNCTION(ca, FilterItem)
+{
+    entity item = M_ARGV(0, entity);
+
+       if (autocvar_g_powerups <= 0)
+       if (item.flags & FL_POWERUP)
+               return true;
+
+       if (autocvar_g_pickup_items <= 0)
+               return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_damage = M_ARGV(7, float);
+       float damage_take = M_ARGV(4, float);
+       float damage_save = M_ARGV(5, float);
+
+       float excess = max(0, frag_damage - damage_take - damage_save);
+
+       if (frag_target != frag_attacker && IS_PLAYER(frag_attacker))
+               GameRules_scoring_add_team(frag_attacker, SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier);
+}
+
+MUTATOR_HOOKFUNCTION(ca, CalculateRespawnTime)
+{
+       // no respawn calculations needed, player is forced to spectate anyway
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerRegen)
+{
+       // no regeneration in CA
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
+{
+       // announce remaining frags
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectateSet)
+{
+    entity client = M_ARGV(0, entity);
+    entity targ = M_ARGV(1, entity);
+
+       if (!autocvar_g_ca_spectate_enemies && client.caplayer)
+       if (DIFF_TEAM(targ, client))
+               return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectateNext)
+{
+    entity client = M_ARGV(0, entity);
+
+       if (!autocvar_g_ca_spectate_enemies && client.caplayer)
+       {
+               entity targ = M_ARGV(1, entity);
+               M_ARGV(1, entity) = CA_SpectateNext(client, targ);
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectatePrev)
+{
+    entity client = M_ARGV(0, entity);
+    entity targ = M_ARGV(1, entity);
+    entity first = M_ARGV(2, entity);
+
+       if (!autocvar_g_ca_spectate_enemies && client.caplayer)
+       {
+               do { targ = targ.chain; }
+               while(targ && DIFF_TEAM(targ, client));
+
+               if (!targ)
+               {
+                       for (targ = first; targ && DIFF_TEAM(targ, client); targ = targ.chain);
+
+                       if (targ == client.enemy)
+                               return MUT_SPECPREV_RETURN;
+               }
+       }
+
+       M_ARGV(1, entity) = targ;
+
+       return MUT_SPECPREV_FOUND;
+}
+
+MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
+{
+       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+               if (IS_PLAYER(it) || it.caplayer == 1)
+                       ++M_ARGV(0, int);
+               ++M_ARGV(1, int);
+       });
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate)
+{
+    entity player = M_ARGV(0, entity);
+
+       if (player.caplayer)
+       {
+               // they're going to spec, we can do other checks
+               if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
+                       Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
+               return MUT_SPECCMD_FORCE;
+       }
+
+       return MUT_SPECCMD_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(ca, WantWeapon)
+{
+       M_ARGV(2, bool) = true; // all weapons
+}
+
+MUTATOR_HOOKFUNCTION(ca, HideTeamNagger)
+{
+       return true; // doesn't work well with the whole spectator as player thing
+}
+
+MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus)
+{
+       entity player = M_ARGV(0, entity);
+
+       return player.caplayer == 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SetWeaponArena)
+{
+       // most weapons arena
+       if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "") M_ARGV(0, string) = "most";
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qh b/qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qh
new file mode 100644 (file)
index 0000000..8a94acd
--- /dev/null
@@ -0,0 +1,56 @@
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#include <server/round_handler.qh>
+#include <server/miscfunctions.qh>
+
+int autocvar_g_ca_point_limit;
+int autocvar_g_ca_point_leadlimit;
+float autocvar_g_ca_round_timelimit;
+bool autocvar_g_ca_team_spawns;
+//int autocvar_g_ca_teams;
+int autocvar_g_ca_teams_override;
+float autocvar_g_ca_warmup;
+
+
+int ca_teams;
+bool allowed_to_spawn;
+
+const int ST_CA_ROUNDS = 1;
+
+bool CA_CheckTeams();
+bool CA_CheckWinner();
+void CA_RoundStart();
+bool ca_isEliminated(entity e);
+
+REGISTER_MUTATOR(ca, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               GameRules_teams(true);
+        GameRules_spawning_teams(autocvar_g_ca_team_spawns);
+        GameRules_limit_score(autocvar_g_ca_point_limit);
+        GameRules_limit_lead(autocvar_g_ca_point_leadlimit);
+
+               ca_teams = autocvar_g_ca_teams_override;
+               if (ca_teams < 2)
+                       ca_teams = cvar("g_ca_teams"); // read the cvar directly as it gets written earlier in the same frame
+
+               ca_teams = BITS(bound(2, ca_teams, 4));
+        GameRules_scoring(ca_teams, SFL_SORT_PRIO_PRIMARY, 0, {
+            field_team(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY);
+        });
+
+               allowed_to_spawn = true;
+               round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart);
+               round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+               EliminatedPlayers_Init(ca_isEliminated);
+       }
+       return 0;
+}
+
+// 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/common/gamemodes/gamemode/ctf/_mod.inc b/qcsrc/common/gamemodes/gamemode/ctf/_mod.inc
new file mode 100644 (file)
index 0000000..dcd8135
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/ctf/ctf.qc>
diff --git a/qcsrc/common/gamemodes/gamemode/ctf/_mod.qh b/qcsrc/common/gamemodes/gamemode/ctf/_mod.qh
new file mode 100644 (file)
index 0000000..c1ddd97
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/ctf/ctf.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/ctf/ctf.qc b/qcsrc/common/gamemodes/gamemode/ctf/ctf.qc
new file mode 100644 (file)
index 0000000..3f96b41
--- /dev/null
@@ -0,0 +1,2779 @@
+#include "ctf.qh"
+
+// TODO: split into sv_ctf
+#ifdef SVQC
+#include <common/effects/all.qh>
+#include <common/vehicles/all.qh>
+#include <server/teamplay.qh>
+
+#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;
+bool autocvar_g_ctf_flag_return_carrying;
+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;
+bool autocvar_g_ctf_score_ignore_fields;
+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 != NULL) ? ftos(actor.playerid) : "")));
+               //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (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, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
+       else if(!ctf_captimerecord)
+               Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time));
+       else if(cap_time < cap_record)
+               Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
+       else
+               Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
+
+       // 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, flag.ctf_pickuptime, cap_time);
+       }
+
+       if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
+               race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
+}
+
+bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
+{
+       int num_perteam = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
+
+       // automatically return if there's only 1 player on the team
+       return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
+               && flag.team);
+}
+
+bool ctf_Return_Customize(entity this, entity client)
+{
+       // only to the carrier
+       return boolean(client == this.owner);
+}
+
+void ctf_FlagcarrierWaypoints(entity player)
+{
+       WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 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(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
+       WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
+
+       if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
+       {
+               if(!player.wps_enemyflagcarrier)
+               {
+                       entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
+                       wp.colormod = WPCOLOR_ENEMYFC(player.team);
+                       setcefc(wp, ctf_Stalemate_Customize);
+
+                       if(IS_REAL_CLIENT(player) && !ctf_stalemate)
+                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
+               }
+
+               if(!player.wps_flagreturn)
+               {
+                       entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
+                       owp.colormod = '0 0.8 0.8';
+                       //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
+                       setcefc(owp, ctf_Return_Customize);
+               }
+       }
+}
+
+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;
+       int players_worseeq, players_total;
+
+       if(ctf_captureshield_max_ratio <= 0)
+               return false;
+
+       s  = GameRules_scoring_add(p, CTF_CAPS, 0);
+       s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0);
+       s3 = GameRules_scoring_add(p, CTF_RETURNS, 0);
+       s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0);
+
+       sr = ((s - s2) + (s3 + s4));
+
+       if(sr >= -ctf_captureshield_min_negscore)
+               return false;
+
+       players_total = players_worseeq = 0;
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               if(DIFF_TEAM(it, p))
+                       continue;
+               se  = GameRules_scoring_add(it, CTF_CAPS, 0);
+               se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0);
+               se3 = GameRules_scoring_add(it, CTF_RETURNS, 0);
+               se4 = GameRules_scoring_add(it, 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(entity this, entity client)
+{
+       if(!client.ctf_captureshielded) { return false; }
+       if(CTF_SAMETEAM(this, client)) { return false; }
+
+       return true;
+}
+
+void ctf_CaptureShield_Touch(entity this, entity toucher)
+{
+       if(!toucher.ctf_captureshielded) { return; }
+       if(CTF_SAMETEAM(this, toucher)) { return; }
+
+       vector mymid = (this.absmin + this.absmax) * 0.5;
+       vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
+
+       Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
+       if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
+}
+
+void ctf_CaptureShield_Spawn(entity flag)
+{
+       entity shield = new(ctf_captureshield);
+
+       shield.enemy = flag;
+       shield.team = flag.team;
+       settouch(shield, ctf_CaptureShield_Touch);
+       setcefc(shield, ctf_CaptureShield_Customize);
+       shield.effects = EF_ADDITIVE;
+       set_movetype(shield, MOVETYPE_NOCLIP);
+       shield.solid = SOLID_TRIGGER;
+       shield.avelocity = '7 0 11';
+       shield.scale = 0.5;
+
+       setorigin(shield, flag.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
+       set_movetype(flag, MOVETYPE_TOSS);
+       flag.takedamage = DAMAGE_YES;
+       flag.angles = '0 0 0';
+       SetResourceAmountExplicit(flag, RESOURCE_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, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
+       _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
+       ctf_EventLog("dropped", player.team, player);
+
+       // scoring
+       GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
+       GameRules_scoring_add(player, CTF_DROPS, 1);
+
+       // waypoints
+       if(autocvar_g_ctf_flag_dropped_waypoint) {
+               entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL, ((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, GetResourceAmount(flag, RESOURCE_HEALTH));
+       }
+
+       player.throw_antispam = time + autocvar_g_ctf_pass_wait;
+
+       if(droptype == DROP_PASS)
+       {
+               flag.pass_distance = 0;
+               flag.pass_sender = NULL;
+               flag.pass_target = NULL;
+       }
+}
+
+void ctf_Handle_Retrieve(entity flag, entity player)
+{
+       entity sender = flag.pass_sender;
+
+       // transfer flag to player
+       flag.owner = player;
+       flag.owner.flagcarried = flag;
+       GameRules_scoring_vip(player, true);
+
+       // 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);
+       }
+       set_movetype(flag, 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);
+
+       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
+               if(it == sender)
+                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
+               else if(it == player)
+                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
+               else if(SAME_TEAM(it, sender))
+                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), 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 = NULL;
+       flag.pass_target = NULL;
+}
+
+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, NULL, "");
+       setorigin(flag, player.origin + FLAG_DROP_OFFSET);
+       flag.owner.flagcarried = NULL;
+       GameRules_scoring_vip(flag.owner, false);
+       flag.owner = NULL;
+       flag.solid = SOLID_TRIGGER;
+       flag.ctf_dropper = player;
+       flag.ctf_droptime = time;
+       navigation_dynamicgoal_set(flag);
+
+       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
+                       set_movetype(flag, 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(NULL, _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, 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, 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);
+
+       if(player.wps_flagreturn)
+               WaypointSprite_Kill(player.wps_flagreturn);
+
+       // captureshield
+       ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
+}
+
+void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
+{
+       return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
+}
+
+// ==============
+// Event Handlers
+// ==============
+
+void nades_GiveBonus(entity player, float score);
+
+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 = NULL, 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((flag.cnt || enemy_flag.cnt) && flag.cnt != enemy_flag.cnt) { return; } // this should catch some edge cases (capturing grouped flag at ungrouped flag disallowed etc)
+
+       if (toucher.goalentity == flag.bot_basewaypoint)
+               toucher.goalentity_lock_timeout = 0;
+
+       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, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
+       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
+       float pscore = 0;
+       if(enemy_flag.score_capture || flag.score_capture)
+               pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
+       GameRules_scoring_add_team(player, SCORE, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
+       float capscore = 0;
+       if(enemy_flag.score_team_capture || flag.score_team_capture)
+               capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
+       GameRules_scoring_add_team(player, CTF_CAPS, ((capscore) ? capscore : 1));
+
+       old_time = GameRules_scoring_add(player, CTF_CAPTIME, 0);
+       new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
+       if(!old_time || new_time < old_time)
+               GameRules_scoring_add(player, 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))
+                       { GameRules_scoring_add_team(enemy_flag.ctf_dropper, SCORE, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
+       }
+
+       flag.enemy = toucher;
+
+       // 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, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
+       }
+       else if(flag.team)
+       {
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, 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))
+       {
+               GameRules_scoring_add_team(player, SCORE, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
+               GameRules_scoring_add(player, 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)
+       {
+               GameRules_scoring_add(flag.ctf_dropper, 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);
+
+       flag.enemy = player;
+
+       // 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
+
+       // attach the flag to the player
+       flag.owner = player;
+       player.flagcarried = flag;
+       GameRules_scoring_vip(player, true);
+       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
+       set_movetype(flag, 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: SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health); break; // reset health/return timelimit
+               default: break;
+       }
+
+       // messages and sounds
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), 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_NUM(flag.team, CENTER_CTF_PICKUP));
+       else
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
+
+       Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
+
+       if(!flag.team)
+               FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), { Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); });
+
+       if(flag.team)
+               FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
+                       if(CTF_SAMETEAM(flag, it))
+                       if(SAME_TEAM(player, it))
+                               Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
+                       else
+                               Send_Notification(NOTIF_ONE, it, 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
+       GameRules_scoring_add(player, CTF_PICKUPS, 1);
+       nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
+       switch(pickuptype)
+       {
+               case PICKUP_BASE:
+               {
+                       GameRules_scoring_add_team(player, SCORE, ((flag.score_pickup) ? flag.score_pickup : 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));
+                       GameRules_scoring_add_team(player, SCORE, 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, GetResourceAmount(flag, RESOURCE_HEALTH)); }
+
+               if((GetResourceAmount(flag, RESOURCE_HEALTH) <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
+               {
+                       switch(returntype)
+                       {
+                               case RETURN_DROPPED:
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
+                               case RETURN_DAMAGE:
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
+                               case RETURN_SPEEDRUN:
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
+                               case RETURN_NEEDKILL:
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
+                               default:
+                               case RETURN_TIMEOUT:
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
+                       }
+                       _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
+                       ctf_EventLog("returned", flag.team, NULL);
+                       flag.enemy = NULL;
+                       ctf_RespawnFlag(flag);
+               }
+       }
+}
+
+bool ctf_Stalemate_Customize(entity this, entity client)
+{
+       // make spectators see what the player would see
+       entity e = WaypointSprite_getviewentity(client);
+       entity wp_owner = this.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()
+{
+       // 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 = NULL; // 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, NULL, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
+                               wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
+                               setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
+                       }
+               }
+
+               if (!wpforenemy_announced)
+               {
+                       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); });
+
+                       wpforenemy_announced = true;
+               }
+       }
+}
+
+void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
+{
+       if(ITEM_DAMAGE_NEEDKILL(deathtype))
+       {
+               if(autocvar_g_ctf_flag_return_damage_delay)
+                       this.ctf_flagdamaged_byworld = true;
+               else
+               {
+                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+                       ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
+               }
+               return;
+       }
+       if(autocvar_g_ctf_flag_return_damage)
+       {
+               // reduce health and check if it should be returned
+               TakeResource(this, RESOURCE_HEALTH, damage);
+               ctf_CheckFlagReturn(this, RETURN_DAMAGE);
+               return;
+       }
+}
+
+void ctf_FlagThink(entity this)
+{
+       // declarations
+       entity tmp_entity;
+
+       this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
+
+       // captureshield
+       if(this == ctf_worldflaglist) // only for the first flag
+               FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only
+
+       // sanity checks
+       if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
+               LOG_TRACE("wtf the flag got squashed?");
+               tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
+               if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
+                       setsize(this, this.m_mins, this.m_maxs);
+       }
+
+       // main think method
+       switch(this.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(vdist(this.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(this, tmp_entity, CAPTURE_DROPPED);
+                       }
+                       return;
+               }
+
+               case FLAG_DROPPED:
+               {
+                       this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
+
+                       if(autocvar_g_ctf_flag_dropped_floatinwater)
+                       {
+                               vector midpoint = ((this.absmin + this.absmax) * 0.5);
+                               if(pointcontents(midpoint) == CONTENT_WATER)
+                               {
+                                       this.velocity = this.velocity * 0.5;
+
+                                       if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
+                                               { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
+                                       else
+                                               { set_movetype(this, MOVETYPE_FLY); }
+                               }
+                               else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
+                       }
+                       if(autocvar_g_ctf_flag_return_dropped)
+                       {
+                               if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
+                               {
+                                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+                                       ctf_CheckFlagReturn(this, RETURN_DROPPED);
+                                       return;
+                               }
+                       }
+                       if(this.ctf_flagdamaged_byworld)
+                       {
+                               TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE));
+                               ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
+                               return;
+                       }
+                       else if(autocvar_g_ctf_flag_return_time)
+                       {
+                               TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE));
+                               ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
+                               return;
+                       }
+                       return;
+               }
+
+               case FLAG_CARRY:
+               {
+                       if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
+                       {
+                               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+                               ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
+
+                               CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
+                               ImpulseCommands(this.owner);
+                       }
+                       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(this, this.owner) && this.team)
+                       {
+                               if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
+                                       ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
+                               else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
+                                       ctf_Handle_Return(this, this.owner);
+                       }
+                       return;
+               }
+
+               case FLAG_PASSING:
+               {
+                       vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
+                       targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
+                       WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
+
+                       if((this.pass_target == NULL)
+                               || (IS_DEAD(this.pass_target))
+                               || (this.pass_target.flagcarried)
+                               || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
+                               || ((trace_fraction < 1) && (trace_ent != this.pass_target))
+                               || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
+                       {
+                               // give up, pass failed
+                               ctf_Handle_Drop(this, NULL, DROP_PASS);
+                       }
+                       else
+                       {
+                               // still a viable target, go for it
+                               ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
+                       }
+                       return;
+               }
+
+               default: // this should never happen
+               {
+                       LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
+                       return;
+               }
+       }
+}
+
+METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
+{
+       return = false;
+       if(game_stopped) return;
+       if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
+
+       bool is_not_monster = (!IS_MONSTER(toucher));
+
+       // 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)
+               {
+                       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, 0);
+                       ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
+               }
+               if(!flag.ctf_flagdamaged_byworld) { return; }
+       }
+
+       // special touch behaviors
+       if(STAT(FROZEN, toucher)) { 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 > flag.wait) // if we haven't in a while, play a sound/effect
+               {
+                       Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
+                       _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
+                       flag.wait = time + FLAG_TOUCHRATE;
+               }
+               return;
+       }
+       else if(IS_DEAD(toucher)) { return; }
+
+       switch(flag.ctf_status)
+       {
+               case FLAG_BASE:
+               {
+                       if(ctf_oneflag)
+                       {
+                               if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
+                                       ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
+                               else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
+                                       ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
+                       }
+                       else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
+                               ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
+                       else if(CTF_DIFFTEAM(toucher, flag) && (toucher.flagcarried) && CTF_SAMETEAM(toucher.flagcarried, toucher) && (!toucher.ctf_captureshielded) && autocvar_g_ctf_flag_return_carrying && (time > toucher.next_take_time) && is_not_monster)
+                       {
+                               ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
+                               ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
+                       }
+                       else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
+                               ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
+                       break;
+               }
+
+               case FLAG_DROPPED:
+               {
+                       if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
+                               ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
+                       else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
+                               ctf_Handle_Pickup(flag, 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?");
+                       break;
+               }
+
+               case FLAG_PASSING:
+               {
+                       if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
+                       {
+                               if(DIFF_TEAM(toucher, flag.pass_sender))
+                               {
+                                       if(ctf_Immediate_Return_Allowed(flag, toucher))
+                                               ctf_Handle_Return(flag, toucher);
+                                       else if(is_not_monster && (!toucher.flagcarried))
+                                               ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
+                               }
+                               else if(!toucher.flagcarried)
+                                       ctf_Handle_Retrieve(flag, 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.owner.wps_flagreturn);
+               WaypointSprite_Kill(flag.wps_flagcarrier);
+
+               flag.owner.flagcarried = NULL;
+               GameRules_scoring_vip(flag.owner, false);
+
+               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, NULL, "");
+       setorigin(flag, flag.ctf_spawnorigin);
+
+       set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
+       flag.takedamage = DAMAGE_NO;
+       SetResourceAmountExplicit(flag, RESOURCE_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 = NULL;
+       flag.pass_distance = 0;
+       flag.pass_sender = NULL;
+       flag.pass_target = NULL;
+       flag.ctf_dropper = NULL;
+       flag.ctf_pickuptime = 0;
+       flag.ctf_droptime = 0;
+       flag.ctf_flagdamaged_byworld = false;
+       navigation_dynamicgoal_unset(flag);
+
+       ctf_CheckStalemate();
+}
+
+void ctf_Reset(entity this)
+{
+       if(this.owner && IS_PLAYER(this.owner))
+               ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
+
+       this.enemy = NULL;
+       ctf_RespawnFlag(this);
+}
+
+bool ctf_FlagBase_Customize(entity this, entity client)
+{
+       entity e = WaypointSprite_getviewentity(client);
+       entity wp_owner = this.owner;
+       entity flag = e.flagcarried;
+       if(flag && CTF_SAMETEAM(e, flag))
+               return false;
+       if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
+               return false;
+       return true;
+}
+
+void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
+{
+       // bot waypoints
+       waypoint_spawnforitem_force(this, this.origin);
+       navigation_dynamicgoal_init(this, true);
+
+       // waypointsprites
+       entity basename;
+       switch (this.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, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
+       wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
+       WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
+       setcefc(wp, ctf_FlagBase_Customize);
+
+       // captureshield setup
+       ctf_CaptureShield_Spawn(this);
+}
+
+.bool pushable;
+
+void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
+{
+       // main setup
+       flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
+       ctf_worldflaglist = flag;
+
+       setattachment(flag, NULL, "");
+
+       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;
+       IL_PUSH(g_items, flag);
+       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);
+       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
+       flag.event_damage = ctf_FlagDamage;
+       flag.pushable = true;
+       flag.teleportable = TELEPORT_NORMAL;
+       flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
+       flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
+       flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
+       if(flag.damagedbycontents)
+               IL_PUSH(g_damagedbycontents, flag);
+       flag.velocity = '0 0 0';
+       flag.mangle = flag.angles;
+       flag.reset = ctf_Reset;
+       settouch(flag, ctf_FlagTouch);
+       setthink(flag, ctf_FlagThink);
+       flag.nextthink = time + FLAG_THINKRATE;
+       flag.ctf_status = FLAG_BASE;
+
+       // crudely force them all to 0
+       if(autocvar_g_ctf_score_ignore_fields)
+               flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
+
+       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)); }
+       if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
+       if (flag.passeffect == "")      { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
+       if (flag.capeffect == "")       { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
+
+       // sounds
+#define X(s,b) \
+               if(flag.s == "") flag.s = b; \
+               precache_sound(flag.s);
+
+       X(snd_flag_taken,               strzone(SND(CTF_TAKEN(teamnumber))))
+       X(snd_flag_returned,    strzone(SND(CTF_RETURNED(teamnumber))))
+       X(snd_flag_capture,     strzone(SND(CTF_CAPTURE(teamnumber))))
+       X(snd_flag_dropped,     strzone(SND(CTF_DROPPED(teamnumber))))
+       X(snd_flag_respawn,     strzone(SND(CTF_RESPAWN)))
+       X(snd_flag_touch,               strzone(SND(CTF_TOUCH)))
+       X(snd_flag_pass,                strzone(SND(CTF_PASS)))
+#undef X
+
+       // precache
+       precache_model(flag.model);
+
+       // appearence
+       _setmodel(flag, flag.model); // precision set below
+       setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
+       flag.m_mins = flag.mins; // store these for squash checks
+       flag.m_maxs = flag.maxs;
+       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;
+               set_movetype(flag, MOVETYPE_NONE);
+       }
+       else // drop to floor, automatically find a platform and set that as spawn origin
+       {
+               flag.noalign = false;
+               droptofloor(flag);
+               set_movetype(flag, MOVETYPE_NONE);
+       }
+
+       InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+// NOTE: LEGACY CODE, needs to be re-written!
+
+void havocbot_ctf_calculate_middlepoint()
+{
+       entity f;
+       vector s = '0 0 0';
+       vector fo = '0 0 0';
+       int n = 0;
+
+       f = ctf_worldflaglist;
+       while (f)
+       {
+               fo = f.origin;
+               s = s + fo;
+               f = f.ctf_worldflagnext;
+               n++;
+       }
+       if(!n)
+               return;
+
+       havocbot_middlepoint = s / n;
+       havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
+
+       havocbot_symmetry_axis_m = 0;
+       havocbot_symmetry_axis_q = 0;
+       if(n == 2)
+       {
+               // for symmetrical editing of waypoints
+               entity f1 = ctf_worldflaglist;
+               entity f2 = f1.ctf_worldflagnext;
+               float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
+               float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
+               havocbot_symmetry_axis_m = m;
+               havocbot_symmetry_axis_q = q;
+       }
+       havocbot_symmetry_origin_order = n;
+}
+
+
+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 NULL;
+}
+
+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 NULL;
+}
+
+int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
+{
+       if (!teamplay)
+               return 0;
+
+       int c = 0;
+
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
+                       continue;
+
+               if(vdist(it.origin - org, <, tc_radius))
+                       ++c;
+       });
+
+       return c;
+}
+
+// unused
+#if 0
+void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if (CTF_SAMETEAM(this, head))
+                       break;
+               head = head.ctf_worldflagnext;
+       }
+       if (head)
+               navigation_routerating(this, head, ratingscale, 10000);
+}
+#endif
+
+void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if (CTF_SAMETEAM(this, head))
+               {
+                       if (this.flagcarried)
+                       if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
+                       {
+                               head = head.ctf_worldflagnext; // skip base if it has a different group
+                               continue;
+                       }
+                       break;
+               }
+               head = head.ctf_worldflagnext;
+       }
+       if (!head)
+               return;
+
+       navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if(ctf_oneflag)
+               {
+                       if(CTF_DIFFTEAM(this, head))
+                       {
+                               if(head.team)
+                               {
+                                       if(this.flagcarried)
+                                               break;
+                               }
+                               else if(!this.flagcarried)
+                                       break;
+                       }
+               }
+               else if(CTF_DIFFTEAM(this, head))
+                       break;
+               head = head.ctf_worldflagnext;
+       }
+       if (head)
+               navigation_routerating(this, head, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
+{
+       if (!bot_waypoints_for_items)
+       {
+               havocbot_goalrating_ctf_enemyflag(this, ratingscale);
+               return;
+       }
+
+       entity head;
+
+       head = havocbot_ctf_find_enemy_flag(this);
+
+       if (!head)
+               return;
+
+       navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
+{
+       entity mf;
+
+       mf = havocbot_ctf_find_flag(this);
+
+       if(mf.ctf_status == FLAG_BASE)
+               return;
+
+       if(mf.tag_entity)
+               navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_droppedflags(entity this, 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==NULL)       // dropped
+               {
+                       if(df_radius)
+                       {
+                               if(vdist(org - head.origin, <, df_radius))
+                                       navigation_routerating(this, head, ratingscale, 10000);
+                       }
+                       else
+                               navigation_routerating(this, head, ratingscale, 10000);
+               }
+
+               head = head.ctf_worldflagnext;
+       }
+}
+
+void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
+{
+       IL_EACH(g_items, it.bot_pickup,
+       {
+               // gather health and armor only
+               if (it.solid)
+               if (GetResourceAmount(it, RESOURCE_HEALTH) || GetResourceAmount(it, RESOURCE_ARMOR))
+               if (vdist(it.origin - org, <, sradius))
+               {
+                       // get the value of the item
+                       float t = it.bot_pickupevalfunc(this, it) * 0.0001;
+                       if (t > 0)
+                               navigation_routerating(this, it, t * ratingscale, 500);
+               }
+       });
+}
+
+void havocbot_ctf_reset_role(entity this)
+{
+       float cdefense, cmiddle, coffense;
+       entity mf, ef;
+       float c;
+
+       if(IS_DEAD(this))
+               return;
+
+       // Check ctf flags
+       if (this.flagcarried)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       mf = havocbot_ctf_find_flag(this);
+       ef = havocbot_ctf_find_enemy_flag(this);
+
+       // Retrieve stolen flag
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(this, 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(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+               return;
+       }
+
+       // if there is only me on the team switch to offense
+       c = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
+
+       if(c==1)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
+               return;
+       }
+
+       // Evaluate best position to take
+       // Count mates on middle position
+       cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
+
+       // Count mates on defense position
+       cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
+
+       // Count mates on offense position
+       coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
+
+       if(cdefense<=coffense)
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
+       else if(coffense<=cmiddle)
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
+       else
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+}
+
+void havocbot_role_ctf_carrier(entity this)
+{
+       if(IS_DEAD(this))
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (this.flagcarried == NULL)
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+
+               if(ctf_oneflag)
+                       havocbot_goalrating_ctf_enemybase(this, 50000);
+               else
+                       havocbot_goalrating_ctf_ourbase(this, 50000);
+
+               if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
+                       havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+
+               entity head = ctf_worldflaglist;
+               while (head)
+               {
+                       if (this.goalentity == head.bot_basewaypoint)
+                       {
+                               this.goalentity_lock_timeout = time + 5;
+                               break;
+                       }
+                       head = head.ctf_worldflagnext;
+               }
+
+               if (this.goalentity)
+                       this.havocbot_cantfindflag = time + 10;
+               else if (time > this.havocbot_cantfindflag)
+               {
+                       // Can't navigate to my own base, suicide!
+                       // TODO: drop it and wander around
+                       Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
+                       return;
+               }
+       }
+}
+
+void havocbot_role_ctf_escort(entity this)
+{
+       entity mf, ef;
+
+       if(IS_DEAD(this))
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (this.flagcarried)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If enemy flag is back on the base switch to previous role
+       ef = havocbot_ctf_find_enemy_flag(this);
+       if(ef.ctf_status==FLAG_BASE)
+       {
+               this.havocbot_role = this.havocbot_previous_role;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       // If the flag carrier reached the base switch to defense
+       mf = havocbot_ctf_find_flag(this);
+       if(mf.ctf_status!=FLAG_BASE)
+       if(vdist(ef.origin - mf.dropped_origin, <, 300))
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!this.havocbot_role_timeout)
+       {
+               this.havocbot_role_timeout = time + random() * 30 + 60;
+       }
+
+       // If nothing happened just switch to previous role
+       if (time > this.havocbot_role_timeout)
+       {
+               this.havocbot_role = this.havocbot_previous_role;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       // Chase the flag carrier
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+
+               havocbot_goalrating_ctf_enemyflag(this, 30000);
+               havocbot_goalrating_ctf_ourstolenflag(this, 40000);
+               havocbot_goalrating_items(this, 10000, this.origin, 10000);
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_ctf_offense(entity this)
+{
+       entity mf, ef;
+       vector pos;
+
+       if(IS_DEAD(this))
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (this.flagcarried)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // Check flags
+       mf = havocbot_ctf_find_flag(this);
+       ef = havocbot_ctf_find_enemy_flag(this);
+
+       // 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(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
+               {
+                       havocbot_role_ctf_setrole(this, 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(vdist(pos - mf.dropped_origin, >, 700))
+               {
+                       havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
+                       return;
+               }
+       }
+
+       // About to fail, switch to middlefield
+       if(GetResourceAmount(this, RESOURCE_HEALTH) < 50)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + 120;
+
+       if (time > this.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+
+               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
+               havocbot_goalrating_ctf_enemybase(this, 20000);
+               havocbot_goalrating_items(this, 5000, this.origin, 1000);
+               havocbot_goalrating_items(this, 1000, this.origin, 10000);
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+// Retriever (temporary role):
+void havocbot_role_ctf_retriever(entity this)
+{
+       entity mf;
+
+       if(IS_DEAD(this))
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (this.flagcarried)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If flag is back on the base switch to previous role
+       mf = havocbot_ctf_find_flag(this);
+       if(mf.ctf_status==FLAG_BASE)
+       {
+               if (mf.enemy == this) // did this bot return the flag?
+                       navigation_goalrating_timeout_force(this);
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + 20;
+
+       if (time > this.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               float rt_radius;
+               rt_radius = 10000;
+
+               navigation_goalrating_start(this);
+
+               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
+               havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
+               havocbot_goalrating_ctf_enemybase(this, 30000);
+               havocbot_goalrating_items(this, 500, this.origin, rt_radius);
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_ctf_middle(entity this)
+{
+       entity mf;
+
+       if(IS_DEAD(this))
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (this.flagcarried)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       mf = havocbot_ctf_find_flag(this);
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
+               return;
+       }
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + 10;
+
+       if (time > this.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               vector org;
+
+               org = havocbot_middlepoint;
+               org.z = this.origin.z;
+
+               navigation_goalrating_start(this);
+
+               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
+               havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(this, 2500, this.origin, 10000);
+               havocbot_goalrating_ctf_enemybase(this, 2500);
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_ctf_defense(entity this)
+{
+       entity mf;
+
+       if(IS_DEAD(this))
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+
+       if (this.flagcarried)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If own flag was captured
+       mf = havocbot_ctf_find_flag(this);
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
+               return;
+       }
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + 30;
+
+       if (time > this.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(this);
+               return;
+       }
+       if (navigation_goalrating_timeout(this))
+       {
+               vector org = mf.dropped_origin;
+
+               navigation_goalrating_start(this);
+
+               // if enemies are closer to our base, go there
+               entity closestplayer = NULL;
+               float distance, bestdistance = 10000;
+               FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
+                       distance = vlen(org - it.origin);
+                       if(distance<bestdistance)
+                       {
+                               closestplayer = it;
+                               bestdistance = distance;
+                       }
+               });
+
+               if(closestplayer)
+               if(DIFF_TEAM(closestplayer, this))
+               if(vdist(org - this.origin, >, 1000))
+               if(checkpvs(this.origin,closestplayer)||random()<0.5)
+                       havocbot_goalrating_ctf_ourbase(this, 30000);
+
+               havocbot_goalrating_ctf_ourstolenflag(this, 20000);
+               havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
+               havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
+               havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
+               havocbot_goalrating_items(this, 5000, this.origin, 10000);
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_ctf_setrole(entity bot, int role)
+{
+       string s = "(null)";
+       switch(role)
+       {
+               case HAVOCBOT_CTF_ROLE_CARRIER:
+                       s = "carrier";
+                       bot.havocbot_role = havocbot_role_ctf_carrier;
+                       bot.havocbot_role_timeout = 0;
+                       bot.havocbot_cantfindflag = time + 10;
+                       if (bot.havocbot_previous_role != bot.havocbot_role)
+                               navigation_goalrating_timeout_force(bot);
+                       break;
+               case HAVOCBOT_CTF_ROLE_DEFENSE:
+                       s = "defense";
+                       bot.havocbot_role = havocbot_role_ctf_defense;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_MIDDLE:
+                       s = "middle";
+                       bot.havocbot_role = havocbot_role_ctf_middle;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_OFFENSE:
+                       s = "offense";
+                       bot.havocbot_role = havocbot_role_ctf_offense;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_RETRIEVER:
+                       s = "retriever";
+                       bot.havocbot_previous_role = bot.havocbot_role;
+                       bot.havocbot_role = havocbot_role_ctf_retriever;
+                       bot.havocbot_role_timeout = time + 10;
+                       if (bot.havocbot_previous_role != bot.havocbot_role)
+                               navigation_goalrating_timeout_expire(bot, 2);
+                       break;
+               case HAVOCBOT_CTF_ROLE_ESCORT:
+                       s = "escort";
+                       bot.havocbot_previous_role = bot.havocbot_role;
+                       bot.havocbot_role = havocbot_role_ctf_escort;
+                       bot.havocbot_role_timeout = time + 30;
+                       if (bot.havocbot_previous_role != bot.havocbot_role)
+                               navigation_goalrating_timeout_expire(bot, 2);
+                       break;
+       }
+       LOG_TRACE(bot.netname, " switched to ", s);
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
+{
+       entity player = M_ARGV(0, entity);
+
+       int t = 0, t2 = 0, t3 = 0;
+       bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC)
+
+       // initially clear items so they can be set as necessary later.
+       STAT(CTF_FLAGSTATUS, player) &= ~(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 | CTF_STALEMATE);
+
+       // scan through all the flags and notify the client about them
+       for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+       {
+               if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING;              t2 = CTF_RED_FLAG_TAKEN;                t3 = CTF_RED_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING;             t2 = CTF_BLUE_FLAG_TAKEN;               t3 = CTF_BLUE_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING;   t2 = CTF_YELLOW_FLAG_TAKEN;             t3 = CTF_YELLOW_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING;             t2 = CTF_PINK_FLAG_TAKEN;               t3 = CTF_PINK_FLAG_LOST; }
+               if(flag.team == 0 && !b5)                  { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING;  t2 = CTF_NEUTRAL_FLAG_TAKEN;    t3 = CTF_NEUTRAL_FLAG_LOST; STAT(CTF_FLAGSTATUS, player) |= CTF_FLAG_NEUTRAL; }
+
+               switch(flag.ctf_status)
+               {
+                       case FLAG_PASSING:
+                       case FLAG_CARRY:
+                       {
+                               if((flag.owner == player) || (flag.pass_sender == player))
+                                       STAT(CTF_FLAGSTATUS, player) |= t; // carrying: player is currently carrying the flag
+                               else
+                                       STAT(CTF_FLAGSTATUS, player) |= t2; // taken: someone else is carrying the flag
+                               break;
+                       }
+                       case FLAG_DROPPED:
+                       {
+                               STAT(CTF_FLAGSTATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
+                               break;
+                       }
+               }
+       }
+
+       // item for stopping players from capturing the flag too often
+       if(player.ctf_captureshielded)
+               STAT(CTF_FLAGSTATUS, player) |= CTF_SHIELDED;
+
+       if(ctf_stalemate)
+               STAT(CTF_FLAGSTATUS, player) |= CTF_STALEMATE;
+
+       // update the health of the flag carrier waypointsprite
+       if(player.wps_flagcarrier)
+               WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
+}
+
+MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_damage = M_ARGV(4, float);
+       vector frag_force = M_ARGV(6, vector);
+
+       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;
+               }
+
+               M_ARGV(4, float) = frag_damage;
+               M_ARGV(6, vector) = frag_force;
+       }
+       else if(frag_target.flagcarried && !IS_DEAD(frag_target) && 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(GetResourceAmount(frag_target, RESOURCE_HEALTH), GetResourceAmount(frag_target, RESOURCE_ARMOR), 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?
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+
+       if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
+       {
+               GameRules_scoring_add_team(frag_attacker, SCORE, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
+               GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1);
+       }
+
+       if(frag_target.flagcarried)
+       {
+               entity tmp_entity = frag_target.flagcarried;
+               ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
+               tmp_entity.ctf_dropper = NULL;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
+{
+       M_ARGV(2, float) = 0; // frag score
+       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, NULL, DROP_NORMAL); }
+
+       for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+       {
+               if(flag.pass_sender == player) { flag.pass_sender = NULL; }
+               if(flag.pass_target == player) { flag.pass_target = NULL; }
+               if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       ctf_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       ctf_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
+{
+       if(!autocvar_g_ctf_leaderboard)
+               return;
+
+       entity player = M_ARGV(0, entity);
+
+       if(IS_REAL_CLIENT(player))
+       {
+               int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+               race_send_rankings_cnt(MSG_ONE);
+               for (int i = 1; i <= m; ++i)
+               {
+                       race_SendRankings(i, 0, 0, MSG_ONE);
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
+{
+       if(!autocvar_g_ctf_leaderboard)
+               return;
+
+       entity player = M_ARGV(0, entity);
+
+       if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
+       {
+               if (!player.stored_netname)
+                       player.stored_netname = strzone(uid2name(player.crypto_idfp));
+               if(player.stored_netname != player.netname)
+               {
+                       db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
+                       strcpy(player.stored_netname, player.netname);
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.flagcarried)
+       if(!autocvar_g_ctf_portalteleport)
+               { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
+{
+       if(MUTATOR_RETURNVALUE || game_stopped) return;
+
+       entity player = M_ARGV(0, entity);
+
+       if((time > player.throw_antispam) && !IS_DEAD(player) && !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 = NULL;
+                       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) && !IS_DEAD(head))
+                               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 && !head.flagcarried)
+                                               {
+                                                       if(closest_target)
+                                                       {
+                                                               vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
+                                                               if(vlen2(passer_center - head_center) < vlen2(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, NULL, 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, NULL, DROP_THROW);
+                               return true;
+                       }
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
+       {
+               player.wps_helpme_time = time;
+               WaypointSprite_HelpMePing(player.wps_flagcarrier);
+       }
+       else // create a normal help me waypointsprite
+       {
+               WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
+               WaypointSprite_Ping(player.wps_helpme);
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
+{
+       entity player = M_ARGV(0, entity);
+       entity veh = M_ARGV(1, entity);
+
+       if(player.flagcarried)
+       {
+               if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
+               {
+                       ctf_Handle_Throw(player, NULL, DROP_NORMAL);
+               }
+               else
+               {
+                       player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
+                       setattachment(player.flagcarried, veh, "");
+                       setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
+                       player.flagcarried.scale = VEHICLE_FLAG_SCALE;
+                       //player.flagcarried.angles = '0 0 0';
+               }
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.flagcarried)
+       {
+               setattachment(player.flagcarried, player, "");
+               setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
+               player.flagcarried.scale = FLAG_SCALE;
+               player.flagcarried.angles = '0 0 0';
+               player.flagcarried.nodrawtoclient = NULL;
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.flagcarried)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
+               ctf_RespawnFlag(player.flagcarried);
+               return true;
+       }
+}
+
+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
+                               set_movetype(flag, 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;
+                       }
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
+{
+       entity bot = M_ARGV(0, entity);
+
+       havocbot_ctf_reset_role(bot);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
+{
+       //M_ARGV(0, float) = ctf_teams;
+       M_ARGV(1, string) = "ctf_team";
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
+{
+       entity spectatee = M_ARGV(0, entity);
+       entity client = M_ARGV(1, entity);
+
+       STAT(CTF_FLAGSTATUS, client) = STAT(CTF_FLAGSTATUS, spectatee);
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GetRecords)
+{
+       int record_page = M_ARGV(0, int);
+       string ret_string = M_ARGV(1, string);
+
+       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");
+               }
+       }
+
+       M_ARGV(1, string) = ret_string;
+}
+
+bool superspec_Spectate(entity this, entity targ); // TODO
+void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
+MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
+{
+       entity player = M_ARGV(0, entity);
+       string cmd_name = M_ARGV(1, string);
+       int cmd_argc = M_ARGV(2, int);
+
+       if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
+
+       if(cmd_name == "followfc")
+       {
+               if(!g_ctf)
+                       return true;
+
+               int _team = 0;
+               bool found = false;
+
+               if(cmd_argc == 2)
+               {
+                       switch(argv(1))
+                       {
+                               case "red":    if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
+                               case "blue":   if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
+                               case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
+                               case "pink":   if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
+                       }
+               }
+
+               FOREACH_CLIENT(IS_PLAYER(it), {
+                       if(it.flagcarried && (it.team == _team || _team == 0))
+                       {
+                               found = true;
+                               if(_team == 0 && IS_SPEC(player) && player.enemy == it)
+                                       continue; // already spectating this fc, try another
+                               return superspec_Spectate(player, it);
+                       }
+               });
+
+               if(!found)
+                       superspec_msg("", "", player, "No active flag carrier\n", 1);
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
+{
+       entity frag_target = M_ARGV(0, entity);
+
+       if(frag_target.flagcarried)
+               ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
+}
+
+
+// ==========
+// 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) { delete(this); return; }
+
+       ctf_FlagSetup(NUM_TEAM_1, this);
+}
+
+/*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) { delete(this); return; }
+
+       ctf_FlagSetup(NUM_TEAM_2, this);
+}
+
+/*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) { delete(this); return; }
+
+       ctf_FlagSetup(NUM_TEAM_3, this);
+}
+
+/*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) { delete(this); return; }
+
+       ctf_FlagSetup(NUM_TEAM_4, this);
+}
+
+/*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) { delete(this); return; }
+       if(!cvar("g_ctf_oneflag")) { delete(this); return; }
+
+       ctf_FlagSetup(0, this);
+}
+
+/*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) { delete(this); return; }
+
+       this.classname = "ctf_team";
+       this.team = this.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);  }
+
+spawnfunc(team_CTF_neutralflag)        { spawnfunc_item_flag_neutral(this);  }
+spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this);  }
+
+// compatibility for wop maps
+spawnfunc(team_redplayer)      { spawnfunc_info_player_team1(this);  }
+spawnfunc(team_blueplayer)     { spawnfunc_info_player_team2(this);  }
+spawnfunc(team_ctl_redlolly)   { spawnfunc_item_flag_team1(this);    }
+spawnfunc(team_CTL_redlolly)   { spawnfunc_item_flag_team1(this);    }
+spawnfunc(team_ctl_bluelolly)  { spawnfunc_item_flag_team2(this);    }
+spawnfunc(team_CTL_bluelolly)  { spawnfunc_item_flag_team2(this);    }
+
+
+// ==============
+// Initialization
+// ==============
+
+// scoreboard setup
+void ctf_ScoreRules(int teams)
+{
+       CheckAllowedTeams(NULL);
+       GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
+        field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
+        field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
+        field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
+        field(SP_CTF_PICKUPS, "pickups", 0);
+        field(SP_CTF_FCKILLS, "fckills", 0);
+        field(SP_CTF_RETURNS, "returns", 0);
+        field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
+       });
+}
+
+// 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_pure(ctf_team);
+       this.netname = teamname;
+       this.cnt = teamcolor - 1;
+       this.spawnfunc_checked = true;
+       this.team = teamcolor;
+}
+
+void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
+{
+       ctf_teams = 0;
+
+       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); }
+
+               switch(tmp_entity.team)
+               {
+                       case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
+                       case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
+                       case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
+                       case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
+               }
+               if(tmp_entity.team == 0) { ctf_oneflag = true; }
+       }
+
+       havocbot_ctf_calculate_middlepoint();
+
+       if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
+       {
+               ctf_teams = 0; // so set the default red and blue teams
+               BITSET_ASSIGN(ctf_teams, BIT(0));
+               BITSET_ASSIGN(ctf_teams, BIT(1));
+       }
+
+       //ctf_teams = bound(2, ctf_teams, 4);
+
+       // if no teams are found, spawn defaults
+       if(find(NULL, classname, "ctf_team") == NULL)
+       {
+               LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
+               if(ctf_teams & BIT(0))
+                       ctf_SpawnTeam("Red", NUM_TEAM_1);
+               if(ctf_teams & BIT(1))
+                       ctf_SpawnTeam("Blue", NUM_TEAM_2);
+               if(ctf_teams & BIT(2))
+                       ctf_SpawnTeam("Yellow", NUM_TEAM_3);
+               if(ctf_teams & BIT(3))
+                       ctf_SpawnTeam("Pink", NUM_TEAM_4);
+       }
+
+       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;
+
+       InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/ctf/ctf.qh b/qcsrc/common/gamemodes/gamemode/ctf/ctf.qh
new file mode 100644 (file)
index 0000000..2f9643b
--- /dev/null
@@ -0,0 +1,189 @@
+#pragma once
+
+#ifdef SVQC
+
+void ctf_Initialize();
+
+REGISTER_MUTATOR(ctf, false)
+{
+    MUTATOR_STATIC();
+    MUTATOR_ONADD
+    {
+        GameRules_teams(true);
+        GameRules_limit_score(autocvar_capturelimit_override);
+        GameRules_limit_lead(autocvar_captureleadlimit_override);
+
+        ctf_Initialize();
+    }
+    return 0;
+}
+
+// used in cheats.qc
+void ctf_RespawnFlag(entity flag);
+
+// score rule declarations
+const int ST_CTF_CAPS = 1;
+
+CLASS(Flag, Pickup)
+    ATTRIB(Flag, m_mins, vector, (PL_MIN_CONST + '0 0 -13') * 1.4); // scaling be damned
+    ATTRIB(Flag, m_maxs, vector, (PL_MAX_CONST + '0 0 -13') * 1.4);
+ENDCLASS(Flag)
+Flag CTF_FLAG; STATIC_INIT(Flag) { CTF_FLAG = NEW(Flag); }
+void ctf_FlagTouch(entity this, entity toucher) { ITEM_HANDLE(Pickup, CTF_FLAG, this, toucher); }
+
+// flag constants // for most of these, there is just one question to be asked: WHYYYYY?
+
+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) ((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;
+
+// score fields
+.float score_assist;
+.float score_capture;
+.float score_drop; // note: negated
+.float score_pickup;
+.float score_return;
+.float score_team_capture; // shouldn't be too high
+
+// 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 wps_helpme;
+.entity wps_flagbase;
+.entity wps_flagcarrier;
+.entity wps_flagdropped;
+.entity wps_flagreturn;
+.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;
+
+bool ctf_Stalemate_Customize(entity this, entity client);
+
+void ctf_Handle_Throw(entity player, entity receiver, float droptype);
+
+// 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_byworld;
+int ctf_teams;
+.entity enemy; // when flag is back in the base, it remembers last player who carried/touched the flag, useful to bots
+
+// 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;
+
+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))
+#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;
+const int CTF_STALEMATE                                        = 8192;
diff --git a/qcsrc/common/gamemodes/gamemode/cts/_mod.inc b/qcsrc/common/gamemodes/gamemode/cts/_mod.inc
new file mode 100644 (file)
index 0000000..ab0d8a4
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/cts/cts.qc>
diff --git a/qcsrc/common/gamemodes/gamemode/cts/_mod.qh b/qcsrc/common/gamemodes/gamemode/cts/_mod.qh
new file mode 100644 (file)
index 0000000..a20b5c3
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/cts/cts.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/cts/cts.qc b/qcsrc/common/gamemodes/gamemode/cts/cts.qc
new file mode 100644 (file)
index 0000000..6190289
--- /dev/null
@@ -0,0 +1,435 @@
+#include "cts.qh"
+
+// TODO: split into sv_cts
+#ifdef SVQC
+#include <server/race.qh>
+#include <server/items.qh>
+
+float autocvar_g_cts_finish_kill_delay;
+bool autocvar_g_cts_selfdamage;
+
+// legacy bot roles
+.float race_checkpoint;
+void havocbot_role_cts(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+
+               bool raw_touch_check = true;
+               int cp = this.race_checkpoint;
+
+               LABEL(search_racecheckpoints)
+               IL_EACH(g_racecheckpoints, true,
+               {
+                       if(it.cnt == cp || cp == -1)
+                       {
+                               // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
+                               // e.g. checkpoint in front of Stormkeep's warpzone
+                               // the same workaround is applied in Race game mode
+                               if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
+                               {
+                                       cp = race_NextCheckpoint(cp);
+                                       raw_touch_check = false;
+                                       goto search_racecheckpoints;
+                               }
+                               navigation_routerating(this, it, 1000000, 5000);
+                       }
+               });
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void cts_ScoreRules()
+{
+    GameRules_score_enabled(false);
+    GameRules_scoring(0, 0, 0, {
+        if (g_race_qualifying) {
+            field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+        } else {
+            field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+            field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+            field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
+        }
+    });
+}
+
+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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void KillIndicator_Think(entity this);
+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;
+    setthink(e.killindicator, KillIndicator_Think);
+    e.killindicator.nextthink = time + (e.lip) * 0.05;
+    e.killindicator.cnt = ceil(autocvar_g_cts_finish_kill_delay);
+    e.killindicator.count = 1; // this is used to indicate that it should be silent
+    e.lip = 0;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerPhysics)
+{
+       entity player = M_ARGV(0, entity);
+       float dt = M_ARGV(1, float);
+
+       player.race_movetime_frac += dt;
+       float f = floor(player.race_movetime_frac);
+       player.race_movetime_frac -= f;
+       player.race_movetime_count += f;
+       player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
+
+#ifdef SVQC
+       if(IS_PLAYER(player))
+       {
+               if (player.race_penalty)
+                       if (time > player.race_penalty)
+                               player.race_penalty = 0;
+               if(player.race_penalty)
+               {
+                       player.velocity = '0 0 0';
+                       set_movetype(player, MOVETYPE_NONE);
+                       player.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(CS(player).movement.x);
+       wishvel.y = fabs(CS(player).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(CS(player).movement.x > 0)
+                               CS(player).movement_x = wishspeed;
+                       else
+                               CS(player).movement_x = -wishspeed;
+                       CS(player).movement_y = 0;
+               }
+               else if(wishvel.y >= 2 * wishvel.x)
+               {
+                       // pure Y motion
+                       CS(player).movement_x = 0;
+                       if(CS(player).movement.y > 0)
+                               CS(player).movement_y = wishspeed;
+                       else
+                               CS(player).movement_y = -wishspeed;
+               }
+               else
+               {
+                       // diagonal
+                       if(CS(player).movement.x > 0)
+                               CS(player).movement_x = M_SQRT1_2 * wishspeed;
+                       else
+                               CS(player).movement_x = -M_SQRT1_2 * wishspeed;
+                       if(CS(player).movement.y > 0)
+                               CS(player).movement_y = M_SQRT1_2 * wishspeed;
+                       else
+                               CS(player).movement_y = -M_SQRT1_2 * wishspeed;
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, reset_map_global)
+{
+       float s;
+
+       Score_NicePrint(NULL);
+
+       race_ClearRecords();
+       PlayerScore_Sort(race_place, 0, 1, 0);
+
+       FOREACH_CLIENT(true, {
+               if(it.race_place)
+               {
+                       s = GameRules_scoring_add(it, RACE_FASTEST, 0);
+                       if(!s)
+                               it.race_place = 0;
+               }
+               cts_EventLog(ftos(it.race_place), it);
+       });
+
+       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();
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, ClientConnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       race_PreparePlayer(player);
+       player.race_checkpoint = -1;
+
+       if(IS_REAL_CLIENT(player))
+       {
+               string rr = CTS_RECORD;
+
+               msg_entity = player;
+               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;
+               int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+               race_send_rankings_cnt(MSG_ONE);
+               for (i = 1; i <= m; ++i)
+               {
+                       race_SendRankings(i, 0, 0, MSG_ONE);
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, AbortSpeedrun)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(autocvar_g_allow_checkpoints)
+               race_PreparePlayer(player); // nice try
+}
+
+MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(GameRules_scoring_add(player, RACE_FASTEST, 0))
+               player.frags = FRAGS_LMS_LOSER;
+       else
+               player.frags = FRAGS_SPECTATOR;
+
+       race_PreparePlayer(player);
+       player.race_checkpoint = -1;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+       entity spawn_spot = M_ARGV(1, entity);
+
+       if(spawn_spot.target == "")
+               // Emergency: this wasn't a real spawnpoint. Can this ever happen?
+               race_PreparePlayer(player);
+
+       // if we need to respawn, do it right
+       player.race_respawn_checkpoint = player.race_checkpoint;
+       player.race_respawn_spotref = spawn_spot;
+
+       player.race_place = 0;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PutClientInServer)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(IS_PLAYER(player))
+       if(!game_stopped)
+       {
+               if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
+                       race_PreparePlayer(player);
+               else // respawn
+                       race_RetractPlayer(player);
+
+               race_AbandonRaceCheck(player);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerDies)
+{
+       entity frag_target = M_ARGV(2, entity);
+
+       frag_target.respawn_flags |= RESPAWN_FORCE;
+       race_AbandonRaceCheck(frag_target);
+}
+
+MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole)
+{
+       entity bot = M_ARGV(0, entity);
+
+       bot.havocbot_role = havocbot_role_cts;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(cts, GetPressedKeys)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
+       {
+               if (!player.stored_netname)
+                       player.stored_netname = strzone(uid2name(player.crypto_idfp));
+               if(player.stored_netname != player.netname)
+               {
+                       db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
+                       strcpy(player.stored_netname, player.netname);
+               }
+       }
+
+       if (!IS_OBSERVER(player))
+       {
+               if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
+               {
+                       speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
+                       speedaward_holder = player.netname;
+                       speedaward_uid = player.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);
+                       }
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon)
+{
+       // no weapon dropping in CTS
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(cts, FilterItem)
+{
+       entity item = M_ARGV(0, entity);
+
+       if (Item_IsLoot(item))
+       {
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, Damage_Calculate)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_deathtype = M_ARGV(3, float);
+       float frag_damage = M_ARGV(4, float);
+
+       if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id)
+       if(!autocvar_g_cts_selfdamage)
+       {
+               frag_damage = 0;
+               M_ARGV(4, float) = frag_damage;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear)
+{
+       return true; // in CTS, you don't lose score by observing
+}
+
+MUTATOR_HOOKFUNCTION(cts, GetRecords)
+{
+       int record_page = M_ARGV(0, int);
+       string ret_string = M_ARGV(1, string);
+
+       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");
+               }
+       }
+
+       M_ARGV(1, string) = ret_string;
+}
+
+void ClientKill_Now(entity this);
+MUTATOR_HOOKFUNCTION(cts, ClientKill)
+{
+    entity player = M_ARGV(0, entity);
+
+       M_ARGV(1, float) = 0; // kill delay
+
+       if(player.killindicator && player.killindicator.count == 1) // player.killindicator.count == 1 means that the kill indicator was spawned by CTS_ClientKill
+       {
+               delete(player.killindicator);
+               player.killindicator = NULL;
+
+               ClientKill_Now(player); // allow instant kill in this case
+               return;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(autocvar_g_cts_finish_kill_delay)
+               CTS_ClientKill(player);
+}
+
+MUTATOR_HOOKFUNCTION(cts, HideTeamNagger)
+{
+       return true; // doesn't work so well (but isn't cts a teamless mode?)
+}
+
+MUTATOR_HOOKFUNCTION(cts, FixClientCvars)
+{
+       entity player = M_ARGV(0, entity);
+
+       stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
+}
+
+MUTATOR_HOOKFUNCTION(cts, WantWeapon)
+{
+       M_ARGV(1, float) = (M_ARGV(0, entity) == WEP_SHOTGUN); // want weapon = weapon info
+       M_ARGV(3, bool) = true; // want mutator blocked
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(cts, ForbidDropCurrentWeapon)
+{
+       return true;
+}
+
+void cts_Initialize()
+{
+       cts_ScoreRules();
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/cts/cts.qh b/qcsrc/common/gamemodes/gamemode/cts/cts.qh
new file mode 100644 (file)
index 0000000..516e903
--- /dev/null
@@ -0,0 +1,26 @@
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#include <server/race.qh>
+
+void cts_Initialize();
+
+REGISTER_MUTATOR(cts, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               g_race_qualifying = true;
+               independent_players = 1;
+        GameRules_limit_score(0);
+        GameRules_limit_lead(0);
+
+               cts_Initialize();
+       }
+       return 0;
+}
+
+// scores
+const float ST_CTS_LAPS = 1;
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/deathmatch/_mod.inc b/qcsrc/common/gamemodes/gamemode/deathmatch/_mod.inc
new file mode 100644 (file)
index 0000000..2403aad
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/deathmatch/deathmatch.qc>
diff --git a/qcsrc/common/gamemodes/gamemode/deathmatch/_mod.qh b/qcsrc/common/gamemodes/gamemode/deathmatch/_mod.qh
new file mode 100644 (file)
index 0000000..2135ec9
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/deathmatch/deathmatch.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qc b/qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qc
new file mode 100644 (file)
index 0000000..5cd7ca1
--- /dev/null
@@ -0,0 +1,10 @@
+#include "deathmatch.qh"
+
+// TODO: sv_deathmatch?
+#ifdef SVQC
+MUTATOR_HOOKFUNCTION(dm, Scores_CountFragsRemaining)
+{
+       // announce remaining frags
+       return true;
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qh b/qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qh
new file mode 100644 (file)
index 0000000..fdae278
--- /dev/null
@@ -0,0 +1,10 @@
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+REGISTER_MUTATOR(dm, false)
+{
+    MUTATOR_STATIC();
+       return 0;
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/domination/_mod.inc b/qcsrc/common/gamemodes/gamemode/domination/_mod.inc
new file mode 100644 (file)
index 0000000..95d00b3
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/domination/domination.qc>
diff --git a/qcsrc/common/gamemodes/gamemode/domination/_mod.qh b/qcsrc/common/gamemodes/gamemode/domination/_mod.qh
new file mode 100644 (file)
index 0000000..e57c30e
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/domination/domination.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/domination/domination.qc b/qcsrc/common/gamemodes/gamemode/domination/domination.qc
new file mode 100644 (file)
index 0000000..24a8220
--- /dev/null
@@ -0,0 +1,673 @@
+#include "domination.qh"
+
+// TODO: sv_domination
+#ifdef SVQC
+#include <server/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;
+float autocvar_g_domination_round_timelimit;
+float autocvar_g_domination_warmup;
+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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void set_dom_state(entity e)
+{
+       STAT(DOM_TOTAL_PPS, e) = total_pps;
+       STAT(DOM_PPS_RED, e) = pps_red;
+       STAT(DOM_PPS_BLUE, e) = pps_blue;
+       if(domination_teams >= 3)
+               STAT(DOM_PPS_YELLOW, e) = pps_yellow;
+       if(domination_teams >= 4)
+               STAT(DOM_PPS_PINK, e) = pps_pink;
+}
+
+void dompoint_captured(entity this)
+{
+       float old_delay, old_team, real_team;
+
+       // now that the delay has expired, switch to the latest team to lay claim to this point
+       entity head = this.owner;
+
+       real_team = this.cnt;
+       this.cnt = -1;
+
+       dom_EventLog("taken", this.team, this.dmg_inflictor);
+       this.dmg_inflictor = NULL;
+
+       this.goalentity = head;
+       this.model = head.mdl;
+       this.modelindex = head.dmg;
+       this.skin = head.skin;
+
+       float points, wait_time;
+       if (autocvar_g_domination_point_amt)
+               points = autocvar_g_domination_point_amt;
+       else
+               points = this.frags;
+       if (autocvar_g_domination_point_rate)
+               wait_time = autocvar_g_domination_point_rate;
+       else
+               wait_time = this.wait;
+
+       if(domination_roundbased)
+               bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
+       else
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
+
+       if(this.enemy.playerid == this.enemy_playerid)
+               GameRules_scoring_add(this.enemy, DOM_TAKES, 1);
+       else
+               this.enemy = NULL;
+
+       if (head.noise != "")
+               if(this.enemy)
+                       _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
+               else
+                       _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
+       if (head.noise1 != "")
+               play2all(head.noise1);
+
+       this.delay = time + wait_time;
+
+       // do trigger work
+       old_delay = this.delay;
+       old_team = this.team;
+       this.team = real_team;
+       this.delay = 0;
+       SUB_UseTargets (this, this, NULL);
+       this.delay = old_delay;
+       this.team = old_team;
+
+       entity msg = WP_DomNeut;
+       switch(real_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(this.sprite, msg, WP_Null, WP_Null);
+
+       total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
+       IL_EACH(g_dompoints, true,
+       {
+               if (autocvar_g_domination_point_amt)
+                       points = autocvar_g_domination_point_amt;
+               else
+                       points = it.frags;
+               if (autocvar_g_domination_point_rate)
+                       wait_time = autocvar_g_domination_point_rate;
+               else
+                       wait_time = it.wait;
+               switch(it.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(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
+       WaypointSprite_Ping(this.sprite);
+
+       this.captime = time;
+
+       FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
+}
+
+void AnimateDomPoint(entity this)
+{
+       if(this.pain_finished > time)
+               return;
+       this.pain_finished = time + this.t_width;
+       if(this.nextthink > this.pain_finished)
+               this.nextthink = this.pain_finished;
+
+       this.frame = this.frame + 1;
+       if(this.frame > this.t_length)
+               this.frame = 0;
+}
+
+void dompointthink(entity this)
+{
+       float fragamt;
+
+       this.nextthink = time + 0.1;
+
+       //this.frame = this.frame + 1;
+       //if(this.frame > 119)
+       //      this.frame = 0;
+       AnimateDomPoint(this);
+
+       // give points
+
+       if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
+               return;
+
+       if(autocvar_g_domination_point_rate)
+               this.delay = time + autocvar_g_domination_point_rate;
+       else
+               this.delay = time + this.wait;
+
+       // give credit to the team
+       // NOTE: this defaults to 0
+       if (!domination_roundbased)
+       if (this.goalentity.netname != "")
+       {
+               if(autocvar_g_domination_point_amt)
+                       fragamt = autocvar_g_domination_point_amt;
+               else
+                       fragamt = this.frags;
+               TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
+               TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
+
+               // give credit to the individual player, if he is still there
+               if (this.enemy.playerid == this.enemy_playerid)
+               {
+                       GameRules_scoring_add(this.enemy, SCORE, fragamt);
+                       GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
+               }
+               else
+                       this.enemy = NULL;
+       }
+}
+
+void dompointtouch(entity this, entity toucher)
+{
+       if(!IS_PLAYER(toucher))
+               return;
+       if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
+               return;
+
+       if(round_handler_IsActive() && !round_handler_IsRoundStarted())
+               return;
+
+       if(time < this.captime + 0.3)
+               return;
+
+       // only valid teams can claim it
+       entity head = find(NULL, classname, "dom_team");
+       while (head && head.team != toucher.team)
+               head = find(head, classname, "dom_team");
+       if (!head || head.netname == "" || head == this.goalentity)
+               return;
+
+       // delay capture
+
+       this.team = this.goalentity.team; // this stores the PREVIOUS team!
+
+       this.cnt = toucher.team;
+       this.owner = head; // team to switch to after the delay
+       this.dmg_inflictor = toucher;
+
+       // this.state = 1;
+       // this.delay = time + cvar("g_domination_point_capturetime");
+       //this.nextthink = time + cvar("g_domination_point_capturetime");
+       //this.think = dompoint_captured;
+
+       // go to neutral team in the mean time
+       head = find(NULL, classname, "dom_team");
+       while (head && head.netname != "")
+               head = find(head, classname, "dom_team");
+       if(head == NULL)
+               return;
+
+       WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
+       WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
+       WaypointSprite_Ping(this.sprite);
+
+       this.goalentity = head;
+       this.model = head.mdl;
+       this.modelindex = head.dmg;
+       this.skin = head.skin;
+
+       this.enemy = toucher; // individual player scoring
+       this.enemy_playerid = toucher.playerid;
+       dompoint_captured(this);
+}
+
+void dom_controlpoint_setup(entity this)
+{
+       entity head;
+       // find the spawnfunc_dom_team representing unclaimed points
+       head = find(NULL, classname, "dom_team");
+       while(head && head.netname != "")
+               head = find(head, classname, "dom_team");
+       if (!head)
+               objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
+
+       // copy important properties from spawnfunc_dom_team entity
+       this.goalentity = head;
+       _setmodel(this, head.mdl); // precision already set
+       this.skin = head.skin;
+
+       this.cnt = -1;
+
+       if(this.message == "")
+               this.message = " has captured a control point";
+
+       if(this.frags <= 0)
+               this.frags = 1;
+       if(this.wait <= 0)
+               this.wait = 5;
+
+       float points, waittime;
+       if (autocvar_g_domination_point_amt)
+               points = autocvar_g_domination_point_amt;
+       else
+               points = this.frags;
+       if (autocvar_g_domination_point_rate)
+               waittime = autocvar_g_domination_point_rate;
+       else
+               waittime = this.wait;
+
+       total_pps += points/waittime;
+
+       if(!this.t_width)
+               this.t_width = 0.02; // frame animation rate
+       if(!this.t_length)
+               this.t_length = 239; // maximum frame
+
+       setthink(this, dompointthink);
+       this.nextthink = time;
+       settouch(this, dompointtouch);
+       this.solid = SOLID_TRIGGER;
+       if(!this.flags & FL_ITEM)
+               IL_PUSH(g_items, this);
+       this.flags = FL_ITEM;
+       setsize(this, '-32 -32 -32', '32 32 32');
+       setorigin(this, this.origin + '0 0 20');
+       droptofloor(this);
+
+       waypoint_spawnforitem(this);
+       WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
+}
+
+float total_controlpoints;
+void Domination_count_controlpoints()
+{
+       total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
+       IL_EACH(g_dompoints, true,
+       {
+               ++total_controlpoints;
+               redowned += (it.goalentity.team == NUM_TEAM_1);
+               blueowned += (it.goalentity.team == NUM_TEAM_2);
+               yellowowned += (it.goalentity.team == NUM_TEAM_3);
+               pinkowned += (it.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, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+
+               game_stopped = true;
+               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, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+               TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
+       }
+       else if(winner_team == -1)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+       }
+
+       game_stopped = true;
+       round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
+
+       return 1;
+}
+
+float Domination_CheckPlayers()
+{
+       return 1;
+}
+
+void Domination_RoundStart()
+{
+       FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
+}
+
+//go to best items, or control points you don't own
+void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
+{
+       IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
+       {
+               if(it.cnt > -1) // this is just being fought
+                       navigation_routerating(this, it, ratingscale, 5000);
+               else if(it.goalentity.cnt == 0) // unclaimed
+                       navigation_routerating(this, it, ratingscale * 0.5, 5000);
+               else if(it.goalentity.team != this.team) // other team's point
+                       navigation_routerating(this, it, ratingscale * 0.2, 5000);
+       });
+}
+
+void havocbot_role_dom(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+               havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
+               havocbot_goalrating_items(this, 8000, this.origin, 8000);
+               //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
+               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(dom, CheckAllowedTeams)
+{
+       // fallback?
+       M_ARGV(0, float) = domination_teams;
+       string ret_string = "dom_team";
+
+       entity head = find(NULL, 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);
+       }
+
+       M_ARGV(1, string) = string_null;
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(dom, reset_map_players)
+{
+       total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               PutClientInServer(it);
+               if(domination_roundbased)
+                       it.player_blocked = 1;
+               if(IS_REAL_CLIENT(it))
+                       set_dom_state(it);
+       });
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(domination_roundbased)
+       if(!round_handler_IsRoundStarted())
+               player.player_blocked = 1;
+       else
+               player.player_blocked = 0;
+}
+
+MUTATOR_HOOKFUNCTION(dom, ClientConnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       set_dom_state(player);
+}
+
+MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
+{
+       entity bot = M_ARGV(0, entity);
+
+       bot.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)
+       {
+               delete(this);
+               return;
+       }
+       setthink(this, dom_controlpoint_setup);
+       this.nextthink = time + 0.1;
+       this.reset = dom_controlpoint_setup;
+
+       if(!this.scale)
+               this.scale = 0.6;
+
+       this.effects = this.effects | EF_LOWPRECISION;
+       if (autocvar_g_domination_point_fullbright)
+               this.effects |= EF_FULLBRIGHT;
+
+       IL_PUSH(g_dompoints, this);
+}
+
+/*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)
+       {
+               delete(this);
+               return;
+       }
+       precache_model(this.model);
+       if (this.noise != "")
+               precache_sound(this.noise);
+       if (this.noise1 != "")
+               precache_sound(this.noise1);
+       this.classname = "dom_team";
+       _setmodel(this, this.model); // precision not needed
+       this.mdl = this.model;
+       this.dmg = this.modelindex;
+       this.model = "";
+       this.modelindex = 0;
+       // this would have to be changed if used in quakeworld
+       if(this.cnt)
+               this.team = this.cnt + 1; // WHY are these different anyway?
+}
+
+// scoreboard setup
+void ScoreRules_dom(int teams)
+{
+       if(domination_roundbased)
+       {
+           GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
+            field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
+            field(SP_DOM_TAKES, "takes", 0);
+           });
+       }
+       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;
+               GameRules_scoring(teams, sp_score, sp_score, {
+            field_team(ST_DOM_TICKS, "ticks", sp_domticks);
+            field(SP_DOM_TICKS, "ticks", sp_domticks);
+            field(SP_DOM_TAKES, "takes", 0);
+               });
+       }
+}
+
+// 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, Sound capsound, string capnarration, string capmessage)
+{
+    TC(Sound, capsound);
+    entity e = new_pure(dom_team);
+       e.netname = strzone(teamname);
+       e.cnt = teamcolor;
+       e.model = pointmodel;
+       e.skin = pointskin;
+       e.noise = strzone(Sound_fixpath(capsound));
+       e.noise1 = strzone(capnarration);
+       e.message = strzone(capmessage);
+
+       // this code is identical to spawnfunc_dom_team
+       _setmodel(e, e.model); // precision not needed
+       e.mdl = e.model;
+       e.dmg = e.modelindex;
+       e.model = "";
+       e.modelindex = 0;
+       // this would have to be changed if used in quakeworld
+       e.team = e.cnt + 1;
+
+       //eprint(e);
+}
+
+void dom_spawnpoint(vector org)
+{
+       entity e = spawn();
+       e.classname = "dom_controlpoint";
+       setthink(e, spawnfunc_dom_controlpoint);
+       e.nextthink = time;
+       setorigin(e, org);
+       spawnfunc_dom_controlpoint(e);
+}
+
+// spawn some default teams if the map is not set up for domination
+void dom_spawnteams(int teams)
+{
+    TC(int, teams);
+       dom_spawnteam(Team_ColoredFullName(NUM_TEAM_1), NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND_DOM_CLAIM, "", "Red team has captured a control point");
+       dom_spawnteam(Team_ColoredFullName(NUM_TEAM_2), 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(Team_ColoredFullName(NUM_TEAM_3), 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(Team_ColoredFullName(NUM_TEAM_4), 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, SND_Null, "", "");
+}
+
+void dom_DelayedInit(entity this) // 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(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
+       {
+               LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
+               domination_teams = autocvar_g_domination_teams_override;
+               if (domination_teams < 2)
+                       domination_teams = autocvar_g_domination_default_teams;
+               domination_teams = bound(2, domination_teams, 4);
+               dom_spawnteams(domination_teams);
+       }
+
+       CheckAllowedTeams(NULL);
+       //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
+
+       int teams = 0;
+       if(c1 >= 0) teams |= BIT(0);
+       if(c2 >= 0) teams |= BIT(1);
+       if(c3 >= 0) teams |= BIT(2);
+       if(c4 >= 0) teams |= BIT(3);
+       domination_teams = teams;
+
+       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(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/domination/domination.qh b/qcsrc/common/gamemodes/gamemode/domination/domination.qh
new file mode 100644 (file)
index 0000000..f4faf50
--- /dev/null
@@ -0,0 +1,54 @@
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
+bool autocvar_g_domination_roundbased;
+int autocvar_g_domination_roundbased_point_limit;
+int autocvar_g_domination_point_leadlimit;
+
+void dom_Initialize();
+
+REGISTER_MUTATOR(dom, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               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;
+
+               GameRules_teams(true);
+        GameRules_limit_score(fraglimit_override);
+        GameRules_limit_lead(autocvar_g_domination_point_leadlimit);
+
+               dom_Initialize();
+       }
+       return 0;
+}
+
+// score rule declarations
+const float ST_DOM_TICKS = 1;
+const float ST_DOM_CAPS = 1;
+
+// pps: points per second
+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;
+
+void AnimateDomPoint(entity this);
+
+IntrusiveList g_dompoints;
+STATIC_INIT(g_dompoints) { g_dompoints = IL_NEW(); }
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/_mod.inc b/qcsrc/common/gamemodes/gamemode/freezetag/_mod.inc
new file mode 100644 (file)
index 0000000..aff5bf9
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/freezetag/freezetag.qc>
diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/_mod.qh b/qcsrc/common/gamemodes/gamemode/freezetag/_mod.qh
new file mode 100644 (file)
index 0000000..1bc2182
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/freezetag/freezetag.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qc b/qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qc
new file mode 100644 (file)
index 0000000..1cdd4d1
--- /dev/null
@@ -0,0 +1,591 @@
+#include "freezetag.qh"
+
+// TODO: sv_freezetag
+#ifdef SVQC
+#include <server/resources.qh>
+
+float autocvar_g_freezetag_frozen_maxtime;
+float autocvar_g_freezetag_revive_clearspeed;
+float autocvar_g_freezetag_round_timelimit;
+//int autocvar_g_freezetag_teams;
+int autocvar_g_freezetag_teams_override;
+float autocvar_g_freezetag_warmup;
+
+void freezetag_count_alive_players()
+{
+       total_players = redalive = bluealive = yellowalive = pinkalive = 0;
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               switch(it.team)
+               {
+                       case NUM_TEAM_1: ++total_players; if(GetResourceAmount(it, RESOURCE_HEALTH) >= 1 && STAT(FROZEN, it) != 1) ++redalive; break;
+                       case NUM_TEAM_2: ++total_players; if(GetResourceAmount(it, RESOURCE_HEALTH) >= 1 && STAT(FROZEN, it) != 1) ++bluealive; break;
+                       case NUM_TEAM_3: ++total_players; if(GetResourceAmount(it, RESOURCE_HEALTH) >= 1 && STAT(FROZEN, it) != 1) ++yellowalive; break;
+                       case NUM_TEAM_4: ++total_players; if(GetResourceAmount(it, RESOURCE_HEALTH) >= 1 && STAT(FROZEN, it) != 1) ++pinkalive; break;
+               }
+       });
+       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+               STAT(REDALIVE, it) = redalive;
+               STAT(BLUEALIVE, it) = bluealive;
+               STAT(YELLOWALIVE, it) = yellowalive;
+               STAT(PINKALIVE, it) = pinkalive;
+       });
+
+       eliminatedPlayers.SendFlags |= 1;
+}
+#define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
+#define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == NumTeams(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, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               return 1;
+       }
+       if(total_players == 0)
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               return 0;
+       }
+       int missing_teams_mask = 0;
+       if(freezetag_teams & BIT(0))
+               missing_teams_mask += (!redalive) * 1;
+       if(freezetag_teams & BIT(1))
+               missing_teams_mask += (!bluealive) * 2;
+       if(freezetag_teams & BIT(2))
+               missing_teams_mask += (!yellowalive) * 4;
+       if(freezetag_teams & BIT(3))
+               missing_teams_mask += (!pinkalive) * 8;
+       if(prev_missing_teams_mask != missing_teams_mask)
+       {
+               Send_Notification(NOTIF_ALL, NULL, 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
+}
+
+void nades_Clear(entity);
+void nades_GiveBonus(entity player, float score);
+
+float freezetag_CheckWinner()
+{
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+               FOREACH_CLIENT(IS_PLAYER(it), {
+                       it.freezetag_frozen_timeout = 0;
+                       nades_Clear(it);
+               });
+               game_stopped = true;
+               round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+               return 1;
+       }
+
+       if(FREEZETAG_ALIVE_TEAMS() > 1)
+               return 0;
+
+       int winner_team = freezetag_getWinnerTeam();
+       if(winner_team > 0)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+               TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
+       }
+       else if(winner_team == -1)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+       }
+
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               it.freezetag_frozen_timeout = 0;
+               nades_Clear(it);
+       });
+
+       game_stopped = true;
+       round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+       return 1;
+}
+
+entity freezetag_LastPlayerForTeam(entity this)
+{
+       entity last_pl = NULL;
+       FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
+               if(GetResourceAmount(it, RESOURCE_HEALTH) >= 1)
+               if(!STAT(FROZEN, it))
+               if(SAME_TEAM(it, this))
+               if(!last_pl)
+                       last_pl = it;
+               else
+                       return NULL;
+       });
+       return last_pl;
+}
+
+void freezetag_LastPlayerForTeam_Notify(entity this)
+{
+       if(round_handler_IsActive())
+       if(round_handler_IsRoundStarted())
+       {
+               entity pl = freezetag_LastPlayerForTeam(this);
+               if(pl)
+                       Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
+       }
+}
+
+void freezetag_Add_Score(entity targ, entity attacker)
+{
+       if(attacker == targ)
+       {
+               // you froze your own dumb targ
+               // counted as "suicide" already
+               GameRules_scoring_add(targ, SCORE, -1);
+       }
+       else if(IS_PLAYER(attacker))
+       {
+               // got frozen by an enemy
+               // counted as "kill" and "death" already
+               GameRules_scoring_add(targ, SCORE, -1);
+               GameRules_scoring_add(attacker, SCORE, +1);
+       }
+       // else nothing - got frozen by the game type rules themselves
+}
+
+void freezetag_Freeze(entity targ, entity attacker)
+{
+       if(STAT(FROZEN, targ))
+               return;
+
+       if(autocvar_g_freezetag_frozen_maxtime > 0)
+               targ.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
+
+       Freeze(targ, 0, 1, true);
+
+       freezetag_count_alive_players();
+
+       freezetag_Add_Score(targ, attacker);
+}
+
+void freezetag_Unfreeze(entity this)
+{
+       this.freezetag_frozen_time = 0;
+       this.freezetag_frozen_timeout = 0;
+
+       Unfreeze(this);
+}
+
+float freezetag_isEliminated(entity e)
+{
+       if(IS_PLAYER(e) && (STAT(FROZEN, e) == 1 || IS_DEAD(e)))
+               return true;
+       return false;
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+void(entity this) havocbot_role_ft_freeing;
+void(entity this) havocbot_role_ft_offense;
+
+void havocbot_goalrating_freeplayers(entity this, float ratingscale, vector org, float sradius)
+{
+       float t;
+       FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
+               if (STAT(FROZEN, it) == 1)
+               {
+                       if(vdist(it.origin - org, >, sradius))
+                               continue;
+                       navigation_routerating(this, it, ratingscale, 2000);
+               }
+               else if(vdist(it.origin - org, >, 400)) // avoid gathering all teammates in one place
+               {
+                       // If teamate is not frozen still seek them out as fight better
+                       // in a group.
+                       t = 0.2 * 150 / (GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR));
+                       navigation_routerating(this, it, t * ratingscale, 2000);
+               }
+       });
+}
+
+void havocbot_role_ft_offense(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + random() * 10 + 20;
+
+       // Count how many players on team are unfrozen.
+       int unfrozen = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !(STAT(FROZEN, it) != 1), { unfrozen++; });
+
+       // If only one left on team or if role has timed out then start trying to free players.
+       if (((unfrozen == 0) && (!STAT(FROZEN, this))) || (time > this.havocbot_role_timeout))
+       {
+               LOG_TRACE("changing role to freeing");
+               this.havocbot_role = havocbot_role_ft_freeing;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+               havocbot_goalrating_items(this, 10000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
+               havocbot_goalrating_freeplayers(this, 9000, this.origin, 10000);
+               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_ft_freeing(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + random() * 10 + 20;
+
+       if (time > this.havocbot_role_timeout)
+       {
+               LOG_TRACE("changing role to offense");
+               this.havocbot_role = havocbot_role_ft_offense;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+               havocbot_goalrating_items(this, 8000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
+               havocbot_goalrating_freeplayers(this, 20000, this.origin, 10000);
+               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+void ft_RemovePlayer(entity this)
+{
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // neccessary to update correctly alive stats
+       if(!STAT(FROZEN, this))
+               freezetag_LastPlayerForTeam_Notify(this);
+       freezetag_Unfreeze(this);
+       freezetag_count_alive_players();
+}
+
+MUTATOR_HOOKFUNCTION(ft, ClientDisconnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       ft_RemovePlayer(player);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       ft_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(ft, PlayerDies)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_deathtype = M_ARGV(3, float);
+
+       if(round_handler_IsActive())
+       if(round_handler_CountdownRunning())
+       {
+               if(STAT(FROZEN, frag_target))
+                       freezetag_Unfreeze(frag_target);
+               freezetag_count_alive_players();
+               return true; // 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(STAT(FROZEN, frag_target) != 1)
+               {
+                       freezetag_Add_Score(frag_target, frag_attacker);
+                       freezetag_count_alive_players();
+                       freezetag_LastPlayerForTeam_Notify(frag_target);
+               }
+               else
+                       freezetag_Unfreeze(frag_target); // remove ice
+               SetResourceAmountExplicit(frag_target, RESOURCE_HEALTH, 0); // Unfreeze resets health
+               frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
+               return true;
+       }
+
+       if(STAT(FROZEN, frag_target))
+               return true;
+
+       freezetag_Freeze(frag_target, frag_attacker);
+       freezetag_LastPlayerForTeam_Notify(frag_target);
+
+       if(frag_attacker == frag_target || frag_attacker == NULL)
+       {
+               if(IS_PLAYER(frag_target))
+                       Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
+       }
+       else
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
+               return true; // do nothing, round is starting right now
+
+       if(player.freezetag_frozen_timeout == -2) // player was dead
+       {
+               freezetag_Freeze(player, NULL);
+               return true;
+       }
+
+       freezetag_count_alive_players();
+
+       if(round_handler_IsActive())
+       if(round_handler_IsRoundStarted())
+       {
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
+               freezetag_Freeze(player, NULL);
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, reset_map_players)
+{
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               CS(it).killcount = 0;
+               it.freezetag_frozen_timeout = -1;
+               PutClientInServer(it);
+               it.freezetag_frozen_timeout = 0;
+       });
+       freezetag_count_alive_players();
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+       M_ARGV(2, float) = 0; // no frags counted in Freeze Tag
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST)
+{
+       if(game_stopped)
+               return true;
+
+       if(round_handler_IsActive())
+       if(!round_handler_IsRoundStarted())
+               return true;
+
+       int n;
+       entity o = NULL;
+       entity player = M_ARGV(0, entity);
+       //if(STAT(FROZEN, player))
+       //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout)
+               //player.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (player.freezetag_frozen_timeout - time) / (player.freezetag_frozen_timeout - player.freezetag_frozen_time);
+
+       if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
+               n = -1;
+       else
+       {
+               vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
+               n = 0;
+               FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
+                       if(STAT(FROZEN, it) == 0)
+                       if(!IS_DEAD(it))
+                       if(SAME_TEAM(it, player))
+                       if(boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
+                       {
+                               if(!o)
+                                       o = it;
+                               if(STAT(FROZEN, player) == 1)
+                                       it.reviving = true;
+                               ++n;
+                       }
+               });
+
+       }
+
+       if(n && STAT(FROZEN, player) == 1) // OK, there is at least one teammate reviving us
+       {
+               STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
+               SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
+
+               if(STAT(REVIVE_PROGRESS, player) >= 1)
+               {
+                       freezetag_Unfreeze(player);
+                       freezetag_count_alive_players();
+
+                       if(n == -1)
+                       {
+                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
+                               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, player.netname, autocvar_g_freezetag_frozen_maxtime);
+                               return true;
+                       }
+
+                       // EVERY team mate nearby gets a point (even if multiple!)
+                       FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
+                               GameRules_scoring_add(it, FREEZETAG_REVIVALS, +1);
+                               GameRules_scoring_add(it, SCORE, +1);
+                               nades_GiveBonus(it,autocvar_g_nades_bonus_score_low);
+                       });
+
+                       Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
+                       Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED, player.netname, o.netname);
+               }
+
+               FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
+                       STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
+                       it.reviving = false;
+               });
+       }
+       else if(!n && STAT(FROZEN, player) == 1) // only if no teammate is nearby will we reset
+       {
+               STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
+               SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
+       }
+       else if(!n && !STAT(FROZEN, player))
+       {
+               STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody
+       }
+
+       return true;
+}
+
+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");
+}
+
+MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
+{
+       entity bot = M_ARGV(0, entity);
+
+       if (!IS_DEAD(bot))
+       {
+               if (random() < 0.5)
+                       bot.havocbot_role = havocbot_role_ft_freeing;
+               else
+                       bot.havocbot_role = havocbot_role_ft_offense;
+       }
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       M_ARGV(0, float) = freezetag_teams;
+}
+
+MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
+{
+       // most weapons arena
+       if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
+               M_ARGV(0, string) = "most";
+}
+
+MUTATOR_HOOKFUNCTION(ft, FragCenterMessage)
+{
+       entity frag_attacker = M_ARGV(0, entity);
+       entity frag_target = M_ARGV(1, entity);
+       //float frag_deathtype = M_ARGV(2, float);
+       int kill_count_to_attacker = M_ARGV(3, int);
+       int kill_count_to_target = M_ARGV(4, int);
+
+       if(STAT(FROZEN, frag_target))
+               return; // target was already frozen, so this is just pushing them off the cliff
+
+       Send_Notification(NOTIF_ONE, frag_attacker, MSG_CHOICE, CHOICE_FRAG_FREEZE, frag_target.netname, kill_count_to_attacker, (IS_BOT_CLIENT(frag_target) ? -1 : CS(frag_target).ping));
+       Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target, 
+                                                                               GetResourceAmount(frag_attacker, RESOURCE_HEALTH), GetResourceAmount(frag_attacker, RESOURCE_ARMOR), (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping));
+
+       return true;
+}
+
+void freezetag_Initialize()
+{
+       freezetag_teams = autocvar_g_freezetag_teams_override;
+       if(freezetag_teams < 2)
+               freezetag_teams = cvar("g_freezetag_teams"); // read the cvar directly as it gets written earlier in the same frame
+
+       freezetag_teams = BITS(bound(2, freezetag_teams, 4));
+       GameRules_scoring(freezetag_teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
+           field(SP_FREEZETAG_REVIVALS, "revivals", 0);
+       });
+
+       round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
+       round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+
+       EliminatedPlayers_Init(freezetag_isEliminated);
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qh b/qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qh
new file mode 100644 (file)
index 0000000..ed38ae5
--- /dev/null
@@ -0,0 +1,37 @@
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+int autocvar_g_freezetag_point_limit;
+int autocvar_g_freezetag_point_leadlimit;
+bool autocvar_g_freezetag_team_spawns;
+void freezetag_Initialize();
+
+REGISTER_MUTATOR(ft, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               GameRules_teams(true);
+        GameRules_spawning_teams(autocvar_g_freezetag_team_spawns);
+        GameRules_limit_score(autocvar_g_freezetag_point_limit);
+        GameRules_limit_lead(autocvar_g_freezetag_point_leadlimit);
+
+               freezetag_Initialize();
+       }
+       return 0;
+}
+
+.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
+
+float autocvar_g_freezetag_revive_extra_size;
+float autocvar_g_freezetag_revive_speed;
+bool autocvar_g_freezetag_revive_nade;
+float autocvar_g_freezetag_revive_nade_health;
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/invasion/_mod.inc b/qcsrc/common/gamemodes/gamemode/invasion/_mod.inc
new file mode 100644 (file)
index 0000000..905aa06
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/invasion/invasion.qc>
diff --git a/qcsrc/common/gamemodes/gamemode/invasion/_mod.qh b/qcsrc/common/gamemodes/gamemode/invasion/_mod.qh
new file mode 100644 (file)
index 0000000..d8e8d22
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/invasion/invasion.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/invasion/invasion.qc b/qcsrc/common/gamemodes/gamemode/invasion/invasion.qc
new file mode 100644 (file)
index 0000000..3dff701
--- /dev/null
@@ -0,0 +1,606 @@
+#include "invasion.qh"
+
+// TODO: sv_invasion
+#ifdef SVQC
+#include <common/monsters/sv_spawn.qh>
+#include <common/monsters/sv_spawner.qh>
+#include <common/monsters/sv_monsters.qh>
+
+#include <server/teamplay.qh>
+
+IntrusiveList g_invasion_roundends;
+IntrusiveList g_invasion_waves;
+IntrusiveList g_invasion_spawns;
+STATIC_INIT(g_invasion)
+{
+       g_invasion_roundends = IL_NEW();
+       g_invasion_waves = IL_NEW();
+       g_invasion_spawns = IL_NEW();
+}
+
+float autocvar_g_invasion_round_timelimit;
+float autocvar_g_invasion_spawnpoint_spawn_delay;
+float autocvar_g_invasion_warmup;
+int autocvar_g_invasion_monster_count;
+bool autocvar_g_invasion_zombies_only;
+float autocvar_g_invasion_spawn_delay;
+
+bool victent_present;
+.bool inv_endreached;
+
+bool inv_warning_shown; // spammy
+
+void target_invasion_roundend_use(entity this, entity actor, entity trigger)
+{
+       if(!IS_PLAYER(actor)) { return; }
+
+       actor.inv_endreached = true;
+
+       int plnum = 0;
+       int realplnum = 0;
+       // let's not count bots
+       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
+               ++realplnum;
+               if(it.inv_endreached)
+                       ++plnum;
+       });
+       if(plnum < ceil(realplnum * min(1, this.count))) // 70% of players
+               return;
+
+       this.winning = true;
+}
+
+spawnfunc(target_invasion_roundend)
+{
+       if(!g_invasion) { delete(this); return; }
+
+       victent_present = true; // a victory entity is present, we don't need to rely on monster count TODO: merge this with the intrusive list (can check empty)
+
+       if(!this.count) { this.count = 0.7; } // require at least 70% of the players to reach the end before triggering victory
+
+       this.use = target_invasion_roundend_use;
+
+       IL_PUSH(g_invasion_roundends, this);
+}
+
+spawnfunc(invasion_wave)
+{
+       if(!g_invasion) { delete(this); return; }
+
+       IL_PUSH(g_invasion_waves, this);
+}
+
+spawnfunc(invasion_spawnpoint)
+{
+       if(!g_invasion) { delete(this); return; }
+
+       this.classname = "invasion_spawnpoint";
+       IL_PUSH(g_invasion_spawns, this);
+}
+
+void ClearWinners();
+
+// Invasion stage mode winning condition: If the attackers triggered a round end (by fulfilling all objectives)
+// they win.
+int WinningCondition_Invasion()
+{
+       WinningConditionHelper(NULL); // set worldstatus
+
+       int status = WINNING_NO;
+
+       if(autocvar_g_invasion_type == INV_TYPE_STAGE)
+       {
+               SetWinners(inv_endreached, true);
+
+               int found = 0;
+               IL_EACH(g_invasion_roundends, true,
+               {
+                       ++found;
+                       if(it.winning)
+                       {
+                               bprint("Invasion: round completed.\n");
+                               // winners already set (TODO: teamplay support)
+
+                               status = WINNING_YES;
+                               break;
+                       }
+               });
+
+               if(!found)
+                       status = WINNING_YES; // just end it? TODO: should warn mapper!
+       }
+       else if(autocvar_g_invasion_type == INV_TYPE_HUNT)
+       {
+               ClearWinners();
+
+               int found = 0; // NOTE: this ends the round if no monsters are placed
+               IL_EACH(g_monsters, !(it.spawnflags & MONSTERFLAG_RESPAWNED),
+               {
+                       ++found;
+               });
+
+               if(found <= 0)
+               {
+                       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+                       {
+                               it.winning = true;
+                       });
+                       status = WINNING_YES;
+               }
+       }
+
+       return status;
+}
+
+Monster invasion_PickMonster(int supermonster_count)
+{
+       RandomSelection_Init();
+
+       FOREACH(Monsters, it != MON_Null,
+       {
+               if((it.spawnflags & MON_FLAG_HIDDEN) || (it.spawnflags & MONSTER_TYPE_PASSIVE) || (it.spawnflags & MONSTER_TYPE_FLY) || (it.spawnflags & MONSTER_TYPE_SWIM) ||
+                       (it.spawnflags & MONSTER_SIZE_QUAKE) || ((it.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
+                       continue;
+               if(autocvar_g_invasion_zombies_only && !(it.spawnflags & MONSTER_TYPE_UNDEAD))
+                       continue;
+        RandomSelection_AddEnt(it, 1, 1);
+       });
+
+       return RandomSelection_chosen_ent;
+}
+
+entity invasion_PickSpawn()
+{
+       RandomSelection_Init();
+
+       IL_EACH(g_invasion_spawns, true,
+       {
+               RandomSelection_AddEnt(it, 1, ((time < it.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
+               it.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
+       });
+
+       return RandomSelection_chosen_ent;
+}
+
+entity invasion_GetWaveEntity(int wavenum)
+{
+       IL_EACH(g_invasion_waves, it.cnt == wavenum,
+       {
+               return it; // found one
+       });
+
+       // if no specific one is found, find the last existing wave ent
+       entity best = NULL;
+       IL_EACH(g_invasion_waves, it.cnt <= wavenum,
+       {
+               if(!best || it.cnt > best.cnt)
+                       best = it;
+       });
+
+       return best;
+}
+
+void invasion_SpawnChosenMonster(Monster mon)
+{
+       entity monster;
+       entity spawn_point = invasion_PickSpawn();
+       entity wave_ent = invasion_GetWaveEntity(inv_roundcnt);
+
+       string tospawn = "";
+       if(wave_ent && wave_ent.spawnmob && wave_ent.spawnmob != "")
+       {
+               RandomSelection_Init();
+               FOREACH_WORD(wave_ent.spawnmob, true,
+               {
+                       RandomSelection_AddString(it, 1, 1);
+               });
+
+               tospawn = RandomSelection_chosen_string;
+       }
+
+       if(spawn_point == NULL)
+       {
+               if(!inv_warning_shown)
+               {
+                       inv_warning_shown = true;
+                       LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations");
+               }
+               entity e = spawn();
+               setsize(e, mon.m_mins, mon.m_maxs);
+
+               if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+                       monster = spawnmonster(e, tospawn, mon.monsterid, NULL, NULL, e.origin, false, false, 2);
+               else
+               {
+                       delete(e);
+                       return;
+               }
+       }
+       else // if spawnmob field falls through (unset), fallback to mon (relying on spawnmonster for that behaviour)
+               monster = spawnmonster(spawn(), ((spawn_point.spawnmob && spawn_point.spawnmob != "") ? spawn_point.spawnmob : tospawn), mon.monsterid, spawn_point, spawn_point, spawn_point.origin, false, false, 2);
+
+       if(!monster)
+               return;
+
+       monster.spawnshieldtime = time;
+
+       if(spawn_point)
+       {
+               if(spawn_point.target_range)
+                       monster.target_range = spawn_point.target_range;
+               monster.target2 = spawn_point.target2;
+       }
+
+       if(teamplay)
+       {
+               if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0)
+                       monster.team = spawn_point.team;
+               else
+               {
+                       RandomSelection_Init();
+                       if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_AddFloat(NUM_TEAM_1, 1, 1);
+                       if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_AddFloat(NUM_TEAM_2, 1, 1);
+                       if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_AddFloat(NUM_TEAM_3, 1, 1); }
+                       if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_AddFloat(NUM_TEAM_4, 1, 1); }
+
+                       monster.team = RandomSelection_chosen_float;
+               }
+
+               monster_setupcolors(monster);
+
+               if(monster.sprite)
+               {
+                       WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0'));
+
+                       monster.sprite.team = 0;
+                       monster.sprite.SendFlags |= 1;
+               }
+       }
+
+       if(monster.monster_attack)
+               IL_REMOVE(g_monster_targets, monster);
+       monster.monster_attack = false; // it's the player's job to kill all the monsters
+
+       if(inv_roundcnt >= inv_maxrounds)
+               monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses
+}
+
+void invasion_SpawnMonsters(int supermonster_count)
+{
+       Monster chosen_monster = invasion_PickMonster(supermonster_count);
+
+       invasion_SpawnChosenMonster(chosen_monster);
+}
+
+bool Invasion_CheckWinner()
+{
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               IL_EACH(g_monsters, true,
+               {
+                       Monster_Remove(it);
+               });
+               IL_CLEAR(g_monsters);
+
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, NULL, 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;
+
+       IL_EACH(g_monsters, GetResourceAmount(it, RESOURCE_HEALTH) > 0,
+       {
+               if((get_monsterinfo(it.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
+                       ++supermonster_count;
+               ++total_alive_monsters;
+
+               if(teamplay)
+               switch(it.team)
+               {
+                       case NUM_TEAM_1: ++red_alive; break;
+                       case NUM_TEAM_2: ++blue_alive; break;
+                       case NUM_TEAM_3: ++yellow_alive; break;
+                       case NUM_TEAM_4: ++pink_alive; break;
+               }
+       });
+
+       if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
+       {
+               if(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 = NULL;
+       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
+       {
+               FOREACH_CLIENT(IS_PLAYER(it), {
+                       float cs = GameRules_scoring_add(it, KILLS, 0);
+                       if(cs > winning_score)
+                       {
+                               winning_score = cs;
+                               winner = it;
+                       }
+               });
+       }
+
+       IL_EACH(g_monsters, true,
+       {
+               Monster_Remove(it);
+       });
+       IL_CLEAR(g_monsters);
+
+       if(teamplay)
+       {
+               if(winner_team)
+               {
+                       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+               }
+       }
+       else if(winner)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
+       }
+
+       round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+
+       return 1;
+}
+
+bool Invasion_CheckPlayers()
+{
+       return true;
+}
+
+void Invasion_RoundStart()
+{
+       int numplayers = 0;
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               it.player_blocked = false;
+               ++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)
+{
+       entity frag_target = M_ARGV(0, entity);
+       entity frag_attacker = M_ARGV(1, entity);
+
+       if(!(frag_target.spawnflags & MONSTERFLAG_RESPAWNED))
+       {
+               if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+               {
+                       inv_numkilled += 1;
+                       inv_maxcurrent -= 1;
+               }
+               if(teamplay) { inv_monsters_perteam[frag_target.team] -= 1; }
+
+               if(IS_PLAYER(frag_attacker))
+               if(SAME_TEAM(frag_attacker, frag_target)) // in non-teamplay modes, same team = same player, so this works
+                       GameRules_scoring_add(frag_attacker, KILLS, -1);
+               else
+               {
+                       GameRules_scoring_add(frag_attacker, KILLS, +1);
+                       if(teamplay)
+                               TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(inv, MonsterSpawn)
+{
+       entity mon = M_ARGV(0, entity);
+       mon.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
+
+       if(autocvar_g_invasion_type == INV_TYPE_HUNT)
+               return false; // allowed
+
+       if(!(mon.spawnflags & MONSTERFLAG_SPAWNED))
+               return true;
+
+       if(!(mon.spawnflags & MONSTERFLAG_RESPAWNED))
+       {
+               inv_numspawned += 1;
+               inv_maxcurrent += 1;
+       }
+
+       mon.monster_skill = inv_monsterskill;
+
+       if((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, mon.monster_name);
+}
+
+MUTATOR_HOOKFUNCTION(inv, SV_StartFrame)
+{
+       if(autocvar_g_invasion_type != INV_TYPE_ROUND)
+               return; // uses map spawned monsters
+
+       monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
+       monsters_killed = inv_numkilled;
+}
+
+MUTATOR_HOOKFUNCTION(inv, PlayerRegen)
+{
+       // no regeneration in invasion, regardless of the game type
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.bot_attack)
+               IL_REMOVE(g_bot_targets, player);
+       player.bot_attack = false;
+}
+
+MUTATOR_HOOKFUNCTION(inv, Damage_Calculate)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_damage = M_ARGV(4, float);
+       vector frag_force = M_ARGV(6, vector);
+
+       if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target)
+       {
+               frag_damage = 0;
+               frag_force = '0 0 0';
+
+               M_ARGV(4, float) = frag_damage;
+               M_ARGV(6, vector) = frag_force;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(inv, BotShouldAttack)
+{
+       entity targ = M_ARGV(1, entity);
+
+       if(!IS_MONSTER(targ))
+               return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, SetStartItems)
+{
+       if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+       {
+               start_health = 200;
+               start_armorvalue = 200;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid)
+{
+       entity frag_target = M_ARGV(1, entity);
+
+       if(IS_MONSTER(frag_target))
+               return MUT_ACCADD_INVALID;
+       return MUT_ACCADD_INDIFFERENT;
+}
+
+MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning)
+{
+       // monster spawning disabled during an invasion
+       M_ARGV(1, string) = "You cannot spawn monsters during an invasion!";
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, CheckRules_World)
+{
+       if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+               return false;
+
+       M_ARGV(0, float) = WinningCondition_Invasion();
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       M_ARGV(0, float) = invasion_teams;
+}
+
+MUTATOR_HOOKFUNCTION(inv, AllowMobButcher)
+{
+       M_ARGV(0, string) = "This command does not work during an invasion!";
+       return true;
+}
+
+void invasion_ScoreRules(int inv_teams)
+{
+       if(inv_teams) { CheckAllowedTeams(NULL); }
+       GameRules_score_enabled(false);
+       GameRules_scoring(inv_teams, 0, 0, {
+           if (inv_teams) {
+            field_team(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
+           }
+           field(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
+       });
+}
+
+void invasion_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
+{
+       if(autocvar_g_invasion_type == INV_TYPE_HUNT || autocvar_g_invasion_type == INV_TYPE_STAGE)
+               cvar_set("fraglimit", "0");
+
+       if(autocvar_g_invasion_teams)
+       {
+               invasion_teams = BITS(bound(2, autocvar_g_invasion_teams, 4));
+       }
+       else
+               invasion_teams = 0;
+
+       independent_players = 1; // to disable extra useless scores
+
+       invasion_ScoreRules(invasion_teams);
+
+       independent_players = 0;
+
+       if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+       {
+               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()
+{
+       InitializeEntity(NULL, invasion_DelayedInit, INITPRIO_GAMETYPE);
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/invasion/invasion.qh b/qcsrc/common/gamemodes/gamemode/invasion/invasion.qh
new file mode 100644 (file)
index 0000000..85cd7ec
--- /dev/null
@@ -0,0 +1,48 @@
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
+int autocvar_g_invasion_teams;
+int autocvar_g_invasion_type;
+bool autocvar_g_invasion_team_spawns;
+bool g_invasion;
+void invasion_Initialize();
+
+REGISTER_MUTATOR(inv, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               if (autocvar_g_invasion_teams >= 2) {
+                       GameRules_teams(true);
+                       GameRules_spawning_teams(autocvar_g_invasion_team_spawns);
+               }
+        GameRules_limit_score(autocvar_g_invasion_point_limit);
+
+               g_invasion = true;
+               cvar_settemp("g_monsters", "1");
+               invasion_Initialize();
+       }
+       return 0;
+}
+
+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;
+
+const int INV_TYPE_ROUND = 0; // round-based waves of enemies
+const int INV_TYPE_HUNT = 1; // clear the map of placed enemies
+const int INV_TYPE_STAGE = 2; // reach the end of the level
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/_mod.inc b/qcsrc/common/gamemodes/gamemode/keepaway/_mod.inc
new file mode 100644 (file)
index 0000000..9426d78
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/keepaway/keepaway.qc>
diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/_mod.qh b/qcsrc/common/gamemodes/gamemode/keepaway/_mod.qh
new file mode 100644 (file)
index 0000000..32872a2
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/keepaway/keepaway.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qc b/qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qc
new file mode 100644 (file)
index 0000000..c8bfbf2
--- /dev/null
@@ -0,0 +1,475 @@
+#include "keepaway.qh"
+
+// TODO: keepaway
+#ifdef SVQC
+#include <common/effects/all.qh>
+
+.entity ballcarried;
+
+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;
+
+bool ka_ballcarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs on waypoints which are attached to ballcarriers, updates once per frame
+{
+       if(view.ballcarried)
+               if(IS_SPEC(player))
+                       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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void ka_TouchEvent(entity this, entity toucher);
+void ka_RespawnBall(entity this) // runs whenever the ball needs to be relocated
+{
+       if(game_stopped) return;
+       vector oldballorigin = this.origin;
+
+       if(!MoveToRandomMapLocation(this, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+       {
+               entity spot = SelectSpawnPoint(this, true);
+               setorigin(this, spot.origin);
+               this.angles = spot.angles;
+       }
+
+       makevectors(this.angles);
+       set_movetype(this, MOVETYPE_BOUNCE);
+       this.velocity = '0 0 200';
+       this.angles = '0 0 0';
+       this.effects = autocvar_g_keepawayball_effects;
+       settouch(this, ka_TouchEvent);
+       setthink(this, ka_RespawnBall);
+       this.nextthink = time + autocvar_g_keepawayball_respawntime;
+       navigation_dynamicgoal_set(this);
+
+       Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1);
+       Send_Effect(EFFECT_ELECTRO_COMBO, this.origin, '0 0 0', 1);
+
+       WaypointSprite_Spawn(WP_KaBall, 0, 0, this, '0 0 64', NULL, this.team, this, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
+       WaypointSprite_Ping(this.waypointsprite_attachedforcarrier);
+
+       sound(this, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+}
+
+void ka_TimeScoring(entity this)
+{
+       if(this.owner.ballcarried)
+       { // add points for holding the ball after a certain amount of time
+               if(autocvar_g_keepaway_score_timepoints)
+                       GameRules_scoring_add(this.owner, SCORE, autocvar_g_keepaway_score_timepoints);
+
+               GameRules_scoring_add(this.owner, KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds"
+               this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
+       }
+}
+
+void ka_TouchEvent(entity this, entity toucher) // runs any time that the ball comes in contact with something
+{
+       if(game_stopped) return;
+       if(!this) return;
+       if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
+       { // The ball fell off the map, respawn it since players can't get to it
+               ka_RespawnBall(this);
+               return;
+       }
+       if(IS_DEAD(toucher)) { return; }
+       if(STAT(FROZEN, toucher)) { return; }
+       if (!IS_PLAYER(toucher))
+       {  // The ball just touched an object, most likely the world
+               Send_Effect(EFFECT_BALL_SPARKS, this.origin, '0 0 0', 1);
+               sound(this, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM);
+               return;
+       }
+       else if(this.wait > time) { return; }
+
+       // attach the ball to the player
+       this.owner = toucher;
+       toucher.ballcarried = this;
+       GameRules_scoring_vip(toucher, true);
+       setattachment(this, toucher, "");
+       setorigin(this, '0 0 0');
+
+       // make the ball invisible/unable to do anything/set up time scoring
+       this.velocity = '0 0 0';
+       set_movetype(this, MOVETYPE_NONE);
+       this.effects |= EF_NODRAW;
+       settouch(this, func_null);
+       setthink(this, ka_TimeScoring);
+       this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
+       this.takedamage = DAMAGE_NO;
+       navigation_dynamicgoal_unset(this);
+
+       // apply effects to player
+       toucher.glow_color = autocvar_g_keepawayball_trail_color;
+       toucher.glow_trail = true;
+       toucher.effects |= autocvar_g_keepaway_ballcarrier_effects;
+
+       // messages and sounds
+       ka_EventLog("pickup", toucher);
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_PICKUP, toucher.netname);
+       Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, toucher.netname);
+       Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF);
+       sound(this.owner, CH_TRIGGER, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+
+       // scoring
+       GameRules_scoring_add(toucher, KEEPAWAY_PICKUPS, 1);
+
+       // waypoints
+       WaypointSprite_AttachCarrier(WP_KaBallCarrier, toucher, RADARICON_FLAGCARRIER);
+       toucher.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
+       WaypointSprite_UpdateRule(toucher.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+       WaypointSprite_Ping(toucher.waypointsprite_attachedforcarrier);
+       WaypointSprite_Kill(this.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, NULL, "");
+       set_movetype(ball, MOVETYPE_BOUNCE);
+       ball.wait = time + 1;
+       settouch(ball, ka_TouchEvent);
+       setthink(ball, 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();
+       entity e = ball.owner; ball.owner = NULL;
+       e.ballcarried = NULL;
+       GameRules_scoring_vip(e, false);
+       navigation_dynamicgoal_set(ball);
+
+       // 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, NULL, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname);
+       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname);
+       sound(NULL, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+
+       // scoring
+       // GameRules_scoring_add(plyr, 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', NULL, 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);
+}
+
+/** used to clear the ballcarrier whenever the match switches from warmup to normal */
+void ka_Reset(entity this)
+{
+       if((this.owner) && (IS_PLAYER(this.owner)))
+               ka_DropEvent(this.owner);
+
+       if(time < game_starttime)
+       {
+               setthink(this, ka_RespawnBall);
+               settouch(this, func_null);
+               this.nextthink = game_starttime;
+       }
+       else
+               ka_RespawnBall(this);
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+void havocbot_goalrating_ball(entity this, float ratingscale, vector org)
+{
+       float t;
+       entity ball_owner;
+       ball_owner = ka_ball.owner;
+
+       if (ball_owner == this)
+               return;
+
+       // If ball is carried by player then hunt them down.
+       if (ball_owner)
+       {
+               t = (GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR)) / (GetResourceAmount(ball_owner, RESOURCE_HEALTH) + GetResourceAmount(ball_owner, RESOURCE_ARMOR));
+               navigation_routerating(this, ball_owner, t * ratingscale, 2000);
+       }
+       else // Ball has been dropped so collect.
+               navigation_routerating(this, ka_ball, ratingscale, 2000);
+}
+
+void havocbot_role_ka_carrier(entity this)
+{
+       if (IS_DEAD(this))
+               return;
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+               havocbot_goalrating_items(this, 10000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
+               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+
+       if (!this.ballcarried)
+       {
+               this.havocbot_role = havocbot_role_ka_collector;
+               navigation_goalrating_timeout_expire(this, 2);
+       }
+}
+
+void havocbot_role_ka_collector(entity this)
+{
+       if (IS_DEAD(this))
+               return;
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+               havocbot_goalrating_items(this, 10000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 1000, this.origin, 10000);
+               havocbot_goalrating_ball(this, 20000, this.origin);
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+
+       if (this.ballcarried)
+       {
+               this.havocbot_role = havocbot_role_ka_carrier;
+               navigation_goalrating_timeout_expire(this, 2);
+       }
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(ka, PlayerDies)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+
+       if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)))
+       {
+               if(frag_target.ballcarried) { // add to amount of times killing carrier
+                       GameRules_scoring_add(frag_attacker, KEEPAWAY_CARRIERKILLS, 1);
+                       if(autocvar_g_keepaway_score_bckill) // add bckills to the score
+                               GameRules_scoring_add(frag_attacker, 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
+                       GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_killac);
+       }
+
+       if(frag_target.ballcarried) { ka_DropEvent(frag_target); } // a player with the ball has died, drop it
+}
+
+MUTATOR_HOOKFUNCTION(ka, GiveFragsForKill)
+{
+       M_ARGV(2, float) = 0; // no frags counted in keepaway
+       return true; // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPreThink)
+{
+       entity player = M_ARGV(0, entity);
+
+       // clear the item used for the ball in keepaway
+       player.items &= ~IT_KEY1;
+
+       // if the player has the ball, make sure they have the item for it (Used for HUD primarily)
+       if(player.ballcarried)
+               player.items |= IT_KEY1;
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerUseKey)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(MUTATOR_RETURNVALUE == 0)
+       if(player.ballcarried)
+       {
+               ka_DropEvent(player);
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ka, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_damage = M_ARGV(4, float);
+       vector frag_force = M_ARGV(6, vector);
+
+       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;
+               }
+       }
+
+       M_ARGV(4, float) = frag_damage;
+       M_ARGV(6, vector) = frag_force;
+}
+
+MUTATOR_HOOKFUNCTION(ka, ClientDisconnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
+}
+
+MUTATOR_HOOKFUNCTION(ka, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPowerups)
+{
+       entity player = M_ARGV(0, entity);
+
+       // 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()
+
+       player.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
+
+       if(player.ballcarried)
+               player.effects |= autocvar_g_keepaway_ballcarrier_effects;
+}
+
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPhysics_UpdateStats)
+{
+       entity player = M_ARGV(0, entity);
+       // these automatically reset, no need to worry
+
+       if(player.ballcarried)
+               STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_keepaway_ballcarrier_highspeed;
+}
+
+MUTATOR_HOOKFUNCTION(ka, BotShouldAttack)
+{
+       entity bot = M_ARGV(0, entity);
+       entity targ = M_ARGV(1, entity);
+
+       // if neither player has ball then don't attack unless the ball is on the ground
+       if(!targ.ballcarried && !bot.ballcarried && ka_ball.owner)
+               return true;
+}
+
+MUTATOR_HOOKFUNCTION(ka, HavocBot_ChooseRole)
+{
+       entity bot = M_ARGV(0, entity);
+
+       if (bot.ballcarried)
+               bot.havocbot_role = havocbot_role_ka_carrier;
+       else
+               bot.havocbot_role = havocbot_role_ka_collector;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ka, DropSpecialItems)
+{
+       entity frag_target = M_ARGV(0, entity);
+
+       if(frag_target.ballcarried)
+               ka_DropEvent(frag_target);
+}
+
+.bool pushable;
+
+// ==============
+// Initialization
+// ==============
+
+MODEL(KA_BALL, "models/orbs/orbblue.md3");
+
+void ka_SpawnBall() // loads various values for the ball, runs only once at start of match
+{
+       entity e = new(keepawayball);
+       setmodel(e, MDL_KA_BALL);
+       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.damageforcescale = autocvar_g_keepawayball_damageforcescale;
+       e.takedamage = DAMAGE_YES;
+       e.solid = SOLID_TRIGGER;
+       set_movetype(e, MOVETYPE_BOUNCE);
+       e.glow_color = autocvar_g_keepawayball_trail_color;
+       e.glow_trail = true;
+       e.flags = FL_ITEM;
+       IL_PUSH(g_items, e);
+       e.pushable = true;
+       e.reset = ka_Reset;
+       settouch(e, ka_TouchEvent);
+       e.owner = NULL;
+       ka_ball = e;
+       navigation_dynamicgoal_init(ka_ball, false);
+
+       InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So.
+}
+
+void ka_Initialize() // run at the start of a match, initiates game mode
+{
+       ka_SpawnBall();
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qh b/qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qh
new file mode 100644 (file)
index 0000000..a4615c1
--- /dev/null
@@ -0,0 +1,31 @@
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+void ka_Initialize();
+
+REGISTER_MUTATOR(ka, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+           GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
+            field(SP_KEEPAWAY_PICKUPS, "pickups", 0);
+            field(SP_KEEPAWAY_CARRIERKILLS, "bckills", 0);
+            field(SP_KEEPAWAY_BCTIME, "bctime", SFL_SORT_PRIO_SECONDARY);
+        });
+
+               ka_Initialize();
+       }
+       return false;
+}
+
+
+entity ka_ball;
+
+void(entity this) havocbot_role_ka_carrier;
+void(entity this) havocbot_role_ka_collector;
+
+void ka_DropEvent(entity plyr);
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/_mod.inc b/qcsrc/common/gamemodes/gamemode/keyhunt/_mod.inc
new file mode 100644 (file)
index 0000000..3861dea
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/keyhunt/keyhunt.qc>
diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/_mod.qh b/qcsrc/common/gamemodes/gamemode/keyhunt/_mod.qh
new file mode 100644 (file)
index 0000000..cd796c7
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/keyhunt/keyhunt.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qc b/qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qc
new file mode 100644 (file)
index 0000000..6523612
--- /dev/null
@@ -0,0 +1,1324 @@
+#include "keyhunt.qh"
+
+// TODO: sv_keyhunt
+#ifdef SVQC
+float autocvar_g_balance_keyhunt_damageforcescale;
+float autocvar_g_balance_keyhunt_delay_collect;
+float autocvar_g_balance_keyhunt_delay_damage_return;
+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_return_when_unreachable;
+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_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;
+
+bool 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 siren_time;  //  time delay the siren
+//.float stuff_time;  //  time delay to stuffcmd a cvar
+
+int 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";
+
+int kh_Team_ByID(int 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;
+//bool kh_tracking_enabled;
+int kh_teams;
+int kh_interferemsg_team;
+float kh_interferemsg_time;
+.entity kh_next, kh_prev; // linked list
+.float kh_droptime;
+.int kh_dropperteam;
+.entity kh_previous_owner;
+.int kh_previous_owner_playerid;
+
+int kh_key_dropped, kh_key_carried;
+
+int kh_Key_AllOwnedByWhichTeam();
+
+const int ST_KH_CAPS = 1;
+void kh_ScoreRules(int teams)
+{
+       GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
+        field_team(ST_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
+        field(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
+        field(SP_KH_PUSHES, "pushes", 0);
+        field(SP_KH_DESTROYS, "destroyed", SFL_LOWER_IS_BETTER);
+        field(SP_KH_PICKUPS, "pickups", 0);
+        field(SP_KH_KCKILLS, "kckills", 0);
+        field(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER);
+       });
+}
+
+bool kh_KeyCarrier_waypointsprite_visible_for_player(entity this, entity player, entity view)  // runs all the time
+{
+       if(!IS_PLAYER(view) || DIFF_TEAM(this, view))
+               if(!kh_tracking_enabled)
+                       return false;
+
+       return true;
+}
+
+bool kh_Key_waypointsprite_visible_for_player(entity this, entity player, entity view)
+{
+       if(!kh_tracking_enabled)
+               return false;
+       if(!this.owner)
+               return true;
+       if(!this.owner.owner)
+               return true;
+       return false;  // draw only when key is not owned
+}
+
+void kh_update_state()
+{
+       entity key;
+       int f;
+       int s = 0;
+       FOR_EACH_KH_KEY(key)
+       {
+               if(key.owner)
+                       f = key.team;
+               else
+                       f = 30;
+               s |= (32 ** key.count) * f;
+       }
+
+       FOREACH_CLIENT(true, { STAT(KH_KEYS, it) = s; });
+
+       FOR_EACH_KH_KEY(key)
+       {
+               if(key.owner)
+                       STAT(KH_KEYS, key.owner) |= (32 ** key.count) * 31;
+       }
+       //print(ftos((nextent(NULL)).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(entity this)  // called a lot
+{
+       if(game_stopped)
+               return;
+       if(this.cnt > 0)
+       {
+               if(getthink(this) != kh_WaitForPlayers)
+                       this.cnt -= 1;
+       }
+       else if(this.cnt == 0)
+       {
+               this.cnt -= 1;
+               kh_Controller_Thinkfunc();
+       }
+       this.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(game_stopped)
+               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 = 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;
+       if(IL_CONTAINS(g_items, key))
+               IL_REMOVE(g_items, key);
+       key.solid = SOLID_NOT;
+       set_movetype(key, MOVETYPE_NONE);
+       key.team = key.owner.team;
+       key.nextthink = time;
+       key.damageforcescale = 0;
+       key.takedamage = DAMAGE_NO;
+       key.modelindex = kh_key_carried;
+       navigation_dynamicgoal_unset(key);
+}
+
+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 = 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, NULL, "");
+       setorigin(key, key.owner.origin + '0 0 1' * (STAT(PL_MIN, key.owner).z - KH_KEY_MIN_z));
+       key.angles = key.owner.angles;
+#else
+       setorigin(key, key.owner.origin + key.origin.z * '0 0 1');
+       setattachment(key, NULL, "");
+       key.angles_y += key.owner.angles.y;
+#endif
+       key.flags = FL_ITEM;
+       if(!IL_CONTAINS(g_items, key))
+               IL_PUSH(g_items, key);
+       key.solid = SOLID_TRIGGER;
+       set_movetype(key, 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;
+       navigation_dynamicgoal_set(key);
+       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
+{
+       if(key.owner == player)
+               return;
+
+       int 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 = NULL;
+               key.kh_prev = NULL;
+
+               if(key.owner.kh_next == NULL)
+               {
+                       // 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 == NULL)
+               {
+                       // 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 = NULL;
+
+       int ownerteam = kh_Key_AllOwnedByWhichTeam();
+       if(ownerteam != ownerteam0)
+       {
+               entity k;
+               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, { first = it; break; });
+                               entity third = WP_Null;
+                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { 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 "Key Carrier"
+                       FOR_EACH_KH_KEY(k)
+                       {
+                               if (!k.owner) continue;
+                               entity first = WP_Null;
+                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; });
+                               entity third = WP_Null;
+                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; });
+                               WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third);
+                       }
+               }
+       }
+}
+
+void kh_Key_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
+{
+       if(this.owner)
+               return;
+       if(ITEM_DAMAGE_NEEDKILL(deathtype))
+       {
+               this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
+               return;
+       }
+       if(force == '0 0 0')
+               return;
+       if(time > this.pushltime)
+               if(IS_PLAYER(attacker))
+                       this.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);
+               GameRules_scoring_add(player, KH_PICKUPS, 1);
+       }
+       key.kh_dropperteam = 0;
+       int realteam = kh_Team_ByID(key.count);
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PICKUP), player.netname);
+
+       kh_Key_AssignTo(key, player); // this also updates .kh_state
+}
+
+void kh_Key_Touch(entity this, entity toucher)  // runs many, many times when a key has been dropped and can be picked up
+{
+       if(game_stopped)
+               return;
+
+       if(this.owner) // already carried
+               return;
+
+       if(ITEM_TOUCH_NEEDKILL())
+       {
+               this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
+               return;
+       }
+
+       if (!IS_PLAYER(toucher))
+               return;
+       if(IS_DEAD(toucher))
+               return;
+       if(toucher == this.enemy)
+               if(time < this.kh_droptime + autocvar_g_balance_keyhunt_delay_collect)
+                       return;  // you just dropped it!
+       kh_Key_Collect(this, toucher);
+}
+
+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 = key.owner;
+       kh_Key_AssignTo(key, NULL);
+       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;
+               }
+       }
+
+       delete(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, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
+       kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
+}
+
+void nades_GiveBonus(entity player, float score);
+
+void kh_WinnerTeam(int winner_team)  // runs when a team wins
+{
+       // all key carriers get some points
+       entity key;
+       float score = (NumTeams(kh_teams) - 1) * autocvar_g_balance_keyhunt_score_capture;
+       DistributeEvenly_Init(score, NumTeams(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 = DistributeEvenly_Get(1);
+               kh_Scores_Event(key.owner, key, "capture", f, 0);
+               GameRules_scoring_add_team(key.owner, KH_CAPS, 1);
+               nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high);
+       }
+
+       bool 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, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_KEYHUNT_CAPTURE), keyowner);
+
+       first = true;
+       vector firstorigin = '0 0 0', lastorigin = '0 0 0', midpoint = '0 0 0';
+       FOR_EACH_KH_KEY(key)
+       {
+               vector thisorigin = kh_AttachedOrigin(key);
+               //dprint("Key origin: ", vtos(thisorigin), "\n");
+               midpoint += thisorigin;
+
+               if(!first)
+                       te_lightning2(NULL, lastorigin, thisorigin);
+               lastorigin = thisorigin;
+               if(first)
+                       firstorigin = thisorigin;
+               first = false;
+       }
+       if(NumTeams(kh_teams) > 2)
+       {
+               te_lightning2(NULL, lastorigin, firstorigin);
+       }
+       midpoint = midpoint * (1 / NumTeams(kh_teams));
+       te_customflash(midpoint, 1000, 1, Team_ColorRGB(winner_team) * 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(int loser_team, entity lostkey)  // runs when a player pushes a flag carrier off the map
+{
+       float f;
+       entity attacker = NULL;
+       if(lostkey.pusher)
+               if(lostkey.pusher.team != loser_team)
+                       if(IS_PLAYER(lostkey.pusher))
+                               attacker = lostkey.pusher;
+
+       if(attacker)
+       {
+               if(lostkey.kh_previous_owner)
+                       kh_Scores_Event(lostkey.kh_previous_owner, NULL, "pushed", 0, -autocvar_g_balance_keyhunt_score_push);
+                       // don't actually GIVE him the -nn points, just log
+               kh_Scores_Event(attacker, NULL, "push", autocvar_g_balance_keyhunt_score_push, 0);
+               GameRules_scoring_add(attacker, KH_PUSHES, 1);
+               //centerprint(attacker, "Your push is the best!"); // does this really need to exist?
+       }
+       else
+       {
+               int players = 0;
+               float of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
+
+               FOREACH_CLIENT(IS_PLAYER(it) && it.team != loser_team, { ++players; });
+
+               entity key;
+               int keys = 0;
+               FOR_EACH_KH_KEY(key)
+                       if(key.owner && key.team != loser_team)
+                               ++keys;
+
+               if(lostkey.kh_previous_owner)
+                       kh_Scores_Event(lostkey.kh_previous_owner, NULL, "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)
+                       GameRules_scoring_add(lostkey.kh_previous_owner, KH_DESTROYS, 1);
+
+               DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players);
+
+               FOR_EACH_KH_KEY(key)
+                       if(key.owner && key.team != loser_team)
+                       {
+                               f = DistributeEvenly_Get(of);
+                               kh_Scores_Event(key.owner, NULL, "destroyed_holdingkey", f, 0);
+                       }
+
+               int fragsleft = DistributeEvenly_Get(players);
+
+               // Now distribute these among all other teams...
+               int j = NumTeams(kh_teams) - 1;
+               for(int i = 0; i < NumTeams(kh_teams); ++i)
+               {
+                       int thisteam = kh_Team_ByID(i);
+                       if(thisteam == loser_team) // bad boy, no cookie - this WILL happen
+                               continue;
+
+                       players = 0;
+                       FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, { ++players; });
+
+                       DistributeEvenly_Init(fragsleft, j);
+                       fragsleft = DistributeEvenly_Get(j - 1);
+                       DistributeEvenly_Init(DistributeEvenly_Get(1), players);
+
+                       FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, {
+                               f = DistributeEvenly_Get(1);
+                               kh_Scores_Event(it, NULL, "destroyed", f, 0);
+                       });
+
+                       --j;
+               }
+       }
+
+       int realteam = kh_Team_ByID(lostkey.count);
+       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(loser_team, CENTER_ROUND_TEAM_LOSS));
+       if(attacker)
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PUSHED), attacker.netname, lostkey.kh_previous_owner.netname);
+       else
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DESTROYED), lostkey.kh_previous_owner.netname);
+
+       play2all(SND(KH_DESTROY));
+       te_tarexplosion(lostkey.origin);
+
+       kh_FinishRound();
+}
+
+void kh_Key_Think(entity this)  // runs all the time
+{
+       if(game_stopped)
+               return;
+
+       if(this.owner)
+       {
+#ifndef KH_PLAYER_USE_ATTACHMENT
+               makevectors('0 1 0' * (this.cnt + (time % 360) * KH_KEY_XYSPEED));
+               setorigin(this, v_forward * KH_KEY_XYDIST + '0 0 1' * this.origin.z);
+#endif
+       }
+
+       // if in nodrop or time over, end the round
+       if(!this.owner)
+               if(time > this.pain_finished)
+                       kh_LoserTeam(this.team, this);
+
+       if(this.owner)
+       if(kh_Key_AllOwnedByWhichTeam() != -1)
+       {
+               if(this.siren_time < time)
+               {
+                       sound(this.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM);  // play a simple alarm
+                       this.siren_time = time + 2.5;  // repeat every 2.5 seconds
+               }
+
+               entity key;
+               vector p = this.owner.origin;
+               FOR_EACH_KH_KEY(key)
+                       if(vdist(key.owner.origin - p, >, autocvar_g_balance_keyhunt_maxdist))
+                               goto not_winning;
+               kh_WinnerTeam(this.team);
+LABEL(not_winning)
+       }
+
+       if(kh_interferemsg_time && time > kh_interferemsg_time)
+       {
+               kh_interferemsg_time = 0;
+               FOREACH_CLIENT(IS_PLAYER(it), {
+                       if(it.team == kh_interferemsg_team)
+                               if(it.kh_next)
+                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_MEET);
+                               else
+                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_HELP);
+                       else
+                               Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE));
+               });
+       }
+
+       this.nextthink = time + 0.05;
+}
+
+void key_reset(entity this)
+{
+       kh_Key_AssignTo(this, NULL);
+       kh_Key_Remove(this);
+}
+
+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 = spawn();
+       key.count = i;
+       key.classname = STR_ITEM_KH_KEY;
+       settouch(key, kh_Key_Touch);
+       setthink(key, 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.damagedbytriggers = autocvar_g_balance_keyhunt_return_when_unreachable;
+       key.damagedbycontents = autocvar_g_balance_keyhunt_return_when_unreachable;
+       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;
+       navigation_dynamicgoal_init(key, false);
+
+       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(initial_owner.team, CENTER_KEYHUNT_START));
+
+       WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, NULL, 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
+int kh_Key_AllOwnedByWhichTeam()  // constantly called. check to see if all the keys are owned by the same team
+{
+       entity key;
+       int teem = -1;
+       int keys = NumTeams(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 = key.owner;
+
+       key.kh_droptime = time;
+       key.enemy = player;
+
+       kh_Scores_Event(player, key, "dropkey", 0, 0);
+       GameRules_scoring_add(player, KH_LOSSES, 1);
+       int realteam = kh_Team_ByID(key.count);
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DROP), player.netname);
+
+       kh_Key_AssignTo(key, NULL);
+       makevectors(player.v_angle);
+       key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false);
+       key.pusher = NULL;
+       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
+{
+       if(player.kh_next)
+       {
+               entity mypusher = NULL;
+               if(player.pusher)
+                       if(time < player.pushltime)
+                               mypusher = player.pusher;
+
+               entity key;
+               while((key = player.kh_next))
+               {
+                       kh_Scores_Event(player, key, "losekey", 0, 0);
+                       GameRules_scoring_add(player, KH_LOSSES, 1);
+                       int realteam = kh_Team_ByID(key.count);
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_LOST), player.netname);
+                       kh_Key_AssignTo(key, NULL);
+                       makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random());
+                       key.velocity = W_CalculateProjectileVelocity(player, 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);
+       }
+}
+
+int kh_GetMissingTeams()
+{
+       int missing_teams = 0;
+       for(int i = 0; i < NumTeams(kh_teams); ++i)
+       {
+               int teem = kh_Team_ByID(i);
+               int players = 0;
+               FOREACH_CLIENT(IS_PLAYER(it), {
+                       if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
+                               ++players;
+               });
+               if (!players)
+                       missing_teams |= (2 ** i);
+       }
+       return missing_teams;
+}
+
+void kh_WaitForPlayers()  // delay start of the round until enough players are present
+{
+       static int prev_missing_teams_mask;
+       if(time < game_starttime)
+       {
+               if (prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
+               return;
+       }
+
+       int missing_teams_mask = kh_GetMissingTeams();
+       if(!missing_teams_mask)
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               Send_Notification(NOTIF_ALL, NULL, 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, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+                       prev_missing_teams_mask = -1;
+               }
+               else
+               {
+                       if(prev_missing_teams_mask != missing_teams_mask)
+                       {
+                               Send_Notification(NOTIF_ALL, NULL, 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, NULL, MSG_CENTER, CPID_KEYHUNT);
+       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
+
+       kh_tracking_enabled = true;
+}
+
+void kh_StartRound()  // runs at the start of each round
+{
+       if(time < game_starttime)
+       {
+               kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
+               return;
+       }
+
+       if(kh_GetMissingTeams())
+       {
+               kh_Controller_SetThink(1, kh_WaitForPlayers);
+               return;
+       }
+
+       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT);
+       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
+
+       for(int i = 0; i < NumTeams(kh_teams); ++i)
+       {
+               int teem = kh_Team_ByID(i);
+               int players = 0;
+               entity my_player = NULL;
+               FOREACH_CLIENT(IS_PLAYER(it), {
+                       if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
+                       {
+                               ++players;
+                               if(random() * players <= 1)
+                                       my_player = it;
+                       }
+               });
+               kh_Key_Spawn(my_player, 360 * i / NumTeams(kh_teams), i);
+       }
+
+       kh_tracking_enabled = false;
+       Send_Notification(NOTIF_ALL, NULL, 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)
+               {
+                       int nk = 0;
+                       for(entity k = targ.kh_next; k != NULL; 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);
+                       GameRules_scoring_add(attacker, 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 = cvar("g_keyhunt_teams"); // read the cvar directly as it gets written earlier in the same frame
+       kh_teams = BITS(bound(2, kh_teams, 4));
+
+       // make a KH entity for controlling the game
+       kh_controller = spawn();
+       setthink(kh_controller, 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;
+
+       kh_ScoreRules(kh_teams);
+}
+
+void kh_finalize()
+{
+       // to be called before intermission
+       kh_FinishRound();
+       delete(kh_controller);
+       kh_controller = NULL;
+}
+
+// legacy bot role
+
+void(entity this) havocbot_role_kh_carrier;
+void(entity this) havocbot_role_kh_defense;
+void(entity this) havocbot_role_kh_offense;
+void(entity this) havocbot_role_kh_freelancer;
+
+
+void havocbot_goalrating_kh(entity this, float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
+{
+       entity head;
+       for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
+       {
+               if(head.owner == this)
+                       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 != this.team)
+                       {
+                               traceline(this.origin + this.view_ofs, head.origin, MOVE_NOMONSTERS, this);
+                               if (trace_fraction < 1 && trace_ent != head)
+                                       continue; // skip what I can't see
+                       }
+               }
+               if(!head.owner)
+                       navigation_routerating(this, head, ratingscale_dropped * 10000, 100000);
+               else if(head.team == this.team)
+                       navigation_routerating(this, head.owner, ratingscale_team * 10000, 100000);
+               else
+                       navigation_routerating(this, head.owner, ratingscale_enemy * 10000, 100000);
+       }
+
+       havocbot_goalrating_items(this, 1, this.origin, 10000);
+}
+
+void havocbot_role_kh_carrier(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (!(this.kh_next))
+       {
+               LOG_TRACE("changing role to freelancer");
+               this.havocbot_role = havocbot_role_kh_freelancer;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+
+               if(kh_Key_AllOwnedByWhichTeam() == this.team)
+                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // bring home
+               else
+                       havocbot_goalrating_kh(this, 4, 4, 1); // play defensively
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_kh_defense(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (this.kh_next)
+       {
+               LOG_TRACE("changing role to carrier");
+               this.havocbot_role = havocbot_role_kh_carrier;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + random() * 10 + 20;
+       if (time > this.havocbot_role_timeout)
+       {
+               LOG_TRACE("changing role to freelancer");
+               this.havocbot_role = havocbot_role_kh_freelancer;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               float key_owner_team;
+               navigation_goalrating_start(this);
+
+               key_owner_team = kh_Key_AllOwnedByWhichTeam();
+               if(key_owner_team == this.team)
+                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend key carriers
+               else if(key_owner_team == -1)
+                       havocbot_goalrating_kh(this, 4, 1, 0.1); // play defensively
+               else
+                       havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_kh_offense(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (this.kh_next)
+       {
+               LOG_TRACE("changing role to carrier");
+               this.havocbot_role = havocbot_role_kh_carrier;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + random() * 10 + 20;
+       if (time > this.havocbot_role_timeout)
+       {
+               LOG_TRACE("changing role to freelancer");
+               this.havocbot_role = havocbot_role_kh_freelancer;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               float key_owner_team;
+
+               navigation_goalrating_start(this);
+
+               key_owner_team = kh_Key_AllOwnedByWhichTeam();
+               if(key_owner_team == this.team)
+                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
+               else if(key_owner_team == -1)
+                       havocbot_goalrating_kh(this, 0.1, 1, 4); // play offensively
+               else
+                       havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK! EMERGENCY!
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void havocbot_role_kh_freelancer(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (this.kh_next)
+       {
+               LOG_TRACE("changing role to carrier");
+               this.havocbot_role = havocbot_role_kh_carrier;
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (!this.havocbot_role_timeout)
+               this.havocbot_role_timeout = time + random() * 10 + 10;
+       if (time > this.havocbot_role_timeout)
+       {
+               if (random() < 0.5)
+               {
+                       LOG_TRACE("changing role to offense");
+                       this.havocbot_role = havocbot_role_kh_offense;
+               }
+               else
+               {
+                       LOG_TRACE("changing role to defense");
+                       this.havocbot_role = havocbot_role_kh_defense;
+               }
+               this.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+
+               int key_owner_team = kh_Key_AllOwnedByWhichTeam();
+               if(key_owner_team == this.team)
+                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
+               else if(key_owner_team == -1)
+                       havocbot_goalrating_kh(this, 1, 10, 4); // prefer dropped keys
+               else
+                       havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+
+// register this as a mutator
+
+MUTATOR_HOOKFUNCTION(kh, ClientDisconnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       kh_Key_DropAll(player, true);
+}
+
+MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       kh_Key_DropAll(player, true);
+}
+
+MUTATOR_HOOKFUNCTION(kh, PlayerDies)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+
+       if(frag_target == frag_attacker)
+               kh_Key_DropAll(frag_target, true);
+       else if(IS_PLAYER(frag_attacker))
+               kh_Key_DropAll(frag_target, false);
+       else
+               kh_Key_DropAll(frag_target, true);
+}
+
+MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+       entity frag_attacker = M_ARGV(0, entity);
+       entity frag_target = M_ARGV(1, entity);
+       float frag_score = M_ARGV(2, float);
+       M_ARGV(2, float) = kh_HandleFrags(frag_attacker, frag_target, frag_score);
+}
+
+MUTATOR_HOOKFUNCTION(kh, MatchEnd)
+{
+       kh_finalize();
+}
+
+MUTATOR_HOOKFUNCTION(kh, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       M_ARGV(0, float) = kh_teams;
+}
+
+MUTATOR_HOOKFUNCTION(kh, SpectateCopy)
+{
+       entity spectatee = M_ARGV(0, entity);
+       entity client = M_ARGV(1, entity);
+
+       STAT(KH_KEYS, client) = STAT(KH_KEYS, spectatee);
+}
+
+MUTATOR_HOOKFUNCTION(kh, PlayerUseKey)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(MUTATOR_RETURNVALUE == 0)
+       {
+               entity k = player.kh_next;
+               if(k)
+               {
+                       kh_Key_DropOne(k);
+                       return true;
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole)
+{
+    entity bot = M_ARGV(0, entity);
+
+       if(IS_DEAD(bot))
+               return true;
+
+       float r = random() * 3;
+       if (r < 1)
+               bot.havocbot_role = havocbot_role_kh_offense;
+       else if (r < 2)
+               bot.havocbot_role = havocbot_role_kh_defense;
+       else
+               bot.havocbot_role = havocbot_role_kh_freelancer;
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(kh, DropSpecialItems)
+{
+       entity frag_target = M_ARGV(0, entity);
+
+       kh_Key_DropAll(frag_target, false);
+}
+
+MUTATOR_HOOKFUNCTION(kh, reset_map_global)
+{
+       kh_WaitForPlayers(); // takes care of killing the "missing teams" message
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qh b/qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qh
new file mode 100644 (file)
index 0000000..a086ee6
--- /dev/null
@@ -0,0 +1,36 @@
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit")
+int autocvar_g_keyhunt_point_leadlimit;
+bool autocvar_g_keyhunt_team_spawns;
+void kh_Initialize();
+
+REGISTER_MUTATOR(kh, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               GameRules_teams(true);
+        GameRules_spawning_teams(autocvar_g_keyhunt_team_spawns);
+        GameRules_limit_score(autocvar_g_keyhunt_point_limit);
+        GameRules_limit_lead(autocvar_g_keyhunt_point_leadlimit);
+
+               kh_Initialize();
+       }
+       return 0;
+}
+
+#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:
+bool kh_tracking_enabled;
+.entity kh_next;
+
+USING(kh_Think_t, void());
+void kh_StartRound();
+void kh_Controller_SetThink(float t, kh_Think_t func);
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/lms/_mod.inc b/qcsrc/common/gamemodes/gamemode/lms/_mod.inc
new file mode 100644 (file)
index 0000000..43bb76d
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/lms/lms.qc>
diff --git a/qcsrc/common/gamemodes/gamemode/lms/_mod.qh b/qcsrc/common/gamemodes/gamemode/lms/_mod.qh
new file mode 100644 (file)
index 0000000..5e780bb
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/lms/lms.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/lms/lms.qc b/qcsrc/common/gamemodes/gamemode/lms/lms.qc
new file mode 100644 (file)
index 0000000..b6bfb4e
--- /dev/null
@@ -0,0 +1,438 @@
+#include "lms.qh"
+
+#ifdef SVQC
+#include <common/mutators/mutator/instagib/items.qh>
+#include <server/campaign.qh>
+#include <server/command/_mod.qh>
+
+int autocvar_g_lms_extra_lives;
+bool autocvar_g_lms_join_anytime;
+int autocvar_g_lms_last_join;
+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);
+}
+
+void ClearWinners();
+
+// LMS winning condition: game terminates if and only if there's at most one
+// one player who's living lives. Top two scores being equal cancels the time
+// limit.
+int WinningCondition_LMS()
+{
+       entity first_player = NULL;
+       int total_players = 0;
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               if (!total_players)
+                       first_player = it;
+               ++total_players;
+       });
+
+       if (total_players)
+       {
+               if (total_players > 1)
+               {
+                       // two or more active players - continue with the game
+
+                       if (autocvar_g_campaign)
+                       {
+                               FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+                                       float pl_lives = GameRules_scoring_add(it, LMS_LIVES, 0);
+                                       if (!pl_lives)
+                                               return WINNING_YES; // human player lost, game over
+                                       break;
+                               });
+                       }
+               }
+               else
+               {
+                       // exactly one player?
+
+                       ClearWinners();
+                       SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
+
+                       if (LMS_NewPlayerLives())
+                       {
+                               // game still running (that is, nobody got removed from the game by a frag yet)? then continue
+                               return WINNING_NO;
+                       }
+                       else
+                       {
+                               // a winner!
+                               // and assign him his first place
+                               GameRules_scoring_add(first_player, LMS_RANK, 1);
+                               if(warmup_stage)
+                                       return WINNING_NO;
+                               else
+                                       return WINNING_YES;
+                       }
+               }
+       }
+       else
+       {
+               // nobody is playing at all...
+               if (LMS_NewPlayerLives())
+               {
+                       // wait for players...
+               }
+               else
+               {
+                       // SNAFU (maybe a draw game?)
+                       ClearWinners();
+                       LOG_TRACE("No players, ending game.");
+                       return WINNING_YES;
+               }
+       }
+
+       // When we get here, we have at least two players who are actually LIVING,
+       // now check if the top two players have equal score.
+       WinningConditionHelper(NULL);
+
+       ClearWinners();
+       if(WinningConditionHelper_winner)
+               WinningConditionHelper_winner.winning = true;
+       if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore)
+               return WINNING_NEVER;
+
+       // Top two have different scores? Way to go for our beloved TIMELIMIT!
+       return WINNING_NO;
+}
+
+// mutator hooks
+MUTATOR_HOOKFUNCTION(lms, reset_map_global)
+{
+       lms_lowest_lives = 999;
+}
+
+MUTATOR_HOOKFUNCTION(lms, reset_map_players)
+{
+       FOREACH_CLIENT(true, {
+               TRANSMUTE(Player, it);
+               it.frags = FRAGS_PLAYER;
+               GameRules_scoring_add(it, LMS_LIVES, LMS_NewPlayerLives());
+               PutClientInServer(it);
+       });
+}
+
+MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.frags == FRAGS_SPECTATOR)
+               TRANSMUTE(Observer, player);
+       else
+       {
+               float tl = GameRules_scoring_add(player, LMS_LIVES, 0);
+               if(tl < lms_lowest_lives)
+                       lms_lowest_lives = tl;
+               if(tl <= 0)
+                       TRANSMUTE(Observer, player);
+               if(warmup_stage)
+                       GameRules_scoring_add(player, LMS_RANK, -GameRules_scoring_add(player, LMS_RANK, 0));
+       }
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(warmup_stage)
+               return false;
+       if(player.frags == FRAGS_SPECTATOR)
+               return true;
+       if(GameRules_scoring_add(player, LMS_LIVES, 0) <= 0)
+       {
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES);
+               return true;
+       }
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerDies)
+{
+       entity frag_target = M_ARGV(2, entity);
+
+       frag_target.respawn_flags |= RESPAWN_FORCE;
+}
+
+void lms_RemovePlayer(entity player)
+{
+       static int quitters = 0;
+       float player_rank = GameRules_scoring_add(player, LMS_RANK, 0);
+       if (!player_rank)
+       {
+               int pl_cnt = 0;
+               FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
+               if (player.lms_spectate_warning != 2)
+               {
+                       if(IS_BOT_CLIENT(player))
+                               bot_clear(player);
+                       player.frags = FRAGS_LMS_LOSER;
+                       GameRules_scoring_add(player, LMS_RANK, pl_cnt + 1);
+               }
+               else
+               {
+                       lms_lowest_lives = 999;
+                       FOREACH_CLIENT(true, {
+                               if (it.frags == FRAGS_LMS_LOSER)
+                               {
+                                       float it_rank = GameRules_scoring_add(it, LMS_RANK, 0);
+                                       if (it_rank > player_rank && it_rank <= 256)
+                                               GameRules_scoring_add(it, LMS_RANK, -1);
+                                       lms_lowest_lives = 0;
+                               }
+                               else if (it.frags != FRAGS_SPECTATOR)
+                               {
+                                       float tl = GameRules_scoring_add(it, LMS_LIVES, 0);
+                                       if(tl < lms_lowest_lives)
+                                               lms_lowest_lives = tl;
+                               }
+                       });
+                       GameRules_scoring_add(player, LMS_RANK, 665 - quitters); // different from 666
+                       if(!warmup_stage)
+                       {
+                               GameRules_scoring_add(player, LMS_LIVES, -GameRules_scoring_add(player, LMS_LIVES, 0));
+                               ++quitters;
+                       }
+                       player.frags = FRAGS_LMS_LOSER;
+                       TRANSMUTE(Observer, player);
+               }
+               if (pl_cnt == 2 && !warmup_stage) // a player is forfeiting leaving only one player
+                       lms_lowest_lives = 0; // end the game now!
+       }
+
+       if(CS(player).killcount != FRAGS_SPECTATOR)
+               if(GameRules_scoring_add(player, LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
+               else
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       lms_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (!IS_PLAYER(player))
+               return true;
+
+       lms_RemovePlayer(player);
+       return true;  // prevent team reset
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientConnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(GameRules_scoring_add(player, LMS_LIVES, LMS_NewPlayerLives()) <= 0)
+       {
+               GameRules_scoring_add(player, LMS_RANK, 666); // mark as forced spectator for the hud code
+               player.frags = FRAGS_SPECTATOR;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(lms, AutoJoinOnConnection)
+{
+       if(autocvar_g_campaign)
+               return false;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(player.deadflag == DEAD_DYING)
+               player.deadflag = DEAD_RESPAWNING;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
+{
+       if(autocvar_g_lms_regenerate)
+               return false;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
+{
+       // forbode!
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
+{
+       entity frag_target = M_ARGV(1, entity);
+
+       if (!warmup_stage)
+       {
+               // remove a life
+               int tl = GameRules_scoring_add(frag_target, LMS_LIVES, -1);
+               if(tl < lms_lowest_lives)
+                       lms_lowest_lives = tl;
+               if(tl <= 0)
+               {
+                       int pl_cnt = 0;
+                       FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
+                       if(IS_BOT_CLIENT(frag_target))
+                               bot_clear(frag_target);
+                       frag_target.frags = FRAGS_LMS_LOSER;
+                       GameRules_scoring_add(frag_target, LMS_RANK, pl_cnt);
+               }
+       }
+       M_ARGV(2, float) = 0; // frag score
+
+       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");
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
+{
+       // don't clear player score
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, FilterItemDefinition)
+{
+       entity definition = M_ARGV(0, entity);
+
+       if (autocvar_g_lms_extra_lives && definition == ITEM_ExtraLife)
+       {
+               return false;
+       }
+       return true;
+}
+
+void lms_extralife(entity this)
+{
+       StartItem(this, ITEM_ExtraLife);
+}
+
+MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
+{
+       if (!autocvar_g_powerups) return false;
+       if (!autocvar_g_lms_extra_lives) return false;
+
+       entity ent = M_ARGV(0, entity);
+
+       // Can't use .itemdef here
+       if (ent.classname != "item_health_mega") return false;
+
+       entity e = spawn();
+       setthink(e, lms_extralife);
+
+       e.nextthink = time + 0.1;
+       e.spawnflags = ent.spawnflags;
+       e.noalign = ent.noalign;
+       setorigin(e, ent.origin);
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ItemTouch)
+{
+       entity item = M_ARGV(0, entity);
+       entity toucher = M_ARGV(1, entity);
+
+       if(item.itemdef == ITEM_ExtraLife)
+       {
+               Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
+               GameRules_scoring_add(toucher, LMS_LIVES, autocvar_g_lms_extra_lives);
+               return MUT_ITEMTOUCH_PICKUP;
+       }
+
+       return MUT_ITEMTOUCH_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
+{
+       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+               ++M_ARGV(0, int); // activerealplayers
+               ++M_ARGV(1, int); // realplayers
+       });
+
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(warmup_stage || player.lms_spectate_warning)
+       {
+               // for the forfeit message...
+               player.lms_spectate_warning = 2;
+       }
+       else
+       {
+               if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER)
+               {
+                       player.lms_spectate_warning = 1;
+                       sprint(player, "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)
+{
+       M_ARGV(0, float) = WinningCondition_LMS();
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, WantWeapon)
+{
+       M_ARGV(2, bool) = true; // all weapons
+}
+
+MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
+{
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
+{
+       if(game_stopped)
+       if(M_ARGV(0, entity) == SP_LMS_RANK) // score field
+               return true; // allow writing to this field in intermission as it is needed for newly joining players
+}
+
+void lms_Initialize()
+{
+       lms_lowest_lives = 9999;
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/lms/lms.qh b/qcsrc/common/gamemodes/gamemode/lms/lms.qh
new file mode 100644 (file)
index 0000000..2889220
--- /dev/null
@@ -0,0 +1,31 @@
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+.float lms_spectate_warning;
+#define autocvar_g_lms_lives_override cvar("g_lms_lives_override")
+void lms_Initialize();
+
+REGISTER_MUTATOR(lms, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+        GameRules_limit_score(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override));
+        GameRules_limit_lead(0);
+        GameRules_score_enabled(false);
+        GameRules_scoring(0, 0, 0, {
+            field(SP_LMS_LIVES, "lives", SFL_SORT_PRIO_SECONDARY);
+            field(SP_LMS_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
+        });
+
+               lms_Initialize();
+       }
+       return 0;
+}
+
+// lives related defs
+float lms_lowest_lives;
+float LMS_NewPlayerLives();
+#endif
index 426b341a63a077b1316305a435d0975241b490ea..487012aa50902e7a834fdd708a7c0e45bb2928cc 100644 (file)
@@ -308,7 +308,7 @@ void football_touch(entity this, entity toucher)
        }
        if (!IS_PLAYER(toucher))
                return;
-       if(toucher.health < 1)
+       if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
                return;
        if(!this.cnt)
                this.nextthink = time + autocvar_g_nexball_delay_idle;
@@ -348,7 +348,7 @@ void basketball_touch(entity this, entity toucher)
        }
        if(!this.cnt && IS_PLAYER(toucher) && !STAT(FROZEN, toucher) && !IS_DEAD(toucher) && (toucher != this.nb_dropper || time > this.nb_droptime + autocvar_g_nexball_delay_collect))
        {
-               if(toucher.health <= 0)
+               if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
                        return;
                LogNB("caught", toucher);
                GiveBall(toucher, this);
index a03e5b3353f8b6156eac2d8012ff5e0af9d2c96a..b8d49b10f2edce3580b517f2556a95f35fd092f7 100644 (file)
@@ -30,7 +30,7 @@ void cpicon_draw(entity this)
        this.cp_bob_spd = this.cp_bob_spd + 1.875 * frametime;
        this.colormod = '1 1 1' * (2 - bound(0, (this.pain_finished - time) / 10, 1));
 
-       if(!this.iscaptured) this.alpha = this.health / this.max_health;
+       if(!this.iscaptured) this.alpha = GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health;
 
        if(this.iscaptured)
        {
@@ -165,14 +165,14 @@ NET_HANDLE(ENT_CLIENT_CONTROLPOINT_ICON, bool isnew)
                this.origin = ReadVector();
                setorigin(this, this.origin);
 
-               this.health = ReadByte();
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, ReadByte());
                this.max_health = ReadByte();
                this.count = ReadByte();
                this.team = ReadByte();
                this.iscaptured = ReadByte();
 
                if(!this.count)
-                       this.count = (this.health - this.max_health) * frametime;
+                       this.count = (GetResourceAmount(this, RESOURCE_HEALTH) - this.max_health) * frametime;
 
                cpicon_changeteam(this);
                cpicon_construct(this, isnew);
@@ -189,9 +189,9 @@ NET_HANDLE(ENT_CLIENT_CONTROLPOINT_ICON, bool isnew)
 
                _tmp = ReadByte();
 
-               if(_tmp != this.health)
+               if(_tmp != GetResourceAmount(this, RESOURCE_HEALTH))
                        cpicon_damage(this, _tmp);
 
-               this.health = _tmp;
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, _tmp);
        }
 }
index a3374bf91e804f4db0ae4993eb107349d3c72411..9d12c5548e6d4eafd4abdb336b6aa7208274bda3 100644 (file)
@@ -48,10 +48,10 @@ void generator_draw(entity this)
        if(time < this.move_time)
                return;
 
-       if(this.health > 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) > 0)
        {
                // damaged fx (less probable the more damaged is the generator)
-               if(random() < 0.9 - this.health / this.max_health)
+               if(random() < 0.9 - GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health)
                if(random() < 0.01)
                {
                        pointparticles(EFFECT_ELECTRO_BALLEXPLODE, this.origin + randompos('-50 -50 -20', '50 50 50'), '0 0 0', 1);
@@ -195,7 +195,7 @@ NET_HANDLE(ENT_CLIENT_GENERATOR, bool isnew)
                this.origin = ReadVector();
                setorigin(this, this.origin);
 
-               this.health = ReadByte();
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, ReadByte());
                this.max_health = ReadByte();
                this.count = ReadByte();
                this.team = ReadByte();
@@ -219,9 +219,9 @@ NET_HANDLE(ENT_CLIENT_GENERATOR, bool isnew)
 
                _tmp = ReadByte();
 
-               if(_tmp != this.health)
+               if(_tmp != GetResourceAmount(this, RESOURCE_HEALTH))
                        generator_damage(this, _tmp);
 
-               this.health = _tmp;
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, _tmp);
        }
 }
index 80d6a6be29fa146940ccf242cfddb9c90413e2ea..5deef7ec22e01351ebf2f8b3796cd4c47b39f3d6 100644 (file)
@@ -36,7 +36,7 @@ MUTATOR_HOOKFUNCTION(cl_ons, WantEventchase)
        entity gen = NULL;
        if(ons_roundlost)
        {
-               IL_EACH(g_onsgenerators, it.health <= 0,
+               IL_EACH(g_onsgenerators, GetResourceAmount(it, RESOURCE_HEALTH) <= 0,
                {
                        gen = it;
                        break;
index d3b6d5c7f4c09f62d510db50b61c79685f1847b0..a00af18ff83119b0c991312fefdf3c8f981f0ec2 100644 (file)
@@ -12,7 +12,7 @@ bool cpicon_send(entity this, entity to, int sf)
        {
                WriteVector(MSG_ENTITY, this.origin);
 
-               WriteByte(MSG_ENTITY, this.health);
+               WriteByte(MSG_ENTITY, GetResourceAmount(this, RESOURCE_HEALTH));
                WriteByte(MSG_ENTITY, this.max_health);
                WriteByte(MSG_ENTITY, this.count);
                WriteByte(MSG_ENTITY, this.team);
@@ -23,10 +23,10 @@ bool cpicon_send(entity this, entity to, int sf)
        {
                WriteByte(MSG_ENTITY, this.team);
 
-               if(this.health <= 0)
+               if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                        WriteByte(MSG_ENTITY, 0);
                else
-                       WriteByte(MSG_ENTITY, ceil((this.health / this.max_health) * 255));
+                       WriteByte(MSG_ENTITY, ceil((GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health) * 255));
        }
 
        return true;
index ac0596f2e2a5abfb3c9c21603b2a29652a31994d..a33a4301249c1dd67ef06ddf6b0dd445570d893e 100644 (file)
@@ -8,7 +8,7 @@ bool generator_send(entity this, entity to, int sf)
        {
                WriteVector(MSG_ENTITY, this.origin);
 
-               WriteByte(MSG_ENTITY, this.health);
+               WriteByte(MSG_ENTITY, GetResourceAmount(this, RESOURCE_HEALTH));
                WriteByte(MSG_ENTITY, this.max_health);
                WriteByte(MSG_ENTITY, this.count);
                WriteByte(MSG_ENTITY, this.team);
@@ -18,10 +18,10 @@ bool generator_send(entity this, entity to, int sf)
        {
                WriteByte(MSG_ENTITY, this.team);
 
-               if(this.health <= 0)
+               if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                        WriteByte(MSG_ENTITY, 0);
                else
-                       WriteByte(MSG_ENTITY, ceil((this.health / this.max_health) * 255));
+                       WriteByte(MSG_ENTITY, ceil((GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health) * 255));
        }
 
        return true;
index c4f4d32c4262f282d49a12872d9f23a7970ae313..fd050df0a0f541cc0137303fe86ad15ab26ba60b 100644 (file)
@@ -400,11 +400,11 @@ void ons_ControlPoint_Icon_Damage(entity this, entity inflictor, entity attacker
                ons_notification_time[this.team] = time;
        }
 
-       this.health = this.health - damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
        if(this.owner.iscaptured)
-               WaypointSprite_UpdateHealth(this.owner.sprite, this.health);
+               WaypointSprite_UpdateHealth(this.owner.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
        else
-               WaypointSprite_UpdateBuildFinished(this.owner.sprite, time + (this.max_health - this.health) / (this.count / ONS_CP_THINKRATE));
+               WaypointSprite_UpdateBuildFinished(this.owner.sprite, time + (this.max_health - GetResourceAmount(this, RESOURCE_HEALTH)) / (this.count / ONS_CP_THINKRATE));
        this.pain_finished = time + 1;
        // particles on every hit
        pointparticles(EFFECT_SPARKS, hitloc, force*-1, 1);
@@ -414,7 +414,7 @@ void ons_ControlPoint_Icon_Damage(entity this, entity inflictor, entity attacker
        else
                sound(this, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE+0.3, ATTEN_NORM);
 
-       if (this.health < 0)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) < 0)
        {
                sound(this, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM);
                pointparticles(EFFECT_ROCKET_EXPLODE, this.origin, '0 0 0', 1);
@@ -447,6 +447,21 @@ void ons_ControlPoint_Icon_Damage(entity this, entity inflictor, entity attacker
        this.SendFlags |= CPSF_STATUS;
 }
 
+bool ons_ControlPoint_Icon_Heal(entity targ, entity inflictor, float amount, float limit)
+{
+       float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
+       if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
+               return false;
+
+       GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+       if(targ.owner.iscaptured)
+               WaypointSprite_UpdateHealth(targ.owner.sprite, GetResourceAmount(targ, RESOURCE_HEALTH));
+       else
+               WaypointSprite_UpdateBuildFinished(targ.owner.sprite, time + (targ.max_health - GetResourceAmount(targ, RESOURCE_HEALTH)) / (targ.count / ONS_CP_THINKRATE));
+       targ.SendFlags |= CPSF_STATUS;
+       return true;
+}
+
 void ons_ControlPoint_Icon_Think(entity this)
 {
        this.nextthink = time + ONS_CP_THINKRATE;
@@ -469,9 +484,9 @@ void ons_ControlPoint_Icon_Think(entity this)
                _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);
 
-               this.health = bound(0, this.health + (_friendly_count - _enemy_count), this.max_health);
+               GiveResourceWithLimit(this, RESOURCE_HEALTH, (_friendly_count - _enemy_count), this.max_health);
                this.SendFlags |= CPSF_STATUS;
-               if(this.health <= 0)
+               if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                {
                        ons_ControlPoint_Icon_Damage(this, this, this, 1, 0, DMG_NOWEP, this.origin, '0 0 0');
                        return;
@@ -480,12 +495,10 @@ void ons_ControlPoint_Icon_Think(entity this)
 
        if (time > this.pain_finished + 5)
        {
-               if(this.health < this.max_health)
+               if(GetResourceAmount(this, RESOURCE_HEALTH) < this.max_health)
                {
-                       this.health = this.health + this.count;
-                       if (this.health >= this.max_health)
-                               this.health = this.max_health;
-                       WaypointSprite_UpdateHealth(this.owner.sprite, this.health);
+                       GiveResourceWithLimit(this, RESOURCE_HEALTH, this.count, this.max_health);
+                       WaypointSprite_UpdateHealth(this.owner.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
                }
        }
 
@@ -504,7 +517,7 @@ void ons_ControlPoint_Icon_Think(entity this)
        }
 
        // damaged fx
-       if(random() < 0.6 - this.health / this.max_health)
+       if(random() < 0.6 - GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health)
        {
                Send_Effect(EFFECT_ELECTRIC_SPARKS, this.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1);
 
@@ -526,13 +539,13 @@ void ons_ControlPoint_Icon_BuildThink(entity this)
        if(!a)
                return;
 
-       this.health = this.health + this.count;
+       GiveResource(this, RESOURCE_HEALTH, this.count);
 
        this.SendFlags |= CPSF_STATUS;
 
-       if (this.health >= this.max_health)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) >= this.max_health)
        {
-               this.health = this.max_health;
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
                this.count = autocvar_g_onslaught_cp_regen * ONS_CP_THINKRATE; // slow repair rate from now on
                setthink(this, ons_ControlPoint_Icon_Think);
                sound(this, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILT, VOL_BASE, ATTEN_NORM);
@@ -542,7 +555,7 @@ void ons_ControlPoint_Icon_BuildThink(entity this)
                Send_Effect(EFFECT_CAP(this.owner.team), this.owner.origin, '0 0 0', 1);
 
                WaypointSprite_UpdateMaxHealth(this.owner.sprite, this.max_health);
-               WaypointSprite_UpdateHealth(this.owner.sprite, this.health);
+               WaypointSprite_UpdateHealth(this.owner.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
 
                if(IS_PLAYER(this.owner.ons_toucher))
                {
@@ -565,7 +578,7 @@ void ons_ControlPoint_Icon_BuildThink(entity this)
        if(this.owner.model != MDL_ONS_CP_PAD2.model_str())
                setmodel_fixsize(this.owner, MDL_ONS_CP_PAD2);
 
-       if(random() < 0.9 - this.health / this.max_health)
+       if(random() < 0.9 - GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health)
                Send_Effect(EFFECT_RAGE, this.origin + 10 * randomvec(), '0 0 -1', 1);
 }
 
@@ -580,15 +593,16 @@ void ons_ControlPoint_Icon_Spawn(entity cp, entity player)
 
        e.owner = cp;
        e.max_health = autocvar_g_onslaught_cp_health;
-       e.health = autocvar_g_onslaught_cp_buildhealth;
+       SetResourceAmountExplicit(e, RESOURCE_HEALTH, autocvar_g_onslaught_cp_buildhealth);
        e.solid = SOLID_NOT;
        e.takedamage = DAMAGE_AIM;
        e.bot_attack = true;
        IL_PUSH(g_bot_targets, e);
        e.event_damage = ons_ControlPoint_Icon_Damage;
+       e.event_heal = ons_ControlPoint_Icon_Heal;
        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
+       e.count = (e.max_health - GetResourceAmount(e, RESOURCE_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);
 
@@ -598,7 +612,7 @@ void ons_ControlPoint_Icon_Spawn(entity cp, entity player)
 
        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_UpdateBuildFinished(cp.sprite, time + (e.max_health - GetResourceAmount(e, RESOURCE_HEALTH)) / (e.count / ONS_CP_THINKRATE));
        WaypointSprite_UpdateRule(cp.sprite,cp.team,SPRITERULE_TEAMPLAY);
        cp.sprite.SendFlags |= 16;
 
@@ -640,7 +654,7 @@ void ons_ControlPoint_UpdateSprite(entity e)
                        else
                        {
                                WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health);
-                               WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health);
+                               WaypointSprite_UpdateHealth(e.sprite, GetResourceAmount(e.goalentity, RESOURCE_HEALTH));
                        }
                }
                if(e.lastshielded)
@@ -889,14 +903,14 @@ void ons_GeneratorDamage(entity this, entity inflictor, entity attacker, float d
                        play2team(this.team, SND(ONS_GENERATOR_UNDERATTACK));
                }
        }
-       this.health = this.health - damage;
-       WaypointSprite_UpdateHealth(this.sprite, this.health);
+       TakeResource(this, RESOURCE_HEALTH, damage);
+       WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
        // choose an animation frame based on health
-       this.frame = 10 * bound(0, (1 - this.health / this.max_health), 1);
+       this.frame = 10 * bound(0, (1 - GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health), 1);
        // see if the generator is still functional, or dying
-       if (this.health > 0)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) > 0)
        {
-               this.lasthealth = this.health;
+               this.lasthealth = GetResourceAmount(this, RESOURCE_HEALTH);
        }
        else
        {
@@ -912,6 +926,7 @@ void ons_GeneratorDamage(entity this, entity inflictor, entity attacker, float d
                this.isshielded = false;
                this.takedamage = DAMAGE_NO; // can't be hurt anymore
                this.event_damage = func_null; // won't do anything if hurt
+               this.event_heal = func_null;
                this.count = 0; // reset counter
                setthink(this, func_null);
                this.nextthink = 0;
@@ -946,31 +961,46 @@ void ons_GeneratorDamage(entity this, entity inflictor, entity attacker, float d
        this.SendFlags |= GSF_STATUS;
 }
 
+bool ons_GeneratorHeal(entity targ, entity inflictor, float amount, float limit)
+{
+       float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
+       if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
+               return false;
+
+       GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+       WaypointSprite_UpdateHealth(targ.sprite, GetResourceAmount(targ, RESOURCE_HEALTH));
+       targ.frame = 10 * bound(0, (1 - GetResourceAmount(targ, RESOURCE_HEALTH) / targ.max_health), 1);
+       targ.lasthealth = GetResourceAmount(targ, RESOURCE_HEALTH);
+       targ.SendFlags |= GSF_STATUS;
+       return true;
+}
+
 void ons_GeneratorThink(entity this)
 {
        this.nextthink = time + GEN_THINKRATE;
-       if (!game_stopped)
+
+       if (game_stopped || this.isshielded || time < this.wait)
+               return;
+
+       this.wait = time + 5;
+       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it),
        {
-               if(!this.isshielded && this.wait < time)
+               if (SAME_TEAM(it, this))
                {
-                       this.wait = time + 5;
-                       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
-                               if(SAME_TEAM(it, this))
-                               {
-                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM);
-                                       soundto(MSG_ONE, it, CHAN_AUTO, SND(KH_ALARM), VOL_BASE, ATTEN_NONE);    // FIXME: unique sound?
-                               }
-                               else
-                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(this.team, CENTER_ONS_NOTSHIELDED));
-                       });
+                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM);
+                       msg_entity = it;
+                       soundto(MSG_ONE, this, CHAN_AUTO, SND(KH_ALARM), VOL_BASE, ATTEN_NONE); // FIXME: unique sound?
                }
-       }
+               else
+                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(this.team, CENTER_ONS_NOTSHIELDED));
+       });
 }
 
 void ons_GeneratorReset(entity this)
 {
        this.team = this.team_saved;
-       this.lasthealth = this.max_health = this.health = autocvar_g_onslaught_gen_health;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, autocvar_g_onslaught_gen_health);
+       this.lasthealth = this.max_health = autocvar_g_onslaught_gen_health;
        this.takedamage = DAMAGE_AIM;
        this.bot_attack = true;
        if(!IL_CONTAINS(g_bot_targets, this))
@@ -979,6 +1009,7 @@ void ons_GeneratorReset(entity this)
        this.islinked = true;
        this.isshielded = true;
        this.event_damage = ons_GeneratorDamage;
+       this.event_heal = ons_GeneratorHeal;
        setthink(this, ons_GeneratorThink);
        this.nextthink = time + GEN_THINKRATE;
 
@@ -988,7 +1019,7 @@ void ons_GeneratorReset(entity this)
        this.SendFlags |= GSF_STATUS;
 
        WaypointSprite_UpdateMaxHealth(this.sprite, this.max_health);
-       WaypointSprite_UpdateHealth(this.sprite, this.health);
+       WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
        WaypointSprite_UpdateRule(this.sprite,this.team,SPRITERULE_TEAMPLAY);
 
        onslaught_updatelinks();
@@ -1035,11 +1066,13 @@ void ons_GeneratorSetup(entity gen) // called when spawning a generator entity o
        gen.team_saved = teamnumber;
        IL_PUSH(g_saved_team, gen);
        set_movetype(gen, MOVETYPE_NONE);
-       gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health;
+       gen.lasthealth = gen.max_health = autocvar_g_onslaught_gen_health;
+       SetResourceAmountExplicit(gen, RESOURCE_HEALTH, autocvar_g_onslaught_gen_health);
        gen.takedamage = DAMAGE_AIM;
        gen.bot_attack = true;
        IL_PUSH(g_bot_targets, gen);
        gen.event_damage = ons_GeneratorDamage;
+       gen.event_heal = ons_GeneratorHeal;
        gen.reset = ons_GeneratorReset;
        setthink(gen, ons_GeneratorThink);
        gen.nextthink = time + GEN_THINKRATE;
@@ -1061,7 +1094,7 @@ void ons_GeneratorSetup(entity gen) // called when spawning a generator entity o
        WaypointSprite_SpawnFixed(WP_Null, gen.origin + CPGEN_WAYPOINT_OFFSET, gen, sprite, RADARICON_NONE);
        WaypointSprite_UpdateRule(gen.sprite, gen.team, SPRITERULE_TEAMPLAY);
        WaypointSprite_UpdateMaxHealth(gen.sprite, gen.max_health);
-       WaypointSprite_UpdateHealth(gen.sprite, gen.health);
+       WaypointSprite_UpdateHealth(gen.sprite, GetResourceAmount(gen, RESOURCE_HEALTH));
 
        InitializeEntity(gen, ons_DelayedGeneratorSetup, INITPRIO_SETLOCATION);
 }
@@ -1079,10 +1112,10 @@ void Onslaught_count_generators()
        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);
+               redowned += (e.team == NUM_TEAM_1 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
+               blueowned += (e.team == NUM_TEAM_2 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
+               yellowowned += (e.team == NUM_TEAM_3 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
+               pinkowned += (e.team == NUM_TEAM_4 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
        }
 }
 
@@ -1223,7 +1256,7 @@ void havocbot_goalrating_ons_offenseitems(entity this, float ratingscale, vector
        bool needarmor = false, needweapons = false;
 
        // Needs armor/health?
-       if(this.health<100)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
                needarmor = true;
 
        // Needs weapons?
@@ -1248,7 +1281,7 @@ void havocbot_goalrating_ons_offenseitems(entity this, float ratingscale, vector
        {
                // gather health and armor only
                if (it.solid)
-               if ( ((it.health || it.armorvalue) && needarmor) || (STAT(WEAPONS, it) && needweapons ) )
+               if ( ((GetResourceAmount(it, RESOURCE_HEALTH) || GetResourceAmount(it, RESOURCE_ARMOR)) && needarmor) || (STAT(WEAPONS, it) && needweapons ) )
                if (vdist(it.origin - org, <, sradius))
                {
                        int t = it.bot_pickupevalfunc(this, it);
@@ -1972,7 +2005,7 @@ MUTATOR_HOOKFUNCTION(ons, SV_ParseClientCommand)
                        {
                                entity source_point = ons_Nearest_ControlPoint(player, player.origin, autocvar_g_onslaught_teleport_radius);
 
-                               if ( !source_point && player.health > 0 )
+                               if ( !source_point && GetResourceAmount(player, RESOURCE_HEALTH) > 0 )
                                {
                                        sprint(player, "\nYou need to be next to a control point\n");
                                        return true;
@@ -1987,7 +2020,7 @@ MUTATOR_HOOKFUNCTION(ons, SV_ParseClientCommand)
                                        return true;
                                }
 
-                               if ( player.health <= 0 )
+                               if ( GetResourceAmount(player, RESOURCE_HEALTH) <= 0 )
                                {
                                        player.ons_spawn_by = closest_target;
                                        player.respawn_flags = player.respawn_flags | RESPAWN_FORCE;
@@ -2053,14 +2086,14 @@ MUTATOR_HOOKFUNCTION(ons, SendWaypoint)
                {
                        entity wp_owner = wp.owner;
                        entity e = WaypointSprite_getviewentity(to);
-                       if(SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { wp_flag |= 2; }
+                       if(SAME_TEAM(e, wp_owner) && GetResourceAmount(wp_owner.goalentity, RESOURCE_HEALTH) >= wp_owner.goalentity.max_health) { wp_flag |= 2; }
                        if(!ons_ControlPoint_Attackable(wp_owner, e.team)) { wp_flag |= 2; }
                }
                if(wp.owner.classname == "onslaught_generator")
                {
                        entity wp_owner = wp.owner;
-                       if(wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { wp_flag |= 2; }
-                       if(wp_owner.health <= 0) { wp_flag |= 2; }
+                       if(wp_owner.isshielded && GetResourceAmount(wp_owner, RESOURCE_HEALTH) >= wp_owner.max_health) { wp_flag |= 2; }
+                       if(GetResourceAmount(wp_owner, RESOURCE_HEALTH) <= 0) { wp_flag |= 2; }
                }
        }
 
diff --git a/qcsrc/common/gamemodes/gamemode/race/_mod.inc b/qcsrc/common/gamemodes/gamemode/race/_mod.inc
new file mode 100644 (file)
index 0000000..73f34a5
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/race/race.qc>
diff --git a/qcsrc/common/gamemodes/gamemode/race/_mod.qh b/qcsrc/common/gamemodes/gamemode/race/_mod.qh
new file mode 100644 (file)
index 0000000..1158df5
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/race/race.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/race/race.qc b/qcsrc/common/gamemodes/gamemode/race/race.qc
new file mode 100644 (file)
index 0000000..c98e1b6
--- /dev/null
@@ -0,0 +1,492 @@
+#include "race.qh"
+
+// TODO: sv_race
+#ifdef SVQC
+#include <server/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(entity this)
+{
+       if(IS_DEAD(this))
+               return;
+
+       if (navigation_goalrating_timeout(this))
+       {
+               navigation_goalrating_start(this);
+
+               bool raw_touch_check = true;
+               int cp = this.race_checkpoint;
+
+               LABEL(search_racecheckpoints)
+               IL_EACH(g_racecheckpoints, true,
+               {
+                       if(it.cnt == cp || cp == -1)
+                       {
+                               // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
+                               // e.g. checkpoint in front of Stormkeep's warpzone
+                               // the same workaround is applied in CTS game mode
+                               if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
+                               {
+                                       cp = race_NextCheckpoint(cp);
+                                       raw_touch_check = false;
+                                       goto search_racecheckpoints;
+                               }
+                               navigation_routerating(this, it, 1000000, 5000);
+                       }
+               });
+
+               navigation_goalrating_end(this);
+
+               navigation_goalrating_timeout_set(this);
+       }
+}
+
+void race_ScoreRules()
+{
+    GameRules_score_enabled(false);
+       GameRules_scoring(race_teams, 0, 0, {
+        if (race_teams) {
+            field_team(ST_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+            field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+            field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+            field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
+        } else if (g_race_qualifying) {
+            field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+        } else {
+            field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+            field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+            field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
+        }
+       });
+}
+
+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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+float WinningCondition_Race(float fraglimit)
+{
+       float wc;
+       float n, c;
+
+       n = 0;
+       c = 0;
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               ++n;
+               if(CS(it).race_completed)
+                       ++c;
+       });
+       if(n && (n == c))
+               return WINNING_YES;
+       wc = WinningCondition_Scores(fraglimit, 0);
+
+       // ALWAYS initiate overtime, unless EVERYONE has finished the race!
+       if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
+       // do NOT support equality when the laps are all raced!
+               return WINNING_STARTSUDDENDEATHOVERTIME;
+       else
+               return WINNING_NEVER;
+}
+
+float WinningCondition_QualifyingThenRace(float limit)
+{
+       float wc;
+       wc = WinningCondition_Scores(limit, 0);
+
+       // NEVER initiate overtime
+       if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
+       {
+               return WINNING_YES;
+       }
+
+       return wc;
+}
+
+MUTATOR_HOOKFUNCTION(rc, ClientKill)
+{
+       if(g_race_qualifying)
+               M_ARGV(1, float) = 0; // killtime
+}
+
+MUTATOR_HOOKFUNCTION(rc, AbortSpeedrun)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(autocvar_g_allow_checkpoints)
+               race_PreparePlayer(player); // nice try
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerPhysics)
+{
+       entity player = M_ARGV(0, entity);
+       float dt = M_ARGV(1, float);
+
+       player.race_movetime_frac += dt;
+       float f = floor(player.race_movetime_frac);
+       player.race_movetime_frac -= f;
+       player.race_movetime_count += f;
+       player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
+
+#ifdef SVQC
+       if(IS_PLAYER(player))
+       {
+               if (player.race_penalty)
+                       if (time > player.race_penalty)
+                               player.race_penalty = 0;
+               if(player.race_penalty)
+               {
+                       player.velocity = '0 0 0';
+                       set_movetype(player, MOVETYPE_NONE);
+                       player.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(CS(player).movement.x);
+       wishvel.y = fabs(CS(player).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(CS(player).movement.x > 0)
+                               CS(player).movement_x = wishspeed;
+                       else
+                               CS(player).movement_x = -wishspeed;
+                       CS(player).movement_y = 0;
+               }
+               else if(wishvel.y >= 2 * wishvel.x)
+               {
+                       // pure Y motion
+                       CS(player).movement_x = 0;
+                       if(CS(player).movement.y > 0)
+                               CS(player).movement_y = wishspeed;
+                       else
+                               CS(player).movement_y = -wishspeed;
+               }
+               else
+               {
+                       // diagonal
+                       if(CS(player).movement.x > 0)
+                               CS(player).movement_x = M_SQRT1_2 * wishspeed;
+                       else
+                               CS(player).movement_x = -M_SQRT1_2 * wishspeed;
+                       if(CS(player).movement.y > 0)
+                               CS(player).movement_y = M_SQRT1_2 * wishspeed;
+                       else
+                               CS(player).movement_y = -M_SQRT1_2 * wishspeed;
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(rc, reset_map_global)
+{
+       float s;
+
+       Score_NicePrint(NULL);
+
+       race_ClearRecords();
+       PlayerScore_Sort(race_place, 0, 1, 0);
+
+       FOREACH_CLIENT(true, {
+               if(it.race_place)
+               {
+                       s = GameRules_scoring_add(it, RACE_FASTEST, 0);
+                       if(!s)
+                               it.race_place = 0;
+               }
+               race_EventLog(ftos(it.race_place), it);
+       });
+
+       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();
+       }
+}
+
+MUTATOR_HOOKFUNCTION(rc, ClientConnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       race_PreparePlayer(player);
+       player.race_checkpoint = -1;
+
+       string rr = RACE_RECORD;
+
+       if(IS_REAL_CLIENT(player))
+       {
+               msg_entity = player;
+               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;
+               int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+               race_send_rankings_cnt(MSG_ONE);
+               for (i = 1; i <= m; ++i)
+               {
+                       race_SendRankings(i, 0, 0, MSG_ONE);
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(g_race_qualifying)
+       if(GameRules_scoring_add(player, RACE_FASTEST, 0))
+               player.frags = FRAGS_LMS_LOSER;
+       else
+               player.frags = FRAGS_SPECTATOR;
+
+       race_PreparePlayer(player);
+       player.race_checkpoint = -1;
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+       entity spawn_spot = M_ARGV(1, entity);
+
+       if(spawn_spot.target == "")
+               // Emergency: this wasn't a real spawnpoint. Can this ever happen?
+               race_PreparePlayer(player);
+
+       // if we need to respawn, do it right
+       player.race_respawn_checkpoint = player.race_checkpoint;
+       player.race_respawn_spotref = spawn_spot;
+
+       player.race_place = 0;
+}
+
+MUTATOR_HOOKFUNCTION(rc, PutClientInServer)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(IS_PLAYER(player))
+       if(!game_stopped)
+       {
+               if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
+                       race_PreparePlayer(player);
+               else // respawn
+                       race_RetractPlayer(player);
+
+               race_AbandonRaceCheck(player);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerDies)
+{
+       entity frag_target = M_ARGV(2, entity);
+
+       frag_target.respawn_flags |= RESPAWN_FORCE;
+       race_AbandonRaceCheck(frag_target);
+}
+
+MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole)
+{
+       entity bot = M_ARGV(0, entity);
+
+       bot.havocbot_role = havocbot_role_race;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(rc, GetPressedKeys)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
+       {
+               if (!player.stored_netname)
+                       player.stored_netname = strzone(uid2name(player.crypto_idfp));
+               if(player.stored_netname != player.netname)
+               {
+                       db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
+                       strcpy(player.stored_netname, player.netname);
+               }
+       }
+
+       if (!IS_OBSERVER(player))
+       {
+               if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
+               {
+                       speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
+                       speedaward_holder = player.netname;
+                       speedaward_uid = player.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);
+                       }
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear)
+{
+       if(g_race_qualifying)
+               return true; // in qualifying, you don't lose score by observing
+}
+
+MUTATOR_HOOKFUNCTION(rc, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       M_ARGV(0, float) = race_teams;
+}
+
+MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
+{
+       // announce remaining frags if not in qualifying mode
+       if(!g_race_qualifying)
+               return true;
+}
+
+MUTATOR_HOOKFUNCTION(rc, GetRecords)
+{
+       int record_page = M_ARGV(0, int);
+       string ret_string = M_ARGV(1, string);
+
+       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");
+               }
+       }
+
+       M_ARGV(1, string) = ret_string;
+}
+
+MUTATOR_HOOKFUNCTION(rc, HideTeamNagger)
+{
+       return true; // doesn't work so well
+}
+
+MUTATOR_HOOKFUNCTION(rc, FixClientCvars)
+{
+       entity player = M_ARGV(0, entity);
+
+       stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
+}
+
+MUTATOR_HOOKFUNCTION(rc, CheckRules_World)
+{
+       float checkrules_timelimit = M_ARGV(1, float);
+       float checkrules_fraglimit = M_ARGV(2, float);
+
+       if(checkrules_timelimit >= 0)
+       {
+               if(!g_race_qualifying)
+               {
+                       M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit);
+                       return true;
+               }
+               else if(g_race_qualifying == 2)
+               {
+                       M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
+                       return true;
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
+{
+       if(g_race_qualifying == 2)
+               warmup_stage = 0;
+}
+
+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)
+       {
+               GameRules_teams(true);
+               race_teams = BITS(bound(2, autocvar_g_race_teams, 4));
+       }
+       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 = autocvar_timelimit_override;
+
+       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(want_qualifying)
+       {
+               g_race_qualifying = 2;
+               independent_players = 1;
+               race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit;
+               race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit;
+               race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit;
+               qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit;
+               fraglimit_override = 0;
+               leadlimit_override = 0;
+               timelimit_override = qualifying_override;
+       }
+       else
+               g_race_qualifying = 0;
+    GameRules_limit_score(fraglimit_override);
+    GameRules_limit_lead(leadlimit_override);
+    GameRules_limit_time(timelimit_override);
+    GameRules_limit_time_qualifying(qualifying_override);
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/race/race.qh b/qcsrc/common/gamemodes/gamemode/race/race.qh
new file mode 100644 (file)
index 0000000..ad966af
--- /dev/null
@@ -0,0 +1,19 @@
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+void rc_SetLimits();
+void race_Initialize();
+
+REGISTER_MUTATOR(rc, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               rc_SetLimits();
+
+               race_Initialize();
+       }
+       return 0;
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/tdm/_mod.inc b/qcsrc/common/gamemodes/gamemode/tdm/_mod.inc
new file mode 100644 (file)
index 0000000..ef7137a
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/tdm/tdm.qc>
diff --git a/qcsrc/common/gamemodes/gamemode/tdm/_mod.qh b/qcsrc/common/gamemodes/gamemode/tdm/_mod.qh
new file mode 100644 (file)
index 0000000..f1965c1
--- /dev/null
@@ -0,0 +1,2 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/tdm/tdm.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/tdm/tdm.qc b/qcsrc/common/gamemodes/gamemode/tdm/tdm.qc
new file mode 100644 (file)
index 0000000..39e5fec
--- /dev/null
@@ -0,0 +1,67 @@
+#include "tdm.qh"
+
+// TODO: sv_tdm
+// TODO? rename to teamdeathmatch
+#ifdef SVQC
+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 || !this.cnt) { delete(this); return; }
+
+       this.classname = "tdm_team";
+       this.team = this.cnt + 1;
+}
+
+// code from here on is just to support maps that don't have team entities
+void tdm_SpawnTeam (string teamname, int teamcolor)
+{
+       entity this = new_pure(tdm_team);
+       this.netname = teamname;
+       this.cnt = teamcolor - 1;
+       this.team = teamcolor;
+       this.spawnfunc_checked = true;
+       //spawnfunc_tdm_team(this);
+}
+
+void tdm_DelayedInit(entity this)
+{
+       // if no teams are found, spawn defaults
+       if(find(NULL, classname, "tdm_team") == NULL)
+       {
+               LOG_TRACE("No \"tdm_team\" entities found on this map, creating them anyway.");
+
+               int numteams = autocvar_g_tdm_teams_override;
+               if(numteams < 2) { numteams = autocvar_g_tdm_teams; }
+
+               int teams = BITS(bound(2, numteams, 4));
+               if(teams & BIT(0))
+                       tdm_SpawnTeam("Red", NUM_TEAM_1);
+               if(teams & BIT(1))
+                       tdm_SpawnTeam("Blue", NUM_TEAM_2);
+               if(teams & BIT(2))
+                       tdm_SpawnTeam("Yellow", NUM_TEAM_3);
+               if(teams & BIT(3))
+                       tdm_SpawnTeam("Pink", NUM_TEAM_4);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(tdm, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       M_ARGV(1, string) = "tdm_team";
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining)
+{
+       // announce remaining frags
+       return true;
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/tdm/tdm.qh b/qcsrc/common/gamemodes/gamemode/tdm/tdm.qh
new file mode 100644 (file)
index 0000000..1c8674a
--- /dev/null
@@ -0,0 +1,24 @@
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+int autocvar_g_tdm_point_limit;
+int autocvar_g_tdm_point_leadlimit;
+bool autocvar_g_tdm_team_spawns;
+void tdm_DelayedInit(entity this);
+
+REGISTER_MUTATOR(tdm, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               GameRules_teams(true);
+        GameRules_spawning_teams(autocvar_g_tdm_team_spawns);
+               GameRules_limit_score(autocvar_g_tdm_point_limit);
+        GameRules_limit_lead(autocvar_g_tdm_point_leadlimit);
+
+               InitializeEntity(NULL, tdm_DelayedInit, INITPRIO_GAMETYPE);
+       }
+       return 0;
+}
+#endif
index 35a643d53b0eac3dde0e9d317f63667c0c7e5687..979477cbafab6c676c4fdc7cabfeedf43134725b 100644 (file)
@@ -1,5 +1,9 @@
 #pragma once
 
+// TODO: find a better location for these?
+float total_players;
+float redalive, bluealive, yellowalive, pinkalive;
+
 // todo: accept the number of teams as a parameter
 void GameRules_teams(bool value);
 
index 8520075019b97c616107cabf94420ef9a27d58d6..9075c0912ed2a5044ba8e7b80b299a05723f31ba 100644 (file)
@@ -21,9 +21,11 @@ const int Inventory_groups_minor = 8; // ceil(Items_MAX / Inventory_groups_major
 #define G_MINOR(id) ((id) % Inventory_groups_minor)
 
 #ifdef CSQC
+Inventory g_inventory;
 NET_HANDLE(ENT_CLIENT_INVENTORY, bool isnew)
 {
     make_pure(this);
+    g_inventory = this;
     const int majorBits = ReadShort();
     for (int i = 0; i < Inventory_groups_major; ++i) {
         if (!(majorBits & BIT(i))) {
index e59152076b754552eb266411710fdbab1e4efcbb..3109e7c92f93c66adc8c0f5d274bef901bf019e3 100644 (file)
@@ -72,9 +72,8 @@ const int IT_PICKUPMASK                       = IT_UNLIMITED_AMMO | IT_JETPACK | IT_FU
 enum
 {
        ITEM_FLAG_NORMAL = BIT(0), ///< Item is usable during normal gameplay.
-       ITEM_FLAG_INSTAGIB = BIT(1), ///< Item is usable in instagib.
-       ITEM_FLAG_OVERKILL = BIT(2), ///< Item is usable in overkill.
-       ITEM_FLAG_MUTATORBLOCKED = BIT(3)
+       ITEM_FLAG_MUTATORBLOCKED = BIT(1),
+    ITEM_FLAG_RESOURCE = BIT(2) ///< Item is is a resource, not a held item.
 };
 
 #define ITEM_HANDLE(signal, ...) __Item_Send_##signal(__VA_ARGS__)
index 1d5bd87baceb116b7e241f9955e4e12b8f6d3ee9..f59bcad22403a65fd5ae46d818284cd18f46015b 100644 (file)
@@ -3,8 +3,10 @@
 #include "pickup.qh"
 #ifdef SVQC
     #include <common/t_items.qh>
+    #include <server/resources.qh>
 #endif
 
+#if 1
 .int ammo_none;
 .int ammo_shells;
 .int ammo_nails;
@@ -17,6 +19,7 @@
 .int ammo_plasma;
 .int ammo_fuel;
 #endif
+#endif
 
 #ifdef SVQC
 PROPERTY(float, g_pickup_ammo_anyway);
@@ -38,10 +41,10 @@ MODEL(Bullets_ITEM, Item_Model("a_bullets.mdl"));
 
 #ifdef SVQC
 PROPERTY(int, g_pickup_nails);
-void ammo_bullets_init(entity item)
+void ammo_bullets_init(Pickup this, entity item)
 {
-    if(!item.ammo_nails)
-        item.ammo_nails = g_pickup_nails;
+    if(!GetResourceAmount(item, RESOURCE_BULLETS))
+        SetResourceAmountExplicit(item, RESOURCE_BULLETS, g_pickup_nails);
 }
 #endif
 
@@ -51,7 +54,7 @@ ENDCLASS(Bullets)
 REGISTER_ITEM(Bullets, Bullets) {
     this.m_canonical_spawnfunc = "item_bullets";
 #ifdef GAMEQC
-       this.spawnflags = ITEM_FLAG_NORMAL;
+       this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
     this.m_model    =   MDL_Bullets_ITEM;
 #endif
     this.netname    =   "bullets";
@@ -72,16 +75,16 @@ MODEL(Cells_ITEM, Item_Model("a_cells.md3"));
 
 #ifdef SVQC
 PROPERTY(int, g_pickup_cells);
-void ammo_cells_init(entity item)
+void ammo_cells_init(Pickup this, entity item)
 {
-    if(!item.ammo_cells)
-        item.ammo_cells = g_pickup_cells;
+    if(!GetResourceAmount(item, RESOURCE_CELLS))
+        SetResourceAmountExplicit(item, RESOURCE_CELLS, g_pickup_cells);
 }
 #endif
 REGISTER_ITEM(Cells, Ammo) {
     this.m_canonical_spawnfunc = "item_cells";
 #ifdef GAMEQC
-       this.spawnflags = ITEM_FLAG_NORMAL;
+       this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
     this.m_model    =   MDL_Cells_ITEM;
 #endif
     this.netname    =   "cells";
@@ -102,16 +105,16 @@ MODEL(Plasma_ITEM, Item_Model("a_cells.md3"));
 
 #ifdef SVQC
 PROPERTY(int, g_pickup_plasma);
-void ammo_plasma_init(entity item)
+void ammo_plasma_init(Pickup this, entity item)
 {
-    if(!item.ammo_plasma)
-        item.ammo_plasma = g_pickup_plasma;
+    if(!GetResourceAmount(item, RESOURCE_PLASMA))
+        SetResourceAmountExplicit(item, RESOURCE_PLASMA, g_pickup_plasma);
 }
 #endif
 REGISTER_ITEM(Plasma, Ammo) {
     this.m_canonical_spawnfunc = "item_plasma";
 #ifdef GAMEQC
-       this.spawnflags = ITEM_FLAG_NORMAL;
+       this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
     this.m_model    =   MDL_Plasma_ITEM;
 #endif
     this.netname    =   "plasma";
@@ -132,16 +135,16 @@ MODEL(Rockets_ITEM, Item_Model("a_rockets.md3"));
 
 #ifdef SVQC
 PROPERTY(int, g_pickup_rockets);
-void ammo_rockets_init(entity item)
+void ammo_rockets_init(Pickup this, entity item)
 {
-    if(!item.ammo_rockets)
-        item.ammo_rockets = g_pickup_rockets;
+    if(!GetResourceAmount(item, RESOURCE_ROCKETS))
+        SetResourceAmountExplicit(item, RESOURCE_ROCKETS, g_pickup_rockets);
 }
 #endif
 REGISTER_ITEM(Rockets, Ammo) {
     this.m_canonical_spawnfunc = "item_rockets";
 #ifdef GAMEQC
-       this.spawnflags = ITEM_FLAG_NORMAL;
+       this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
     this.m_model    =   MDL_Rockets_ITEM;
 #endif
     this.netname    =   "rockets";
@@ -162,10 +165,10 @@ MODEL(Shells_ITEM, Item_Model("a_shells.md3"));
 
 #ifdef SVQC
 PROPERTY(int, g_pickup_shells);
-void ammo_shells_init(entity item)
+void ammo_shells_init(Pickup this, entity item)
 {
-    if(!item.ammo_shells)
-        item.ammo_shells = g_pickup_shells;
+    if(!GetResourceAmount(item, RESOURCE_SHELLS))
+        SetResourceAmountExplicit(item, RESOURCE_SHELLS, g_pickup_shells);
 }
 #endif
 
@@ -175,7 +178,7 @@ ENDCLASS(Shells)
 REGISTER_ITEM(Shells, Shells) {
     this.m_canonical_spawnfunc = "item_shells";
 #ifdef GAMEQC
-       this.spawnflags = ITEM_FLAG_NORMAL;
+       this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
     this.m_model    =   MDL_Shells_ITEM;
 #endif
     this.netname    =   "shells";
index 7f37c75aec002465260b1852810253e05c6a4b11..ee39aa59242111771347d38005fe0d3c89113e4c 100644 (file)
@@ -22,19 +22,19 @@ SOUND(ArmorSmall, Item_Sound("armor1"));
 PROPERTY(float, g_pickup_armorsmall_anyway);
 PROPERTY(int, g_pickup_armorsmall);
 PROPERTY(int, g_pickup_armorsmall_max);
-void item_armorsmall_init(entity item)
+void item_armorsmall_init(Pickup this, entity item)
 {
     if(!item.max_armorvalue)
         item.max_armorvalue = g_pickup_armorsmall_max;
-    if(!item.armorvalue)
-        item.armorvalue = g_pickup_armorsmall;
+    if(!GetResourceAmount(item, RESOURCE_ARMOR))
+        SetResourceAmountExplicit(item, RESOURCE_ARMOR, g_pickup_armorsmall);
 }
 #endif
 
 REGISTER_ITEM(ArmorSmall, Armor) {
     this.m_canonical_spawnfunc = "item_armor_small";
 #ifdef GAMEQC
-    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
+    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
     this.m_model                =   MDL_ArmorSmall_ITEM;
     this.m_sound                =   SND_ArmorSmall;
 #endif
@@ -60,19 +60,19 @@ SOUND(ArmorMedium, Item_Sound("armor10"));
 PROPERTY(float, g_pickup_armormedium_anyway);
 PROPERTY(int, g_pickup_armormedium);
 PROPERTY(int, g_pickup_armormedium_max);
-void item_armormedium_init(entity item)
+void item_armormedium_init(Pickup this, entity item)
 {
     if(!item.max_armorvalue)
         item.max_armorvalue = g_pickup_armormedium_max;
-    if(!item.armorvalue)
-        item.armorvalue = g_pickup_armormedium;
+    if(!GetResourceAmount(item, RESOURCE_ARMOR))
+        SetResourceAmountExplicit(item, RESOURCE_ARMOR, g_pickup_armormedium);
 }
 #endif
 
 REGISTER_ITEM(ArmorMedium, Armor) {
     this.m_canonical_spawnfunc = "item_armor_medium";
 #ifdef GAMEQC
-    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
+    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
     this.m_model                =   MDL_ArmorMedium_ITEM;
     this.m_sound                =   SND_ArmorMedium;
 #endif
@@ -98,19 +98,19 @@ SOUND(ArmorBig, Item_Sound("armor17_5"));
 PROPERTY(float, g_pickup_armorbig_anyway);
 PROPERTY(int, g_pickup_armorbig);
 PROPERTY(int, g_pickup_armorbig_max);
-void item_armorbig_init(entity item)
+void item_armorbig_init(Pickup this, entity item)
 {
     if(!item.max_armorvalue)
         item.max_armorvalue = g_pickup_armorbig_max;
-    if(!item.armorvalue)
-        item.armorvalue = g_pickup_armorbig;
+    if(!GetResourceAmount(item, RESOURCE_ARMOR))
+        SetResourceAmountExplicit(item, RESOURCE_ARMOR, g_pickup_armorbig);
 }
 #endif
 
 REGISTER_ITEM(ArmorBig, Armor) {
     this.m_canonical_spawnfunc = "item_armor_big";
 #ifdef GAMEQC
-    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
+    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
     this.m_model                =   MDL_ArmorBig_ITEM;
     this.m_sound                =   SND_ArmorBig;
 #endif
@@ -138,19 +138,19 @@ SOUND(ArmorMega, Item_Sound("armor25"));
 PROPERTY(float, g_pickup_armormega_anyway);
 PROPERTY(int, g_pickup_armormega);
 PROPERTY(int, g_pickup_armormega_max);
-void item_armormega_init(entity item)
+void item_armormega_init(Pickup this, entity item)
 {
     if(!item.max_armorvalue)
         item.max_armorvalue = g_pickup_armormega_max;
-    if(!item.armorvalue)
-        item.armorvalue = g_pickup_armormega;
+    if(!GetResourceAmount(item, RESOURCE_ARMOR))
+        SetResourceAmountExplicit(item, RESOURCE_ARMOR, g_pickup_armormega);
 }
 #endif
 
 REGISTER_ITEM(ArmorMega, Armor) {
     this.m_canonical_spawnfunc = "item_armor_mega";
 #ifdef GAMEQC
-    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
+    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
     this.m_model                =   MDL_ArmorMega_ITEM;
     this.m_sound                =   SND_ArmorMega;
 #endif
index da431086e18587448cf697c8127390fd166134f0..bf515fe4dd7f7c167d31c7f85b6eb3bc9e409fb3 100644 (file)
@@ -22,19 +22,19 @@ SOUND(HealthSmall, Item_Sound("minihealth"));
 PROPERTY(float, g_pickup_healthsmall_anyway);
 PROPERTY(int, g_pickup_healthsmall);
 PROPERTY(int, g_pickup_healthsmall_max);
-void item_healthsmall_init(entity item)
+void item_healthsmall_init(Pickup this, entity item)
 {
     if(!item.max_health)
         item.max_health = g_pickup_healthsmall_max;
-    if(!item.health)
-        item.health = g_pickup_healthsmall;
+    if(!GetResourceAmount(item, RESOURCE_HEALTH))
+        SetResourceAmountExplicit(item, RESOURCE_HEALTH, g_pickup_healthsmall);
 }
 #endif
 
 REGISTER_ITEM(HealthSmall, Health) {
     this.m_canonical_spawnfunc = "item_health_small";
 #ifdef GAMEQC
-       this.spawnflags = ITEM_FLAG_NORMAL;
+       this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
     this.m_model                =   MDL_HealthSmall_ITEM;
     this.m_sound                =   SND_HealthSmall;
 #endif
@@ -60,19 +60,19 @@ SOUND(HealthMedium, Item_Sound("mediumhealth"));
 PROPERTY(float, g_pickup_healthmedium_anyway);
 PROPERTY(int, g_pickup_healthmedium);
 PROPERTY(int, g_pickup_healthmedium_max);
-void item_healthmedium_init(entity item)
+void item_healthmedium_init(Pickup this, entity item)
 {
     if(!item.max_health)
         item.max_health = g_pickup_healthmedium_max;
-    if(!item.health)
-        item.health = g_pickup_healthmedium;
+    if(!GetResourceAmount(item, RESOURCE_HEALTH))
+        SetResourceAmountExplicit(item, RESOURCE_HEALTH, g_pickup_healthmedium);
 }
 #endif
 
 REGISTER_ITEM(HealthMedium, Health) {
     this.m_canonical_spawnfunc = "item_health_medium";
 #ifdef GAMEQC
-       this.spawnflags = ITEM_FLAG_NORMAL;
+       this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
     this.m_model                =   MDL_HealthMedium_ITEM;
     this.m_sound                =   SND_HealthMedium;
 #endif
@@ -98,19 +98,19 @@ SOUND(HealthBig, Item_Sound("mediumhealth"));
 PROPERTY(float, g_pickup_healthbig_anyway);
 PROPERTY(int, g_pickup_healthbig);
 PROPERTY(int, g_pickup_healthbig_max);
-void item_healthbig_init(entity item)
+void item_healthbig_init(Pickup this, entity item)
 {
     if(!item.max_health)
         item.max_health = g_pickup_healthbig_max;
-    if(!item.health)
-        item.health = g_pickup_healthbig;
+    if(!GetResourceAmount(item, RESOURCE_HEALTH))
+        SetResourceAmountExplicit(item, RESOURCE_HEALTH, g_pickup_healthbig);
 }
 #endif
 
 REGISTER_ITEM(HealthBig, Health) {
     this.m_canonical_spawnfunc = "item_health_big";
 #ifdef GAMEQC
-       this.spawnflags = ITEM_FLAG_NORMAL;
+       this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
     this.m_model                =   MDL_HealthBig_ITEM;
     this.m_sound                =   SND_HealthBig;
 #endif
@@ -138,19 +138,19 @@ SOUND(HealthMega, Item_Sound("megahealth"));
 PROPERTY(float, g_pickup_healthmega_anyway);
 PROPERTY(int, g_pickup_healthmega);
 PROPERTY(int, g_pickup_healthmega_max);
-void item_healthmega_init(entity item)
+void item_healthmega_init(Pickup this, entity item)
 {
     if(!item.max_health)
         item.max_health = g_pickup_healthmega_max;
-    if(!item.health)
-        item.health = g_pickup_healthmega;
+    if(!GetResourceAmount(item, RESOURCE_HEALTH))
+        SetResourceAmountExplicit(item, RESOURCE_HEALTH, g_pickup_healthmega);
 }
 #endif
 
 REGISTER_ITEM(HealthMega, Health) {
     this.m_canonical_spawnfunc = "item_health_mega";
 #ifdef GAMEQC
-    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
+    this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
     this.m_model                =   MDL_HealthMega_ITEM;
     this.m_sound                =   SND_HealthMega;
 #endif
index 284bf3d390fce1c7b62653a9570b9ef20a7dffe5..760033861a7db8377342bc6ff7dbd6bef1901b4b 100644 (file)
@@ -17,10 +17,10 @@ MODEL(Jetpack_ITEM, Item_Model("g_jetpack.md3"));
 
 #ifdef SVQC
 PROPERTY(int, g_pickup_fuel_jetpack);
-void powerup_jetpack_init(entity item)
+void powerup_jetpack_init(Pickup this, entity item)
 {
-    if(!item.ammo_fuel)
-        item.ammo_fuel = g_pickup_fuel_jetpack;
+    if(!GetResourceAmount(item, RESOURCE_FUEL))
+        SetResourceAmountExplicit(item, RESOURCE_FUEL, g_pickup_fuel_jetpack);
 }
 #endif
 
@@ -35,10 +35,10 @@ REGISTER_ITEM(Jetpack, Powerup) {
     this.m_itemid               =   IT_JETPACK;
 #endif
     this.netname                =   "jetpack";
-    this.m_name                 =   "Jet pack";
+    this.m_name                 =   "Jetpack";
     this.m_icon                 =   "jetpack";
     this.m_color                =   '0.5 0.5 0.5';
-    this.m_waypoint             =   _("Jet Pack");
+    this.m_waypoint             =   _("Jetpack");
     this.m_waypointblink        =   2;
 #ifdef SVQC
     this.m_botvalue             =   3000;
@@ -55,16 +55,16 @@ MODEL(JetpackFuel_ITEM, Item_Model("g_fuel.md3"));
 
 #ifdef SVQC
 PROPERTY(int, g_pickup_fuel);
-void ammo_fuel_init(entity item)
+void ammo_fuel_init(Pickup this, entity item)
 {
-    if(!item.ammo_fuel)
-        item.ammo_fuel = g_pickup_fuel;
+    if(!GetResourceAmount(item, RESOURCE_FUEL))
+        SetResourceAmountExplicit(item, RESOURCE_FUEL, g_pickup_fuel);
 }
 #endif
 REGISTER_ITEM(JetpackFuel, Ammo) {
     this.m_canonical_spawnfunc = "item_fuel";
 #ifdef GAMEQC
-       this.spawnflags = ITEM_FLAG_NORMAL;
+       this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
     this.m_model    =   MDL_JetpackFuel_ITEM;
 #endif
     this.netname    =   "fuel";
index fb4bc28cd8ede336f7d6d656ca25edbdd4972e42..0f09901af214c9c24bd78023b163b365400bd8d2 100644 (file)
@@ -42,7 +42,7 @@ CLASS(Pickup, GameItem)
     ATTRIB(Pickup, m_respawntime, float());
     ATTRIB(Pickup, m_respawntimejitter, float());
     ATTRIB(Pickup, m_pickupanyway, float());
-    ATTRIB(Pickup, m_iteminit, void(entity item));
+    ATTRIB(Pickup, m_iteminit, void(Pickup this, entity item));
     float Item_GiveTo(entity item, entity player);
     METHOD(Pickup, giveTo, bool(Pickup this, entity item, entity player));
     bool ITEM_HANDLE(Pickup, Pickup this, entity item, entity player);
index fe47b63430ddd1726b774f080f56905a4dc7568c..1c10afa488a581065888af5fa7a7324e82ca2eb6 100644 (file)
@@ -24,7 +24,7 @@ SOUND(Strength, Item_Sound("powerup"));
 
 #ifdef SVQC
 float autocvar_g_balance_powerup_strength_time;
-void powerup_strength_init(entity item)
+void powerup_strength_init(Pickup this, entity item)
 {
     if(!item.strength_finished)
         item.strength_finished = autocvar_g_balance_powerup_strength_time;
@@ -60,7 +60,7 @@ SOUND(Shield, Item_Sound("powerup_shield"));
 
 #ifdef SVQC
 float autocvar_g_balance_powerup_invincible_time;
-void powerup_shield_init(entity item)
+void powerup_shield_init(Pickup this, entity item)
 {
     if(!item.invincible_finished)
         item.invincible_finished = autocvar_g_balance_powerup_invincible_time;
index 2dd84596e46991e8f3f75daeb63b524273825d01..4addd24001f6d32a594424a2761855c222f3b300 100644 (file)
@@ -96,7 +96,7 @@ REGISTRY_CHECK(Gametypes)
 CLASS(Deathmatch, Gametype)
     INIT(Deathmatch)
     {
-        this.gametype_init(this, _("Deathmatch"),"dm","g_dm",false,"","timelimit=20 pointlimit=30 leadlimit=0",_("Score as many frags as you can"));
+        this.gametype_init(this, _("Deathmatch"),"dm","g_dm",false,"","timelimit=15 pointlimit=30 leadlimit=0",_("Score as many frags as you can"));
     }
     METHOD(Deathmatch, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
     {
@@ -181,7 +181,7 @@ REGISTER_GAMETYPE(CTS, NEW(RaceCTS));
 CLASS(TeamDeathmatch, Gametype)
     INIT(TeamDeathmatch)
     {
-        this.gametype_init(this, _("Team Deathmatch"),"tdm","g_tdm",true,"","timelimit=20 pointlimit=50 teams=2 leadlimit=0",_("Help your team score the most frags against the enemy team"));
+        this.gametype_init(this, _("Team Deathmatch"),"tdm","g_tdm",true,"","timelimit=15 pointlimit=50 teams=2 leadlimit=0",_("Help your team score the most frags against the enemy team"));
     }
     METHOD(TeamDeathmatch, m_parse_mapinfo, bool(string k, string v))
     {
index d09ccd5e00a8cede8584138723c607d37637ea9c..cb17ac442cb4244aa71b708e6c7c27deaf220bb4 100644 (file)
@@ -1,7 +1,6 @@
 #include "breakable.qh"
 #ifdef SVQC
 
-#include <server/g_subs.qh>
 #include <server/g_damage.qh>
 #include <server/bot/api.qh>
 #include <common/csqcmodel_settings.qh>
@@ -84,7 +83,7 @@ void func_breakable_colormod(entity this)
        float h;
        if (!(this.spawnflags & BREAKABLE_INDICATE_DAMAGE))
                return;
-       h = this.health / this.max_health;
+       h = GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health;
        if(h < 0.25)
                this.colormod = '1 0 0';
        else if(h <= 0.75)
@@ -130,7 +129,7 @@ void func_breakable_look_restore(entity this)
 
 void func_breakable_behave_destroyed(entity this)
 {
-       this.health = this.max_health;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
        this.takedamage = DAMAGE_NO;
        if(this.bot_attack)
                IL_REMOVE(g_bot_targets, this);
@@ -142,6 +141,11 @@ void func_breakable_behave_destroyed(entity this)
        func_breakable_colormod(this);
        if (this.noise1)
                stopsound (this, CH_TRIGGER_SINGLE);
+
+       IL_EACH(g_projectiles, it.classname == "grapplinghook" && it.aiment == this,
+       {
+               RemoveHook(it);
+       });
 }
 
 void func_breakable_think(entity this)
@@ -153,11 +157,11 @@ void func_breakable_think(entity this)
 void func_breakable_destroy(entity this, entity actor, entity trigger);
 void func_breakable_behave_restore(entity this)
 {
-       this.health = this.max_health;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
        if(this.sprite)
        {
                WaypointSprite_UpdateMaxHealth(this.sprite, this.max_health);
-               WaypointSprite_UpdateHealth(this.sprite, this.health);
+               WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
        }
        if(!(this.spawnflags & BREAKABLE_NODAMAGE))
        {
@@ -201,6 +205,16 @@ void func_breakable_restore(entity this, entity actor, entity trigger)
 
 void func_breakable_restore_self(entity this)
 {
+       // TODO: use a clipgroup for all func_breakables so they don't collide with eachother
+       float oldhit = this.dphitcontentsmask;
+       this.dphitcontentsmask = DPCONTENTS_BODY; // we really only care about when players are standing inside, obey the mapper in other cases!
+       tracebox(this.origin, this.mins, this.maxs, this.origin, MOVE_NORMAL, this);
+       this.dphitcontentsmask = oldhit;
+       if(trace_startsolid || trace_fraction < 1)
+       {
+               this.nextthink = time + 5; // retry every 5 seconds until the area becomes clear
+               return;
+       }
        func_breakable_restore(this, NULL, NULL);
 }
 
@@ -258,15 +272,15 @@ void func_breakable_damage(entity this, entity inflictor, entity attacker, float
                if(attacker.team == this.team)
                        return;
        this.pain_finished = time;
-       this.health = this.health - damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
        if(this.sprite)
        {
                WaypointSprite_Ping(this.sprite);
-               WaypointSprite_UpdateHealth(this.sprite, this.health);
+               WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
        }
        func_breakable_colormod(this);
 
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
        {
                debrisforce = force;
 
@@ -301,9 +315,9 @@ void func_breakable_reset(entity this)
 spawnfunc(func_breakable)
 {
        float n, i;
-       if(!this.health)
-               this.health = 100;
-       this.max_health = this.health;
+       if(!GetResourceAmount(this, RESOURCE_HEALTH))
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 100);
+       this.max_health = GetResourceAmount(this, RESOURCE_HEALTH);
 
        // yes, I know, MOVETYPE_NONE is not available here, not that one would want it here anyway
        if(!this.debrismovetype) this.debrismovetype = MOVETYPE_BOUNCE;
index 28e6481c880886c18d240fc3cc55719eb80668d6..44e31284336aae99eb35f580dff390b5db98797b 100644 (file)
@@ -27,7 +27,7 @@ void button_return(entity this)
        this.state = STATE_DOWN;
        SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, button_done);
        this.frame = 0;                 // use normal textures
-       if (this.health)
+       if (GetResourceAmount(this, RESOURCE_HEALTH))
                this.takedamage = DAMAGE_YES;   // can be shot again
 }
 
@@ -40,7 +40,7 @@ void button_blocked(entity this, entity blocker)
 
 void button_fire(entity this)
 {
-       this.health = this.max_health;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
        this.takedamage = DAMAGE_NO;    // will be reset upon return
 
        if (this.state == STATE_UP || this.state == STATE_TOP)
@@ -55,14 +55,14 @@ void button_fire(entity this)
 
 void button_reset(entity this)
 {
-       this.health = this.max_health;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
        setorigin(this, this.pos1);
        this.frame = 0;                 // use normal textures
        this.state = STATE_BOTTOM;
        this.velocity = '0 0 0';
        setthink(this, func_null);
        this.nextthink = 0;
-       if (this.health)
+       if (GetResourceAmount(this, RESOURCE_HEALTH))
                this.takedamage = DAMAGE_YES;   // can be shot again
 }
 
@@ -96,7 +96,7 @@ void button_damage(entity this, entity inflictor, entity attacker, float damage,
                        return;
        if (this.spawnflags & BUTTON_DONTACCUMULATEDMG)
        {
-               if (this.health <= damage)
+               if (GetResourceAmount(this, RESOURCE_HEALTH) <= damage)
                {
                        this.enemy = attacker;
                        button_fire(this);
@@ -104,8 +104,8 @@ void button_damage(entity this, entity inflictor, entity attacker, float damage,
        }
        else
        {
-               this.health = this.health - damage;
-               if (this.health <= 0)
+               TakeResource(this, RESOURCE_HEALTH, damage);
+               if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                {
                        this.enemy = attacker;
                        button_fire(this);
@@ -138,9 +138,9 @@ spawnfunc(func_button)
 
 //     if (this.health == 0) // all buttons are now shootable
 //             this.health = 10;
-       if (this.health)
+       if (GetResourceAmount(this, RESOURCE_HEALTH))
        {
-               this.max_health = this.health;
+               this.max_health = GetResourceAmount(this, RESOURCE_HEALTH);
                this.event_damage = button_damage;
                this.takedamage = DAMAGE_YES;
        }
index c19041aa0b1b7ad2269597caa1ea93040a26afa6..8d40a377be081fbc583f3adef2486a129530d694 100644 (file)
@@ -113,7 +113,7 @@ void door_go_down(entity this)
        if (this.max_health)
        {
                this.takedamage = DAMAGE_YES;
-               this.health = this.max_health;
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
        }
 
        this.state = STATE_DOWN;
@@ -265,7 +265,7 @@ void door_damage(entity this, entity inflictor, entity attacker, float damage, i
        if(this.spawnflags & NOSPLASH)
                if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
                        return;
-       this.health = this.health - damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
 
        if (this.itemkeys)
        {
@@ -273,10 +273,10 @@ void door_damage(entity this, entity inflictor, entity attacker, float damage, i
                return;
        }
 
-       if (this.health <= 0)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
        {
-               this.owner.health = this.owner.max_health;
-               this.owner.takedamage = DAMAGE_NO;      // wil be reset upon return
+               SetResourceAmountExplicit(this.owner, RESOURCE_HEALTH, this.owner.max_health);
+               this.owner.takedamage = DAMAGE_NO;      // will be reset upon return
                door_use(this.owner, NULL, NULL);
        }
 }
@@ -357,7 +357,7 @@ Spawned if a door lacks a real activator
 
 void door_trigger_touch(entity this, entity toucher)
 {
-       if (toucher.health < 1)
+       if (GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
 #ifdef SVQC
                if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
 #elif defined(CSQC)
@@ -441,7 +441,7 @@ void LinkDoors(entity this)
        {
                this.owner = this.enemy = this;
 
-               if (this.health)
+               if (GetResourceAmount(this, RESOURCE_HEALTH))
                        return;
                IFTARGETED
                        return;
@@ -474,8 +474,8 @@ void LinkDoors(entity this)
        cmaxs = this.absmax;
        for(t = this; ; t = t.enemy)
        {
-               if(t.health && !this.health)
-                       this.health = t.health;
+               if(GetResourceAmount(t, RESOURCE_HEALTH) && !GetResourceAmount(this, RESOURCE_HEALTH))
+                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(t, RESOURCE_HEALTH));
                if((t.targetname != "") && (this.targetname == ""))
                        this.targetname = t.targetname;
                if((t.message != "") && (this.message == ""))
@@ -499,7 +499,7 @@ void LinkDoors(entity this)
        // distribute health, targetname, message
        for(t = this; t; t = t.enemy)
        {
-               t.health = this.health;
+               SetResourceAmountExplicit(t, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH));
                t.targetname = this.targetname;
                t.message = this.message;
                if(t.enemy == this)
@@ -509,7 +509,7 @@ void LinkDoors(entity this)
        // shootable, or triggered doors just needed the owner/enemy links,
        // they don't spawn a field
 
-       if (this.health)
+       if (GetResourceAmount(this, RESOURCE_HEALTH))
                return;
        IFTARGETED
                return;
@@ -630,7 +630,7 @@ void door_reset(entity this)
 // common code for func_door and func_door_rotating spawnfuncs
 void door_init_shared(entity this)
 {
-       this.max_health = this.health;
+       this.max_health = GetResourceAmount(this, RESOURCE_HEALTH);
 
        // unlock sound
        if(this.noise == "")
@@ -683,7 +683,7 @@ void door_init_shared(entity this)
 
        this.state = STATE_BOTTOM;
 
-       if (this.health)
+       if (GetResourceAmount(this, RESOURCE_HEALTH))
        {
                //this.canteamdamage = true; // TODO
                this.takedamage = DAMAGE_YES;
index 41fd05e574af53295a71c6b105c3a76f3ac78e52..39c02a8669881528f5a57d792c921fe03530c890 100644 (file)
@@ -58,7 +58,7 @@ void door_rotating_go_down(entity this)
        if (this.max_health)
        {
                this.takedamage = DAMAGE_YES;
-               this.health = this.max_health;
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
        }
 
        this.state = STATE_DOWN;
index 78e0dd64e9cafc275eb55c1a6d1302cdcde2cdd5..f06f39e91126e963fa507e139a19304bfbcb6453 100644 (file)
@@ -13,7 +13,7 @@ void fd_secret_use(entity this, entity actor, entity trigger)
        float temp;
        string message_save;
 
-       this.health = 10000;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 10000);
        if(!this.bot_attack)
                IL_PUSH(g_bot_targets, this);
        this.bot_attack = true;
@@ -122,7 +122,7 @@ void fd_secret_done(entity this)
 {
        if (this.spawnflags&DOOR_SECRET_YES_SHOOT)
        {
-               this.health = 10000;
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 10000);
                this.takedamage = DAMAGE_YES;
                //this.th_pain = fd_secret_use;
        }
@@ -168,7 +168,7 @@ void secret_reset(entity this)
 {
        if (this.spawnflags & DOOR_SECRET_YES_SHOOT)
        {
-               this.health = 10000;
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 10000);
                this.takedamage = DAMAGE_YES;
        }
        setorigin(this, this.oldorigin);
@@ -253,7 +253,7 @@ spawnfunc(func_door_secret)
        if (this.spawnflags & DOOR_SECRET_YES_SHOOT)
        {
                //this.canteamdamage = true; // TODO
-               this.health = 10000;
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 10000);
                this.takedamage = DAMAGE_YES;
                this.event_damage = fd_secret_damage;
        }
index 9b1f2f8a49309593a815954fb9800be70302e7be..92ff464b71016951d4f675e0c667b11429b5e13f 100644 (file)
@@ -3,7 +3,6 @@
 #ifdef SVQC
 #include <server/defs.qh>
 #include <server/miscfunctions.qh>
-#include <server/g_subs.qh>
 #include <common/net_linked.qh>
 #include "subs.qh"
 #include "triggers.qh"
@@ -193,4 +192,222 @@ spawnfunc(func_static)            { G_MODEL_INIT      (this, SOLID_NOT) } // DEP
 // solid brush entities
 spawnfunc(func_wall)              { G_MODEL_INIT      (this, SOLID_BSP) } // Q1 name
 spawnfunc(func_clientwall)        { G_CLIENTMODEL_INIT(this, SOLID_BSP) } // brush entity (WARNING: MISPREDICTED)
+#elif defined(CSQC)
+.float alpha;
+.float scale;
+.vector movedir;
+
+void Ent_Wall_PreDraw(entity this)
+{
+       if (this.inactive)
+       {
+               this.alpha = 0;
+       }
+       else
+       {
+               vector org = getpropertyvec(VF_ORIGIN);
+               if(!checkpvs(org, this))
+                       this.alpha = 0;
+               else if(this.fade_start || this.fade_end) {
+                       vector offset = '0 0 0';
+                       offset_z = this.fade_vertical_offset;
+                       float player_dist = vlen(org - this.origin - 0.5 * (this.mins + this.maxs) + offset);
+                       if (this.fade_end == this.fade_start)
+                       {
+                               if (player_dist >= this.fade_start)
+                                       this.alpha = 0;
+                               else
+                                       this.alpha = 1;
+                       }
+                       else
+                       {
+                               this.alpha = (this.alpha_min + this.alpha_max * bound(0,
+                                                          (this.fade_end - player_dist)
+                                                          / (this.fade_end - this.fade_start), 1)) / 100.0;
+                       }
+               }
+               else
+               {
+                       this.alpha = 1;
+               }
+       }
+       if(this.alpha <= 0)
+               this.drawmask = 0;
+       else
+               this.drawmask = MASK_NORMAL;
+}
+
+void Ent_Wall_Draw(entity this)
+{
+       float f;
+       var .vector fld;
+
+       if(this.bgmscriptangular)
+               fld = angles;
+       else
+               fld = origin;
+       this.(fld) = this.saved;
+
+       if(this.lodmodelindex1)
+       {
+               if(autocvar_cl_modeldetailreduction <= 0)
+               {
+                       if(this.lodmodelindex2 && autocvar_cl_modeldetailreduction <= -2)
+                               this.modelindex = this.lodmodelindex2;
+                       else if(autocvar_cl_modeldetailreduction <= -1)
+                               this.modelindex = this.lodmodelindex1;
+                       else
+                               this.modelindex = this.lodmodelindex0;
+               }
+               else
+               {
+                       float distance = vlen(NearestPointOnBox(this, view_origin) - view_origin);
+                       f = (distance * current_viewzoom + 100.0) * autocvar_cl_modeldetailreduction;
+                       f *= 1.0 / bound(0.01, view_quality, 1);
+                       if(this.lodmodelindex2 && f > this.loddistance2)
+                               this.modelindex = this.lodmodelindex2;
+                       else if(f > this.loddistance1)
+                               this.modelindex = this.lodmodelindex1;
+                       else
+                               this.modelindex = this.lodmodelindex0;
+               }
+       }
+
+       InterpolateOrigin_Do(this);
+
+       this.saved = this.(fld);
+
+       f = doBGMScript(this);
+       if(f >= 0)
+       {
+               if(this.lip < 0) // < 0: alpha goes from 1 to 1-|lip| when toggled (toggling subtracts lip)
+                       this.alpha = 1 + this.lip * f;
+               else // > 0: alpha goes from 1-|lip| to 1 when toggled (toggling adds lip)
+                       this.alpha = 1 - this.lip * (1 - f);
+               this.(fld) = this.(fld) + this.movedir * f;
+       }
+       else
+               this.alpha = 1;
+
+       if(this.alpha >= ALPHA_MIN_VISIBLE)
+               this.drawmask = MASK_NORMAL;
+       else
+               this.drawmask = 0;
+}
+
+void Ent_Wall_Remove(entity this)
+{
+       strfree(this.bgmscript);
+}
+
+NET_HANDLE(ENT_CLIENT_WALL, bool isnew)
+{
+       int f;
+       var .vector fld;
+
+       InterpolateOrigin_Undo(this);
+       this.iflags = IFLAG_ANGLES | IFLAG_ORIGIN;
+
+       if(this.bgmscriptangular)
+               fld = angles;
+       else
+               fld = origin;
+       this.(fld) = this.saved;
+
+       f = ReadByte();
+
+       if(f & 1)
+       {
+               if(f & 0x40)
+                       this.colormap = ReadShort();
+               else
+                       this.colormap = 0;
+               this.skin = ReadByte();
+       }
+
+       if(f & 2)
+       {
+               this.origin = ReadVector();
+               setorigin(this, this.origin);
+       }
+
+       if(f & 4)
+       {
+               if(f & 0x10)
+               {
+                       this.angles_x = ReadAngle();
+                       this.angles_y = ReadAngle();
+                       this.angles_z = ReadAngle();
+               }
+               else
+                       this.angles = '0 0 0';
+       }
+
+       if(f & 8)
+       {
+               if(f & 0x80)
+               {
+                       this.lodmodelindex0 = ReadShort();
+                       this.loddistance1 = ReadShort();
+                       this.lodmodelindex1 = ReadShort();
+                       this.loddistance2 = ReadShort();
+                       this.lodmodelindex2 = ReadShort();
+               }
+               else
+               {
+                       this.modelindex = ReadShort();
+                       this.loddistance1 = 0;
+                       this.loddistance2 = 0;
+               }
+               this.solid = ReadByte();
+               this.scale = ReadShort() / 256.0;
+               if(f & 0x20)
+               {
+                       this.mins = ReadVector();
+                       this.maxs = ReadVector();
+               }
+               else
+                       this.mins = this.maxs = '0 0 0';
+               setsize(this, this.mins, this.maxs);
+
+               string s = ReadString();
+               if(substring(s, 0, 1) == "<")
+               {
+                       strcpy(this.bgmscript, substring(s, 1, -1));
+                       this.bgmscriptangular = 1;
+               }
+               else
+               {
+                       strcpy(this.bgmscript, s);
+                       this.bgmscriptangular = 0;
+               }
+               if(this.bgmscript != "")
+               {
+                       this.bgmscriptattack = ReadByte() / 64.0;
+                       this.bgmscriptdecay = ReadByte() / 64.0;
+                       this.bgmscriptsustain = ReadByte() / 255.0;
+                       this.bgmscriptrelease = ReadByte() / 64.0;
+                       this.movedir = ReadVector();
+                       this.lip = ReadByte() / 255.0;
+               }
+               this.fade_start = ReadByte();
+               this.fade_end = ReadByte();
+               this.alpha_max = ReadByte();
+               this.alpha_min = ReadByte();
+               this.inactive = ReadByte();
+               this.fade_vertical_offset = ReadShort();
+               BGMScript_InitEntity(this);
+       }
+
+       return = true;
+
+       InterpolateOrigin_Note(this);
+
+       this.saved = this.(fld);
+
+       this.entremove = Ent_Wall_Remove;
+       this.draw = Ent_Wall_Draw;
+       if (isnew) IL_PUSH(g_drawables, this);
+       setpredraw(this, Ent_Wall_PreDraw);
+}
 #endif
index 6f70f09beec2219624baeca92e2cd7deaa104fb4..50170e251b5fb8cb518fe24e7fe1fbd22b08f34a 100644 (file)
@@ -1 +1,22 @@
 #pragma once
+
+#ifdef CSQC
+entityclass(Wall);
+classfield(Wall) .float lip;
+classfield(Wall) .float bgmscriptangular;
+classfield(Wall) .int lodmodelindex0, lodmodelindex1, lodmodelindex2;
+classfield(Wall) .float loddistance1, loddistance2;
+classfield(Wall) .vector saved;
+
+// Needed for interactive clientwalls
+.float inactive; // Clientwall disappears when inactive
+.float alpha_max, alpha_min;
+// If fade_start > fade_end, fadeout will be inverted
+// fade_vertical_offset is a vertival offset for player position
+.float fade_start, fade_end, fade_vertical_offset;
+.float default_solid;
+
+void Ent_Wall_Draw(entity this);
+
+void Ent_Wall_Remove(entity this);
+#endif
index 4747877314a3ac52f78c30c06233ec3f63dcf170..cc909e5c5600fa0def2e10ab424282b6b0cb64f9 100644 (file)
@@ -93,7 +93,7 @@ void plat_center_touch(entity this, entity toucher)
        if (!toucher.iscreature)
                return;
 
-       if (toucher.health <= 0)
+       if (GetResourceAmount(toucher, RESOURCE_HEALTH) <= 0)
                return;
 #elif defined(CSQC)
        if (!IS_PLAYER(toucher))
@@ -114,7 +114,7 @@ void plat_outside_touch(entity this, entity toucher)
        if (!toucher.iscreature)
                return;
 
-       if (toucher.health <= 0)
+       if (GetResourceAmount(toucher, RESOURCE_HEALTH) <= 0)
                return;
 #elif defined(CSQC)
        if (!IS_PLAYER(toucher))
index bc699813180d1f13e2c7eeb894d6d6f3016ace81..4877d0fb499589034306574c0194d4f63b1b60b9 100644 (file)
@@ -437,6 +437,97 @@ void SetBrushEntityModelNoLOD(entity this)
        ApplyMinMaxScaleAngles(this);
 }
 
+bool LOD_customize(entity this, entity client)
+{
+       if(autocvar_loddebug)
+       {
+               int d = autocvar_loddebug;
+               if(d == 1)
+                       this.modelindex = this.lodmodelindex0;
+               else if(d == 2 || !this.lodmodelindex2)
+                       this.modelindex = this.lodmodelindex1;
+               else // if(d == 3)
+                       this.modelindex = this.lodmodelindex2;
+               return true;
+       }
+
+       // TODO csqc network this so it only gets sent once
+       vector near_point = NearestPointOnBox(this, client.origin);
+       if(vdist(near_point - client.origin, <, this.loddistance1))
+               this.modelindex = this.lodmodelindex0;
+       else if(!this.lodmodelindex2 || vdist(near_point - client.origin, <, this.loddistance2))
+               this.modelindex = this.lodmodelindex1;
+       else
+               this.modelindex = this.lodmodelindex2;
+
+       return true;
+}
+
+void LOD_uncustomize(entity this)
+{
+       this.modelindex = this.lodmodelindex0;
+}
+
+void LODmodel_attach(entity this)
+{
+       entity e;
+
+       if(!this.loddistance1)
+               this.loddistance1 = 1000;
+       if(!this.loddistance2)
+               this.loddistance2 = 2000;
+       this.lodmodelindex0 = this.modelindex;
+
+       if(this.lodtarget1 != "")
+       {
+               e = find(NULL, targetname, this.lodtarget1);
+               if(e)
+               {
+                       this.lodmodel1 = e.model;
+                       delete(e);
+               }
+       }
+       if(this.lodtarget2 != "")
+       {
+               e = find(NULL, targetname, this.lodtarget2);
+               if(e)
+               {
+                       this.lodmodel2 = e.model;
+                       delete(e);
+               }
+       }
+
+       if(autocvar_loddebug < 0)
+       {
+               this.lodmodel1 = this.lodmodel2 = ""; // don't even initialize
+       }
+
+       if(this.lodmodel1 != "")
+       {
+               vector mi, ma;
+               mi = this.mins;
+               ma = this.maxs;
+
+               precache_model(this.lodmodel1);
+               _setmodel(this, this.lodmodel1);
+               this.lodmodelindex1 = this.modelindex;
+
+               if(this.lodmodel2 != "")
+               {
+                       precache_model(this.lodmodel2);
+                       _setmodel(this, this.lodmodel2);
+                       this.lodmodelindex2 = this.modelindex;
+               }
+
+               this.modelindex = this.lodmodelindex0;
+               setsize(this, mi, ma);
+       }
+
+       if(this.lodmodelindex1)
+               if (!getSendEntity(this))
+                       SetCustomizer(this, LOD_customize, LOD_uncustomize);
+}
+
 /*
 ================
 InitTrigger
index 3d265364cf8e17fd9f9690c5a8951e2bd8c2c066..0fa7db2f1c6eab6a59bc39ad39e4dbaf348d08a2 100644 (file)
@@ -1,8 +1,30 @@
 #pragma once
 #include "defs.qh"
 
-void SUB_SetFade (entity ent, float when, float fading_time);
-void SUB_VanishOrRemove (entity ent);
+.float friction;
+void SUB_Friction(entity this);
+
+void SUB_NullThink(entity this);
+
+/*
+==================
+SUB_VanishOrRemove
+
+Makes client invisible or removes non-client
+==================
+*/
+void SUB_VanishOrRemove(entity ent);
+
+void SUB_SetFade_Think(entity this);
+
+/*
+==================
+SUB_SetFade
+
+Fade 'ent' out when time >= 'when'
+==================
+*/
+void SUB_SetFade(entity ent, float when, float fading_time);
 
 .vector                finaldest, finalangle;          //plat.qc stuff
 .void(entity this) think1;
@@ -95,6 +117,23 @@ void ApplyMinMaxScaleAngles(entity e);
 void SetBrushEntityModel(entity this);
 
 void SetBrushEntityModelNoLOD(entity this);
+
+int autocvar_loddebug;
+.string lodtarget1;
+.string lodtarget2;
+.string lodmodel1;
+.string lodmodel2;
+.float lodmodelindex0;
+.float lodmodelindex1;
+.float lodmodelindex2;
+.float loddistance1;
+.float loddistance2;
+
+bool LOD_customize(entity this, entity client);
+
+void LOD_uncustomize(entity this);
+
+void LODmodel_attach(entity this);
 #endif
 
 /*
index ccf3f674e6f069394ec256b6a086df3eb1076aee..fac00152a8eb78ed9545f8fe2c0d98ce7cc10e06 100644 (file)
@@ -25,4 +25,6 @@ void Ent_TriggerMusic_Remove(entity this);
 
 #elif defined(SVQC)
 void target_music_kill();
+
+void TargetMusic_RestoreGame();
 #endif
index ec6a26d18338949336ed54656a11dc7b93c234d2..403d956c59b22552521ec569d243f2d198db90ff 100644 (file)
@@ -44,12 +44,12 @@ void tdeath(entity player, entity teleporter, entity telefragger, vector telefra
 {
        TDEATHLOOP(player.origin)
        {
-               if (IS_PLAYER(player) && player.health >= 1)
+               if (IS_PLAYER(player) && GetResourceAmount(player, RESOURCE_HEALTH) >= 1)
                {
                        if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team))
                        {
                                if(IS_PLAYER(head))
-                                       if(head.health >= 1)
+                                       if(GetResourceAmount(head, RESOURCE_HEALTH) >= 1)
                                                ++tdeath_hit;
                                Damage (head, teleporter, telefragger, 10000, DEATH_TELEFRAG.m_id, DMG_NOWEP, head.origin, '0 0 0');
                        }
index cfcd726fcdb8e02c4a8997cf1477b0556f1c54dd..866fd88a569ddd4e67488ba23ba0a72e8ac5715f 100644 (file)
@@ -18,14 +18,9 @@ void trigger_heal_touch(entity this, entity toucher)
                                toucher.triggerhealtime = time + this.delay;
 
                        bool playthesound = (this.spawnflags & HEAL_SOUND_ALWAYS);
-                       if (toucher.health < this.max_health)
-                       {
-                               playthesound = true;
-                               toucher.health = min(toucher.health + this.health, this.max_health);
-                               toucher.pauserothealth_finished = max(toucher.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
-                       }
+                       bool healed = Heal(toucher, this, GetResourceAmount(this, RESOURCE_HEALTH), this.max_health);
 
-                       if(playthesound)
+                       if(playthesound || healed)
                                _sound (toucher, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
                }
        }
@@ -41,8 +36,8 @@ void trigger_heal_init(entity this)
        this.active = ACTIVE_ACTIVE;
        if(!this.delay)
                this.delay = 1;
-       if(!this.health)
-               this.health = 10;
+       if(!GetResourceAmount(this, RESOURCE_HEALTH))
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 10); // TODO: use a special field for this, it doesn't have actual health!
        if(!this.max_health)
                this.max_health = 200; // max health topoff for field
        if(this.noise == "")
index accfbe8ac47b057d745a4542cb214ae10a66acbd..5447b992c373e1c1694b7e5bacb926cd8953f2e9 100644 (file)
@@ -7,7 +7,7 @@ void multi_wait(entity this)
 {
        if (this.max_health)
        {
-               this.health = this.max_health;
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
                this.takedamage = DAMAGE_YES;
                this.solid = SOLID_BBOX;
        }
@@ -120,8 +120,8 @@ void multi_eventdamage(entity this, entity inflictor, entity attacker, float dam
        if(this.team)
                if(((this.spawnflags & INVERT_TEAMS) == 0) == (this.team != attacker.team))
                        return;
-       this.health = this.health - damage;
-       if (this.health <= 0)
+       TakeResource(this, RESOURCE_HEALTH, damage);
+       if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
        {
                this.enemy = attacker;
                this.goalentity = inflictor;
@@ -135,7 +135,7 @@ void multi_reset(entity this)
                settouch(this, multi_touch);
        if (this.max_health)
        {
-               this.health = this.max_health;
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
                this.takedamage = DAMAGE_YES;
                this.solid = SOLID_BBOX;
        }
@@ -181,12 +181,12 @@ spawnfunc(trigger_multiple)
        this.team_saved = this.team;
        IL_PUSH(g_saved_team, this);
 
-       if (this.health)
+       if (GetResourceAmount(this, RESOURCE_HEALTH))
        {
                if (this.spawnflags & SPAWNFLAG_NOTOUCH)
                        objerror (this, "health and notouch don't make sense\n");
                this.canteamdamage = true;
-               this.max_health = this.health;
+               this.max_health = GetResourceAmount(this, RESOURCE_HEALTH);
                this.event_damage = multi_eventdamage;
                this.takedamage = DAMAGE_YES;
                this.solid = SOLID_BBOX;
index 9377332e2fb138299e1f3af53b653d57e55163d6..5d7c5b6f464a2c921fa6605cbab669de62b034af 100644 (file)
@@ -73,7 +73,7 @@ spawnfunc(trigger_secret)
        this.targetname = "";
 
        // you can't just shoot a room to find it, can you?
-       this.health = 0;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
 
        // a secret can not be delayed
        this.delay = 0;
index 058e41ca278e0cad04833d247dc3c2ba6621265f..8e3fd739de5694b34dd563a999707de4544fc45f 100644 (file)
@@ -18,6 +18,7 @@
 
 .float swamp_interval; //Hurt players in swamp with this interval
 .float swamp_slowdown; //Players in swamp get slowd down by this mutch 0-1 is slowdown 1-~ is speedup (!?)
+.float swamp_lifetime;  // holds the points remaining until slug dies (not quite health!) 
 .entity swampslug;
 
 #ifdef SVQC
@@ -40,10 +41,10 @@ void swampslug_think(entity this);
 void swampslug_think(entity this)
 {
        //Slowly kill the slug
-       this.health = this.health - 1;
+       this.swamp_lifetime -= 1;
 
        //Slug dead? then remove curses.
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
        {
                this.owner.in_swamp = 0;
                delete(this);
@@ -76,7 +77,7 @@ void swamp_touch(entity this, entity toucher)
                // If not attach one.
                //centerprint(toucher,"Entering swamp!\n");
                toucher.swampslug = spawn();
-               toucher.swampslug.health = 2;
+               toucher.swampslug.swamp_lifetime = 2;
                setthink(toucher.swampslug, swampslug_think);
                toucher.swampslug.nextthink = time;
                toucher.swampslug.owner = toucher;
@@ -90,7 +91,7 @@ void swamp_touch(entity this, entity toucher)
        //toucher.in_swamp = 1;
 
        //Revitalize players swampslug
-       toucher.swampslug.health = 2;
+       toucher.swampslug.swamp_lifetime = 2;
 }
 
 REGISTER_NET_LINKED(ENT_CLIENT_SWAMP)
index a0f6195d1232f6ec6ca994d4514de840b099c58a..415984a2a8a914b17822a98a69a861d927315c2d 100644 (file)
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "cl_minigames_hud.qh"
+
 // Get a square in the center of the avaliable area
 // \note macro to pass by reference pos and mySize
 #define minigame_hud_fitsqare(pos, mySize) \
@@ -92,12 +94,6 @@ void minigame_cmd_workaround(float dummy, string...cmdargc);
 // (ie: it's their turn and they should get back to the minigame)
 void minigame_prompt();
 
-float HUD_MinigameMenu_IsOpened();
-void HUD_MinigameMenu_Close(entity this, entity actor, entity trigger);
-
-// Adds a game-specific entry to the menu
-void HUD_MinigameMenu_CustomEntry(entity parent, string message, string event_arg);
-
 
 #define FOREACH_MINIGAME_ENTITY(entityvar) \
        entityvar=NULL; \
index ef44ea025f5633635496415635e9ce6665293075..c14985f627ceb8497b37b7854c5071cd6a7abe75 100644 (file)
@@ -2,3 +2,9 @@
 
 float HUD_Minigame_InputEvent(float bInputType, float nPrimary, float nSecondary);
 void HUD_Minigame_Mouse();
+
+float HUD_MinigameMenu_IsOpened();
+void HUD_MinigameMenu_Close(entity this, entity actor, entity trigger);
+
+// Adds a game-specific entry to the menu
+void HUD_MinigameMenu_CustomEntry(entity parent, string message, string event_arg);
index 82b7d273c23458fb9f333e7bf090c5949f99a453..88120a0ea7550acbb93cd46e9b94022165b18ba2 100644 (file)
@@ -87,23 +87,31 @@ bool M_Mage_Defend_Heal_Check(entity this, entity targ)
 {
        if(targ == NULL)
                return false;
-       if(targ.health <= 0)
+       if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0)
                return false;
        if(DIFF_TEAM(targ, this) && targ != this.monster_follow)
                return false;
        if(STAT(FROZEN, targ))
                return false;
        if(!IS_PLAYER(targ))
-               return (IS_MONSTER(targ) && targ.health < targ.max_health);
+               return (IS_MONSTER(targ) && GetResourceAmount(targ, RESOURCE_HEALTH) < targ.max_health);
        if(targ.items & ITEM_Shield.m_itemid)
                return false;
 
        switch(this.skin)
        {
-               case 0: return (targ.health < autocvar_g_balance_health_regenstable);
-               case 1: return ((targ.ammo_cells && targ.ammo_cells < g_pickup_cells_max) || (targ.ammo_plasma && targ.ammo_plasma < g_pickup_plasma_max) || (targ.ammo_rockets && targ.ammo_rockets < g_pickup_rockets_max) || (targ.ammo_nails && targ.ammo_nails < g_pickup_nails_max) || (targ.ammo_shells && targ.ammo_shells < g_pickup_shells_max));
-               case 2: return (targ.armorvalue < autocvar_g_balance_armor_regenstable);
-               case 3: return (targ.health > 0);
+               case 0: return (GetResourceAmount(targ, RESOURCE_HEALTH) < autocvar_g_balance_health_regenstable);
+               case 1:
+               {
+                       return ((GetResourceAmount(targ, RESOURCE_CELLS) && GetResourceAmount(targ, RESOURCE_CELLS) < g_pickup_cells_max)
+                               ||  (GetResourceAmount(targ, RESOURCE_PLASMA) && GetResourceAmount(targ, RESOURCE_PLASMA) < g_pickup_plasma_max)
+                               ||  (GetResourceAmount(targ, RESOURCE_ROCKETS) && GetResourceAmount(targ, RESOURCE_ROCKETS) < g_pickup_rockets_max)
+                               ||  (GetResourceAmount(targ, RESOURCE_BULLETS) && GetResourceAmount(targ, RESOURCE_BULLETS) < g_pickup_nails_max)
+                               ||  (GetResourceAmount(targ, RESOURCE_SHELLS) && GetResourceAmount(targ, RESOURCE_SHELLS) < g_pickup_shells_max)
+                                       );
+               }
+               case 2: return (GetResourceAmount(targ, RESOURCE_ARMOR) < autocvar_g_balance_armor_regenstable);
+               case 3: return (GetResourceAmount(targ, RESOURCE_HEALTH) > 0);
        }
 
        return false;
@@ -136,7 +144,7 @@ void M_Mage_Attack_Spike_Touch(entity this, entity toucher)
 // copied from W_Seeker_Think
 void M_Mage_Attack_Spike_Think(entity this)
 {
-       if (time > this.ltime || (this.enemy && this.enemy.health <= 0) || this.owner.health <= 0) {
+       if (time > this.ltime || (this.enemy && GetResourceAmount(this.enemy, RESOURCE_HEALTH) <= 0) || GetResourceAmount(this.owner, RESOURCE_HEALTH) <= 0) {
                this.projectiledeathtype |= HITTYPE_SPLASH;
                M_Mage_Attack_Spike_Explode(this, NULL);
        }
@@ -226,26 +234,32 @@ void M_Mage_Defend_Heal(entity this)
                        switch(this.skin)
                        {
                                case 0:
-                                       if(it.health < autocvar_g_balance_health_regenstable) it.health = bound(0, it.health + (autocvar_g_monster_mage_heal_allies), autocvar_g_balance_health_regenstable);
+                               {
+                                       Heal(it, this, autocvar_g_monster_mage_heal_allies, autocvar_g_balance_health_regenstable);
                                        fx = EFFECT_HEALING;
                                        break;
+                               }
                                case 1:
-                                       if(it.ammo_cells) it.ammo_cells = bound(it.ammo_cells, it.ammo_cells + 1, g_pickup_cells_max);
-                                       if(it.ammo_plasma) it.ammo_plasma = bound(it.ammo_plasma, it.ammo_plasma + 1, g_pickup_plasma_max);
-                                       if(it.ammo_rockets) it.ammo_rockets = bound(it.ammo_rockets, it.ammo_rockets + 1, g_pickup_rockets_max);
-                                       if(it.ammo_shells) it.ammo_shells = bound(it.ammo_shells, it.ammo_shells + 2, g_pickup_shells_max);
-                                       if(it.ammo_nails) it.ammo_nails = bound(it.ammo_nails, it.ammo_nails + 5, g_pickup_nails_max);
+                               {
+                                       if(GetResourceAmount(this, RESOURCE_CELLS)) GiveResourceWithLimit(it, RESOURCE_CELLS, 1, g_pickup_cells_max);
+                                       if(GetResourceAmount(this, RESOURCE_PLASMA)) GiveResourceWithLimit(it, RESOURCE_PLASMA, 1, g_pickup_plasma_max);
+                                       if(GetResourceAmount(this, RESOURCE_ROCKETS)) GiveResourceWithLimit(it, RESOURCE_ROCKETS, 1, g_pickup_rockets_max);
+                                       if(GetResourceAmount(this, RESOURCE_SHELLS)) GiveResourceWithLimit(it, RESOURCE_SHELLS, 2, g_pickup_shells_max);
+                                       if(GetResourceAmount(this, RESOURCE_BULLETS)) GiveResourceWithLimit(it, RESOURCE_BULLETS, 5, g_pickup_nails_max);
+                                       // TODO: fuel?
                                        fx = EFFECT_AMMO_REGEN;
                                        break;
+                               }
                                case 2:
-                                       if(it.armorvalue < autocvar_g_balance_armor_regenstable)
+                                       if(GetResourceAmount(it, RESOURCE_ARMOR) < autocvar_g_balance_armor_regenstable)
                                        {
-                                               it.armorvalue = bound(0, it.armorvalue + (autocvar_g_monster_mage_heal_allies), autocvar_g_balance_armor_regenstable);
+                                               GiveResourceWithLimit(it, RESOURCE_ARMOR, autocvar_g_monster_mage_heal_allies, autocvar_g_balance_armor_regenstable);
                                                fx = EFFECT_ARMOR_REPAIR;
                                        }
                                        break;
                                case 3:
-                                       it.health = bound(0, it.health - ((it == this)  ? (autocvar_g_monster_mage_heal_self) : (autocvar_g_monster_mage_heal_allies)), autocvar_g_balance_health_regenstable);
+                                       float hp = ((it == this) ? autocvar_g_monster_mage_heal_self : autocvar_g_monster_mage_heal_allies);
+                                       TakeResource(it, RESOURCE_HEALTH, hp); // TODO: use regular damage functions? needs a way to bypass friendly fire checks
                                        fx = EFFECT_RAGE;
                                        break;
                        }
@@ -255,9 +269,9 @@ void M_Mage_Defend_Heal(entity this)
                else
                {
                        Send_Effect(EFFECT_HEALING, it.origin, '0 0 0', 1);
-                       it.health = bound(0, it.health + (autocvar_g_monster_mage_heal_allies), it.max_health);
+                       Heal(it, this, autocvar_g_monster_mage_heal_allies, RESOURCE_LIMIT_NONE);
                        if(!(it.spawnflags & MONSTERFLAG_INVINCIBLE) && it.sprite)
-                               WaypointSprite_UpdateHealth(it.sprite, it.health);
+                               WaypointSprite_UpdateHealth(it.sprite, GetResourceAmount(it, RESOURCE_HEALTH));
                }
        });
 
@@ -311,14 +325,14 @@ void M_Mage_Attack_Teleport(entity this, entity targ)
 void M_Mage_Defend_Shield_Remove(entity this)
 {
        this.effects &= ~(EF_ADDITIVE | EF_BLUE);
-       this.armorvalue = autocvar_g_monsters_armor_blockpercent;
+       SetResourceAmountExplicit(this, RESOURCE_ARMOR, autocvar_g_monsters_armor_blockpercent);
 }
 
 void M_Mage_Defend_Shield(entity this)
 {
        this.effects |= (EF_ADDITIVE | EF_BLUE);
        this.mage_shield_delay = time + (autocvar_g_monster_mage_shield_delay);
-       this.armorvalue = (autocvar_g_monster_mage_shield_blockpercent);
+       SetResourceAmountExplicit(this, RESOURCE_ARMOR, autocvar_g_monster_mage_shield_blockpercent);
        this.mage_shield_time = time + (autocvar_g_monster_mage_shield_time);
        setanim(this, this.anim_shoot, true, true, true);
        this.attack_finished_single[0] = time + 1;
@@ -405,16 +419,16 @@ METHOD(Mage, mr_think, bool(Mage thismon, entity actor))
        });
     }
 
-    if(actor.health < (autocvar_g_monster_mage_heal_minhealth) || need_help)
+    if(GetResourceAmount(actor, RESOURCE_HEALTH) < (autocvar_g_monster_mage_heal_minhealth) || need_help)
     if(time >= actor.attack_finished_single[0])
     if(random() < 0.5)
         M_Mage_Defend_Heal(actor);
 
-    if(time >= actor.mage_shield_time && actor.armorvalue)
+    if(time >= actor.mage_shield_time && GetResourceAmount(actor, RESOURCE_ARMOR))
         M_Mage_Defend_Shield_Remove(actor);
 
     if(actor.enemy)
-    if(actor.health < actor.max_health)
+    if(GetResourceAmount(actor, RESOURCE_HEALTH) < actor.max_health)
     if(time >= actor.mage_shield_delay)
     if(random() < 0.5)
         M_Mage_Defend_Shield(actor);
@@ -455,7 +469,7 @@ METHOD(Mage, mr_anim, bool(Mage this, entity actor))
 METHOD(Mage, mr_setup, bool(Mage this, entity actor))
 {
     TC(Mage, this);
-    if(!actor.health) actor.health = (autocvar_g_monster_mage_health);
+    if(!GetResourceAmount(this, RESOURCE_HEALTH)) SetResourceAmountExplicit(actor, RESOURCE_HEALTH, autocvar_g_monster_mage_health);
     if(!actor.speed) { actor.speed = (autocvar_g_monster_mage_speed_walk); }
     if(!actor.speed2) { actor.speed2 = (autocvar_g_monster_mage_speed_run); }
     if(!actor.stopspeed) { actor.stopspeed = (autocvar_g_monster_mage_speed_stop); }
index eeefeae8ca51119bc5aa1775e525c2fba3d288cf..9981474f9bc7ad92a696dc9f8792d01a88cb0d15 100644 (file)
@@ -85,15 +85,15 @@ void M_Shambler_Attack_Lightning_Explode_use(entity this, entity actor, entity t
 
 void M_Shambler_Attack_Lightning_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       if (this.health <= 0)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
 
        if (!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
                return; // g_projectiles_damage says to halt
 
-       this.health = this.health - damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
 
-       if (this.health <= 0)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                W_PrepareExplosionByDamage(this, attacker, adaptor_think2use);
 }
 
@@ -136,7 +136,7 @@ void M_Shambler_Attack_Lightning(entity this)
        settouch(gren, M_Shambler_Attack_Lightning_Touch);
 
        gren.takedamage = DAMAGE_YES;
-       gren.health = 50;
+       SetResourceAmountExplicit(gren, RESOURCE_HEALTH, 50);
        gren.damageforcescale = 0;
        gren.event_damage = M_Shambler_Attack_Lightning_Damage;
        gren.damagedbycontents = true;
@@ -246,7 +246,7 @@ METHOD(Shambler, mr_anim, bool(Shambler this, entity actor))
 METHOD(Shambler, mr_setup, bool(Shambler this, entity actor))
 {
     TC(Shambler, this);
-    if(!actor.health) actor.health = (autocvar_g_monster_shambler_health);
+    if(!GetResourceAmount(this, RESOURCE_HEALTH)) SetResourceAmountExplicit(actor, RESOURCE_HEALTH, autocvar_g_monster_shambler_health);
     if(!actor.attack_range) actor.attack_range = 150;
     if(!actor.speed) { actor.speed = (autocvar_g_monster_shambler_speed_walk); }
     if(!actor.speed2) { actor.speed2 = (autocvar_g_monster_shambler_speed_run); }
index 12277d1d640f4c5ed99d860bf9424225e8100409..5e2cc0513851594d70a64167656be7196135500d 100644 (file)
@@ -103,7 +103,7 @@ void M_Spider_Attack_Web_Explode(entity this)
                Send_Effect(EFFECT_ELECTRO_IMPACT, this.origin, '0 0 0', 1);
                RadiusDamage(this, this.realowner, 0, 0, 25, NULL, NULL, 25, this.projectiledeathtype, DMG_NOWEP, NULL);
 
-               FOREACH_ENTITY_RADIUS(this.origin, 25, it != this && it.takedamage && !IS_DEAD(it) && it.health > 0 && it.monsterid != MON_SPIDER.monsterid,
+               FOREACH_ENTITY_RADIUS(this.origin, 25, it != this && it.takedamage && !IS_DEAD(it) && GetResourceAmount(it, RESOURCE_HEALTH) > 0 && it.monsterid != MON_SPIDER.monsterid,
                {
                        it.spider_slowness = time + (autocvar_g_monster_spider_attack_web_damagetime);
                });
@@ -151,7 +151,7 @@ void M_Spider_Attack_Web(entity this)
        setsize(proj, '-4 -4 -4', '4 4 4');
        proj.takedamage = DAMAGE_NO;
        proj.damageforcescale = 0;
-       proj.health = 500;
+       SetResourceAmountExplicit(proj, RESOURCE_HEALTH, 500);
        proj.event_damage = func_null;
        proj.flags = FL_PROJECTILE;
        IL_PUSH(g_projectiles, proj);
@@ -227,7 +227,7 @@ METHOD(Spider, mr_anim, bool(Spider this, entity actor))
 METHOD(Spider, mr_setup, bool(Spider this, entity actor))
 {
     TC(Spider, this);
-    if(!actor.health) actor.health = (autocvar_g_monster_spider_health);
+    if(!GetResourceAmount(this, RESOURCE_HEALTH)) SetResourceAmountExplicit(actor, RESOURCE_HEALTH, autocvar_g_monster_spider_health);
     if(!actor.speed) { actor.speed = (autocvar_g_monster_spider_speed_walk); }
     if(!actor.speed2) { actor.speed2 = (autocvar_g_monster_spider_speed_run); }
     if(!actor.stopspeed) { actor.stopspeed = (autocvar_g_monster_spider_speed_stop); }
index 0a52e61090e553a140817abc3536a9429a0fd425..f6c905d6d1893bcd4d1d3f134e28d62799da3ae7 100644 (file)
@@ -152,7 +152,7 @@ METHOD(Wyvern, mr_anim, bool(Wyvern this, entity actor))
 METHOD(Wyvern, mr_setup, bool(Wyvern this, entity actor))
 {
     TC(Wyvern, this);
-    if(!actor.health) actor.health = (autocvar_g_monster_wyvern_health);
+    if(!GetResourceAmount(this, RESOURCE_HEALTH)) SetResourceAmountExplicit(actor, RESOURCE_HEALTH, autocvar_g_monster_wyvern_health);
     if(!actor.speed) { actor.speed = (autocvar_g_monster_wyvern_speed_walk); }
     if(!actor.speed2) { actor.speed2 = (autocvar_g_monster_wyvern_speed_run); }
     if(!actor.stopspeed) { actor.stopspeed = (autocvar_g_monster_wyvern_speed_stop); }
index 297bab87ddeac5409cc962be2776fdeec6fcc263..aaa27d21b2894ed7e86a6fe021993c041b164db4 100644 (file)
@@ -51,7 +51,7 @@ const float zombie_anim_spawn                         = 30;
 
 void M_Zombie_Attack_Leap_Touch(entity this, entity toucher)
 {
-       if (this.health <= 0)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
 
        vector angles_face;
@@ -74,16 +74,16 @@ void M_Zombie_Attack_Leap_Touch(entity this, entity toucher)
 
 void M_Zombie_Defend_Block_End(entity this)
 {
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
 
        setanim(this, this.anim_blockend, false, true, true);
-       this.armorvalue = autocvar_g_monsters_armor_blockpercent;
+       SetResourceAmountExplicit(this, RESOURCE_ARMOR, autocvar_g_monsters_armor_blockpercent);
 }
 
 bool M_Zombie_Defend_Block(entity this)
 {
-       this.armorvalue = 0.9;
+       SetResourceAmountExplicit(this, RESOURCE_ARMOR, 0.9);
        this.state = MONSTER_ATTACK_MELEE; // freeze monster
        this.attack_finished_single[0] = time + 2.1;
        this.anim_finished = this.attack_finished_single[0];
@@ -100,7 +100,7 @@ bool M_Zombie_Attack(int attack_type, entity actor, entity targ, .entity weapone
        {
                case MONSTER_ATTACK_MELEE:
                {
-                       if(random() < 0.3 && actor.health < 75 && actor.enemy.health > 10)
+                       if(random() < 0.3 && GetResourceAmount(actor, RESOURCE_HEALTH) < 75 && GetResourceAmount(actor.enemy, RESOURCE_HEALTH) > 10)
                                return M_Zombie_Defend_Block(actor);
 
                        float anim_chance = random();
@@ -148,7 +148,7 @@ METHOD(Zombie, mr_pain, float(Zombie this, entity actor, float damage_take, enti
 METHOD(Zombie, mr_death, bool(Zombie this, entity actor))
 {
     TC(Zombie, this);
-    actor.armorvalue = autocvar_g_monsters_armor_blockpercent;
+    SetResourceAmountExplicit(actor, RESOURCE_ARMOR, autocvar_g_monsters_armor_blockpercent);
 
     setanim(actor, ((random() > 0.5) ? actor.anim_die1 : actor.anim_die2), false, true, true);
     return true;
@@ -180,7 +180,7 @@ METHOD(Zombie, mr_anim, bool(Zombie this, entity actor))
 METHOD(Zombie, mr_setup, bool(Zombie this, entity actor))
 {
     TC(Zombie, this);
-    if(!actor.health) actor.health = (autocvar_g_monster_zombie_health);
+    if(!GetResourceAmount(actor, RESOURCE_HEALTH)) SetResourceAmountExplicit(actor, RESOURCE_HEALTH, autocvar_g_monster_zombie_health);
     if(!actor.speed) { actor.speed = (autocvar_g_monster_zombie_speed_walk); }
     if(!actor.speed2) { actor.speed2 = (autocvar_g_monster_zombie_speed_run); }
     if(!actor.stopspeed) { actor.stopspeed = (autocvar_g_monster_zombie_speed_stop); }
index 718838cde5a08efaf2b61d207d644163700624c1..84355c7f3530ffddcf3dccde86781d487c7fd717 100644 (file)
@@ -1,6 +1,5 @@
 #include "sv_monsters.qh"
 
-#include <server/g_subs.qh>
 #include <lib/warpzone/common.qh>
 #include "../constants.qh"
 #include "../teams.qh"
@@ -85,7 +84,7 @@ bool Monster_ValidTarget(entity this, entity targ)
        || (game_stopped)
        || (targ.items & IT_INVISIBILITY)
        || (IS_SPEC(targ) || IS_OBSERVER(targ)) // don't attack spectators
-       || (!IS_VEHICLE(targ) && (IS_DEAD(targ) || IS_DEAD(this) || targ.health <= 0 || this.health <= 0))
+       || (!IS_VEHICLE(targ) && (IS_DEAD(targ) || IS_DEAD(this) || GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(this, RESOURCE_HEALTH) <= 0))
        || (this.monster_follow == targ || targ.monster_follow == this)
        || (!IS_VEHICLE(targ) && (targ.flags & FL_NOTARGET))
        || (!autocvar_g_monsters_typefrag && PHYS_INPUT_BUTTON_CHAT(targ))
@@ -376,7 +375,7 @@ bool Monster_Attack_Leap_Check(entity this, vector vel)
                return false; // already attacking
        if(!IS_ONGROUND(this))
                return false; // not on the ground
-       if(this.health <= 0 || IS_DEAD(this))
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0 || IS_DEAD(this))
                return false; // called when dead?
        if(time < this.attack_finished_single[0])
                return false; // still attacking
@@ -487,7 +486,7 @@ void Monster_Miniboss_Check(entity this)
        // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss
        if ((this.spawnflags & MONSTERFLAG_MINIBOSS) || (chance < autocvar_g_monsters_miniboss_chance))
        {
-               this.health += autocvar_g_monsters_miniboss_healthboost;
+               GiveResource(this, RESOURCE_HEALTH, autocvar_g_monsters_miniboss_healthboost);
                this.effects |= EF_RED;
                if(!this.weapon)
                        this.weapon = WEP_VORTEX.m_id;
@@ -528,10 +527,11 @@ void Monster_Dead_Fade(entity this)
                        this.pos2 = this.angles;
                }
                this.event_damage = func_null;
+               this.event_heal = func_null;
                this.takedamage = DAMAGE_NO;
                setorigin(this, this.pos1);
                this.angles = this.pos2;
-               this.health = this.max_health;
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
                setmodel(this, MDL_Null);
        }
        else
@@ -560,7 +560,7 @@ vector Monster_Move_Target(entity this, entity targ)
 
                // cases where the enemy may have changed their state (don't need to check everything here)
                if((!this.enemy)
-                       || (IS_DEAD(this.enemy) || this.enemy.health < 1)
+                       || (IS_DEAD(this.enemy) || GetResourceAmount(this.enemy, RESOURCE_HEALTH) < 1)
                        || (STAT(FROZEN, this.enemy))
                        || (this.enemy.flags & FL_NOTARGET)
                        || (this.enemy.alpha < 0.5 && this.enemy.alpha != 0)
@@ -897,7 +897,7 @@ void Monster_Reset(entity this)
 
        Unfreeze(this); // remove any icy remains
 
-       this.health = this.max_health;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
        this.velocity = '0 0 0';
        this.enemy = NULL;
        this.goalentity = NULL;
@@ -907,11 +907,11 @@ void Monster_Reset(entity this)
 
 void Monster_Dead_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       this.health -= damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
 
        Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
 
-       if(this.health <= -50) // 100 health until gone?
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= -50) // 100 health until gone?
        {
                Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
 
@@ -933,7 +933,7 @@ void Monster_Dead(entity this, entity attacker, float gibbed)
        if(STAT(FROZEN, this))
        {
                Unfreeze(this); // remove any icy remains
-               this.health = 0; // reset by Unfreeze
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // reset by Unfreeze (TODO)
        }
 
        monster_dropitem(this, attacker);
@@ -957,6 +957,7 @@ void Monster_Dead(entity this, entity attacker, float gibbed)
                _setmodel(this, this.mdl_dead);
 
        this.event_damage       = ((gibbed) ? func_null : Monster_Dead_Damage);
+       this.event_heal         = func_null;
        this.solid                      = SOLID_CORPSE;
        this.takedamage         = DAMAGE_AIM;
        this.deadflag           = DEAD_DEAD;
@@ -1001,7 +1002,7 @@ void Monster_Damage(entity this, entity inflictor, entity attacker, float damage
        if(deathtype == DEATH_FALL.m_id && this.draggedby != NULL)
                return;
 
-       vector v = healtharmor_applydamage(100, this.armorvalue / 100, deathtype, damage);
+       vector v = healtharmor_applydamage(100, GetResourceAmount(this, RESOURCE_ARMOR) / 100, deathtype, damage);
        float take = v.x;
        //float save = v.y;
 
@@ -1010,12 +1011,12 @@ void Monster_Damage(entity this, entity inflictor, entity attacker, float damage
 
        if(take)
        {
-               this.health -= take;
+               TakeResource(this, RESOURCE_HEALTH, take);
                Monster_Sound(this, monstersound_pain, 1.2, true, CH_PAIN);
        }
 
        if(this.sprite)
-               WaypointSprite_UpdateHealth(this.sprite, this.health);
+               WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
 
        this.dmg_time = time;
 
@@ -1033,7 +1034,7 @@ void Monster_Damage(entity this, entity inflictor, entity attacker, float damage
                        Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker);
        }
 
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
        {
                if(deathtype == DEATH_KILL.m_id)
                        this.candrop = false; // killed by mobkill command
@@ -1042,13 +1043,13 @@ void Monster_Damage(entity this, entity inflictor, entity attacker, float damage
                SUB_UseTargets(this, attacker, this.enemy);
                this.target2 = this.oldtarget2; // reset to original target on death, incase we respawn
 
-               Monster_Dead(this, attacker, (this.health <= -100 || deathtype == DEATH_KILL.m_id));
+               Monster_Dead(this, attacker, (GetResourceAmount(this, RESOURCE_HEALTH) <= -100 || deathtype == DEATH_KILL.m_id));
 
                WaypointSprite_Kill(this.sprite);
 
                MUTATOR_CALLHOOK(MonsterDies, this, attacker, deathtype);
 
-               if(this.health <= -100 || deathtype == DEATH_KILL.m_id) // check if we're already gibbed
+               if(GetResourceAmount(this, RESOURCE_HEALTH) <= -100 || deathtype == DEATH_KILL.m_id) // check if we're already gibbed
                {
                        Violence_GibSplash(this, 1, 0.5, attacker);
 
@@ -1058,6 +1059,18 @@ void Monster_Damage(entity this, entity inflictor, entity attacker, float damage
        }
 }
 
+bool Monster_Heal(entity targ, entity inflictor, float amount, float limit)
+{
+       float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
+       if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
+               return false;
+
+       GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+       if(targ.sprite)
+               WaypointSprite_UpdateHealth(targ.sprite, GetResourceAmount(targ, RESOURCE_HEALTH));
+       return true;
+}
+
 // don't check for enemies, just keep walking in a straight line
 void Monster_Move_2D(entity this, float mspeed, bool allow_jumpoff)
 {
@@ -1151,11 +1164,11 @@ void Monster_Frozen_Think(entity this)
        if(STAT(FROZEN, this) == 2)
        {
                STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + this.ticrate * this.revive_speed, 1);
-               this.health = max(1, STAT(REVIVE_PROGRESS, this) * this.max_health);
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * this.max_health));
                this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
 
                if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE) && this.sprite)
-                       WaypointSprite_UpdateHealth(this.sprite, this.health);
+                       WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
 
                if(STAT(REVIVE_PROGRESS, this) >= 1)
                        Unfreeze(this);
@@ -1163,15 +1176,15 @@ void Monster_Frozen_Think(entity this)
        else if(STAT(FROZEN, this) == 3)
        {
                STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - this.ticrate * this.revive_speed, 1);
-               this.health = max(0, autocvar_g_nades_ice_health + (this.max_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this) );
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(0, autocvar_g_nades_ice_health + (this.max_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
 
                if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE) && this.sprite)
-                       WaypointSprite_UpdateHealth(this.sprite, this.health);
+                       WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
 
-               if(this.health < 1)
+               if(GetResourceAmount(this, RESOURCE_HEALTH) < 1)
                {
                        Unfreeze(this);
-                       this.health = 0;
+                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
                        if(this.event_damage)
                                this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
                }
@@ -1211,7 +1224,7 @@ void Monster_Think(entity this)
 
        if(this.monster_lifetime && time >= this.monster_lifetime)
        {
-               Damage(this, this, this, this.health + this.max_health, DEATH_KILL.m_id, DMG_NOWEP, this.origin, this.origin);
+               Damage(this, this, this, GetResourceAmount(this, RESOURCE_HEALTH) + this.max_health, DEATH_KILL.m_id, DMG_NOWEP, this.origin, this.origin);
                return;
        }
 
@@ -1243,8 +1256,8 @@ bool Monster_Spawn_Setup(entity this)
        mon.mr_setup(mon, this);
 
        // ensure some basic needs are met
-       if(!this.health) { this.health = 100; }
-       if(!this.armorvalue) { this.armorvalue = bound(0.2, 0.5 * MONSTER_SKILLMOD(this), 0.9); }
+       if(!GetResourceAmount(this, RESOURCE_HEALTH)) { SetResourceAmountExplicit(this, RESOURCE_HEALTH, 100); }
+       if(!GetResourceAmount(this, RESOURCE_ARMOR)) { SetResourceAmountExplicit(this, RESOURCE_ARMOR, bound(0.2, 0.5 * MONSTER_SKILLMOD(this), 0.9)); }
        if(!this.target_range) { this.target_range = autocvar_g_monsters_target_range; }
        if(!this.respawntime) { this.respawntime = autocvar_g_monsters_respawn_delay; }
        if(!this.monster_moveflags) { this.monster_moveflags = MONSTER_MOVE_WANDER; }
@@ -1254,13 +1267,13 @@ bool Monster_Spawn_Setup(entity this)
        if(!(this.spawnflags & MONSTERFLAG_RESPAWNED))
        {
                Monster_Miniboss_Check(this);
-               this.health *= MONSTER_SKILLMOD(this);
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH) * MONSTER_SKILLMOD(this));
 
                if(!this.skin)
                        this.skin = rint(random() * 4);
        }
 
-       this.max_health = this.health;
+       this.max_health = GetResourceAmount(this, RESOURCE_HEALTH);
        this.pain_finished = this.nextthink;
 
        if(IS_PLAYER(this.monster_follow))
@@ -1289,7 +1302,7 @@ bool Monster_Spawn_Setup(entity this)
                if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE))
                {
                        WaypointSprite_UpdateMaxHealth(this.sprite, this.max_health);
-                       WaypointSprite_UpdateHealth(this.sprite, this.health);
+                       WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
                }
        }
 
@@ -1354,6 +1367,7 @@ bool Monster_Spawn(entity this, bool check_appear, int mon_id)
        this.damagedbycontents  = true;
        this.monsterid                  = mon_id;
        this.event_damage               = Monster_Damage;
+       this.event_heal                 = Monster_Heal;
        settouch(this, Monster_Touch);
        this.use                                = Monster_Use;
        this.solid                              = SOLID_BBOX;
index 6f70f09beec2219624baeca92e2cd7deaa104fb4..1fd3ec672fb6cc750979ee7fa51aa00b996e325f 100644 (file)
@@ -1 +1,3 @@
 #pragma once
+
+.string spawnmob;
index d6cf1ca82e0fa4a4b77b86efe26e77ffdbfebb5a..35005e7a8fea65285d869afd82a8f247da833f78 100644 (file)
@@ -64,6 +64,7 @@ STATIC_INIT(REGISTER_BUFFS) {
 #endif
 
 string Buff_UndeprecateName(string buffname);
+entity buff_FirstFromFlags(int _buffs);
 
 REGISTER_BUFF(Null);
 BUFF_SPAWNFUNCS(random, BUFF_Null)
index 4be441dc1096106c0b4db2c1836880d0596e14e9..27f71b56c24ee02f88798ed51e214c15a5b05127 100644 (file)
@@ -544,7 +544,7 @@ MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate)
                float amount = bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal,
                        GetResourceAmount(frag_target, RESOURCE_HEALTH));
                GiveResourceWithLimit(frag_attacker, RESOURCE_HEALTH, amount, g_pickup_healthsmall_max);
-               if (frag_target.armorvalue)
+               if (GetResourceAmount(frag_target, RESOURCE_ARMOR))
                {
                        amount = bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal,
                                GetResourceAmount(frag_target, RESOURCE_ARMOR));
index fe0070afcb1589bd8cbcd45af5e6486aa4632430..3f8d087166353715a224acd0d7bbf4383cdb2154 100644 (file)
@@ -16,16 +16,16 @@ SOUND(VaporizerCells, Item_Sound("itempickup"));
 
 #ifdef SVQC
 int autocvar_g_instagib_ammo_drop;
-void ammo_vaporizercells_init(entity item)
+void ammo_vaporizercells_init(Pickup this, entity item)
 {
-    if(!item.ammo_cells)
-        item.ammo_cells = autocvar_g_instagib_ammo_drop;
+    if(!GetResourceAmount(item, RESOURCE_CELLS))
+        SetResourceAmountExplicit(item, RESOURCE_CELLS, autocvar_g_instagib_ammo_drop);
 }
 #endif
 REGISTER_ITEM(VaporizerCells, Ammo) {
     this.m_canonical_spawnfunc = "item_vaporizer_cells";
 #ifdef GAMEQC
-    this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
+    this.spawnflags = ITEM_FLAG_MUTATORBLOCKED;
     this.m_model                =   MDL_VaporizerCells_ITEM;
     this.m_sound                =   SND_VaporizerCells;
 #endif
@@ -52,8 +52,7 @@ SOUND(ExtraLife, Item_Sound("megahealth"));
 REGISTER_ITEM(ExtraLife, Powerup) {
     this.m_canonical_spawnfunc = "item_extralife";
 #ifdef GAMEQC
-       this.spawnflags = ITEM_FLAG_INSTAGIB;
-    this.m_model                =   MDL_ExtraLife_ITEM;
+       this.m_model                =   MDL_ExtraLife_ITEM;
     this.m_sound                =   SND_ExtraLife;
 #endif
     this.netname                =   "extralife";
@@ -76,13 +75,13 @@ SOUND(Invisibility, Item_Sound("powerup"));
 /// \brief Initializes the invisibility powerup.
 /// \param[in,out] item Item to initialize.
 /// \return No return.
-void powerup_invisibility_init(entity item);
+void powerup_invisibility_init(Pickup this, entity item);
 #endif
 
 REGISTER_ITEM(Invisibility, Powerup) {
     this.m_canonical_spawnfunc = "item_invisibility";
 #ifdef GAMEQC
-       this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
+       this.spawnflags = ITEM_FLAG_MUTATORBLOCKED;
     this.m_model            =   MDL_Invisibility_ITEM;
     this.m_sound            =   SND_Invisibility;
     this.m_glow             =   true;
@@ -111,13 +110,13 @@ SOUND(Speed, Item_Sound("powerup_shield"));
 /// \brief Initializes the speed powerup.
 /// \param[in,out] item Item to initialize.
 /// \return No return.
-void powerup_speed_init(entity item);
+void powerup_speed_init(Pickup this, entity item);
 #endif
 
 REGISTER_ITEM(Speed, Powerup) {
     this.m_canonical_spawnfunc = "item_speed";
 #ifdef GAMEQC
-       this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
+       this.spawnflags = ITEM_FLAG_MUTATORBLOCKED;
     this.m_model            =   MDL_Speed_ITEM;
     this.m_sound            =   SND_Speed;
     this.m_glow             =   true;
index f93b69235df1213ce71b19aa5e446c8c0383dda7..3cda4485f1a3efde2bcb324335fbc2da635ea11b 100644 (file)
@@ -1,5 +1,9 @@
 #include "sv_instagib.qh"
 
+#include <server/client.qh>
+#include <common/items/_mod.qh>
+#include "../random_items/sv_random_items.qh"
+
 bool autocvar_g_instagib_damagedbycontents = true;
 bool autocvar_g_instagib_blaster_keepdamage = false;
 bool autocvar_g_instagib_blaster_keepforce = false;
@@ -13,9 +17,15 @@ bool autocvar_g_instagib_ammo_convert_bullets;
 int autocvar_g_instagib_extralives;
 float autocvar_g_instagib_speed_highspeed;
 
-#include <server/client.qh>
-
-#include <common/items/_mod.qh>
+IntrusiveList g_instagib_items;
+STATIC_INIT()
+{
+       g_instagib_items = IL_NEW();
+       IL_PUSH(g_instagib_items, ITEM_VaporizerCells);
+       IL_PUSH(g_instagib_items, ITEM_ExtraLife);
+       IL_PUSH(g_instagib_items, ITEM_Invisibility);
+       IL_PUSH(g_instagib_items, ITEM_Speed);
+}
 
 void instagib_invisibility(entity this)
 {
@@ -34,6 +44,26 @@ void instagib_speed(entity this)
        StartItem(this, ITEM_Speed);
 }
 
+/// \brief Returns a random classname of the instagib item.
+/// \param[in] prefix Prefix of the cvars that hold probabilities.
+/// \return Random classname of the instagib item.
+string RandomItems_GetRandomInstagibItemClassName(string prefix)
+{
+       RandomSelection_Init();
+       IL_EACH(g_instagib_items, Item_IsDefinitionAllowed(it),
+       {
+               string cvar_name = sprintf("g_%s_%s_probability", prefix,
+                       it.m_canonical_spawnfunc);
+               if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+               {
+                       LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+                       continue;
+               }
+               RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
+       });
+       return RandomSelection_chosen_string;
+}
+
 .float instagib_nextthink;
 .float instagib_needammo;
 void instagib_stop_countdown(entity e)
@@ -131,6 +161,13 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, MatchEnd)
        FOREACH_CLIENT(IS_PLAYER(it), { instagib_stop_countdown(it); });
 }
 
+MUTATOR_HOOKFUNCTION(mutator_instagib, RandomItems_GetRandomItemClassName)
+{
+       M_ARGV(1, string) = RandomItems_GetRandomInstagibItemClassName(
+               M_ARGV(0, string));
+       return true;
+}
+
 MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterDropItem)
 {
        entity item = M_ARGV(1, entity);
index 5c0d29dd321327c73ba6452bee95598065e3a3c1..c944f56c337055c3f7e941df76ebc250f1b8540a 100644 (file)
@@ -7,7 +7,7 @@ float autocvar_g_instagib_invisibility_time;
 /// \brief Time of speed powerup in seconds.
 float autocvar_g_instagib_speed_time;
 
-void powerup_invisibility_init(entity item)
+void powerup_invisibility_init(Pickup this, entity item)
 {
        if(!item.strength_finished)
        {
@@ -16,7 +16,7 @@ void powerup_invisibility_init(entity item)
 }
 
 
-void powerup_speed_init(entity item)
+void powerup_speed_init(Pickup this, entity item)
 {
        if(!item.invincible_finished)
        {
index e68c687bdeb65058bac0c887c5f8aab8967bd560..443fe2478139a9527df39ab979f13a85805f09f8 100644 (file)
@@ -7,10 +7,10 @@ MUTATOR_HOOKFUNCTION(invincibleprojectiles, EditProjectile)
 {
        entity proj = M_ARGV(1, entity);
 
-       if(proj.health)
+       if(GetResourceAmount(proj, RESOURCE_HEALTH))
        {
                // disable health which in effect disables damage calculations
-               proj.health = 0;
+               SetResourceAmountExplicit(proj, RESOURCE_HEALTH, 0);
        }
 }
 
index cde0016f1bffe7112f2692a83ab49894caa8cbb5..83cf74247fcdf2275c5a2bcc4df83025925700fb 100644 (file)
@@ -1,3 +1,5 @@
+#include <common/effects/all.qh>
+
 EFFECT(0, NADE_EXPLODE_RED,         "nade_red_explode")
 EFFECT(0, NADE_EXPLODE_BLUE,        "nade_blue_explode")
 EFFECT(0, NADE_EXPLODE_YELLOW,      "nade_yellow_explode")
index dbd04a70acc9af324cdd06371a52768855e083ff..68a3af3baf76d5ae65f46819004a865c91ce604e 100644 (file)
@@ -158,7 +158,6 @@ void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expan
 #include <common/gamemodes/_mod.qh>
 #include <common/monsters/sv_spawn.qh>
 #include <common/monsters/sv_monsters.qh>
-#include <server/g_subs.qh>
 
 REGISTER_MUTATOR(nades, autocvar_g_nades);
 
@@ -979,7 +978,7 @@ void toss_nade(entity e, bool set_owner, vector _velocity, float _time)
        settouch(_nade, nade_touch);
        _nade.spawnshieldtime = time + 0.1; // prevent instantly picking up again
        SetResourceAmount(_nade, RESOURCE_HEALTH, autocvar_g_nades_nade_health);
-       _nade.max_health = _nade.health;
+       _nade.max_health = GetResourceAmount(_nade, RESOURCE_HEALTH);
        _nade.takedamage = DAMAGE_AIM;
        _nade.event_damage = nade_damage;
        setcefc(_nade, func_null);
index e109fa7a35ddbddab0f90e18357a8a72daccbc30..2729316a88b4fd8c95eb24f1afc1580b3d7126f3 100644 (file)
@@ -99,3 +99,9 @@ void nades_GiveBonus(entity player, float score);
 MUTATOR_HOOKABLE(Nade_Damage, EV_Nade_Damage);
 
 #endif
+
+#ifdef CSQC
+bool Projectile_isnade(int proj); // TODO: remove
+
+void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expand_time); // TODO: mutator
+#endif
index a691b866f789b448c8cfdf0d699cb2de892e9f85..1fdf5fd7aad1a5e2ef0e85c68d326da0fb6845bc 100644 (file)
@@ -32,7 +32,7 @@ void orb_setup(entity e)
 
        e.draw = orb_draw;
        IL_PUSH(g_drawables, e);
-       e.health = 255;
+       SetResourceAmountExplicit(e, RESOURCE_HEALTH, 255);
        set_movetype(e, MOVETYPE_NONE);
        e.solid = SOLID_NOT;
        e.drawmask = MASK_NORMAL;
index 540edc69d9c6578c7ef4277a9f35b1a2f37cfb4b..37dac8f93123d0624d78e44f90e918e828c659d8 100644 (file)
@@ -108,7 +108,7 @@ REGISTER_MUTATOR(nt, expr_evaluate(cvar_string("g_new_toys")) && !MUTATOR_IS_ENA
 .string new_toys;
 
 float autocvar_g_new_toys_autoreplace;
-bool autocvar_g_new_toys_use_pickupsound = true;
+bool autocvar_g_new_toys_use_pickupsound = false;
 const float NT_AUTOREPLACE_NEVER = 0;
 const float NT_AUTOREPLACE_ALWAYS = 1;
 const float NT_AUTOREPLACE_RANDOM = 2;
index 57fb6c2460d9c3e21948893c4662ad7b9788c493..017cc2082adfc7a9468e293dde68c276160abff5 100644 (file)
@@ -10,3 +10,6 @@
 #include <common/mutators/mutator/overkill/oknex.qc>
 #include <common/mutators/mutator/overkill/okrpc.qc>
 #include <common/mutators/mutator/overkill/okshotgun.qc>
+#ifdef SVQC
+    #include <common/mutators/mutator/overkill/sv_weapons.qc>
+#endif
index 9279c0c65809b0689435453d69f8a89fdafafcc7..997b49de9d6161aab5b74374d8498f790b6107e3 100644 (file)
@@ -13,7 +13,7 @@ CLASS(OverkillMachineGun, Weapon)
 #endif
 /* crosshair */ ATTRIB(OverkillMachineGun, w_crosshair, string, "gfx/crosshairuzi");
 /* crosshair */ ATTRIB(OverkillMachineGun, w_crosshair_size, float, 0.6);
-/* wepimg      */ ATTRIB(OverkillMachineGun, model2, string, "weaponuzi");
+/* wepimg      */ ATTRIB(OverkillMachineGun, model2, string, "ok_weapon_smg");
 /* refname   */ ATTRIB(OverkillMachineGun, netname, string, "okmachinegun");
 /* wepname   */ ATTRIB(OverkillMachineGun, m_name, string, _("Overkill MachineGun"));
 
index 617ce9dc587d83a0b6160f2d650bc22cd4e371c7..f38588e39e6a9a2ef11804b6edea7711e821d30e 100644 (file)
@@ -14,7 +14,7 @@ CLASS(OverkillNex, Weapon)
 /* crosshair */ ATTRIB(OverkillNex, w_crosshair, string, "gfx/crosshairnex");
 /* crosshair */ ATTRIB(OverkillNex, w_crosshair_size, float, 0.65);
 /* reticle   */ ATTRIB(OverkillNex, w_reticle, string, "gfx/reticle_nex");
-/* wepimg    */ ATTRIB(OverkillNex, model2, string, "weaponnex");
+/* wepimg    */ ATTRIB(OverkillNex, model2, string, "ok_weapon_rail");
 /* refname   */ ATTRIB(OverkillNex, netname, string, "oknex");
 /* wepname   */ ATTRIB(OverkillNex, m_name, string, _("Overkill Nex"));
 
index c06ca5b78cf3923816cf9950b4afb53172eb3967..37d82e22ef72d9f1579d4056349bcf39f61d012a 100644 (file)
@@ -28,15 +28,15 @@ void W_OverkillRocketPropelledChainsaw_Touch (entity this, entity toucher)
 
 void W_OverkillRocketPropelledChainsaw_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       if (this.health <= 0)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
 
        if (!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
                return; // g_projectiles_damage says to halt
 
-       this.health = this.health - damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
 
-       if (this.health <= 0)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                W_PrepareExplosionByDamage(this, attacker, W_OverkillRocketPropelledChainsaw_Explode_think);
 }
 
@@ -78,7 +78,7 @@ void W_OverkillRocketPropelledChainsaw_Attack (Weapon thiswep, entity actor, .en
 
        missile.takedamage = DAMAGE_YES;
        missile.damageforcescale = WEP_CVAR_PRI(okrpc, damageforcescale);
-       missile.health = WEP_CVAR_PRI(okrpc, health);
+       SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR_PRI(okrpc, health));
        missile.event_damage = W_OverkillRocketPropelledChainsaw_Damage;
        missile.damagedbycontents = true;
        IL_PUSH(g_damagedbycontents, missile);
index dc6b44ee2f758ee18c88598c68da3a01eb51beb9..a383c9d7a8b1bef15311879e438bd60ccc9c0649 100644 (file)
@@ -13,7 +13,7 @@ CLASS(OverkillShotgun, Weapon)
 #endif
 /* crosshair */ ATTRIB(OverkillShotgun, w_crosshair, string, "gfx/crosshairshotgun");
 /* crosshair */ ATTRIB(OverkillShotgun, w_crosshair_size, float, 0.65);
-/* wepimg    */ ATTRIB(OverkillShotgun, model2, string, "weaponshotgun");
+/* wepimg    */ ATTRIB(OverkillShotgun, model2, string, "ok_weapon_shotgun");
 /* refname   */ ATTRIB(OverkillShotgun, netname, string, "okshotgun");
 /* wepname   */ ATTRIB(OverkillShotgun, m_name, string, _("Overkill Shotgun"));
 
index f4432a9cbb830973f25fea4a1e6dffc4e6203f77..9fa66e8b2a18c12523da72e4c45820d0c711fe7e 100644 (file)
@@ -1,11 +1,73 @@
 #include "sv_overkill.qh"
 
+#include "okshotgun.qh"
+#include "okhmg.qh"
+#include "okrpc.qh"
+
 bool autocvar_g_overkill_powerups_replace;
 
 bool autocvar_g_overkill_itemwaypoints = true;
 
 .Weapon ok_lastwep[MAX_WEAPONSLOTS];
 
+IntrusiveList g_overkill_items;
+STATIC_INIT()
+{
+       g_overkill_items = IL_NEW();
+       IL_PUSH(g_overkill_items, ITEM_HealthMega);
+       IL_PUSH(g_overkill_items, ITEM_ArmorSmall);
+       IL_PUSH(g_overkill_items, ITEM_ArmorMedium);
+       IL_PUSH(g_overkill_items, ITEM_ArmorBig);
+       IL_PUSH(g_overkill_items, ITEM_ArmorMega);
+}
+
+/// \brief Returns a random classname of the overkill item.
+/// \param[in] prefix Prefix of the cvars that hold probabilities.
+/// \return Random classname of the overkill item.
+string RandomItems_GetRandomOverkillItemClassName(string prefix)
+{
+       RandomSelection_Init();
+       IL_EACH(g_overkill_items, !(it.spawnflags & ITEM_FLAG_MUTATORBLOCKED) &&
+               Item_IsDefinitionAllowed(it),
+       {
+               string cvar_name = sprintf("g_%s_%s_probability", prefix,
+                       it.m_canonical_spawnfunc);
+               if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+               {
+                       LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+                       continue;
+               }
+               RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
+       });
+       string cvar_name = sprintf("g_%s_weapon_okhmg_probability", prefix);
+       if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+       {
+               LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+       }
+       else
+       {
+               RandomSelection_AddString("weapon_okhmg", cvar(cvar_name), 1);
+       }
+       cvar_name = sprintf("g_%s_weapon_okrpc_probability", prefix);
+       if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+       {
+               LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+       }
+       else
+       {
+               RandomSelection_AddString("weapon_okrpc", cvar(cvar_name), 1);
+       }
+       return RandomSelection_chosen_string;
+}
+
+
+MUTATOR_HOOKFUNCTION(ok, RandomItems_GetRandomItemClassName)
+{
+       M_ARGV(1, string) = RandomItems_GetRandomOverkillItemClassName(
+               M_ARGV(0, string));
+       return true;
+}
+
 MUTATOR_HOOKFUNCTION(ok, Damage_Calculate, CBC_ORDER_LAST)
 {
        entity frag_attacker = M_ARGV(1, entity);
index 4949edb1e82fdac49e7edf57b69efb092ceef229..79e5dd3c5b0cbce85cee0d53108046f8a14bf3fa 100644 (file)
@@ -1,10 +1,5 @@
 #pragma once
 
-#include "okshotgun.qh"
-#include "okmachinegun.qh"
-#include "okhmg.qh"
-#include "okrpc.qh"
-
 string autocvar_g_overkill;
 bool autocvar_g_overkill_filter_healthmega;
 bool autocvar_g_overkill_filter_armormedium;
@@ -35,13 +30,6 @@ REGISTER_MUTATOR(ok, expr_evaluate(autocvar_g_overkill) && !MUTATOR_IS_ENABLED(m
                {
                        ITEM_ArmorMega.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
                }
-
-               WEP_OVERKILL_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
-               WEP_OVERKILL_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
-
-               WEP_OVERKILL_SHOTGUN.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
-               WEP_OVERKILL_MACHINEGUN.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
-               WEP_OVERKILL_NEX.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
        }
 
        MUTATOR_ONREMOVE
@@ -50,12 +38,5 @@ REGISTER_MUTATOR(ok, expr_evaluate(autocvar_g_overkill) && !MUTATOR_IS_ENABLED(m
                ITEM_ArmorMedium.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
                ITEM_ArmorBig.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
                ITEM_ArmorMega.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
-
-               WEP_OVERKILL_RPC.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
-               WEP_OVERKILL_HMG.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
-
-               WEP_OVERKILL_SHOTGUN.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
-               WEP_OVERKILL_MACHINEGUN.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
-               WEP_OVERKILL_NEX.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
        }
 }
diff --git a/qcsrc/common/mutators/mutator/overkill/sv_weapons.qc b/qcsrc/common/mutators/mutator/overkill/sv_weapons.qc
new file mode 100644 (file)
index 0000000..4a131e3
--- /dev/null
@@ -0,0 +1,22 @@
+string autocvar_g_overkill_weapons;
+
+REGISTER_MUTATOR(ok_weapons, expr_evaluate(autocvar_g_overkill_weapons) || MUTATOR_IS_ENABLED(ok))
+{
+       MUTATOR_ONADD
+       {
+               WEP_OVERKILL_SHOTGUN.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+               WEP_OVERKILL_MACHINEGUN.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+               WEP_OVERKILL_NEX.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+               WEP_OVERKILL_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+               WEP_OVERKILL_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               WEP_OVERKILL_SHOTGUN.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
+               WEP_OVERKILL_MACHINEGUN.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
+               WEP_OVERKILL_NEX.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
+               WEP_OVERKILL_HMG.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
+               WEP_OVERKILL_RPC.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
+       }
+}
index 251e57b8749cad4a70dbfe747daa8303d08bf3aa..183808021b92ecdbdc2324a2431a5ecdbe135c9d 100644 (file)
@@ -53,13 +53,9 @@ string RandomItems_GetRandomItemClassNameWithProperty(string prefix,
 
 string RandomItems_GetRandomItemClassName(string prefix)
 {
-       if (MUTATOR_IS_ENABLED(mutator_instagib))
+       if (MUTATOR_CALLHOOK(RandomItems_GetRandomItemClassName, prefix))
        {
-               return RandomItems_GetRandomInstagibItemClassName(prefix);
-       }
-       if (MUTATOR_IS_ENABLED(ok))
-       {
-               return RandomItems_GetRandomOverkillItemClassName(prefix);
+               return M_ARGV(1, string);
        }
        return RandomItems_GetRandomVanillaItemClassName(prefix,
                RANDOM_ITEM_TYPE_ALL);
@@ -196,61 +192,6 @@ string RandomItems_GetRandomVanillaItemClassName(string prefix, int types)
        return "";
 }
 
-string RandomItems_GetRandomInstagibItemClassName(string prefix)
-{
-       RandomSelection_Init();
-       FOREACH(Items, it.spawnflags & ITEM_FLAG_INSTAGIB &&
-               Item_IsDefinitionAllowed(it),
-       {
-               string cvar_name = sprintf("g_%s_%s_probability", prefix,
-                       it.m_canonical_spawnfunc);
-               if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
-               {
-                       LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
-                       continue;
-               }
-               RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
-       });
-       return RandomSelection_chosen_string;
-}
-
-string RandomItems_GetRandomOverkillItemClassName(string prefix)
-{
-       RandomSelection_Init();
-       FOREACH(Items, (it.spawnflags & ITEM_FLAG_OVERKILL) &&
-               !(it.spawnflags & ITEM_FLAG_MUTATORBLOCKED) &&
-               Item_IsDefinitionAllowed(it),
-       {
-               string cvar_name = sprintf("g_%s_overkill_%s_probability", prefix,
-                       it.m_canonical_spawnfunc);
-               if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
-               {
-                       LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
-                       continue;
-               }
-               RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
-       });
-       string cvar_name = sprintf("g_%s_overkill_weapon_okhmg_probability", prefix);
-       if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
-       {
-               LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
-       }
-       else
-       {
-               RandomSelection_AddString("weapon_okhmg", cvar(cvar_name), 1);
-       }
-       cvar_name = sprintf("g_%s_overkill_weapon_okrpc_probability", prefix);
-       if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
-       {
-               LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
-       }
-       else
-       {
-               RandomSelection_AddString("weapon_okrpc", cvar(cvar_name), 1);
-       }
-       return RandomSelection_chosen_string;
-}
-
 //========================= Free functions ====================================
 
 /// \brief Returns list of classnames to replace a map item with.
index d49e3effa83fed685b08f5144fad4d8cfb19f6d0..c94375ceaeeb98d7648179ed6845bfff251dacd1 100644 (file)
@@ -33,15 +33,13 @@ string RandomItems_GetRandomItemClassName(string prefix);
 /// jetpack and new toys.
 string RandomItems_GetRandomVanillaItemClassName(string prefix, int types);
 
-/// \brief Returns a random classname of the instagib item.
-/// \param[in] prefix Prefix of the cvars that hold probabilities.
-/// \return Random classname of the instagib item.
-string RandomItems_GetRandomInstagibItemClassName(string prefix);
-
-/// \brief Returns a random classname of the overkill item.
-/// \param[in] prefix Prefix of the cvars that hold probabilities.
-/// \return Random classname of the overkill item.
-string RandomItems_GetRandomOverkillItemClassName(string prefix);
+/// \brief Called when random item classname is requested.
+#define EV_RandomItems_GetRandomItemClassName(i, o) \
+       /** prefix */    i(string, MUTATOR_ARGV_0_string) \
+       /** classname */ o(string, MUTATOR_ARGV_1_string) \
+    /**/
+MUTATOR_HOOKABLE(RandomItems_GetRandomItemClassName,
+       EV_RandomItems_GetRandomItemClassName);
 
 REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
        autocvar_g_random_loot));
index 61c302c3e7e0fa74bdd67d0a81a67ea00012efe1..ee2a5be7f504d11c376bd85b31ec28b505b7eeec 100644 (file)
@@ -91,7 +91,7 @@ MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn)
 
                        if (PHYS_INPUT_BUTTON_CHAT(it)) continue;
                        if (!SAME_TEAM(player, it)) continue;
-                       if (autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health && it.health < autocvar_g_balance_health_regenstable) continue;
+                       if (autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health && GetResourceAmount(it, RESOURCE_HEALTH) < autocvar_g_balance_health_regenstable) continue;
                        if (IS_DEAD(it)) continue;
                        if (time < it.msnt_timer) continue;
                        if (time < it.spawnshieldtime) continue;
index b446c927052e0fc824a07708f03a9633f4c4fdad..56198186f1492b1e27648ca7abe6491b6c0f4ce8 100644 (file)
@@ -14,7 +14,7 @@ MUTATOR_HOOKFUNCTION(vampire, PlayerDamage_SplitHealthArmor)
        if(!IS_DEAD(frag_target))
        {
                GiveResource(frag_attacker, RESOURCE_HEALTH,
-                       bound(0, damage_take, frag_target.health));
+                       bound(0, damage_take, GetResourceAmount(frag_target, RESOURCE_HEALTH)));
        }
 }
 
index d0f01a576a278e8cd7f78327b4b8d66e356ef260..115e6ca9109341fcaa4c61974eaafe416b342f15 100644 (file)
@@ -21,18 +21,16 @@ MUTATOR_HOOKFUNCTION(vh, GrappleHookThink)
        if(!STAT(FROZEN, thehook.aiment))
        if(time >= game_starttime)
        if(DIFF_TEAM(thehook.owner, thehook.aiment) || autocvar_g_vampirehook_teamheal)
-       if(thehook.aiment.health > 0)
+       if(GetResourceAmount(thehook.aiment, RESOURCE_HEALTH) > 0)
        if(autocvar_g_vampirehook_damage)
        {
                thehook.last_dmg = time + autocvar_g_vampirehook_damagerate;
                thehook.owner.damage_dealt += autocvar_g_vampirehook_damage;
                Damage(dmgent, thehook, thehook.owner, autocvar_g_vampirehook_damage, WEP_HOOK.m_id, DMG_NOWEP, thehook.origin, '0 0 0');
-               if(SAME_TEAM(thehook.owner, thehook.aiment))
-                       thehook.aiment.health = min(thehook.aiment.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max);
-               else
-                       thehook.owner.health = min(thehook.owner.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max);
+               entity targ = ((SAME_TEAM(thehook.owner, thehook.aiment)) ? thehook.aiment : thehook.owner);
+               Heal(targ, thehook.owner, autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max);
 
                if(dmgent == thehook.owner)
-                       dmgent.health -= autocvar_g_vampirehook_damage; // FIXME: friendly fire?!
+                       TakeResource(dmgent, RESOURCE_HEALTH, autocvar_g_vampirehook_damage); // FIXME: friendly fire?!
        }
 }
index 326a26219b34e8008fe275f5b6d22076c958454a..dcbb65f65cd29472659141b93617b7a8182c2075 100644 (file)
@@ -34,7 +34,7 @@ bool WaypointSprite_SendEntity(entity this, entity to, float sendflags)
     {
         if (this.max_health)
         {
-            WriteByte(MSG_ENTITY, (this.health / this.max_health) * 191.0);
+            WriteByte(MSG_ENTITY, (GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health) * 191.0);
         }
         else
         {
@@ -134,7 +134,7 @@ void Ent_WaypointSprite(entity this, bool isnew)
         int t = ReadByte();
         if (t < 192)
         {
-            this.health = t / 191.0;
+            SetResourceAmountExplicit(this, RESOURCE_HEALTH, t / 191.0);
             this.build_finished = 0;
         }
         else
@@ -142,7 +142,7 @@ void Ent_WaypointSprite(entity this, bool isnew)
             t = (t - 192) * 256 + ReadByte();
             this.build_started = servertime;
             if (this.build_finished)
-                this.build_starthealth = bound(0, this.health, 1);
+                this.build_starthealth = bound(0, GetResourceAmount(this, RESOURCE_HEALTH), 1);
             else
                 this.build_starthealth = 0;
             this.build_finished = servertime + t / 32;
@@ -150,7 +150,7 @@ void Ent_WaypointSprite(entity this, bool isnew)
     }
     else
     {
-        this.health = -1;
+        SetResourceAmountExplicit(this, RESOURCE_HEALTH, -1);
         this.build_finished = 0;
     }
 
@@ -654,14 +654,14 @@ void Draw_WaypointSprite(entity this)
         if (time < this.build_finished + 0.25)
         {
             if (time < this.build_started)
-                this.health = this.build_starthealth;
+                SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.build_starthealth);
             else if (time < this.build_finished)
-                this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
+                SetResourceAmountExplicit(this, RESOURCE_HEALTH, (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth);
             else
-                this.health = 1;
+                SetResourceAmountExplicit(this, RESOURCE_HEALTH, 1);
         }
         else
-            this.health = -1;
+            SetResourceAmountExplicit(this, RESOURCE_HEALTH, -1);
     }
 
     o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
@@ -709,7 +709,7 @@ void Draw_WaypointSprite(entity this)
     }
 
     draw_beginBoldFont();
-    if (this.health >= 0)
+    if (GetResourceAmount(this, RESOURCE_HEALTH) >= 0)
     {
         float align = 0, marg;
         if (this.build_finished)
@@ -726,7 +726,7 @@ void Draw_WaypointSprite(entity this)
         drawhealthbar(
                 o,
                 0,
-                this.health,
+                GetResourceAmount(this, RESOURCE_HEALTH),
                 '0 0 0',
                 '0 0 0',
                 SPRITE_HEALTHBAR_WIDTH * t,
@@ -831,9 +831,9 @@ void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
 void WaypointSprite_UpdateHealth(entity e, float f)
 {
     f = bound(0, f, e.max_health);
-    if (f != e.health || e.pain_finished)
+    if (f != GetResourceAmount(e, RESOURCE_HEALTH) || e.pain_finished)
     {
-        e.health = f;
+        SetResourceAmountExplicit(e, RESOURCE_HEALTH, f);
         e.pain_finished = 0;
         e.SendFlags |= 0x80;
     }
@@ -1160,10 +1160,10 @@ entity WaypointSprite_AttachCarrier(
 {
     WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
     entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
-    if (carrier.health)
+    if (GetResourceAmount(carrier, RESOURCE_HEALTH))
     {
         WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
-        WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
+        WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(carrier, RESOURCE_HEALTH), GetResourceAmount(carrier, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
     }
     return e;
 }
index 1134205185474bda2be0142568448d3e711ac494..0eecd6bdf317e9a4bc67dfd45a9d5582a69062a6 100644 (file)
@@ -11,4 +11,6 @@ void sv_notice_join(entity _to);
 
 #ifdef CSQC
 void cl_notice_read();
+
+void cl_notice_run();
 #endif
index b4d14034def463e93ce6747dc98bcb9bdac73c61..a15ef80d1e7f8feece7b0dc8f7927e3962d50db0 100644 (file)
     MSG_CENTER_NOTIF(ITEM_BUFF_DROP,                    N_ENABLE,    0, 1, "item_buffname",                      CPID_ITEM, "item_centime 0", _("^BGYou dropped the %s^BG buff!"), "")
     MSG_CENTER_NOTIF(ITEM_BUFF_GOT,                     N_ENABLE,    0, 1, "item_buffname",                      CPID_ITEM, "item_centime 0", _("^BGYou got the %s^BG buff!"), "")
     MSG_CENTER_NOTIF(ITEM_FUELREGEN_GOT,                N_ENABLE,    0, 0, "",                                   CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1Fuel regenerator"), "")
-    MSG_CENTER_NOTIF(ITEM_JETPACK_GOT,                  N_ENABLE,    0, 0, "",                                   CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1Jet pack"), "")
+    MSG_CENTER_NOTIF(ITEM_JETPACK_GOT,                  N_ENABLE,    0, 0, "",                                   CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1Jetpack"), "")
     MSG_CENTER_NOTIF(ITEM_WEAPON_DONTHAVE,              N_ENABLE,    0, 1, "item_wepname",                       CPID_ITEM, "item_centime 0", _("^BGYou do not have the ^F1%s"), "")
     MSG_CENTER_NOTIF(ITEM_WEAPON_DROP,                  N_ENABLE,    1, 1, "item_wepname item_wepammo",          CPID_ITEM, "item_centime 0", _("^BGYou dropped the ^F1%s^BG%s"), "")
     MSG_CENTER_NOTIF(ITEM_WEAPON_GOT,                   N_ENABLE,    0, 1, "item_wepname",                       CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1%s"), "")
index 12bd3b68395a519a2281d236fffb324e7a83e5e7..7982ee01f73acfc3348bed12334f125d60af494e 100644 (file)
@@ -5,6 +5,7 @@
 #include <common/constants.qh>
 #include <common/teams.qh>
 #include <common/util.qh>
+#include <common/sounds/sound.qh>
 
 #ifdef CSQC
 #include <client/autocvars.qh>
index 2b9808a5540fd95733660635f89e108f52deb41f..2f4ebb1ff002c9c37278901d70a4838bb0e043e1 100644 (file)
@@ -792,7 +792,7 @@ void PM_jetpack(entity this, float maxspd_mod, float dt)
 
 #ifdef SVQC
                if (!(ITEMS_STAT(this) & IT_UNLIMITED_WEAPON_AMMO))
-                       this.ammo_fuel -= PHYS_JETPACK_FUEL(this) * dt * fvel * f;
+                       TakeResource(this, RESOURCE_FUEL, PHYS_JETPACK_FUEL(this) * dt * fvel * f);
 
                ITEMS_STAT(this) |= IT_USING_JETPACK;
 
index a562292b40a5c5bcd7f00d779f647c2a3bb87357..8e33c649b3bf5e68e09b5050252f53f48002e8b7 100644 (file)
@@ -5,6 +5,10 @@
 /// \author Lyberta
 /// \copyright GNU GPLv2 or any later version.
 
+/// \brief Unconditional maximum amount of resources the entity can have.
+const int RESOURCE_AMOUNT_HARD_LIMIT = 999;
+const int RESOURCE_LIMIT_NONE = -1;
+
 /// \brief Describes the available resource types.
 enum
 {
index 5a849ee1412988f148d418ee4db59fe881f02afe..68fa7ef578ee302b29fd3697e22f89b5a2b6d856 100644 (file)
@@ -824,7 +824,8 @@ float Item_GiveTo(entity item, entity player)
        {
                pickedup = true;
                player.items |= its;
-               Send_Notification(NOTIF_ONE, player, MSG_INFO, INFO_ITEM_WEAPON_GOT, item.netname);
+               // TODO: we probably want to show a message in the console, but not this one!
+               //Send_Notification(NOTIF_ONE, player, MSG_INFO, INFO_ITEM_WEAPON_GOT, item.netname);
        }
 
        if (item.strength_finished)
@@ -1070,12 +1071,12 @@ float ammo_pickupevalfunc(entity player, entity item)
        if(item.itemdef.instanceOfWeaponPickup)
        {
                entity ammo = NULL;
-               if(item.ammo_shells)       { need_shells  = true; ammo = ITEM_Shells;      }
-               else if(item.ammo_nails)   { need_nails   = true; ammo = ITEM_Bullets;     }
-               else if(item.ammo_rockets) { need_rockets = true; ammo = ITEM_Rockets;     }
-               else if(item.ammo_cells)   { need_cells   = true; ammo = ITEM_Cells;       }
-               else if(item.ammo_plasma)  { need_plasma  = true; ammo = ITEM_Plasma;      }
-               else if(item.ammo_fuel)    { need_fuel    = true; ammo = ITEM_JetpackFuel; }
+               if(GetResourceAmount(item, RESOURCE_SHELLS))       { need_shells  = true; ammo = ITEM_Shells;      }
+               else if(GetResourceAmount(item, RESOURCE_BULLETS))   { need_nails   = true; ammo = ITEM_Bullets;     }
+               else if(GetResourceAmount(item, RESOURCE_ROCKETS)) { need_rockets = true; ammo = ITEM_Rockets;     }
+               else if(GetResourceAmount(item, RESOURCE_CELLS))   { need_cells   = true; ammo = ITEM_Cells;       }
+               else if(GetResourceAmount(item, RESOURCE_PLASMA))  { need_plasma  = true; ammo = ITEM_Plasma;      }
+               else if(GetResourceAmount(item, RESOURCE_FUEL))    { need_fuel    = true; ammo = ITEM_JetpackFuel; }
 
                if(!ammo)
                        return 0;
@@ -1103,23 +1104,23 @@ float ammo_pickupevalfunc(entity player, entity item)
 
        float noammorating = 0.5;
 
-       if ((need_shells) && (item.ammo_shells) && (player.ammo_shells < g_pickup_shells_max))
-               c = item.ammo_shells / max(noammorating, player.ammo_shells);
+       if ((need_shells) && GetResourceAmount(item, RESOURCE_SHELLS) && (GetResourceAmount(player, RESOURCE_SHELLS) < g_pickup_shells_max))
+               c = GetResourceAmount(item, RESOURCE_SHELLS) / max(noammorating, GetResourceAmount(player, RESOURCE_SHELLS));
 
-       if ((need_nails) && (item.ammo_nails) && (player.ammo_nails < g_pickup_nails_max))
-               c = item.ammo_nails / max(noammorating, player.ammo_nails);
+       if ((need_nails) && GetResourceAmount(item, RESOURCE_BULLETS) && (GetResourceAmount(player, RESOURCE_BULLETS) < g_pickup_nails_max))
+               c = GetResourceAmount(item, RESOURCE_BULLETS) / max(noammorating, GetResourceAmount(player, RESOURCE_BULLETS));
 
-       if ((need_rockets) && (item.ammo_rockets) && (player.ammo_rockets < g_pickup_rockets_max))
-               c = item.ammo_rockets / max(noammorating, player.ammo_rockets);
+       if ((need_rockets) && GetResourceAmount(item, RESOURCE_ROCKETS) && (GetResourceAmount(player, RESOURCE_ROCKETS) < g_pickup_rockets_max))
+               c = GetResourceAmount(item, RESOURCE_ROCKETS) / max(noammorating, GetResourceAmount(player, RESOURCE_ROCKETS));
 
-       if ((need_cells) && (item.ammo_cells) && (player.ammo_cells < g_pickup_cells_max))
-               c = item.ammo_cells / max(noammorating, player.ammo_cells);
+       if ((need_cells) && GetResourceAmount(item, RESOURCE_CELLS) && (GetResourceAmount(player, RESOURCE_CELLS) < g_pickup_cells_max))
+               c = GetResourceAmount(item, RESOURCE_CELLS) / max(noammorating, GetResourceAmount(player, RESOURCE_CELLS));
 
-       if ((need_plasma) && (item.ammo_plasma) && (player.ammo_plasma < g_pickup_plasma_max))
-               c = item.ammo_plasma / max(noammorating, player.ammo_plasma);
+       if ((need_plasma) && GetResourceAmount(item, RESOURCE_PLASMA) && (GetResourceAmount(player, RESOURCE_PLASMA) < g_pickup_plasma_max))
+               c = GetResourceAmount(item, RESOURCE_PLASMA) / max(noammorating, GetResourceAmount(player, RESOURCE_PLASMA));
 
-       if ((need_fuel) && (item.ammo_fuel) && (player.ammo_fuel < g_pickup_fuel_max))
-               c = item.ammo_fuel / max(noammorating, player.ammo_fuel);
+       if ((need_fuel) && GetResourceAmount(item, RESOURCE_FUEL) && (GetResourceAmount(player, RESOURCE_FUEL) < g_pickup_fuel_max))
+               c = GetResourceAmount(item, RESOURCE_FUEL) / max(noammorating, GetResourceAmount(player, RESOURCE_FUEL));
 
        rating *= min(c, 2);
        if(wpn)
@@ -1132,8 +1133,8 @@ float healtharmor_pickupevalfunc(entity player, entity item)
        float c = 0;
        float rating = item.bot_pickupbasevalue;
 
-       float itemarmor = item.armorvalue;
-       float itemhealth = item.health;
+       float itemarmor = GetResourceAmount(item, RESOURCE_ARMOR);
+       float itemhealth = GetResourceAmount(item, RESOURCE_HEALTH);
 
        if(item.item_group)
        {
@@ -1141,11 +1142,11 @@ float healtharmor_pickupevalfunc(entity player, entity item)
                itemhealth *= min(4, item.item_group_count);
        }
 
-       if (itemarmor && (player.armorvalue < item.max_armorvalue))
-               c = itemarmor / max(1, player.armorvalue * 2/3 + player.health * 1/3);
+       if (itemarmor && (GetResourceAmount(player, RESOURCE_ARMOR) < item.max_armorvalue))
+               c = itemarmor / max(1, GetResourceAmount(player, RESOURCE_ARMOR) * 2/3 + GetResourceAmount(player, RESOURCE_HEALTH) * 1/3);
 
-       if (itemhealth && (player.health < item.max_health))
-               c = itemhealth / max(1, player.health);
+       if (itemhealth && (GetResourceAmount(player, RESOURCE_HEALTH) < item.max_health))
+               c = itemhealth / max(1, GetResourceAmount(player, RESOURCE_HEALTH));
 
        rating *= min(2, c);
        return rating;
@@ -1178,7 +1179,7 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
     this.item_pickupsound_ent = pickupsound;
 
     if(def.m_iteminit)
-       def.m_iteminit(this);
+       def.m_iteminit(def, this);
 
        if(!this.respawntime) // both need to be set
        {
@@ -1334,7 +1335,7 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default
                if(def.instanceOfPowerup)
                        this.ItemStatus |= ITS_ANIMATE1;
 
-               if(this.armorvalue || this.health)
+               if(GetResourceAmount(this, RESOURCE_ARMOR) || GetResourceAmount(this, RESOURCE_HEALTH))
                        this.ItemStatus |= ITS_ANIMATE2;
        }
 
@@ -1554,14 +1555,14 @@ spawnfunc(target_items)
                this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, this.superweapons_finished * boolean(this.items & IT_SUPERWEAPON), "superweapons");
                this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, boolean(this.items & ITEM_Jetpack.m_itemid), "jetpack");
                this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, boolean(this.items & ITEM_JetpackRegen.m_itemid), "fuel_regen");
-               if(this.ammo_shells != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_shells), "shells");
-               if(this.ammo_nails != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_nails), "nails");
-               if(this.ammo_rockets != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_rockets), "rockets");
-               if(this.ammo_cells != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_cells), "cells");
-               if(this.ammo_plasma != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_plasma), "plasma");
-               if(this.ammo_fuel != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_fuel), "fuel");
-               if(this.health != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.health), "health");
-               if(this.armorvalue != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.armorvalue), "armor");
+               if(GetResourceAmount(this, RESOURCE_SHELLS) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_SHELLS)), "shells");
+               if(GetResourceAmount(this, RESOURCE_BULLETS) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_BULLETS)), "nails");
+               if(GetResourceAmount(this, RESOURCE_ROCKETS) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_ROCKETS)), "rockets");
+               if(GetResourceAmount(this, RESOURCE_CELLS) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_CELLS)), "cells");
+               if(GetResourceAmount(this, RESOURCE_PLASMA) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_PLASMA)), "plasma");
+               if(GetResourceAmount(this, RESOURCE_FUEL) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_FUEL)), "fuel");
+               if(GetResourceAmount(this, RESOURCE_HEALTH) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_HEALTH)), "health");
+               if(GetResourceAmount(this, RESOURCE_ARMOR) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_ARMOR)), "armor");
                FOREACH(Buffs, it != BUFF_Null, this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, !!(STAT(BUFFS, this) & (it.m_itemid)), it.m_name));
                FOREACH(Weapons, it != WEP_Null, this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, !!(STAT(WEAPONS, this) & (it.m_wepset)), it.netname));
        }
@@ -1661,6 +1662,31 @@ void GiveRot(entity e, float v0, float v1, .float rotfield, float rottime, .floa
        else if(v0 > v1)
                e.(regenfield) = max(e.(regenfield), time + regentime);
 }
+bool GiveResourceValue(entity e, int resource_type, int op, int val)
+{
+       int v0 = GetResourceAmount(e, resource_type);
+       switch (op)
+       {
+               case OP_SET:
+                       SetResourceAmount(e, resource_type, val);
+                       break;
+               case OP_MIN:
+                       SetResourceAmount(e, resource_type, max(v0, val)); // min 100 cells = at least 100 cells
+                       break;
+               case OP_MAX:
+                       SetResourceAmount(e, resource_type, min(v0, val));
+                       break;
+               case OP_PLUS:
+                       SetResourceAmount(e, resource_type, v0 + val);
+                       break;
+               case OP_MINUS:
+                       SetResourceAmount(e, resource_type, v0 - val);
+                       break;
+       }
+       int v1 = GetResourceAmount(e, resource_type);
+       return v0 != v1;
+}
+
 float GiveItems(entity e, float beginarg, float endarg)
 {
        float got, i, val, op;
@@ -1693,14 +1719,14 @@ float GiveItems(entity e, float beginarg, float endarg)
        PREGIVE(e, strength_finished);
        PREGIVE(e, invincible_finished);
        PREGIVE(e, superweapons_finished);
-       PREGIVE(e, ammo_nails);
-       PREGIVE(e, ammo_cells);
-       PREGIVE(e, ammo_plasma);
-       PREGIVE(e, ammo_shells);
-       PREGIVE(e, ammo_rockets);
-       PREGIVE(e, ammo_fuel);
-       PREGIVE(e, armorvalue);
-       PREGIVE(e, health);
+       PREGIVE_RESOURCE(e, RESOURCE_BULLETS);
+       PREGIVE_RESOURCE(e, RESOURCE_CELLS);
+       PREGIVE_RESOURCE(e, RESOURCE_PLASMA);
+       PREGIVE_RESOURCE(e, RESOURCE_SHELLS);
+       PREGIVE_RESOURCE(e, RESOURCE_ROCKETS);
+       PREGIVE_RESOURCE(e, RESOURCE_FUEL);
+       PREGIVE_RESOURCE(e, RESOURCE_ARMOR);
+       PREGIVE_RESOURCE(e, RESOURCE_HEALTH);
 
        for(i = beginarg; i < endarg; ++i)
        {
@@ -1737,19 +1763,19 @@ float GiveItems(entity e, float beginarg, float endarg)
                                got += GiveBit(e, items, IT_UNLIMITED_AMMO, op, val);
                        case "all":
                                got += GiveBit(e, items, ITEM_Jetpack.m_itemid, op, val);
-                               got += GiveValue(e, health, op, val);
-                               got += GiveValue(e, armorvalue, op, val);
+                               got += GiveResourceValue(e, RESOURCE_HEALTH, op, val);
+                               got += GiveResourceValue(e, RESOURCE_ARMOR, op, val);
                        case "allweapons":
                                FOREACH(Weapons, it != WEP_Null && !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED), got += GiveWeapon(e, it.m_id, op, val));
                        //case "allbuffs": // all buffs makes a player god, do not want!
                                //FOREACH(Buffs, it != BUFF_Null, got += GiveBuff(e, it.m_itemid, op, val));
                        case "allammo":
-                               got += GiveValue(e, ammo_cells, op, val);
-                               got += GiveValue(e, ammo_plasma, op, val);
-                               got += GiveValue(e, ammo_shells, op, val);
-                               got += GiveValue(e, ammo_nails, op, val);
-                               got += GiveValue(e, ammo_rockets, op, val);
-                               got += GiveValue(e, ammo_fuel, op, val);
+                               got += GiveResourceValue(e, RESOURCE_CELLS, op, val);
+                               got += GiveResourceValue(e, RESOURCE_PLASMA, op, val);
+                               got += GiveResourceValue(e, RESOURCE_SHELLS, op, val);
+                               got += GiveResourceValue(e, RESOURCE_BULLETS, op, val);
+                               got += GiveResourceValue(e, RESOURCE_ROCKETS, op, val);
+                               got += GiveResourceValue(e, RESOURCE_FUEL, op, val);
                                break;
                        case "unlimited_ammo":
                                got += GiveBit(e, items, IT_UNLIMITED_AMMO, op, val);
@@ -1776,29 +1802,29 @@ float GiveItems(entity e, float beginarg, float endarg)
                                got += GiveValue(e, superweapons_finished, op, val);
                                break;
                        case "cells":
-                               got += GiveValue(e, ammo_cells, op, val);
+                               got += GiveResourceValue(e, RESOURCE_CELLS, op, val);
                                break;
                        case "plasma":
-                               got += GiveValue(e, ammo_plasma, op, val);
+                               got += GiveResourceValue(e, RESOURCE_PLASMA, op, val);
                                break;
                        case "shells":
-                               got += GiveValue(e, ammo_shells, op, val);
+                               got += GiveResourceValue(e, RESOURCE_SHELLS, op, val);
                                break;
                        case "nails":
                        case "bullets":
-                               got += GiveValue(e, ammo_nails, op, val);
+                               got += GiveResourceValue(e, RESOURCE_BULLETS, op, val);
                                break;
                        case "rockets":
-                               got += GiveValue(e, ammo_rockets, op, val);
+                               got += GiveResourceValue(e, RESOURCE_ROCKETS, op, val);
                                break;
                        case "health":
-                               got += GiveValue(e, health, op, val);
+                               got += GiveResourceValue(e, RESOURCE_HEALTH, op, val);
                                break;
                        case "armor":
-                               got += GiveValue(e, armorvalue, op, val);
+                               got += GiveResourceValue(e, RESOURCE_ARMOR, op, val);
                                break;
                        case "fuel":
-                               got += GiveValue(e, ammo_fuel, op, val);
+                               got += GiveResourceValue(e, RESOURCE_FUEL, op, val);
                                break;
                        default:
                                FOREACH(Buffs, it != BUFF_Null && Buff_UndeprecateName(cmd) == it.m_name,
@@ -1829,14 +1855,14 @@ float GiveItems(entity e, float beginarg, float endarg)
        POSTGIVE_VALUE(e, strength_finished, 1, SND_POWERUP, SND_POWEROFF);
        POSTGIVE_VALUE(e, invincible_finished, 1, SND_Shield, SND_POWEROFF);
        //POSTGIVE_VALUE(e, superweapons_finished, 1, SND_Null, SND_Null);
-       POSTGIVE_VALUE(e, ammo_nails, 0, SND_ITEMPICKUP, SND_Null);
-       POSTGIVE_VALUE(e, ammo_cells, 0, SND_ITEMPICKUP, SND_Null);
-       POSTGIVE_VALUE(e, ammo_plasma, 0, SND_ITEMPICKUP, SND_Null);
-       POSTGIVE_VALUE(e, ammo_shells, 0, SND_ITEMPICKUP, SND_Null);
-       POSTGIVE_VALUE(e, ammo_rockets, 0, SND_ITEMPICKUP, SND_Null);
-       POSTGIVE_VALUE_ROT(e, ammo_fuel, 1, pauserotfuel_finished, autocvar_g_balance_pause_fuel_rot, pauseregen_finished, autocvar_g_balance_pause_fuel_regen, SND_ITEMPICKUP, SND_Null);
-       POSTGIVE_VALUE_ROT(e, armorvalue, 1, pauserotarmor_finished, autocvar_g_balance_pause_armor_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_ARMOR25, SND_Null);
-       POSTGIVE_VALUE_ROT(e, health, 1, pauserothealth_finished, autocvar_g_balance_pause_health_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_MEGAHEALTH, SND_Null);
+       POSTGIVE_RESOURCE(e, RESOURCE_BULLETS, 0, SND_ITEMPICKUP, SND_Null);
+       POSTGIVE_RESOURCE(e, RESOURCE_CELLS, 0, SND_ITEMPICKUP, SND_Null);
+       POSTGIVE_RESOURCE(e, RESOURCE_PLASMA, 0, SND_ITEMPICKUP, SND_Null);
+       POSTGIVE_RESOURCE(e, RESOURCE_SHELLS, 0, SND_ITEMPICKUP, SND_Null);
+       POSTGIVE_RESOURCE(e, RESOURCE_ROCKETS, 0, SND_ITEMPICKUP, SND_Null);
+       POSTGIVE_RESOURCE_ROT(e, RESOURCE_FUEL, 1, pauserotfuel_finished, autocvar_g_balance_pause_fuel_rot, pauseregen_finished, autocvar_g_balance_pause_fuel_regen, SND_ITEMPICKUP, SND_Null);
+       POSTGIVE_RESOURCE_ROT(e, RESOURCE_ARMOR, 1, pauserotarmor_finished, autocvar_g_balance_pause_armor_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_ARMOR25, SND_Null);
+       POSTGIVE_RESOURCE_ROT(e, RESOURCE_HEALTH, 1, pauserothealth_finished, autocvar_g_balance_pause_health_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_MEGAHEALTH, SND_Null);
 
        if(e.superweapons_finished <= 0)
                if(STAT(WEAPONS, e) & WEPSET_SUPERWEAPONS)
index bd46599aa39ed1ae750ebdca4dcc2f57c802a5a7..9fdb0b0925798580c57106a7eb598c24e3a5ad41 100644 (file)
@@ -124,10 +124,15 @@ void GiveSound(entity e, float v0, float v1, float t, Sound snd_incr, Sound snd_
 
 void GiveRot(entity e, float v0, float v1, .float rotfield, float rottime, .float regenfield, float regentime);
 
+spawnfunc(target_items);
+
 #define PREGIVE_WEAPONS(e) WepSet save_weapons; save_weapons = STAT(WEAPONS, e)
 #define PREGIVE(e,f) float save_##f; save_##f = (e).f
+#define PREGIVE_RESOURCE(e,f) float save_##f = GetResourceAmount((e), (f))
 #define POSTGIVE_WEAPON(e,b,snd_incr,snd_decr) GiveSound((e), !!(save_weapons & WepSet_FromWeapon(b)), !!(STAT(WEAPONS, e) & WepSet_FromWeapon(b)), 0, snd_incr, snd_decr)
 #define POSTGIVE_BIT(e,f,b,snd_incr,snd_decr) GiveSound((e), save_##f & (b), (e).f & (b), 0, snd_incr, snd_decr)
+#define POSTGIVE_RESOURCE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, GetResourceAmount((e), (f)), t, snd_incr, snd_decr)
+#define POSTGIVE_RESOURCE_ROT(e,f,t,rotfield,rottime,regenfield,regentime,snd_incr,snd_decr) GiveRot((e),save_##f,GetResourceAmount((e),(f)),rotfield,rottime,regenfield,regentime);GiveSound((e),save_##f,GetResourceAmount((e),(f)),t,snd_incr,snd_decr)
 #define POSTGIVE_VALUE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr)
 #define POSTGIVE_VALUE_ROT(e,f,t,rotfield,rottime,regenfield,regentime,snd_incr,snd_decr) GiveRot((e), save_##f, (e).f, rotfield, rottime, regenfield, regentime); GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr)
 
index ba7b5d01bb5ba25c87ce00d1ad83b55b1f81923e..ac68003a6cde83c1a6b652959af1f349240654e1 100644 (file)
@@ -37,7 +37,7 @@ void turret_draw(entity this)
 
        this.tur_head.angles += dt * this.tur_head.avelocity;
 
-       if (this.health < 127)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) < 127)
        {
                dt = random();
 
@@ -45,11 +45,11 @@ void turret_draw(entity this)
                        te_spark(this.origin + '0 0 40', randomvec() * 256 + '0 0 256', 16);
        }
 
-       if(this.health < 85)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) < 85)
        if(dt < 0.01)
                pointparticles(EFFECT_SMOKE_LARGE, (this.origin + (randomvec() * 80)), '0 0 0', 1);
 
-       if(this.health < 32)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) < 32)
        if(dt < 0.015)
                pointparticles(EFFECT_SMOKE_SMALL, (this.origin + (randomvec() * 80)), '0 0 0', 1);
 
@@ -180,7 +180,7 @@ void turret_draw2d(entity this)
        drawhealthbar(
                        o,
                        0,
-                       this.health / 255,
+                       GetResourceAmount(this, RESOURCE_HEALTH) / 255,
                        '0 0 0',
                        '0 0 0',
                        0.5 * SPRITE_HEALTHBAR_WIDTH * t,
@@ -221,7 +221,7 @@ void turret_construct(entity this, bool isnew)
        set_movetype(this.tur_head, MOVETYPE_NOCLIP);
        set_movetype(this, MOVETYPE_NOCLIP);
        this.tur_head.angles                    = this.angles;
-       this.health                                             = 255;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 255);
        this.solid                                              = SOLID_BBOX;
        this.tur_head.solid                             = SOLID_NOT;
        set_movetype(this, MOVETYPE_NOCLIP);
@@ -422,13 +422,15 @@ NET_HANDLE(ENT_CLIENT_TURRET, bool isnew)
                }
 
                _tmp = ReadByte();
-               if(_tmp == 0 && this.health != 0)
+               float myhp = GetResourceAmount(this, RESOURCE_HEALTH);
+               if(_tmp == 0 && myhp != 0)
                        turret_die(this);
-               else if(this.health && this.health != _tmp)
+               else if(myhp && myhp > _tmp)
                        this.helpme = servertime + 10;
+               else if(myhp && myhp < _tmp)
+                       this.helpme = 0; // we're being healed, don't spam help me waypoints
 
-               this.health = _tmp;
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, _tmp);
        }
-       //this.enemy.health = this.health / 255;
        return true;
 }
index 2a7c41c98c9a8a7175a5b9b5af8acc07d31f6cff..b68aca16feddd93b6ea01b555b49aa6b17d2c53a 100644 (file)
@@ -182,9 +182,10 @@ void turret_die(entity this)
        this.tur_head.solid      = this.solid;
 
        this.event_damage                 = func_null;
+       this.event_heal = func_null;
        this.takedamage                  = DAMAGE_NO;
 
-       this.health                      = 0;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
 
 // Go boom
        //RadiusDamage (this,this, min(this.ammo,50),min(this.ammo,50) * 0.25,250,NULL,min(this.ammo,50)*5,DEATH_TURRET,NULL);
@@ -230,7 +231,7 @@ void turret_damage(entity this, entity inflictor, entity attacker, float damage,
                        return;
        }
 
-       this.health -= damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
 
        // thorw head slightly off aim when hit?
        if (this.damage_flags & TFL_DMG_HEADSHAKE)
@@ -244,10 +245,12 @@ void turret_damage(entity this, entity inflictor, entity attacker, float damage,
        if (this.turret_flags & TUR_FLAG_MOVE)
                this.velocity = this.velocity + vforce;
 
-       if (this.health <= 0)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
        {
                this.event_damage                 = func_null;
                this.tur_head.event_damage = func_null;
+               this.event_heal = func_null;
+               this.tur_head.event_heal = func_null;
                this.takedamage                  = DAMAGE_NO;
                this.nextthink = time;
                setthink(this, turret_die);
@@ -256,6 +259,17 @@ void turret_damage(entity this, entity inflictor, entity attacker, float damage,
        this.SendFlags  |= TNSF_STATUS;
 }
 
+bool turret_heal(entity targ, entity inflictor, float amount, float limit)
+{
+       float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
+       if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
+               return false;
+
+       GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+       targ.SendFlags |= TNSF_STATUS;
+       return true;
+}
+
 void turret_think(entity this);
 void turret_respawn(entity this)
 {
@@ -268,10 +282,11 @@ void turret_respawn(entity this)
        this.solid                                      = SOLID_BBOX;
        this.takedamage                         = DAMAGE_AIM;
        this.event_damage                       = turret_damage;
+       this.event_heal                         = turret_heal;
        this.avelocity                          = '0 0 0';
        this.tur_head.avelocity         = this.avelocity;
        this.tur_head.angles            = this.idle_aim;
-       this.health                                     = this.max_health;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
        this.enemy                                      = NULL;
        this.volly_counter                      = this.shot_volly;
        this.ammo                                       = this.ammo_max;
@@ -350,10 +365,10 @@ bool turret_send(entity this, entity to, float sf)
        {
                WriteByte(MSG_ENTITY, this.team);
 
-               if(this.health <= 0)
+               if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                        WriteByte(MSG_ENTITY, 0);
                else
-                       WriteByte(MSG_ENTITY, ceil((this.health / this.max_health) * 255));
+                       WriteByte(MSG_ENTITY, ceil((GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health) * 255));
        }
 
        return true;
@@ -384,7 +399,7 @@ void load_unit_settings(entity ent, bool is_reload)
                ent.tur_head.angles = '0 0 0';
        }
 
-       ent.health       = cvar(strcat(sbase,"_health")) * ent.turret_scale_health;
+       SetResourceAmountExplicit(ent, RESOURCE_HEALTH, cvar(strcat(sbase,"_health")) * ent.turret_scale_health);
        ent.respawntime = cvar(strcat(sbase,"_respawntime")) * ent.turret_scale_respawn;
 
        ent.shot_dmg             = cvar(strcat(sbase,"_shot_dmg")) * ent.turret_scale_damage;
@@ -451,9 +466,9 @@ void turret_projectile_touch(entity this, entity toucher)
 void turret_projectile_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector vforce)
 {
        this.velocity  += vforce;
-       this.health     -= damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
        //this.realowner = attacker; // Dont change realowner, it does not make much sense for turrets
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                W_PrepareExplosionByDamage(this, this.owner, turret_projectile_explode);
 }
 
@@ -483,7 +498,7 @@ entity turret_projectile(entity actor, Sound _snd, float _size, float _health, f
        PROJECTILE_MAKETRIGGER(proj);
        if(_health)
        {
-               proj.health              = _health;
+               SetResourceAmountExplicit(proj, RESOURCE_HEALTH, _health);
                proj.takedamage  = DAMAGE_YES;
                proj.event_damage  = turret_projectile_damage;
        }
@@ -720,7 +735,7 @@ float turret_validate_target(entity e_turret, entity e_target, float validate_fl
                if (e_target.vehicle_health <= 0)
                        return -6;
        }
-       else if (e_target.health <= 0)
+       else if (GetResourceAmount(e_target, RESOURCE_HEALTH) <= 0)
                return -6;
        else if(STAT(FROZEN, e_target) > 0)
                return -6;
@@ -1239,6 +1254,12 @@ void turret_initparams(entity tur)
        #undef TRY
 }
 
+bool turret_closetotarget(entity this, vector targ)
+{
+       vector path_extra_size = '64 64 64';
+       return boxesoverlap(targ - path_extra_size, targ + path_extra_size, this.absmin - path_extra_size, this.absmax + path_extra_size);
+}
+
 void turret_findtarget(entity this)
 {
        entity e = find(NULL, classname, "turret_manager");
@@ -1286,7 +1307,7 @@ bool turret_initialize(entity this, Turret tur)
 
        if(!this.team || !teamplay)             { this.team = FLOAT_MAX; }
        if(!this.ticrate)                               { this.ticrate = ((this.turret_flags & TUR_FLAG_SUPPORT) ? 0.2 : 0.1); }
-       if(!this.health)                                { this.health = 1000; }
+       if(!GetResourceAmount(this, RESOURCE_HEALTH)) { SetResourceAmountExplicit(this, RESOURCE_HEALTH, 1000); }
        if(!this.shot_refire)                   { this.shot_refire = 1; }
        if(!this.tur_shotorg)                   { this.tur_shotorg = '50 0 50'; }
        if(!this.turret_flags)                  { this.turret_flags = TUR_FLAG_SPLASH | TUR_FLAG_MEDPROJ | TUR_FLAG_PLAYER; }
@@ -1343,7 +1364,7 @@ bool turret_initialize(entity this, Turret tur)
        this.effects                            = EF_NODRAW;
        this.netname                            = tur.turret_name;
        this.ticrate                            = bound(sys_frametime, this.ticrate, 60);
-       this.max_health                         = this.health;
+       this.max_health                         = GetResourceAmount(this, RESOURCE_HEALTH);
        this.target_validate_flags      = this.target_select_flags;
        this.ammo                                       = this.ammo_max;
        this.ammo_recharge                 *= this.ticrate;
@@ -1354,6 +1375,7 @@ bool turret_initialize(entity this, Turret tur)
        this.idle_aim                           = '0 0 0';
        this.turret_firecheckfunc       = turret_firecheck;
        this.event_damage                       = turret_damage;
+       this.event_heal                         = turret_heal;
        this.use                                        = turret_use;
        this.bot_attack                         = true;
        this.nextthink                          = time + 1;
index 41a7bd962dc6e8a19ffcef03eb4291db7e55ed29..deee313ab109204ce7e1acbba2cb1aaaa6921aac 100644 (file)
@@ -89,6 +89,9 @@ void turrets_setframe(entity this, float _frame, float client_only);
 
 bool turret_initialize(entity this, Turret tur);
 
+// returns true when box overlaps with a given location
+bool turret_closetotarget(entity this, vector targ);
+
 /// Function to use for target evaluation. usualy turret_targetscore_generic
 .float(entity _turret, entity _target) turret_score_target;
 
index 5625d23fc935e8be8453afb9517ef40e333243a8..c0a0b177ee2d179afb99aeb774203521daf5eeeb 100644 (file)
@@ -17,7 +17,7 @@ const int ewheel_anim_bck_fast = 4;
 void ewheel_move_path(entity this)
 {
     // Are we close enough to a path node to switch to the next?
-    if(vdist(this.origin - this.pathcurrent.origin, <, 64))
+    if(turret_closetotarget(this, this.pathcurrent.origin))
     {
 #ifdef EWHEEL_FANCYPATH
         if (this.pathcurrent.path_next == NULL)
@@ -49,7 +49,6 @@ void ewheel_move_path(entity this)
 
     if (this.pathcurrent)
     {
-
         this.moveto = this.pathcurrent.origin;
         this.steerto = steerlib_attract2(this, this.moveto, 0.5, 500, 0.95);
 
@@ -229,17 +228,17 @@ void ewheel_draw(entity this)
     setorigin(this, this.origin + this.velocity * dt);
     this.tur_head.angles += dt * this.tur_head.avelocity;
 
-    if (this.health < 127)
+    if(GetResourceAmount(this, RESOURCE_HEALTH) < 127)
     if(random() < 0.05)
         te_spark(this.origin + '0 0 40', randomvec() * 256 + '0 0 256', 16);
 }
 
-        METHOD(EWheel, tr_setup, void(EWheel this, entity it))
-        {
-            it.gravity         = 1;
-            set_movetype(it, MOVETYPE_BOUNCE);
-            it.move_time               = time;
-            it.draw                    = ewheel_draw;
-        }
+METHOD(EWheel, tr_setup, void(EWheel this, entity it))
+{
+    it.gravity         = 1;
+    set_movetype(it, MOVETYPE_BOUNCE);
+    it.move_time               = time;
+    it.draw                    = ewheel_draw;
+}
 
 #endif // CSQC
index 3141b3d10f8c579fc596f9adc45d30b3b8ccea76..0e38ebfad388c01d01a858666917e494542f0ca1 100644 (file)
@@ -251,7 +251,7 @@ bool hk_is_valid_target(entity this, entity proj, entity targ)
         return false;
 
     // Cant touch this
-    if ((targ.takedamage == DAMAGE_NO) || (targ.health < 0))
+    if ((targ.takedamage == DAMAGE_NO) || (GetResourceAmount(targ, RESOURCE_HEALTH) < 0))
         return false;
 
     // player
index 8cf795a93895887139b2b014543af8c1623a6d97..6aa0865e69d1e4fac73d93ab9177f72278071985 100644 (file)
@@ -86,10 +86,10 @@ void walker_rocket_touch(entity this, entity toucher)
 
 void walker_rocket_damage(entity this, entity inflictor, entity attacker, float damage, float deathtype, .entity weaponentity, vector hitloc, vector vforce)
 {
-    this.health = this.health - damage;
+    TakeResource(this, RESOURCE_HEALTH, damage);
     this.velocity = this.velocity + vforce;
 
-    if (this.health <= 0)
+    if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
         W_PrepareExplosionByDamage(this, this.owner, walker_rocket_explode);
 }
 
@@ -218,7 +218,7 @@ void walker_fire_rocket(entity this, vector org)
     rocket.bot_dodgerating     = 50;
     rocket.takedamage           = DAMAGE_YES;
     rocket.damageforcescale   = 2;
-    rocket.health                       = 25;
+    SetResourceAmountExplicit(rocket, RESOURCE_HEALTH, 25);
     rocket.tur_shotorg         = randomvec() * 512;
     rocket.cnt                         = time + 1;
     rocket.enemy                         = this.enemy;
@@ -281,7 +281,8 @@ void walker_move_path(entity this)
 {
 #ifdef WALKER_FANCYPATHING
     // Are we close enougth to a path node to switch to the next?
-    if(vdist(this.origin - this.pathcurrent.origin, <, 64))
+    if(turret_closetotarget(this, this.pathcurrent.origin))
+    {
         if (this.pathcurrent.path_next == NULL)
         {
             // Path endpoint reached
@@ -304,13 +305,14 @@ void walker_move_path(entity this)
         }
         else
             this.pathcurrent = this.pathcurrent.path_next;
+    }
 
     this.moveto = this.pathcurrent.origin;
     this.steerto = steerlib_attract2(this, this.moveto,0.5,500,0.95);
     walker_move_to(this, this.moveto, 0);
 
 #else
-    if(vdist(this.origin - this.pathcurrent.origin, <, 64))
+    if(turret_closetotarget(this, this.pathcurrent.origin))
         this.pathcurrent = this.pathcurrent.enemy;
 
     if(!this.pathcurrent)
@@ -352,7 +354,7 @@ METHOD(WalkerTurret, tr_think, void(WalkerTurret thistur, entity it))
 {
     fixedmakevectors(it.angles);
 
-    if (it.spawnflags & TSF_NO_PATHBREAK && it.pathcurrent)
+    if ((it.spawnflags & TSF_NO_PATHBREAK) && it.pathcurrent)
         walker_move_path(it);
     else if (it.enemy == NULL)
     {
@@ -627,17 +629,17 @@ void walker_draw(entity this)
     setorigin(this, this.origin + this.velocity * dt);
     this.tur_head.angles += dt * this.tur_head.avelocity;
 
-    if (this.health < 127)
+    if(GetResourceAmount(this, RESOURCE_HEALTH) < 127)
     if(random() < 0.15)
         te_spark(this.origin + '0 0 40', randomvec() * 256 + '0 0 256', 16);
 }
 
-        METHOD(WalkerTurret, tr_setup, void(WalkerTurret this, entity it))
-        {
-            it.gravity         = 1;
-            set_movetype(it, MOVETYPE_BOUNCE);
-            it.move_time               = time;
-            it.draw                    = walker_draw;
-        }
+METHOD(WalkerTurret, tr_setup, void(WalkerTurret this, entity it))
+{
+    it.gravity         = 1;
+    set_movetype(it, MOVETYPE_BOUNCE);
+    it.move_time               = time;
+    it.draw                    = walker_draw;
+}
 
 #endif // CSQC
index 4c84f268d3024679102f158cf5e017bf229ff28a..4e5cb0d5b970a1b7cd57413db2fd5ca6649bf137 100644 (file)
@@ -5,4 +5,10 @@
 float turret_tag_fire_update(entity this);
 void FireImoBeam(entity this, vector start, vector end, vector smin, vector smax, float bforce, float f_dmg, float f_velfactor, float deathtype);
 
+#ifdef TURRET_DEBUG
+void mark_error(vector where,float lifetime);
+void mark_info(vector where,float lifetime);
+entity mark_misc(vector where,float lifetime);
+#endif
+
 #endif
index 95ab69ca6e39cbbea6a73898e8165be8860f35c2..183302b3a1e0460c246c9c1c8357edfbace43c53 100644 (file)
@@ -2,7 +2,7 @@
 
 #if defined(CSQC)
     #include "constants.qh"
-       #include "../client/mutators/events.qh"
+       #include <client/mutators/_mod.qh>
     #include "mapinfo.qh"
     #include "notifications/all.qh"
        #include "scores.qh"
 #elif defined(MENUQC)
 #elif defined(SVQC)
     #include "constants.qh"
-       #include "../server/mutators/events.qh"
+       #include <server/mutators/_mod.qh>
     #include "notifications/all.qh"
     #include <common/deathtypes/all.qh>
        #include "scores.qh"
     #include "mapinfo.qh"
 #endif
 
+#ifdef SVQC
+float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity) // returns the number of traces done, for benchmarking
+{
+       vector pos, dir, t;
+       float nudge;
+       entity stopentity;
+
+       //nudge = 2 * cvar("collision_impactnudge"); // why not?
+       nudge = 0.5;
+
+       dir = normalize(v2 - v1);
+
+       pos = v1 + dir * nudge;
+
+       float c;
+       c = 0;
+
+       for (;;)
+       {
+               if(pos * dir >= v2 * dir)
+               {
+                       // went too far
+                       trace_fraction = 1;
+                       trace_endpos = v2;
+                       return c;
+               }
+
+               tracebox(pos, mi, ma, v2, nomonsters, forent);
+               ++c;
+
+               if(c == 50)
+               {
+                       LOG_TRACE("When tracing from ", vtos(v1), " to ", vtos(v2));
+                       LOG_TRACE("  Nudging gets us nowhere at ", vtos(pos));
+                       LOG_TRACE("  trace_endpos is ", vtos(trace_endpos));
+                       LOG_TRACE("  trace distance is ", ftos(vlen(pos - trace_endpos)));
+               }
+
+               stopentity = trace_ent;
+
+               if(trace_startsolid)
+               {
+                       // we started inside solid.
+                       // then trace from endpos to pos
+                       t = trace_endpos;
+                       tracebox(t, mi, ma, pos, nomonsters, forent);
+                       ++c;
+                       if(trace_startsolid)
+                       {
+                               // t is still inside solid? bad
+                               // force advance, then, and retry
+                               pos = t + dir * nudge;
+
+                               // but if we hit an entity, stop RIGHT before it
+                               if(stopatentity && stopentity && stopentity != ignorestopatentity)
+                               {
+                                       trace_ent = stopentity;
+                                       trace_endpos = t;
+                                       trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
+                                       return c;
+                               }
+                       }
+                       else
+                       {
+                               // we actually LEFT solid!
+                               trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
+                               return c;
+                       }
+               }
+               else
+               {
+                       // pos is outside solid?!? but why?!? never mind, just return it.
+                       trace_endpos = pos;
+                       trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
+                       return c;
+               }
+       }
+}
+
+void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity)
+{
+       tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity);
+}
+#endif
+
 #ifdef GAMEQC
+/*
+==================
+findbetterlocation
+
+Returns a point at least 12 units away from walls
+(useful for explosion animations, although the blast is performed where it really happened)
+Ripped from DPMod
+==================
+*/
+vector findbetterlocation (vector org, float mindist)
+{
+       vector vec = mindist * '1 0 0';
+       int c = 0;
+       while (c < 6)
+       {
+               traceline (org, org + vec, true, NULL);
+               vec = vec * -1;
+               if (trace_fraction < 1)
+               {
+                       vector loc = trace_endpos;
+                       traceline (loc, loc + vec, true, NULL);
+                       if (trace_fraction >= 1)
+                               org = loc + vec;
+               }
+               if (c & 1)
+               {
+                       float h = vec.y;
+                       vec.y = vec.x;
+                       vec.x = vec.z;
+                       vec.z = h;
+               }
+               c = c + 1;
+       }
+
+       return org;
+}
+
 /*
 * Get "real" origin, in worldspace, even if ent is attached to something else.
 */
index 3304d0e7a455453b02615f44a5664bfaafe8cbdb..a1c0d6785fb416efa1b02c641d409b1e5e05d407 100644 (file)
@@ -1,6 +1,27 @@
 #pragma once
 
+#ifdef SVQC
+       #include <server/autocvars.qh>
+#endif
+
+#ifdef SVQC
+float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity); // returns the number of traces done, for benchmarking
+
+void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity);
+#endif
+
 #ifdef GAMEQC
+/*
+==================
+findbetterlocation
+
+Returns a point at least 12 units away from walls
+(useful for explosion animations, although the blast is performed where it really happened)
+Ripped from DPMod
+==================
+*/
+vector findbetterlocation (vector org, float mindist);
+
 vector real_origin(entity ent);
 #endif
 
index 0eaf69eac6eab5ffbbf057538603fb3a2d406d84..df0f5d91693946c7230fb95d37f0dfb6b773aecd 100644 (file)
@@ -205,9 +205,9 @@ void vehicles_projectile_damage(entity this, entity inflictor, entity attacker,
        if(inflictor.owner == this.owner)
                return;
 
-       this.health -= damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
        this.velocity += force;
-       if(this.health < 1)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) < 1)
        {
                this.takedamage = DAMAGE_NO;
                this.event_damage = func_null;
@@ -282,7 +282,7 @@ entity vehicles_projectile(entity this, string _mzlfx, Sound _mzlsound,
        {
                proj.takedamage    = DAMAGE_AIM;
                proj.event_damage        = vehicles_projectile_damage;
-               proj.health                = _health;
+               SetResourceAmountExplicit(proj, RESOURCE_HEALTH, _health);
        }
        else
                proj.flags |= FL_NOTARGET;
@@ -727,6 +727,20 @@ void vehicles_damage(entity this, entity inflictor, entity attacker, float damag
        }
 }
 
+bool vehicles_heal(entity targ, entity inflictor, float amount, float limit)
+{
+       float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
+       //if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
+       if(targ.vehicle_health <= 0 || targ.vehicle_health >= true_limit)
+               return false;
+
+       targ.vehicle_health = min(targ.vehicle_health + amount, true_limit);
+       //GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+       //if(targ.owner)
+               //targ.owner.vehicle_health = (targ.vehicle_health / targ.max_health) * 100;
+       return true;
+}
+
 bool vehicles_crushable(entity e)
 {
        if(IS_PLAYER(e) && time >= e.vehicle_enter_delay)
@@ -1005,6 +1019,7 @@ void vehicles_enter(entity pl, entity veh)
        setsize(pl, STAT(PL_MIN, pl), STAT(PL_MAX, pl));
 
        veh.event_damage        = vehicles_damage;
+       veh.event_heal          = vehicles_heal;
        veh.nextthink           = 0;
        pl.items &= ~IT_USING_JETPACK;
        pl.angles                       = veh.angles;
@@ -1118,6 +1133,7 @@ void vehicles_spawn(entity this)
        this.owner                              = NULL;
        settouch(this, vehicles_touch);
        this.event_damage               = vehicles_damage;
+       this.event_heal                 = vehicles_heal;
        this.reset                              = vehicles_reset;
        this.iscreature                 = true;
        this.teleportable               = false; // no teleporting for vehicles, too buggy
@@ -1230,6 +1246,7 @@ bool vehicle_initialize(entity this, Vehicle info, bool nodrop)
        this.vehicleid                          = info.vehicleid;
        this.PlayerPhysplug                     = info.PlayerPhysplug;
        this.event_damage                       = func_null;
+       this.event_heal                         = func_null;
        settouch(this, vehicles_touch);
        setthink(this, vehicles_spawn);
        this.nextthink                          = time;
index 0ddd02aa7e0ce77125b2a9dc835df604c54d8cf0..0cc9da56ea11a57779850fc85f1bdd53ca0864e1 100644 (file)
@@ -45,14 +45,14 @@ float autocvar_g_vehicles_weapon_damagerate = 2;
 .entity gunner1;
 .entity gunner2;
 
-.float vehicle_health = _STAT(VEHICLESTAT_HEALTH);  /// If ent is player this is 0..100 indicating precentage of health left on vehicle. If ent is vehile, this is the real health value.
-.float vehicle_energy = _STAT(VEHICLESTAT_ENERGY);  /// If ent is player this is 0..100 indicating precentage of energy left on vehicle. If ent is vehile, this is the real energy value.
-.float vehicle_shield = _STAT(VEHICLESTAT_SHIELD);  /// If ent is player this is 0..100 indicating precentage of shield left on vehicle. If ent is vehile, this is the real shield value.
+.float vehicle_health = _STAT(VEHICLESTAT_HEALTH);  /// If ent is player this is 0..100 indicating precentage of health left on vehicle. If ent is vehicle, this is the real health value.
+.float vehicle_energy = _STAT(VEHICLESTAT_ENERGY);  /// If ent is player this is 0..100 indicating precentage of energy left on vehicle. If ent is vehicle, this is the real energy value.
+.float vehicle_shield = _STAT(VEHICLESTAT_SHIELD);  /// If ent is player this is 0..100 indicating precentage of shield left on vehicle. If ent is vehicle, this is the real shield value.
 
-.float vehicle_ammo1 = _STAT(VEHICLESTAT_AMMO1);   /// If ent is player this is 0..100 indicating percentage of primary ammo left UNLESS value is already stored in vehicle_energy. If ent is vehile, this is the real ammo1 value.
-.float vehicle_reload1 = _STAT(VEHICLESTAT_RELOAD1); /// If ent is player this is 0..100 indicating percentage of primary reload status. If ent is vehile, this is the real reload1 value.
-.float vehicle_ammo2 = _STAT(VEHICLESTAT_AMMO2);   /// If ent is player this is 0..100 indicating percentage of secondary ammo left. If ent is vehile, this is the real ammo2 value.
-.float vehicle_reload2 = _STAT(VEHICLESTAT_RELOAD2); /// If ent is player this is 0..100 indicating percentage of secondary reload status. If ent is vehile, this is the real reload2 value.
+.float vehicle_ammo1 = _STAT(VEHICLESTAT_AMMO1);   /// If ent is player this is 0..100 indicating percentage of primary ammo left UNLESS value is already stored in vehicle_energy. If ent is vehicle, this is the real ammo1 value.
+.float vehicle_reload1 = _STAT(VEHICLESTAT_RELOAD1); /// If ent is player this is 0..100 indicating percentage of primary reload status. If ent is vehicle, this is the real reload1 value.
+.float vehicle_ammo2 = _STAT(VEHICLESTAT_AMMO2);   /// If ent is player this is 0..100 indicating percentage of secondary ammo left. If ent is vehicle, this is the real ammo2 value.
+.float vehicle_reload2 = _STAT(VEHICLESTAT_RELOAD2); /// If ent is player this is 0..100 indicating percentage of secondary reload status. If ent is vehicle, this is the real reload2 value.
 
 .float sound_nexttime;
 const float VOL_VEHICLEENGINE = 1;
@@ -79,7 +79,6 @@ const int MAX_AXH = 4;
 .float  lock_strength;
 .float  lock_time;
 .float  lock_soundtime;
-const float    DAMAGE_TARGETDRONE = 10;
 
 // vehicle functions
 .void(int _spawnflag) vehicle_spawn;  /// Vehicles custom fucntion to be efecuted when vehicle (re)spawns
@@ -87,7 +86,7 @@ const float   DAMAGE_TARGETDRONE = 10;
 .void(entity this, int exit_flags) vehicle_exit;
 .bool(entity this, entity player) vehicle_enter;
 const int VHEF_NORMAL = 0;  /// User pressed exit key
-const int VHEF_EJECT  = 1;  /// User pressed exit key 3 times fast (not implemented) or vehile is dying
+const int VHEF_EJECT  = 1;  /// User pressed exit key 3 times fast (not implemented) or vehicle is dying
 const int VHEF_RELEASE = 2;  /// Release ownership, client possibly allready dissconnected / went spec / changed team / used "kill" (not implemented)
 
 float  force_fromtag_power;
@@ -107,6 +106,7 @@ bool vehicle_initialize(entity this, Vehicle info, float nodrop);
 bool vehicle_impulse(entity this, int imp);
 bool vehicles_crushable(entity e);
 float vehicle_altitude(entity this, float amax);
+void vehicles_enter(entity pl, entity veh);
 
 IntrusiveList g_vehicle_returners;
 STATIC_INIT(g_vehicle_returners) { g_vehicle_returners = IL_NEW(); }
index 4e842a865c76b31f91eea2b698da3930ea6d8e85..c340d947035617492da6d224bbede286baf8e7be 100644 (file)
@@ -544,36 +544,27 @@ bool bumblebee_pilot_frame(entity this, float dt)
                        else
                        {
                                if(!IS_DEAD(trace_ent))
+                               {
                                        if((teamplay && trace_ent.team == this.team) || !teamplay)
                                        {
+                                               if(autocvar_g_vehicle_bumblebee_healgun_hps)
+                                               {
+                                                       float hplimit = ((IS_PLAYER(trace_ent)) ? autocvar_g_vehicle_bumblebee_healgun_hmax : RESOURCE_LIMIT_NONE);
+                                                       Heal(trace_ent, this, autocvar_g_vehicle_bumblebee_healgun_hps * dt, hplimit);
+                                               }
 
                                                if(IS_VEHICLE(trace_ent))
                                                {
                                                        if(autocvar_g_vehicle_bumblebee_healgun_sps && trace_ent.vehicle_health <= trace_ent.max_health)
                                                                trace_ent.vehicle_shield = min(trace_ent.vehicle_shield + autocvar_g_vehicle_bumblebee_healgun_sps * dt, trace_ent.tur_head.max_health);
-
-                                                       if(autocvar_g_vehicle_bumblebee_healgun_hps)
-                                                               trace_ent.vehicle_health = min(trace_ent.vehicle_health + autocvar_g_vehicle_bumblebee_healgun_hps * dt, trace_ent.max_health);
                                                }
                                                else if(IS_CLIENT(trace_ent))
                                                {
-                                                       if(trace_ent.health <= autocvar_g_vehicle_bumblebee_healgun_hmax && autocvar_g_vehicle_bumblebee_healgun_hps)
-                                                               trace_ent.health = min(trace_ent.health + autocvar_g_vehicle_bumblebee_healgun_hps * dt, autocvar_g_vehicle_bumblebee_healgun_hmax);
-
-                                                       if(trace_ent.armorvalue <= autocvar_g_vehicle_bumblebee_healgun_amax && autocvar_g_vehicle_bumblebee_healgun_aps)
-                                                               trace_ent.armorvalue = min(trace_ent.armorvalue + autocvar_g_vehicle_bumblebee_healgun_aps * dt, autocvar_g_vehicle_bumblebee_healgun_amax);
-
-                                                       trace_ent.health = min(trace_ent.health + autocvar_g_vehicle_bumblebee_healgun_hps * dt, autocvar_g_vehicle_bumblebee_healgun_hmax);
-                                               }
-                                               else if(IS_TURRET(trace_ent))
-                                               {
-                                                       if(trace_ent.health  <= trace_ent.max_health && autocvar_g_vehicle_bumblebee_healgun_hps)
-                                                               trace_ent.health = min(trace_ent.health + autocvar_g_vehicle_bumblebee_healgun_hps * dt, trace_ent.max_health);
-                                                       //else ..hmmm what? ammo?
-
-                                                       trace_ent.SendFlags |= TNSF_STATUS;
+                                                       if(GetResourceAmount(trace_ent, RESOURCE_ARMOR) <= autocvar_g_vehicle_bumblebee_healgun_amax && autocvar_g_vehicle_bumblebee_healgun_aps)
+                                                               GiveResourceWithLimit(trace_ent, RESOURCE_ARMOR, autocvar_g_vehicle_bumblebee_healgun_aps * dt, autocvar_g_vehicle_bumblebee_healgun_amax);
                                                }
                                        }
+                               }
                        }
                }
 
@@ -803,7 +794,7 @@ METHOD(Bumblebee, vr_death, void(Bumblebee thisveh, entity instance))
 
     Send_Effect(EFFECT_EXPLOSION_MEDIUM, findbetterlocation(instance.origin, 16), '0 0 0', 1);
 
-    instance.health                    = 0;
+    SetResourceAmountExplicit(instance, RESOURCE_HEALTH, 0);
     instance.event_damage      = func_null;
     instance.solid                     = SOLID_NOT;
     instance.takedamage                = DAMAGE_NO;
index 93ed6d31d531ac6b06baa424090d54718f8d2219..18e13bcbbe68d3f23bc558dad7b0e0f9427c4d2b 100644 (file)
@@ -566,7 +566,7 @@ METHOD(Racer, vr_death, void(Racer thisveh, entity instance))
 {
 #ifdef SVQC
     setSendEntity(instance, func_null); // stop networking this racer (for now)
-    instance.health                    = 0;
+    SetResourceAmountExplicit(instance, RESOURCE_HEALTH, 0);
     instance.event_damage      = func_null;
     instance.solid                     = SOLID_CORPSE;
     instance.takedamage                = DAMAGE_NO;
index bf3e4436205c22d91ee0c7a17a25faaf1108a034..f44dcc578464e7a2e485c5da437a286bd721bc87 100644 (file)
@@ -609,7 +609,7 @@ METHOD(Raptor, vr_enter, void(Raptor thisveh, entity instance))
 }
 METHOD(Raptor, vr_death, void(Raptor thisveh, entity instance))
 {
-    instance.health                            = 0;
+       SetResourceAmountExplicit(instance, RESOURCE_HEALTH, 0);
     instance.event_damage              = func_null;
     instance.solid                             = SOLID_CORPSE;
     instance.takedamage                        = DAMAGE_NO;
index 37c4fc391f78c16455127716451e4ace115eeb9f..53475d6cfd382e6efe95ae50ae09642b6fe04a89 100644 (file)
@@ -74,7 +74,7 @@ METHOD(RaptorFlare, wr_think, void(entity thiswep, entity actor, .entity weapone
             _flare.solid = SOLID_CORPSE;
             _flare.takedamage = DAMAGE_YES;
             _flare.event_damage = raptor_flare_damage;
-            _flare.health = 20;
+            SetResourceAmountExplicit(_flare, RESOURCE_HEALTH, 20);
             _flare.tur_impacttime = time + autocvar_g_vehicle_raptor_flare_lifetime;
             settouch(_flare, raptor_flare_touch);
         }
@@ -191,8 +191,8 @@ void raptor_flare_touch(entity this, entity toucher)
 
 void raptor_flare_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-    this.health -= damage;
-    if(this.health <= 0)
+    TakeResource(this, RESOURCE_HEALTH, damage);
+    if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
         delete(this);
 }
 
index 994a642d73ebac110dd657f7eba910d32aa28fa8..09d0eb2af9175c6012b0a903677483e2e7c6d0cc 100644 (file)
@@ -540,7 +540,7 @@ METHOD(Spiderbot, vr_think, void(Spiderbot thisveh, entity instance))
 }
 METHOD(Spiderbot, vr_death, void(Spiderbot thisveh, entity instance))
 {
-    instance.health                            = 0;
+       SetResourceAmountExplicit(instance, RESOURCE_HEALTH, 0);
     instance.event_damage              = func_null;
     instance.takedamage                        = DAMAGE_NO;
     settouch(instance, func_null);
index 2a151d1afcc0d402acc31fd6689ace7f50b0624e..9b4c0b46814308dfb037375345a6100c14d444f4 100644 (file)
@@ -367,4 +367,8 @@ ENUMCLASS_END(WFRAME)
 
 vector shotorg_adjust_values(vector vecs, bool y_is_right, bool visual, int algn);
 void CL_WeaponEntity_SetModel(entity this, string name, bool _anim);
+
+#ifdef SVQC
+void wframe_send(entity actor, entity weaponentity, vector a, bool restartanim);
+#endif
 #endif
index e5d4f2eb23253dccbe414e522d0bc5ec84e8c1f0..c7066e6fbd34720e16d0852f305c6fa36d426323 100644 (file)
@@ -1,6 +1,8 @@
 #include "arc.qh"
 
 #ifdef SVQC
+#include <common/gamemodes/gamemode/onslaught/sv_onslaught.qh>
+#include <common/gamemodes/gamemode/onslaught/sv_generator.qh>
 
 bool W_Arc_Beam_Send(entity this, entity to, int sf)
 {
@@ -102,16 +104,16 @@ void W_Arc_Bolt_Explode_use(entity this, entity actor, entity trigger)
 
 void W_Arc_Bolt_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
 
        if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1))
                return; // g_projectiles_damage says to halt
 
-       this.health = this.health - damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
        this.angles = vectoangles(this.velocity);
 
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                W_PrepareExplosionByDamage(this, attacker, getthink(this));
 }
 
@@ -138,7 +140,7 @@ void W_Arc_Attack_Bolt(Weapon thiswep, entity actor, .entity weaponentity)
        missile.bot_dodgerating = WEP_CVAR(arc, bolt_damage);
 
        missile.takedamage = DAMAGE_YES;
-       missile.health = WEP_CVAR(arc, bolt_health);
+       SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR(arc, bolt_health));
        missile.damageforcescale = WEP_CVAR(arc, bolt_damageforcescale);
        missile.event_damage = W_Arc_Bolt_Damage;
        missile.damagedbycontents = true;
@@ -189,11 +191,9 @@ void W_Arc_Beam_Think(entity this)
        if(
                !IS_PLAYER(own)
                ||
-               (!thiswep.wr_checkammo1(thiswep, own, weaponentity) && !(own.items & IT_UNLIMITED_WEAPON_AMMO))
-               ||
                IS_DEAD(own)
                ||
-               forbidWeaponUse(own)
+               !weapon_prepareattack_check(thiswep, own, weaponentity, this.beam_bursting, -1)
                ||
                own.(weaponentity).m_switchweapon != WEP_ARC
                ||
@@ -408,7 +408,7 @@ void W_Arc_Beam_Think(entity this)
                beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos);
                new_dir = WarpZone_TransformVelocity(WarpZone_trace_transform, new_dir);
 
-               float is_player = (
+               bool is_player = (
                        IS_PLAYER(trace_ent)
                        ||
                        trace_ent.classname == "body"
@@ -416,65 +416,42 @@ void W_Arc_Beam_Think(entity this)
                        IS_MONSTER(trace_ent)
                );
 
-               if(trace_ent && trace_ent.takedamage && (is_player || WEP_CVAR(arc, beam_nonplayerdamage)))
+               if(trace_ent)
                {
-                       // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
-                       // NO. trace_endpos should be just fine. If not,
-                       // that's an engine bug that needs proper debugging.
-                       vector hitorigin = trace_endpos;
-
-                       float falloff = ExponentialFalloff(
-                               WEP_CVAR(arc, beam_falloff_mindist),
-                               WEP_CVAR(arc, beam_falloff_maxdist),
-                               WEP_CVAR(arc, beam_falloff_halflifedist),
-                               vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg)
-                       );
-
-                       if(is_player && SAME_TEAM(own, trace_ent))
+                       if(SAME_TEAM(own, trace_ent))
                        {
-                               float roothealth, rootarmor;
-                               if(burst)
-                               {
-                                       roothealth = WEP_CVAR(arc, burst_healing_hps);
-                                       rootarmor = WEP_CVAR(arc, burst_healing_aps);
-                               }
-                               else
+                               float roothealth = ((burst) ? WEP_CVAR(arc, burst_healing_hps) : WEP_CVAR(arc, beam_healing_hps));
+                               float rootarmor = ((burst) ? WEP_CVAR(arc, burst_healing_aps) : WEP_CVAR(arc, beam_healing_aps));
+                               float hplimit = ((IS_PLAYER(trace_ent)) ? WEP_CVAR(arc, beam_healing_hmax) : RESOURCE_LIMIT_NONE);
+                               Heal(trace_ent, own, (roothealth * coefficient), hplimit);
+                               if(IS_PLAYER(trace_ent) && rootarmor)
                                {
-                                       roothealth = WEP_CVAR(arc, beam_healing_hps);
-                                       rootarmor = WEP_CVAR(arc, beam_healing_aps);
+                                       if(GetResourceAmount(trace_ent, RESOURCE_ARMOR) <= WEP_CVAR(arc, beam_healing_amax))
+                                       {
+                                               GiveResourceWithLimit(trace_ent, RESOURCE_ARMOR, (rootarmor * coefficient), WEP_CVAR(arc, beam_healing_amax));
+                                               trace_ent.pauserotarmor_finished = max(
+                                                       trace_ent.pauserotarmor_finished,
+                                                       time + autocvar_g_balance_pause_armor_rot
+                                               );
+                                       }
                                }
-
-                               if(trace_ent.health <= WEP_CVAR(arc, beam_healing_hmax) && roothealth)
-                               {
-                                       trace_ent.health = min(
-                                               trace_ent.health + (roothealth * coefficient),
-                                               WEP_CVAR(arc, beam_healing_hmax)
-                                       );
-                               }
-                               if(trace_ent.armorvalue <= WEP_CVAR(arc, beam_healing_amax) && rootarmor)
-                               {
-                                       trace_ent.armorvalue = min(
-                                               trace_ent.armorvalue + (rootarmor * coefficient),
-                                               WEP_CVAR(arc, beam_healing_amax)
-                                       );
-                               }
-
-                               // stop rot, set visual effect
                                if(roothealth || rootarmor)
-                               {
-                                       trace_ent.pauserothealth_finished = max(
-                                               trace_ent.pauserothealth_finished,
-                                               time + autocvar_g_balance_pause_health_rot
-                                       );
-                                       trace_ent.pauserotarmor_finished = max(
-                                               trace_ent.pauserotarmor_finished,
-                                               time + autocvar_g_balance_pause_armor_rot
-                                       );
                                        new_beam_type = ARC_BT_HEAL;
-                               }
                        }
-                       else
+                       else if(trace_ent.takedamage && (is_player || WEP_CVAR(arc, beam_nonplayerdamage)))
                        {
+                               // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
+                               // NO. trace_endpos should be just fine. If not,
+                               // that's an engine bug that needs proper debugging.
+                               vector hitorigin = trace_endpos;
+
+                               float falloff = ExponentialFalloff(
+                                       WEP_CVAR(arc, beam_falloff_mindist),
+                                       WEP_CVAR(arc, beam_falloff_maxdist),
+                                       WEP_CVAR(arc, beam_falloff_halflifedist),
+                                       vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg)
+                               );
+
                                float rootdamage;
                                if(is_player)
                                {
index e48bf5fb3481e8db22c4698662997a648adc3db0..77e0b734e2e7e2c1009ee42fb400691a9bca8ed1 100644 (file)
@@ -71,4 +71,6 @@ SPAWNFUNC_WEAPON(weapon_crylink, WEP_CRYLINK)
 
 .entity queuenext;
 .entity queueprev;
+
+void W_Crylink_Dequeue(entity e);
 #endif
index c53e110fddb07a699988578d09067db6e73a462c..ab97e3d7f0b6c5c16ba538092b3ece2bd2aeb4df 100644 (file)
@@ -289,20 +289,20 @@ void W_Devastator_Touch(entity this, entity toucher)
 
 void W_Devastator_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
 
        if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
                return; // g_projectiles_damage says to halt
 
-       this.health = this.health - damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
        this.angles = vectoangles(this.velocity);
 
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                W_PrepareExplosionByDamage(this, attacker, W_Devastator_Explode_think);
 }
 
-void W_Devastator_Attack(Weapon thiswep, entity actor, .entity weaponentity)
+void W_Devastator_Attack(Weapon thiswep, entity actor, .entity weaponentity, int fire)
 {
        W_DecreaseAmmo(thiswep, actor, WEP_CVAR(devastator, ammo), weaponentity);
 
@@ -324,7 +324,7 @@ void W_Devastator_Attack(Weapon thiswep, entity actor, .entity weaponentity)
 
        missile.takedamage = DAMAGE_YES;
        missile.damageforcescale = WEP_CVAR(devastator, damageforcescale);
-       missile.health = WEP_CVAR(devastator, health);
+       SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR(devastator, health));
        missile.event_damage = W_Devastator_Damage;
        missile.damagedbycontents = true;
        IL_PUSH(g_damagedbycontents, missile);
@@ -342,6 +342,7 @@ void W_Devastator_Attack(Weapon thiswep, entity actor, .entity weaponentity)
        setthink(missile, W_Devastator_Think);
        missile.nextthink = time;
        missile.cnt = time + WEP_CVAR(devastator, lifetime);
+       missile.rl_detonate_later = (fire & 2); // allow instant detonation
        missile.flags = FL_PROJECTILE;
        IL_PUSH(g_projectiles, missile);
        IL_PUSH(g_bot_dodge, missile);
@@ -358,6 +359,11 @@ void W_Devastator_Attack(Weapon thiswep, entity actor, .entity weaponentity)
 
        // common properties
        MUTATOR_CALLHOOK(EditProjectile, actor, missile);
+
+       if (time >= missile.nextthink)
+       {
+               getthink(missile)(missile);
+       }
 }
 
 METHOD(Devastator, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
@@ -431,7 +437,7 @@ METHOD(Devastator, wr_aim, void(entity thiswep, entity actor, .entity weaponenti
         // but don't fire a new shot at the same time!
         if(desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
             PHYS_INPUT_BUTTON_ATCK2(actor) = true;
-        if((skill > 6.5) && (selfdamage > actor.health))
+        if((skill > 6.5) && (selfdamage > GetResourceAmount(actor, RESOURCE_HEALTH)))
             PHYS_INPUT_BUTTON_ATCK2(actor) = false;
         //if(PHYS_INPUT_BUTTON_ATCK2(actor) == true)
         //     dprint(ftos(desirabledamage),"\n");
@@ -449,7 +455,7 @@ METHOD(Devastator, wr_think, void(entity thiswep, entity actor, .entity weaponen
             if(actor.(weaponentity).rl_release || WEP_CVAR(devastator, guidestop))
             if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR(devastator, refire)))
             {
-                W_Devastator_Attack(thiswep, actor, weaponentity);
+                W_Devastator_Attack(thiswep, actor, weaponentity, fire);
                 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(devastator, animtime), w_ready);
                 actor.(weaponentity).rl_release = 0;
             }
index 2276b6042ac1f45893aa0d2451c6d3b43f1f9ee5..89738289f2b84b0205020e9ae0b4f33eeca05afb 100644 (file)
@@ -258,7 +258,7 @@ void W_Electro_Orb_Stick(entity this, entity to)
 
        newproj.takedamage = this.takedamage;
        newproj.damageforcescale = this.damageforcescale;
-       newproj.health = this.health;
+       SetResourceAmountExplicit(newproj, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH));
        newproj.event_damage = this.event_damage;
        newproj.spawnshieldtime = this.spawnshieldtime;
        newproj.damagedbycontents = true;
@@ -300,7 +300,7 @@ void W_Electro_Orb_Touch(entity this, entity toucher)
 
 void W_Electro_Orb_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
 
        // note: combos are usually triggered by W_Electro_TriggerCombo, not damage
@@ -309,8 +309,8 @@ void W_Electro_Orb_Damage(entity this, entity inflictor, entity attacker, float
        if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, (is_combo ? 1 : -1)))
                return; // g_projectiles_damage says to halt
 
-       this.health = this.health - damage;
-       if(this.health <= 0)
+       TakeResource(this, RESOURCE_HEALTH, damage);
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
        {
                this.takedamage = DAMAGE_NO;
                this.nextthink = time;
@@ -381,7 +381,7 @@ void W_Electro_Attack_Orb(Weapon thiswep, entity actor, .entity weaponentity)
        setsize(proj, '-4 -4 -4', '4 4 4');
        proj.takedamage = DAMAGE_YES;
        proj.damageforcescale = WEP_CVAR_SEC(electro, damageforcescale);
-       proj.health = WEP_CVAR_SEC(electro, health);
+       SetResourceAmountExplicit(proj, RESOURCE_HEALTH, WEP_CVAR_SEC(electro, health));
        proj.event_damage = W_Electro_Orb_Damage;
        proj.flags = FL_PROJECTILE;
        IL_PUSH(g_projectiles, proj);
index ee4b2e0847f27b032837872c4070e3026254113f..84113b7020f210352d85360cfc5f6b0d6d488d61 100644 (file)
@@ -14,9 +14,9 @@ void W_Fireball_Explode(entity this, entity directhitentity)
        this.takedamage = DAMAGE_NO;
 
        // 1. dist damage
-       d = (this.realowner.health + this.realowner.armorvalue);
+       d = (GetResourceAmount(this.realowner, RESOURCE_HEALTH) + GetResourceAmount(this.realowner, RESOURCE_ARMOR));
        RadiusDamage(this, this.realowner, WEP_CVAR_PRI(fireball, damage), WEP_CVAR_PRI(fireball, edgedamage), WEP_CVAR_PRI(fireball, radius), NULL, NULL, WEP_CVAR_PRI(fireball, force), this.projectiledeathtype, this.weaponentity_fld, directhitentity);
-       if(this.realowner.health + this.realowner.armorvalue >= d)
+       if(GetResourceAmount(this.realowner, RESOURCE_HEALTH) + GetResourceAmount(this.realowner, RESOURCE_ARMOR) >= d)
        if(!this.cnt)
        {
                modeleffect_spawn("models/sphere/sphere.md3", 0, 0, this.origin, '0 0 0', '0 0 0', '0 0 0', 0, WEP_CVAR_PRI(fireball, bfgradius), 0.2, 0.05, 0.25);
@@ -119,14 +119,14 @@ void W_Fireball_Think(entity this)
 
 void W_Fireball_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
 
        if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
                return; // g_projectiles_damage says to halt
 
-       this.health = this.health - damage;
-       if(this.health <= 0)
+       TakeResource(this, RESOURCE_HEALTH, damage);
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
        {
                this.cnt = 1;
                W_PrepareExplosionByDamage(this, attacker, W_Fireball_Explode_think);
@@ -147,7 +147,7 @@ void W_Fireball_Attack1(entity actor, .entity weaponentity)
        proj.use = W_Fireball_Explode_use;
        setthink(proj, W_Fireball_Think);
        proj.nextthink = time;
-       proj.health = WEP_CVAR_PRI(fireball, health);
+       SetResourceAmountExplicit(proj, RESOURCE_HEALTH, WEP_CVAR_PRI(fireball, health));
        proj.team = actor.team;
        proj.event_damage = W_Fireball_Damage;
        proj.takedamage = DAMAGE_YES;
index d9164dc19ab6c999534cf72f868801f2750b466a..7e76ffb1cedf9616c6a9cdff802e61074197543d 100644 (file)
@@ -32,7 +32,7 @@ void W_Hagar_Explode2_use(entity this, entity actor, entity trigger)
 
 void W_Hagar_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
 
        float is_linkexplode = ( ((inflictor.owner != NULL) ? (inflictor.owner == this.owner) : true)
@@ -47,10 +47,10 @@ void W_Hagar_Damage(entity this, entity inflictor, entity attacker, float damage
        if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, is_linkexplode))
                return; // g_projectiles_damage says to halt
 
-       this.health = this.health - damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
        this.angles = vectoangles(this.velocity);
 
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                W_PrepareExplosionByDamage(this, attacker, getthink(this));
 }
 
@@ -91,7 +91,7 @@ void W_Hagar_Attack(Weapon thiswep, entity actor, .entity weaponentity)
        missile.bot_dodgerating = WEP_CVAR_PRI(hagar, damage);
 
        missile.takedamage = DAMAGE_YES;
-       missile.health = WEP_CVAR_PRI(hagar, health);
+       SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR_PRI(hagar, health));
        missile.damageforcescale = WEP_CVAR_PRI(hagar, damageforcescale);
        missile.event_damage = W_Hagar_Damage;
        missile.damagedbycontents = true;
@@ -137,7 +137,7 @@ void W_Hagar_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
        missile.bot_dodgerating = WEP_CVAR_SEC(hagar, damage);
 
        missile.takedamage = DAMAGE_YES;
-       missile.health = WEP_CVAR_SEC(hagar, health);
+       SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR_SEC(hagar, health));
        missile.damageforcescale = WEP_CVAR_SEC(hagar, damageforcescale);
        missile.event_damage = W_Hagar_Damage;
        missile.damagedbycontents = true;
@@ -200,7 +200,7 @@ void W_Hagar_Attack2_Load_Release(entity actor, .entity weaponentity)
                missile.bot_dodgerating = WEP_CVAR_SEC(hagar, damage);
 
                missile.takedamage = DAMAGE_YES;
-               missile.health = WEP_CVAR_SEC(hagar, health);
+               SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR_SEC(hagar, health));
                missile.damageforcescale = WEP_CVAR_SEC(hagar, damageforcescale);
                missile.event_damage = W_Hagar_Damage;
                missile.damagedbycontents = true;
@@ -255,9 +255,7 @@ void W_Hagar_Attack2_Load_Release(entity actor, .entity weaponentity)
 void W_Hagar_Attack2_Load(Weapon thiswep, entity actor, .entity weaponentity)
 {
        // loadable hagar secondary attack, must always run each frame
-       if(round_handler_IsActive() && !round_handler_IsRoundStarted())
-               return;
-       if(time < game_starttime)
+       if(!weapon_prepareattack_check(thiswep, actor, weaponentity, true, -1))
                return;
 
        bool loaded = actor.(weaponentity).hagar_load >= WEP_CVAR_SEC(hagar, load_max);
@@ -363,7 +361,7 @@ void W_Hagar_Attack2_Load(Weapon thiswep, entity actor, .entity weaponentity)
 
 void W_Hagar_Attack_Auto(Weapon thiswep, entity actor, .entity weaponentity, int fire)
 {
-       if(!(fire & 1) || actor.(weaponentity).hagar_load || actor.(weaponentity).hagar_loadblock || actor.(weaponentity).m_switchweapon != WEP_HAGAR)
+       if(!(fire & 1) || actor.(weaponentity).hagar_load || actor.(weaponentity).hagar_loadblock || actor.(weaponentity).m_switchweapon != WEP_HAGAR || !weapon_prepareattack_check(thiswep, actor, weaponentity, false, -1))
        {
                w_ready(thiswep, actor, weaponentity, fire);
                return;
index 985d2ae59ef3faabdd7fce020d09d0310ddfb3a0..e17e97620884e3ed75c1259a43f8da23d603abe2 100644 (file)
@@ -48,15 +48,15 @@ void W_Hook_Explode2_use(entity this, entity actor, entity trigger)
 
 void W_Hook_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
 
        if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
                return; // g_projectiles_damage says to halt
 
-       this.health = this.health - damage;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH));
 
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                W_PrepareExplosionByDamage(this, this.realowner, W_Hook_Explode2);
 }
 
@@ -88,7 +88,7 @@ void W_Hook_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
        settouch(gren, W_Hook_Touch2);
 
        gren.takedamage = DAMAGE_YES;
-       gren.health = WEP_CVAR_SEC(hook, health);
+       SetResourceAmountExplicit(gren, RESOURCE_HEALTH, WEP_CVAR_SEC(hook, health));
        gren.damageforcescale = WEP_CVAR_SEC(hook, damageforcescale);
        gren.event_damage = W_Hook_Damage;
        gren.damagedbycontents = true;
@@ -165,7 +165,7 @@ METHOD(Hook, wr_think, void(entity thiswep, entity actor, .entity weaponentity,
             {
                 if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
                 {
-                    if( actor.ammo_fuel >= (time - actor.(weaponentity).hook_time_fueldecrease) * hooked_fuel )
+                    if( GetResourceAmount(actor, RESOURCE_FUEL) >= (time - actor.(weaponentity).hook_time_fueldecrease) * hooked_fuel )
                     {
                         W_DecreaseAmmo(thiswep, actor, (time - actor.(weaponentity).hook_time_fueldecrease) * hooked_fuel, weaponentity);
                         actor.(weaponentity).hook_time_fueldecrease = time;
@@ -173,7 +173,7 @@ METHOD(Hook, wr_think, void(entity thiswep, entity actor, .entity weaponentity,
                     }
                     else
                     {
-                        actor.ammo_fuel = 0;
+                        SetResourceAmount(actor, RESOURCE_FUEL, 0);
                         actor.(weaponentity).hook_state |= HOOK_REMOVING;
                         if(actor.(weaponentity).m_weapon != WEP_Null) // offhand
                                W_SwitchWeapon_Force(actor, w_getbestweapon(actor, weaponentity), weaponentity);
@@ -214,9 +214,9 @@ METHOD(Hook, wr_checkammo1, bool(Hook thiswep, entity actor, .entity weaponentit
     if (!thiswep.ammo_factor) return true;
 
     if(actor.(weaponentity).hook)
-       return actor.ammo_fuel > 0;
+       return GetResourceAmount(actor, RESOURCE_FUEL) > 0;
 
-    return actor.ammo_fuel >= WEP_CVAR_PRI(hook, ammo);
+    return GetResourceAmount(actor, RESOURCE_FUEL) >= WEP_CVAR_PRI(hook, ammo);
 }
 METHOD(Hook, wr_checkammo2, bool(Hook thiswep, entity actor, .entity weaponentity))
 {
index b5c3bf2fa42555bf51224afd9a759e394bc772a9..6319dc36ae3ba04b6c5324e6bb6743e547b47e86 100644 (file)
@@ -86,7 +86,7 @@ void W_MachineGun_Attack(Weapon thiswep, int deathtype, entity actor, .entity we
 // weapon frames
 void W_MachineGun_Attack_Frame(Weapon thiswep, entity actor, .entity weaponentity, int fire)
 {
-       if(actor.(weaponentity).m_weapon != actor.(weaponentity).m_switchweapon) // abort immediately if switching
+       if(actor.(weaponentity).m_weapon != actor.(weaponentity).m_switchweapon || !weapon_prepareattack_check(thiswep, actor, weaponentity, (fire & 2), -1)) // abort immediately if switching
        {
                w_ready(thiswep, actor, weaponentity, fire);
                return;
@@ -113,7 +113,7 @@ void W_MachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity weaponentity
 {
        float machinegun_spread;
 
-       if(!(fire & 1))
+       if(!(fire & 1) || !weapon_prepareattack_check(thiswep, actor, weaponentity, false, -1))
        {
                w_ready(thiswep, actor, weaponentity, fire);
                return;
index d8075c9fe4d167cf127b4411252bb7cdd75f58db..6d3270a2b22788e6539f82115f93d72a2ef8b18c 100644 (file)
@@ -27,7 +27,7 @@ void W_MineLayer_Stick(entity this, entity to)
 
        newmine.takedamage = this.takedamage;
        newmine.damageforcescale = this.damageforcescale;
-       newmine.health = this.health;
+       SetResourceAmountExplicit(newmine, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH));
        newmine.event_damage = this.event_damage;
        newmine.spawnshieldtime = this.spawnshieldtime;
        newmine.damagedbycontents = true;
@@ -249,7 +249,7 @@ void W_MineLayer_Touch(entity this, entity toucher)
 
 void W_MineLayer_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
 
        float is_from_enemy = (inflictor.realowner != this.realowner);
@@ -257,10 +257,10 @@ void W_MineLayer_Damage(entity this, entity inflictor, entity attacker, float da
        if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, (is_from_enemy ? 1 : -1)))
                return; // g_projectiles_damage says to halt
 
-       this.health = this.health - damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
        this.angles = vectoangles(this.velocity);
 
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                W_PrepareExplosionByDamage(this, attacker, W_MineLayer_Explode_think);
 }
 
@@ -300,7 +300,7 @@ void W_MineLayer_Attack(Weapon thiswep, entity actor, .entity weaponentity)
 
        mine.takedamage = DAMAGE_YES;
        mine.damageforcescale = WEP_CVAR(minelayer, damageforcescale);
-       mine.health = WEP_CVAR(minelayer, health);
+       SetResourceAmountExplicit(mine, RESOURCE_HEALTH, WEP_CVAR(minelayer, health));
        mine.event_damage = W_MineLayer_Damage;
        mine.damagedbycontents = true;
        IL_PUSH(g_damagedbycontents, mine);
@@ -439,7 +439,7 @@ METHOD(MineLayer, wr_aim, void(entity thiswep, entity actor, .entity weaponentit
         // but don't fire a new shot at the same time!
         if(desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
             PHYS_INPUT_BUTTON_ATCK2(actor) = true;
-        if((skill > 6.5) && (selfdamage > actor.health))
+        if((skill > 6.5) && (selfdamage > GetResourceAmount(actor, RESOURCE_HEALTH)))
             PHYS_INPUT_BUTTON_ATCK2(actor) = false;
         //if(PHYS_INPUT_BUTTON_ATCK2(actor) == true)
         //     dprint(ftos(desirabledamage),"\n");
index 2dcde20d044ff8f0e85c3cf8fc75bce808e96f4d..51267e50d4f80ad7d2d1de58b156328a2bd967ca 100644 (file)
@@ -54,15 +54,15 @@ void W_Mortar_Grenade_Explode2_use(entity this, entity actor, entity trigger)
 
 void W_Mortar_Grenade_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
 
        if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
                return; // g_projectiles_damage says to halt
 
-       this.health = this.health - damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
 
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                W_PrepareExplosionByDamage(this, attacker, adaptor_think2use);
 }
 
@@ -176,7 +176,7 @@ void W_Mortar_Attack(Weapon thiswep, entity actor, .entity weaponentity)
        settouch(gren, W_Mortar_Grenade_Touch1);
 
        gren.takedamage = DAMAGE_YES;
-       gren.health = WEP_CVAR_PRI(mortar, health);
+       SetResourceAmountExplicit(gren, RESOURCE_HEALTH, WEP_CVAR_PRI(mortar, health));
        gren.damageforcescale = WEP_CVAR_PRI(mortar, damageforcescale);
        gren.event_damage = W_Mortar_Grenade_Damage;
        gren.damagedbycontents = true;
@@ -227,7 +227,7 @@ void W_Mortar_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
        settouch(gren, W_Mortar_Grenade_Touch2);
 
        gren.takedamage = DAMAGE_YES;
-       gren.health = WEP_CVAR_SEC(mortar, health);
+       SetResourceAmountExplicit(gren, RESOURCE_HEALTH, WEP_CVAR_SEC(mortar, health));
        gren.damageforcescale = WEP_CVAR_SEC(mortar, damageforcescale);
        gren.event_damage = W_Mortar_Grenade_Damage;
        gren.damagedbycontents = true;
index 9f79bf440c44bf6b42950d5595c1e99af5f39c16..243f5bc34af0497394cfd9cf1c83ffe9739f991e 100644 (file)
@@ -2,6 +2,7 @@
 
 #ifdef SVQC
 #include <common/mapobjects/trigger/jumppads.qh>
+#include <server/weapons/throwing.qh>
 
 REGISTER_MUTATOR(porto_ticker, true);
 MUTATOR_HOOKFUNCTION(porto_ticker, SV_StartFrame) {
@@ -20,7 +21,6 @@ void W_Porto_Success(entity this)
        delete(this);
 }
 
-string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo, .entity weaponentity);
 void W_Porto_Fail(entity this, float failhard)
 {
        if(this.realowner == NULL)
index 93b3a6e9f7da4d73251da4dd731ab1810390227c..a77093944b6e0fbab37bb5ca42aafc5eacc39174 100644 (file)
@@ -44,4 +44,6 @@ SPAWNFUNC_WEAPON(weapon_porto, WEP_PORTO)
 .float porto_v_angle_held;
 .vector right_vector;
 .float porto_forbidden;
+
+void W_Porto_Fail(entity this, float failhard);
 #endif
index 0f0c426eccac166645dced19d330f71452d14ebb..10080bbad275bd71b067ffcefcfd333b03998894 100644 (file)
@@ -124,18 +124,18 @@ void W_Seeker_Missile_Think(entity this)
 
 void W_Seeker_Missile_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
 
        if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
                return; // g_projectiles_damage says to halt
 
        if(this.realowner == attacker)
-               this.health = this.health - (damage * 0.25);
+               TakeResource(this, RESOURCE_HEALTH, (damage * 0.25));
        else
-               this.health = this.health - damage;
+               TakeResource(this, RESOURCE_HEALTH, damage);
 
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                W_PrepareExplosionByDamage(this, attacker, W_Seeker_Missile_Explode_think);
 }
 
@@ -190,7 +190,7 @@ void W_Seeker_Fire_Missile(Weapon thiswep, entity actor, .entity weaponentity, v
        missile.scale           = 2;
        missile.takedamage      = DAMAGE_YES;
        missile.weaponentity_fld = weaponentity;
-       missile.health          = WEP_CVAR(seeker, missile_health);
+       SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR(seeker, missile_health));
        missile.damageforcescale = WEP_CVAR(seeker, missile_damageforcescale);
        missile.damagedbycontents = true;
        IL_PUSH(g_damagedbycontents, missile);
@@ -415,10 +415,10 @@ void W_Seeker_Tag_Explode(entity this)
 
 void W_Seeker_Tag_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
-       this.health = this.health - damage;
-       if(this.health <= 0)
+       TakeResource(this, RESOURCE_HEALTH, damage);
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                W_Seeker_Tag_Explode(this);
 }
 
@@ -506,7 +506,7 @@ void W_Seeker_Fire_Tag(Weapon thiswep, entity actor, .entity weaponentity)
 
        missile.takedamage       = DAMAGE_YES;
        missile.event_damage     = W_Seeker_Tag_Damage;
-       missile.health           = WEP_CVAR(seeker, tag_health);
+       SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR(seeker, tag_health));
        missile.damageforcescale = WEP_CVAR(seeker, tag_damageforcescale);
 
        setorigin(missile, w_shotorg);
index c3baa12127b6c3b39a947aa8fec40524362a1443..2dd5cae1552e6434c95a6eddd0cdbfa9cb2cd226 100644 (file)
@@ -295,7 +295,7 @@ METHOD(Vaporizer, wr_think, void(entity thiswep, entity actor, .entity weaponent
     } else if(WEP_CVAR(vaporizer, reload_ammo) && actor.(weaponentity).clip_load < vaporizer_ammo) { // forced reload
         thiswep.wr_reload(thiswep, actor, weaponentity);
     }
-    if((fire & 1) && (actor.ammo_cells || !autocvar_g_rm) && !forbidWeaponUse(actor))
+    if((fire & 1) && (GetResourceAmount(actor, RESOURCE_CELLS) || !autocvar_g_rm) && !forbidWeaponUse(actor))
     {
         if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(vaporizer, refire)))
         {
@@ -303,7 +303,7 @@ METHOD(Vaporizer, wr_think, void(entity thiswep, entity actor, .entity weaponent
             weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(vaporizer, animtime), w_ready);
         }
     }
-    if((fire & 2) || ((fire & 1) && !actor.ammo_cells && autocvar_g_rm))
+    if((fire & 2) || ((fire & 1) && !GetResourceAmount(actor, RESOURCE_CELLS) && autocvar_g_rm))
     {
         if((autocvar_g_rm && autocvar_g_rm_laser) || autocvar_g_rm_laser == 2)
         {
index 225c7307ded6c60a192056eafde3078912c4ae52..f87f00033762b4fbe18f2d5305989941a191828b 100644 (file)
@@ -291,7 +291,7 @@ void CSQCPlayer_SetCamera()
                // note: these two only work in WIP2, but are harmless in WIP1
                if (PHYS_HEALTH(NULL) <= 0 && PHYS_HEALTH(NULL) != -666 && PHYS_HEALTH(NULL) != -2342) refdefflags |= REFDEFFLAG_DEAD;
                if (intermission) refdefflags |= REFDEFFLAG_INTERMISSION;
-               V_CalcRefdef(view, refdefflags);
+               V_CalcRefdef(view, refdefflags); // TODO? uses .health stat in the engine when this isn't called here, may be broken!
        }
        else
        {
index e727b39e6446acbb10e8745c5b0a5bb93d3fcdea..bd45230c97ab61964f43a7153d69242fb896e38c 100644 (file)
@@ -3,7 +3,7 @@
 #include "../menu.qh"
 #include "../item.qh"
 
-#include "../mutators/events.qh"
+#include <menu/mutators/_mod.qh>
 
 #include <common/command/_mod.qh>
 
index 98fb4815c1ce28cc699a429537ea75c2643b4487..26980926907738b2dcf21eff8fd91fb0392e639f 100644 (file)
@@ -1 +1,2 @@
 // generated file; do not modify
+#include <menu/mutators/events.qc>
index 98fb4815c1ce28cc699a429537ea75c2643b4487..93432be42cad402636ed8ad992678b44471b873f 100644 (file)
@@ -1 +1,2 @@
 // generated file; do not modify
+#include <menu/mutators/events.qh>
diff --git a/qcsrc/menu/mutators/events.qc b/qcsrc/menu/mutators/events.qc
new file mode 100644 (file)
index 0000000..c2dbb70
--- /dev/null
@@ -0,0 +1 @@
+#include "events.qh"
index 994c5a7c95cc3f44ff03e6e842aad3ff864a7f2c..21e7ecadc454fdc7168a1e0c5314892f5d028749 100644 (file)
@@ -87,7 +87,7 @@ string XonoticMutatorsDialog_toString(entity me)
        if(cvar("g_bloodloss") > 0)
                s = cons_mid(s, ", ", _("Blood loss"));
        if(cvar("g_jetpack"))
-               s = cons_mid(s, ", ", _("Jet pack"));
+               s = cons_mid(s, ", ", _("Jetpack"));
        if(cvar("g_buffs") > 0)
                s = cons_mid(s, ", ", _("Buffs"));
        if(cvar("g_overkill"))
@@ -209,7 +209,7 @@ void XonoticMutatorsDialog_fill(entity me)
                        _("Players spawn with the grappling hook")));
        me.TR(me);
                me.TDempty(me, 0.2);
-               me.TD(me, 1, 1.8, e = makeXonoticCheckBox_T(0, "g_jetpack", _("Jet pack"),
+               me.TD(me, 1, 1.8, e = makeXonoticCheckBox_T(0, "g_jetpack", _("Jetpack"),
                        _("Players spawn with the jetpack")));
        me.TR(me);
                me.TDempty(me, 0.2);
@@ -252,14 +252,12 @@ void XonoticMutatorsDialog_fill(entity me)
                w = Weapons_from(i);
                if(w.spawnflags & WEP_FLAG_HIDDEN)
                        continue;
-               if((j & 1) == 0)
-                       me.TDempty(me, 0.2);
-               else
+               if ((j % 3) == 0)
                {
                        me.TR(me);
                        me.TDempty(me, 0.4);
                }
-               me.TD(me, 1, 1.7, e = makeXonoticWeaponarenaCheckBox(strzone(w.netname), strzone(w.m_name)));
+               me.TD(me, 1, 1.2, e = makeXonoticWeaponarenaCheckBox(strzone(w.netname), strzone(w.m_name)));
                        setDependentWeird(e, checkCompatibility_weaponarena_weapon);
                ++j;
        }
index cde80d693d422a6a44b1664cd06f29fa7ceee87d..a14406bcec7202aa518df979bcb29f619f5c90a3 100644 (file)
@@ -34,7 +34,7 @@ void Xonotic_KeyBinds_Read()
        KEYBIND_DEF("+jump"                                 , _("jump / swim"));
        KEYBIND_DEF("+crouch"                               , _("crouch / sink"));
        KEYBIND_DEF("+hook"                                 , _("off-hand hook"));
-       KEYBIND_DEF("+jetpack"                              , _("jet pack"));
+       KEYBIND_DEF("+jetpack"                              , _("jetpack"));
        KEYBIND_DEF(""                                      , "");
        KEYBIND_DEF(""                                      , _("Attacking"));
        KEYBIND_DEF("+fire"                                 , _("primary fire"));
index 0afd27c8d67ee5802309671cc895bf37f3dd34fa..110b796be61d09ce0375b4827ec663fc1707e773 100644 (file)
@@ -1,6 +1,6 @@
 #include "mainwindow.qh"
 
-#include "../mutators/events.qh"
+#include <menu/mutators/_mod.qh>
 
 #include "nexposee.qh"
 #include "inputbox.qh"
index 9f6a2a76b4c8ebc4ce482315d95a77fec8870290..a4cb8bd5e1c5779b2274a01448ceeb39a689c867 100644 (file)
@@ -6,7 +6,6 @@
 #include <server/client.qc>
 #include <server/g_damage.qc>
 #include <server/g_hook.qc>
-#include <server/g_subs.qc>
 #include <server/g_world.qc>
 #include <server/handicap.qc>
 #include <server/impulse.qc>
index 527ec0dfbe4fd1c32d7779df82fb844a79dd63ac..0a00d680786fa51c84701339d0b9f82fe01000aa 100644 (file)
@@ -6,7 +6,6 @@
 #include <server/client.qh>
 #include <server/g_damage.qh>
 #include <server/g_hook.qh>
-#include <server/g_subs.qh>
 #include <server/g_world.qh>
 #include <server/handicap.qh>
 #include <server/impulse.qh>
index 4062f7f660df2d15dfb01ea48bdf822a81599995..c6e26e09e8fdbf77e641a6f9937e1ade5e60ac24 100644 (file)
@@ -5,6 +5,7 @@
     #include <server/defs.qh>
     #include <common/state.qh>
     #include <common/vehicles/all.qh>
+       #include <lib/warpzone/common.qh>
     #include "antilag.qh"
 #endif
 
@@ -146,3 +147,78 @@ void antilag_restore_all(entity ignore)
                antilag_restore(it, it);
        });
 }
+
+/*
+==================
+traceline_antilag
+
+A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
+Additionally it moves players back into the past before the trace and restores them afterward.
+==================
+*/
+void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
+{
+       // check whether antilagged traces are enabled
+       if (lag < 0.001)
+               lag = 0;
+       if (!IS_REAL_CLIENT(forent))
+               lag = 0; // only antilag for clients
+
+       // change shooter to SOLID_BBOX so the shot can hit corpses
+       int oldsolid = source.dphitcontentsmask;
+       if(source)
+               source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
+
+       if (lag)
+               antilag_takeback_all(forent, lag);
+
+       // do the trace
+       if(wz)
+               WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
+       else
+               tracebox (v1, mi, ma, v2, nomonst, forent);
+
+       // restore players to current positions
+       if (lag)
+               antilag_restore_all(forent);
+
+       // restore shooter solid type
+       if(source)
+               source.dphitcontentsmask = oldsolid;
+}
+void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
+{
+       tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, false);
+}
+void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
+{
+       bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
+       if (autocvar_g_antilag != 2 || noantilag)
+               lag = 0;
+       traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
+}
+void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
+{
+       bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
+       if (autocvar_g_antilag != 2 || noantilag)
+               lag = 0;
+       tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, false);
+}
+void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
+{
+       tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, true);
+}
+void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
+{
+       bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
+       if (autocvar_g_antilag != 2 || noantilag)
+               lag = 0;
+       WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
+}
+void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
+{
+       bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
+       if (autocvar_g_antilag != 2 || noantilag)
+               lag = 0;
+       tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, true);
+}
index d57762ccd3899ef8588a560229b39e25ac89210e..c3be5553a946837a3f8e7960121fdc0d0681cb66 100644 (file)
@@ -13,3 +13,19 @@ void antilag_restore_all(entity ignore);
 
 #define ANTILAG_LATENCY(e) min(0.4, CS(e).ping * 0.001)
 // add one ticrate?
+
+/*
+==================
+traceline_antilag
+
+A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
+Additionally it moves players back into the past before the trace and restores them afterward.
+==================
+*/
+void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz);
+void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
+void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
+void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag);
+void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
+void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
+void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag);
index c1abd004105df81edf5525d4bb705912e3e1ba56..7d73a73a8b7e65086756c1e261a1b9edcacd3b90 100644 (file)
@@ -257,7 +257,6 @@ bool autocvar_lastlevel;
 //int autocvar_leadlimit;
 int autocvar_leadlimit_and_fraglimit;
 int autocvar_leadlimit_override;
-int autocvar_loddebug;
 int autocvar_minplayers;
 string autocvar_nextmap;
 string autocvar_quit_and_redirect;
index 51ac148fc13c42c1185866cd87d6430165f61b8e..0ecd7b87725b41f7ca894570f7343836a845d3da 100644 (file)
@@ -79,7 +79,9 @@ bool havocbot_goalrating_item_pickable_check_players(entity this, vector org, en
 
 vector havocbot_middlepoint;
 float havocbot_middlepoint_radius;
-vector havocbot_symmetryaxis_equation;
+float havocbot_symmetry_axis_m;
+float havocbot_symmetry_axis_q;
+float havocbot_symmetry_origin_order;
 
 .float goalentity_lock_timeout;
 .float ignoregoaltime;
index 383ead19431caf409d052165203f23a8da05b26a..3a9befde403f9ee2a8fb6c2f99076af8ca433d9f 100644 (file)
@@ -11,7 +11,7 @@
 
 #include "../../weapons/weaponsystem.qh"
 
-#include "../../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
 
 // traces multiple trajectories to find one that will impact the target
 // 'end' vector is the place it aims for,
index b4272e3e0648224f6de70f8a1e2bd8f871bb0b5c..2c5627bf63daa7311af2cdcf3e6bd84c7680787c 100644 (file)
@@ -21,7 +21,7 @@
 #include "../../race.qh"
 #include <common/t_items.qh>
 
-#include "../../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
 
 #include "../../weapons/accuracy.qh"
 
@@ -74,12 +74,6 @@ void bot_think(entity this)
 
        this.bot_nextthink = max(time, this.bot_nextthink) + max(0.01, autocvar_bot_ai_thinkinterval * (0.5 ** this.bot_aiskill) * min(14 / (skill + 14), 1));
 
-       //if (this.bot_painintensity > 0)
-       //      this.bot_painintensity = this.bot_painintensity - (skill + 1) * 40 * frametime;
-
-       //this.bot_painintensity = this.bot_painintensity + this.bot_oldhealth - this.health;
-       //this.bot_painintensity = bound(0, this.bot_painintensity, 100);
-
        if (!IS_PLAYER(this) || (autocvar_g_campaign && !campaign_bots_may_start))
        {
                CS(this).movement = '0 0 0';
index 91b5c463d8fdcc007ff998480f7ecc93c92d8156..13e1c24c363397de6016ac9f81cce909d298af1c 100644 (file)
@@ -500,7 +500,7 @@ void havocbot_movetogoal(entity this)
        // Jetpack navigation
        if(this.navigation_jetpack_goal)
        if(this.goalcurrent==this.navigation_jetpack_goal)
-       if(this.ammo_fuel)
+       if(GetResourceAmount(this, RESOURCE_FUEL))
        {
                if(autocvar_bot_debug_goalstack)
                {
@@ -695,7 +695,7 @@ void havocbot_movetogoal(entity this)
 
                        return;
                }
-               else if(this.health + this.armorvalue > ROCKETJUMP_DAMAGE())
+               else if(GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR) > ROCKETJUMP_DAMAGE())
                {
                        if(this.velocity.z < 0)
                        {
@@ -1167,7 +1167,7 @@ void havocbot_chooseenemy(entity this)
                        traceline(this.origin+this.view_ofs, ( this.enemy.absmin + this.enemy.absmax ) * 0.5,false,NULL);
                        if (trace_ent == this.enemy || trace_fraction == 1)
                        if (vdist(((this.enemy.absmin + this.enemy.absmax) * 0.5) - this.origin, <, 1000))
-                       if (this.health > 30)
+                       if (GetResourceAmount(this, RESOURCE_HEALTH) > 30)
                        {
                                // remain tracking him for a shot while (case he went after a small corner or pilar
                                this.havocbot_chooseenemy_finished = time + 0.5;
index 4c70c1b1bda385a12ee7edf1b25bc97273f36da5..9ca2b1208e91a2e828b13ba4ce1646111ac1fc22 100644 (file)
@@ -3,6 +3,7 @@
 #include <server/defs.qh>
 #include <server/miscfunctions.qh>
 #include <server/items.qh>
+#include <server/resources.qh>
 #include "havocbot.qh"
 
 #include "../cvars.qh"
@@ -49,14 +50,14 @@ void havocbot_goalrating_waypoints(entity this, float ratingscale, vector org, f
 
 bool havocbot_goalrating_item_can_be_left_to_teammate(entity this, entity player, entity item)
 {
-       if (item.health && player.health <= this.health) {return true;}
-       if (item.armorvalue && player.armorvalue <= this.armorvalue) {return true;}
+       if (GetResourceAmount(item, RESOURCE_HEALTH) && GetResourceAmount(player, RESOURCE_HEALTH) <= GetResourceAmount(this, RESOURCE_HEALTH)) {return true;}
+       if (GetResourceAmount(item, RESOURCE_ARMOR) && GetResourceAmount(player, RESOURCE_ARMOR) <= GetResourceAmount(this, RESOURCE_ARMOR)) {return true;}
        if (STAT(WEAPONS, item) && !(STAT(WEAPONS, player) & STAT(WEAPONS, item))) {return true;}
-       if (item.ammo_shells && player.ammo_shells <= this.ammo_shells) {return true;}
-       if (item.ammo_nails && player.ammo_nails <= this.ammo_nails) {return true;}
-       if (item.ammo_rockets && player.ammo_rockets <= this.ammo_rockets) {return true;}
-       if (item.ammo_cells && player.ammo_cells <= this.ammo_cells) {return true;}
-       if (item.ammo_plasma && player.ammo_plasma <= this.ammo_plasma) {return true;}
+       if (GetResourceAmount(item, RESOURCE_SHELLS) && GetResourceAmount(player, RESOURCE_SHELLS) <= GetResourceAmount(this, RESOURCE_SHELLS)) {return true;}
+       if (GetResourceAmount(item, RESOURCE_BULLETS) && GetResourceAmount(player, RESOURCE_BULLETS) <= GetResourceAmount(this, RESOURCE_BULLETS)) {return true;}
+       if (GetResourceAmount(item, RESOURCE_ROCKETS) && GetResourceAmount(player, RESOURCE_ROCKETS) <= GetResourceAmount(this, RESOURCE_ROCKETS)) {return true;}
+       if (GetResourceAmount(item, RESOURCE_CELLS) && GetResourceAmount(player, RESOURCE_CELLS) <= GetResourceAmount(this, RESOURCE_CELLS)) {return true;}
+       if (GetResourceAmount(item, RESOURCE_PLASMA) && GetResourceAmount(player, RESOURCE_PLASMA) <= GetResourceAmount(this, RESOURCE_PLASMA)) {return true;}
        if (item.itemdef.instanceOfPowerup) {return true;}
 
        return false;
@@ -207,7 +208,7 @@ void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org
                        continue;
                */
 
-               t = ((this.health + this.armorvalue) - (it.health + it.armorvalue)) / 150;
+               t = ((GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR)) - (GetResourceAmount(it, RESOURCE_HEALTH) + GetResourceAmount(it, RESOURCE_ARMOR))) / 150;
                t = bound(0, 1 + t, 3);
                if (skill > 3)
                {
index 9334f011a6773289eb3df36e4cbbd66569e5e867..512d3a2062b0049cc8295b892c518a4ec2fe7f78 100644 (file)
@@ -246,6 +246,7 @@ vector resurface_limited(vector org, float lim, vector m1)
 // rough simulation of walking from one point to another to test if a path
 // can be traveled, used for waypoint linking and havocbot
 // if end_height is > 0 destination is any point in the vertical segment [end, end + end_height * eZ]
+// INFO: the command sv_cmd trace walk is useful to test this function in game
 bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode)
 {
        if(autocvar_bot_debug_tracewalk)
@@ -1314,10 +1315,10 @@ void navigation_routerating(entity this, entity e, float f, float rangebias)
                        t += xydistance / autocvar_g_jetpack_maxspeed_side;
                        fuel = t * autocvar_g_jetpack_fuel * 0.8;
 
-                       LOG_DEBUG("jetpack ai: required fuel ", ftos(fuel), " this.ammo_fuel ", ftos(this.ammo_fuel));
+                       LOG_DEBUG("jetpack ai: required fuel ", ftos(fuel), ", have ", ftos(GetResourceAmount(this, RESOURCE_FUEL)));
 
                        // enough fuel ?
-                       if(this.ammo_fuel>fuel || (this.items & IT_UNLIMITED_WEAPON_AMMO))
+                       if(GetResourceAmount(this, RESOURCE_FUEL) > fuel || (this.items & IT_UNLIMITED_WEAPON_AMMO))
                        {
                                // Estimate cost
                                // (as onground costs calculation is mostly based on distances, here we do the same establishing some relationship
@@ -1838,7 +1839,7 @@ void navigation_unstuck(entity this)
                float d = vlen2(this.origin - bot_waypoint_queue_goal.origin);
                LOG_DEBUG(this.netname, " evaluating ", bot_waypoint_queue_goal.classname, " with distance ", ftos(d));
                set_tracewalk_dest(bot_waypoint_queue_goal, this.origin, false);
-               if (tracewalk(bot_waypoint_queue_goal, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
+               if (tracewalk(this, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
                        tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
                {
                        if( d > bot_waypoint_queue_bestgoalrating)
index 82d82cb590f3835dbc0691ae7b17840176adfd00..48975b8367e58766668e59027e5e73000b9b4d51 100644 (file)
@@ -620,10 +620,11 @@ float bot_cmd_eval(entity this, string expr)
                return cvar(substring(expr, 5, strlen(expr)));
 
        // Search for fields
+       // TODO: expand with support for more fields (key carrier, ball carrier, armor etc)
        switch(expr)
        {
                case "health":
-                       return this.health;
+                       return GetResourceAmount(this, RESOURCE_HEALTH);
                case "speed":
                        return vlen(this.velocity);
                case "flagcarrier":
index bc29b31da1da5e2a667027ce6df521783fc80202..674ab634a1c768889ff3ac4025ea07008221b813 100644 (file)
@@ -141,8 +141,8 @@ vector waypoint_getSymmetricalOrigin(vector org, int ctf_flags)
        }
        else if (fabs(autocvar_g_waypointeditor_symmetrical) == 2)
        {
-               float m = havocbot_symmetryaxis_equation.x;
-               float q = havocbot_symmetryaxis_equation.y;
+               float m = havocbot_symmetry_axis_m;
+               float q = havocbot_symmetry_axis_q;
                if (autocvar_g_waypointeditor_symmetrical == -2)
                {
                        m = autocvar_g_waypointeditor_symmetrical_axis.x;
@@ -246,15 +246,12 @@ void waypoint_spawn_fromeditor(entity pl)
 {
        entity e;
        vector org = pl.origin;
-       int ctf_flags = havocbot_symmetryaxis_equation.z;
+       int ctf_flags = havocbot_symmetry_origin_order;
        bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
                   || (autocvar_g_waypointeditor_symmetrical < 0));
-       int order = ctf_flags;
        if(autocvar_g_waypointeditor_symmetrical_order >= 2)
-       {
-               order = autocvar_g_waypointeditor_symmetrical_order;
-               ctf_flags = order;
-       }
+               ctf_flags = autocvar_g_waypointeditor_symmetrical_order;
+       int wp_num = ctf_flags;
 
        if(!PHYS_INPUT_BUTTON_CROUCH(pl))
        {
@@ -285,8 +282,8 @@ void waypoint_spawn_fromeditor(entity pl)
                org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
                if (vdist(org - pl.origin, >, 32))
                {
-                       if(order > 2)
-                               order--;
+                       if(wp_num > 2)
+                               wp_num--;
                        else
                                sym = false;
                        goto add_wp;
@@ -309,15 +306,12 @@ void waypoint_remove_fromeditor(entity pl)
 {
        entity e = navigation_findnearestwaypoint(pl, false);
 
-       int ctf_flags = havocbot_symmetryaxis_equation.z;
+       int ctf_flags = havocbot_symmetry_origin_order;
        bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
                   || (autocvar_g_waypointeditor_symmetrical < 0));
-       int order = ctf_flags;
        if(autocvar_g_waypointeditor_symmetrical_order >= 2)
-       {
-               order = autocvar_g_waypointeditor_symmetrical_order;
-               ctf_flags = order;
-       }
+               ctf_flags = autocvar_g_waypointeditor_symmetrical_order;
+       int wp_num = ctf_flags;
 
        LABEL(remove_wp);
        if (!e) return;
@@ -348,8 +342,8 @@ void waypoint_remove_fromeditor(entity pl)
        if (sym && wp_sym)
        {
                e = wp_sym;
-               if(order > 2)
-                       order--;
+               if(wp_num > 2)
+                       wp_num--;
                else
                        sym = false;
                goto remove_wp;
@@ -567,6 +561,9 @@ void waypoint_think(entity this)
 
        bot_calculate_stepheightvec();
 
+       int dphitcontentsmask_save = this.dphitcontentsmask;
+       this.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+
        bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL);
 
        //dprint("waypoint_think wpisbox = ", ftos(this.wpisbox), "\n");
@@ -619,7 +616,7 @@ void waypoint_think(entity this)
                                relink_walkculled += 0.5;
                        else
                        {
-                               if (tracewalk(it, ev, PL_MIN_CONST, PL_MAX_CONST, sv2, sv2_height, MOVE_NOMONSTERS))
+                               if (tracewalk(this, ev, PL_MIN_CONST, PL_MAX_CONST, sv2, sv2_height, MOVE_NOMONSTERS))
                                        waypoint_addlink(it, this);
                                else
                                        relink_walkculled += 0.5;
@@ -628,6 +625,7 @@ void waypoint_think(entity this)
        });
        navigation_testtracewalk = 0;
        this.wplinked = true;
+       this.dphitcontentsmask = dphitcontentsmask_save;
 }
 
 void waypoint_clearlinks(entity wp)
index 0b3c1b27d6bf211336acbe16c4daffa60df81d9a..04172b5eb2b59d74de854c3c1d440ad4c5f7827a 100644 (file)
@@ -3,12 +3,14 @@
 #include <server/defs.qh>
 #include <server/miscfunctions.qh>
 #include <common/effects/all.qh>
+#include <server/resources.qh>
 
 #include "g_damage.qh"
+#include "player.qh"
 #include "race.qh"
 #include "../common/mapobjects/teleporters.qh"
 
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
 
 #include "weapons/tracing.qh"
 
@@ -32,8 +34,6 @@
 #include "../lib/warpzone/anglestransform.qh"
 #include "../lib/warpzone/util_server.qh"
 
-void CopyBody(entity this, float keepvelocity);
-
 #ifdef NOCHEATS
 
 float CheatImpulse(entity this, int imp) { return 0; }
@@ -152,14 +152,14 @@ float CheatImpulse(entity this, int imp)
                        this.personal.origin = this.origin;
                        this.personal.v_angle = this.v_angle;
                        this.personal.velocity = this.velocity;
-                       this.personal.ammo_rockets = this.ammo_rockets;
-                       this.personal.ammo_nails = this.ammo_nails;
-                       this.personal.ammo_cells = this.ammo_cells;
-                       this.personal.ammo_plasma = this.ammo_plasma;
-                       this.personal.ammo_shells = this.ammo_shells;
-                       this.personal.ammo_fuel = this.ammo_fuel;
-                       this.personal.health = max(1, this.health);
-                       this.personal.armorvalue = this.armorvalue;
+                       SetResourceAmount(this.personal, RESOURCE_ROCKETS, GetResourceAmount(this, RESOURCE_ROCKETS));
+                       SetResourceAmount(this.personal, RESOURCE_BULLETS, GetResourceAmount(this, RESOURCE_BULLETS));
+                       SetResourceAmount(this.personal, RESOURCE_CELLS, GetResourceAmount(this, RESOURCE_CELLS));
+                       SetResourceAmount(this.personal, RESOURCE_PLASMA, GetResourceAmount(this, RESOURCE_PLASMA));
+                       SetResourceAmount(this.personal, RESOURCE_SHELLS, GetResourceAmount(this, RESOURCE_SHELLS));
+                       SetResourceAmount(this.personal, RESOURCE_FUEL, GetResourceAmount(this, RESOURCE_FUEL));
+                       SetResourceAmount(this.personal, RESOURCE_HEALTH, max(1, GetResourceAmount(this, RESOURCE_HEALTH)));
+                       SetResourceAmount(this.personal, RESOURCE_ARMOR, GetResourceAmount(this, RESOURCE_ARMOR));
                        STAT(WEAPONS, this.personal) = STAT(WEAPONS, this);
                        this.personal.items = this.items;
                        this.personal.pauserotarmor_finished = this.pauserotarmor_finished;
@@ -211,14 +211,14 @@ float CheatImpulse(entity this, int imp)
                                        MUTATOR_CALLHOOK(AbortSpeedrun, this);
                                }
 
-                               this.ammo_rockets = this.personal.ammo_rockets;
-                               this.ammo_nails = this.personal.ammo_nails;
-                               this.ammo_cells = this.personal.ammo_cells;
-                               this.ammo_plasma = this.personal.ammo_plasma;
-                               this.ammo_shells = this.personal.ammo_shells;
-                               this.ammo_fuel = this.personal.ammo_fuel;
-                               this.health = this.personal.health;
-                               this.armorvalue = this.personal.armorvalue;
+                               SetResourceAmount(this, RESOURCE_ROCKETS, GetResourceAmount(this.personal, RESOURCE_ROCKETS));
+                               SetResourceAmount(this, RESOURCE_BULLETS, GetResourceAmount(this.personal, RESOURCE_BULLETS));
+                               SetResourceAmount(this, RESOURCE_CELLS, GetResourceAmount(this.personal, RESOURCE_CELLS));
+                               SetResourceAmount(this, RESOURCE_PLASMA, GetResourceAmount(this.personal, RESOURCE_PLASMA));
+                               SetResourceAmount(this, RESOURCE_SHELLS, GetResourceAmount(this.personal, RESOURCE_SHELLS));
+                               SetResourceAmount(this, RESOURCE_FUEL, GetResourceAmount(this.personal, RESOURCE_FUEL));
+                               SetResourceAmount(this, RESOURCE_HEALTH, GetResourceAmount(this.personal, RESOURCE_HEALTH));
+                               SetResourceAmount(this, RESOURCE_ARMOR, GetResourceAmount(this.personal, RESOURCE_ARMOR));
                                STAT(WEAPONS, this) = STAT(WEAPONS, this.personal);
                                this.items = this.personal.items;
                                this.pauserotarmor_finished = time + this.personal.pauserotarmor_finished - this.personal.teleport_time;
@@ -291,7 +291,6 @@ float CheatImpulse(entity this, int imp)
        END_CHEAT_FUNCTION();
 }
 
-void DragBox_Think(entity this);
 float drag_lastcnt;
 float CheatCommand(entity this, int argc)
 {
@@ -356,7 +355,7 @@ float CheatCommand(entity this, int argc)
                                        entity e = spawn();
                                        e.model = strzone(argv(1));
                                        e.mdl = "rocket_explode";
-                                       e.health = 1000;
+                                       SetResourceAmountExplicit(e, RESOURCE_HEALTH, 1000);
                                        setorigin(e, trace_endpos);
                                        e.effects = EF_NOMODELFLAGS;
                                        if(f == 1)
@@ -711,18 +710,6 @@ float CheatCommand(entity this, int argc)
        END_CHEAT_FUNCTION();
 }
 
-float Drag(entity this, float force_allow_pick, float ischeat);
-void Drag_Begin(entity dragger, entity draggee, vector touchpoint);
-void Drag_Finish(entity dragger);
-float Drag_IsDraggable(entity draggee);
-float Drag_MayChangeAngles(entity draggee);
-void Drag_MoveForward(entity dragger);
-void Drag_SetSpeed(entity dragger, float s);
-void Drag_MoveBackward(entity dragger);
-void Drag_Update(entity dragger);
-float Drag_CanDrag(entity dragger);
-float Drag_IsDragging(entity dragger);
-void Drag_MoveDrag(entity from, entity to);
 .entity dragentity;
 
 float CheatFrame(entity this)
index 1bf08064106c410874df00f9607d9222929ab1c0..0dc6a92d9c45b5f103e432fabaa59f8c3db45bb1 100644 (file)
@@ -15,3 +15,16 @@ float CheatFrame(entity this);
 const float CHRAME_DRAG = 8;
 
 void Drag_MoveDrag(entity from, entity to); // call this from CopyBody
+void DragBox_Think(entity this);
+float Drag(entity this, float force_allow_pick, float ischeat);
+void Drag_Begin(entity dragger, entity draggee, vector touchpoint);
+void Drag_Finish(entity dragger);
+float Drag_IsDraggable(entity draggee);
+float Drag_MayChangeAngles(entity draggee);
+void Drag_MoveForward(entity dragger);
+void Drag_SetSpeed(entity dragger, float s);
+void Drag_MoveBackward(entity dragger);
+void Drag_Update(entity dragger);
+float Drag_CanDrag(entity dragger);
+float Drag_IsDragging(entity dragger);
+void Drag_MoveDrag(entity from, entity to);
index 8443efb90497b0f98c8036f721256cc1b38209f6..cf114e5f058860e6a55ef47febf11f0e17a31f63 100644 (file)
@@ -16,6 +16,7 @@
 #include "handicap.qh"
 #include "g_hook.qh"
 #include "command/common.qh"
+#include "command/vote.qh"
 #include "cheats.qh"
 #include "g_world.qh"
 #include "race.qh"
 #include "../common/net_linked.qh"
 #include "../common/physics/player.qh"
 
+#include <common/vehicles/sv_vehicles.qh>
+
 #include "../common/items/_mod.qh"
 
 #include "../common/mutators/mutator/waypoints/all.qh"
 #include "../common/mutators/mutator/instagib/sv_instagib.qh"
+#include <common/gamemodes/_mod.qh>
 
 #include "../common/mapobjects/subs.qh"
 #include "../common/mapobjects/triggers.qh"
@@ -73,8 +77,6 @@ STATIC_METHOD(Client, Add, void(Client this, int _team))
     PutClientInServer(this);
 }
 
-void PutObserverInServer(entity this);
-
 STATIC_METHOD(Client, Remove, void(Client this))
 {
     TRANSMUTE(Observer, this);
@@ -165,9 +167,6 @@ void ClientData_Touch(entity e)
        });
 }
 
-void SetSpectatee(entity this, entity spectatee);
-void SetSpectatee_status(entity this, int spectatee_num);
-
 
 /*
 =============
@@ -221,7 +220,6 @@ void setplayermodel(entity e, string modelname)
                UpdatePlayerSounds(e);
 }
 
-void FixPlayermodel(entity player);
 /** putting a client as observer in the server */
 void PutObserverInServer(entity this)
 {
@@ -230,7 +228,7 @@ void PutObserverInServer(entity this)
 
        if (IS_PLAYER(this))
        {
-               if(this.health >= 1)
+               if(GetResourceAmount(this, RESOURCE_HEALTH) >= 1)
                {
                        // despawn effect
                        Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
@@ -325,15 +323,14 @@ void PutObserverInServer(entity this)
        if(this.damagedbycontents)
                IL_REMOVE(g_damagedbycontents, this);
        this.damagedbycontents = false;
-       this.health = FRAGS_SPECTATOR;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, FRAGS_SPECTATOR);
        SetSpectatee_status(this, etof(this));
        this.takedamage = DAMAGE_NO;
        this.solid = SOLID_NOT;
        set_movetype(this, MOVETYPE_FLY_WORLDONLY); // user preference is controlled by playerprethink
        this.flags = FL_CLIENT | FL_NOTARGET;
-       this.armorvalue = 666;
        this.effects = 0;
-       this.armorvalue = autocvar_g_balance_armor_start;
+       SetResourceAmountExplicit(this, RESOURCE_ARMOR, autocvar_g_balance_armor_start); // was 666?!
        this.pauserotarmor_finished = 0;
        this.pauserothealth_finished = 0;
        this.pauseregen_finished = 0;
@@ -382,6 +379,7 @@ void PutObserverInServer(entity this)
        this.oldvelocity = this.velocity;
        this.fire_endtime = -1;
        this.event_damage = func_null;
+       this.event_heal = func_null;
 
        for(int slot = 0; slot < MAX_AXH; ++slot)
        {
@@ -555,24 +553,24 @@ void PutPlayerInServer(entity this)
        this.effects = EF_TELEPORT_BIT | EF_RESTARTANIM_BIT;
 
        if (warmup_stage) {
-               this.ammo_shells = warmup_start_ammo_shells;
-               this.ammo_nails = warmup_start_ammo_nails;
-               this.ammo_rockets = warmup_start_ammo_rockets;
-               this.ammo_cells = warmup_start_ammo_cells;
-               this.ammo_plasma = warmup_start_ammo_plasma;
-               this.ammo_fuel = warmup_start_ammo_fuel;
-               this.health = warmup_start_health;
-               this.armorvalue = warmup_start_armorvalue;
+               SetResourceAmount(this, RESOURCE_SHELLS, warmup_start_ammo_shells);
+               SetResourceAmount(this, RESOURCE_BULLETS, warmup_start_ammo_nails);
+               SetResourceAmount(this, RESOURCE_ROCKETS, warmup_start_ammo_rockets);
+               SetResourceAmount(this, RESOURCE_CELLS, warmup_start_ammo_cells);
+               SetResourceAmount(this, RESOURCE_PLASMA, warmup_start_ammo_plasma);
+               SetResourceAmount(this, RESOURCE_FUEL, warmup_start_ammo_fuel);
+               SetResourceAmount(this, RESOURCE_HEALTH, warmup_start_health);
+               SetResourceAmount(this, RESOURCE_ARMOR, warmup_start_armorvalue);
                STAT(WEAPONS, this) = WARMUP_START_WEAPONS;
        } else {
-               this.ammo_shells = start_ammo_shells;
-               this.ammo_nails = start_ammo_nails;
-               this.ammo_rockets = start_ammo_rockets;
-               this.ammo_cells = start_ammo_cells;
-               this.ammo_plasma = start_ammo_plasma;
-               this.ammo_fuel = start_ammo_fuel;
-               this.health = start_health;
-               this.armorvalue = start_armorvalue;
+               SetResourceAmount(this, RESOURCE_SHELLS, start_ammo_shells);
+               SetResourceAmount(this, RESOURCE_BULLETS, start_ammo_nails);
+               SetResourceAmount(this, RESOURCE_ROCKETS, start_ammo_rockets);
+               SetResourceAmount(this, RESOURCE_CELLS, start_ammo_cells);
+               SetResourceAmount(this, RESOURCE_PLASMA, start_ammo_plasma);
+               SetResourceAmount(this, RESOURCE_FUEL, start_ammo_fuel);
+               SetResourceAmount(this, RESOURCE_HEALTH, start_health);
+               SetResourceAmount(this, RESOURCE_ARMOR, start_armorvalue);
                STAT(WEAPONS, this) = start_weapons;
                if (MUTATOR_CALLHOOK(ForbidRandomStartWeapons, this) == false)
                {
@@ -676,6 +674,7 @@ void PutPlayerInServer(entity this)
        STAT(HUD, this) = HUD_NORMAL;
 
        this.event_damage = PlayerDamage;
+       this.event_heal = PlayerHeal;
 
        if(!this.bot_attack)
                IL_PUSH(g_bot_targets, this);
@@ -751,6 +750,8 @@ void PutPlayerInServer(entity this)
                this.(weaponentity).weaponname = "";
                this.(weaponentity).m_switchingweapon = WEP_Null;
                this.(weaponentity).cnt = -1;
+
+               W_WeaponFrame(this, weaponentity);
        }
 
        MUTATOR_CALLHOOK(PlayerWeaponSelect, this);
@@ -789,8 +790,6 @@ void PutClientInServer(entity this)
        }
 }
 
-void ClientInit_misc(entity this);
-
 // TODO do we need all these fields, or should we stop autodetecting runtime
 // changes and just have a console command to update this?
 bool ClientInit_SendEntity(entity this, entity to, int sf)
@@ -971,7 +970,7 @@ void KillIndicator_Think(entity this)
                ClientKill_Now(this.owner);
                return;
        }
-    else if(this.health == 1) // health == 1 means that it's silent
+    else if(this.count == 1) // count == 1 means that it's silent
     {
         this.nextthink = time + 1;
         this.cnt -= 1;
@@ -1086,6 +1085,8 @@ void ClientKill_TeamChange (entity this, float targetteam) // 0 = don't change,
 
 void ClientKill (entity this)
 {
+       // TODO: once .health is removed, will need to check it here for the "already dead" message!
+
        if(game_stopped) return;
        if(this.player_blocked) return;
        if(STAT(FROZEN, this)) return;
@@ -1226,16 +1227,7 @@ void ClientConnect(entity this)
        JoinBestTeam(this, false); // if the team number is valid, keep it
        this.playerid = playerid_save;
 
-       if (autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0) {
-               TRANSMUTE(Observer, this);
-       } else {
-               if (!teamplay || autocvar_g_balance_teams) {
-                       TRANSMUTE(Player, this);
-                       campaign_bots_may_start = true;
-               } else {
-                       TRANSMUTE(Observer, this); // do it anyway
-               }
-       }
+       TRANSMUTE(Observer, this);
 
        PlayerStats_GameReport_AddEvent(sprintf("kills-%d", this.playerid));
 
@@ -1339,7 +1331,6 @@ Called when a client disconnects from the server
 =============
 */
 .entity chatbubbleentity;
-void ReadyCount();
 void ClientDisconnect(entity this)
 {
        assert(IS_CLIENT(this), return);
@@ -1476,6 +1467,54 @@ void respawn(entity this)
        PutClientInServer(this);
 }
 
+void PrintToChat(entity client, string text)
+{
+       text = strcat("\{1}^7", text, "\n");
+       sprint(client, text);
+}
+
+void DebugPrintToChat(entity client, string text)
+{
+       if (autocvar_developer)
+       {
+               PrintToChat(client, text);
+       }
+}
+
+void PrintToChatAll(string text)
+{
+       text = strcat("\{1}^7", text, "\n");
+       bprint(text);
+}
+
+void DebugPrintToChatAll(string text)
+{
+       if (autocvar_developer)
+       {
+               PrintToChatAll(text);
+       }
+}
+
+void PrintToChatTeam(int team_num, string text)
+{
+       text = strcat("\{1}^7", text, "\n");
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               if (it.team == team_num)
+               {
+                       sprint(it, text);
+               }
+       });
+}
+
+void DebugPrintToChatTeam(int team_num, string text)
+{
+       if (autocvar_developer)
+       {
+               PrintToChatTeam(team_num, text);
+       }
+}
+
 void play_countdown(entity this, float finished, Sound samp)
 {
     TC(Sound, samp);
@@ -1689,13 +1728,17 @@ void player_regen(entity this)
                limith = limith * limit_mod;
                limita = limita * limit_mod;
 
-               this.armorvalue = CalcRotRegen(this.armorvalue, mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regen_mod * frametime * (time > this.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rot_mod * frametime * (time > this.pauserotarmor_finished), limita);
-               this.health = CalcRotRegen(this.health, regen_health_stable, regen_health, regen_health_linear, regen_mod * frametime * (time > this.pauseregen_finished), regen_health_rotstable, regen_health_rot, regen_health_rotlinear, rot_mod * frametime * (time > this.pauserothealth_finished), limith);
+               SetResourceAmount(this, RESOURCE_ARMOR, CalcRotRegen(GetResourceAmount(this, RESOURCE_ARMOR), mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, 
+                                                                       regen_mod * frametime * (time > this.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear,
+                                                                       rot_mod * frametime * (time > this.pauserotarmor_finished), limita));
+               SetResourceAmount(this, RESOURCE_HEALTH, CalcRotRegen(GetResourceAmount(this, RESOURCE_HEALTH), regen_health_stable, regen_health, regen_health_linear,
+                                                                       regen_mod * frametime * (time > this.pauseregen_finished), regen_health_rotstable, regen_health_rot, regen_health_rotlinear,
+                                                                       rot_mod * frametime * (time > this.pauserothealth_finished), limith));
        }
 
        // if player rotted to death...  die!
        // check this outside above checks, as player may still be able to rot to death
-       if(this.health < 1)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) < 1)
        {
                if(this.vehicle)
                        vehicles_exit(this.vehicle, VHEF_RELEASE);
@@ -1711,20 +1754,10 @@ void player_regen(entity this)
                minf = autocvar_g_balance_fuel_regenstable;
                limitf = GetResourceLimit(this, RESOURCE_FUEL);
 
-               this.ammo_fuel = CalcRotRegen(this.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf);
-       }
-       // Ugly hack to make sure the health and armor don't go beyond hard limit.
-       // TODO: Remove this hack when all code uses GivePlayerHealth and
-       // GivePlayerArmor.
-       if (this.health > RESOURCE_AMOUNT_HARD_LIMIT)
-       {
-               this.health = RESOURCE_AMOUNT_HARD_LIMIT;
-       }
-       if (this.armorvalue > RESOURCE_AMOUNT_HARD_LIMIT)
-       {
-               this.armorvalue = RESOURCE_AMOUNT_HARD_LIMIT;
+               SetResourceAmount(this, RESOURCE_FUEL, CalcRotRegen(GetResourceAmount(this, RESOURCE_FUEL), minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, 
+                                                                               frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0),
+                                                                               maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf));
        }
-       // End hack.
 }
 
 bool zoomstate_set;
@@ -1769,15 +1802,15 @@ void SpectateCopy(entity this, entity spectatee)
        MUTATOR_CALLHOOK(SpectateCopy, spectatee, this);
        PS(this) = PS(spectatee);
        this.armortype = spectatee.armortype;
-       this.armorvalue = spectatee.armorvalue;
-       this.ammo_cells = spectatee.ammo_cells;
-       this.ammo_plasma = spectatee.ammo_plasma;
-       this.ammo_shells = spectatee.ammo_shells;
-       this.ammo_nails = spectatee.ammo_nails;
-       this.ammo_rockets = spectatee.ammo_rockets;
-       this.ammo_fuel = spectatee.ammo_fuel;
+       SetResourceAmountExplicit(this, RESOURCE_ARMOR, GetResourceAmount(spectatee, RESOURCE_ARMOR));
+       SetResourceAmountExplicit(this, RESOURCE_CELLS, GetResourceAmount(spectatee, RESOURCE_CELLS));
+       SetResourceAmountExplicit(this, RESOURCE_PLASMA, GetResourceAmount(spectatee, RESOURCE_PLASMA));
+       SetResourceAmountExplicit(this, RESOURCE_SHELLS, GetResourceAmount(spectatee, RESOURCE_SHELLS));
+       SetResourceAmountExplicit(this, RESOURCE_BULLETS, GetResourceAmount(spectatee, RESOURCE_BULLETS));
+       SetResourceAmountExplicit(this, RESOURCE_ROCKETS, GetResourceAmount(spectatee, RESOURCE_ROCKETS));
+       SetResourceAmountExplicit(this, RESOURCE_FUEL, GetResourceAmount(spectatee, RESOURCE_FUEL));
        this.effects = spectatee.effects & EFMASK_CHEAP; // eat performance
-       this.health = spectatee.health;
+       SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(spectatee, RESOURCE_HEALTH));
        CS(this).impulse = 0;
        this.items = spectatee.items;
        STAT(LAST_PICKUP, this) = STAT(LAST_PICKUP, spectatee);
@@ -2146,9 +2179,11 @@ void PrintWelcomeMessage(entity this)
        }
 }
 
+const int MIN_SPEC_TIME = 1;
 bool joinAllowed(entity this)
 {
        if (CS(this).version_mismatch) return false;
+       if (time < CS(this).jointime + MIN_SPEC_TIME) return false;
        if (!nJoinAllowed(this, this)) return false;
        if (teamplay && lockteams) return false;
        if (MUTATOR_CALLHOOK(ForbidSpawn, this)) return false;
@@ -2272,7 +2307,7 @@ bool PlayerThink(entity this)
                }
 
                this.items_added = 0;
-               if ((this.items & ITEM_Jetpack.m_itemid) && ((this.items & ITEM_JetpackRegen.m_itemid) || this.ammo_fuel >= 0.01))
+               if ((this.items & ITEM_Jetpack.m_itemid) && ((this.items & ITEM_JetpackRegen.m_itemid) || GetResourceAmount(this, RESOURCE_FUEL) >= 0.01))
             this.items_added |= IT_FUEL;
 
                this.items |= this.items_added;
@@ -2406,7 +2441,6 @@ void SpectatorThink(entity this)
        this.flags |= FL_CLIENT | FL_NOTARGET;
 }
 
-void vehicles_enter (entity pl, entity veh);
 void PlayerUseKey(entity this)
 {
        if (!IS_PLAYER(this))
@@ -2529,7 +2563,7 @@ void PlayerPreThink (entity this)
                if (STAT(FROZEN, this) == 2)
                {
                        STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
-                       this.health = max(1, STAT(REVIVE_PROGRESS, this) * start_health);
+                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
                        this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
 
                        if (STAT(REVIVE_PROGRESS, this) >= 1)
@@ -2538,9 +2572,9 @@ void PlayerPreThink (entity this)
                else if (STAT(FROZEN, this) == 3)
                {
                        STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1);
-                       this.health = max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this) );
+                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
 
-                       if (this.health < 1)
+                       if (GetResourceAmount(this, RESOURCE_HEALTH) < 1)
                        {
                                if (this.vehicle)
                                        vehicles_exit(this.vehicle, VHEF_RELEASE);
@@ -2587,6 +2621,8 @@ void PlayerPreThink (entity this)
                PrintWelcomeMessage(this);
 
        if (IS_PLAYER(this)) {
+               if (IS_REAL_CLIENT(this) && time < CS(this).jointime + MIN_SPEC_TIME)
+                       error("Client can't be spawned as player on connection!");
                if(!PlayerThink(this))
                        return;
        }
@@ -2595,6 +2631,20 @@ void PlayerPreThink (entity this)
                        IntermissionThink(this);
                return;
        }
+       else if (IS_REAL_CLIENT(this) && !CS(this).autojoin_checked && time >= CS(this).jointime + MIN_SPEC_TIME)
+       {
+               CS(this).autojoin_checked = true;
+               // don't do this in ClientConnect
+               // many things can go wrong if a client is spawned as player on connection
+               if (MUTATOR_CALLHOOK(AutoJoinOnConnection, this)
+                       || (!(autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0)
+                               && (!teamplay || autocvar_g_balance_teams)))
+               {
+                       campaign_bots_may_start = true;
+                       Join(this);
+                       return;
+               }
+       }
        else if (IS_OBSERVER(this)) {
                ObserverThink(this);
        }
@@ -2766,7 +2816,7 @@ void PlayerPostThink (entity this)
        }
 
        if (this.waypointsprite_attachedforcarrier) {
-           vector v = healtharmor_maxdamage(this.health, this.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id);
+           vector v = healtharmor_maxdamage(GetResourceAmount(this, RESOURCE_HEALTH), GetResourceAmount(this, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id);
                WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, '1 0 0' * v);
     }
 
index 7499ee8ddfac0e0fbbd299ab4de285b53c62ea83..2d3a099b3988bc715589d9f5018fd310be1fc7f6 100644 (file)
@@ -114,6 +114,7 @@ CLASS(Client, Object)
     ATTRIB(Client, cmd_floodtime, float, this.cmd_floodtime);
     ATTRIB(Client, wasplayer, bool, this.wasplayer);
     ATTRIB(Client, weaponorder_byimpulse, string, this.weaponorder_byimpulse);
+    ATTRIB(Client, autojoin_checked, bool, this.wasplayer);
 
     // networked cvars
 
@@ -225,11 +226,58 @@ METHOD(Client, m_unwind, bool(Client this))
     return false;
 }
 
+/// \brief Print the string to the client's chat.
+/// \param[in] client Client to print to.
+/// \param[in] text Text to print.
+void PrintToChat(entity client, string text);
+
+/// \brief Print the string to the client's chat if the server cvar "developer"
+/// is not 0.
+/// \param[in] client Client to print to.
+/// \param[in] text Text to print.
+void DebugPrintToChat(entity client, string text);
+
+/// \brief Prints the string to all clients' chat.
+/// \param[in] text Text to print.
+void PrintToChatAll(string text);
+
+/// \brief Prints the string to all clients' chat if the server cvar "developer"
+/// is not 0.
+/// \param[in] text Text to print.
+void DebugPrintToChatAll(string text);
+
+/// \brief Print the string to chat of all clients of the specified team.
+/// \param[in] team_num Team to print to. See NUM_TEAM constants.
+/// \param[in] text Text to print.
+void PrintToChatTeam(int team_num, string text);
+
+/// \brief Print the string to chat of all clients of the specified team if the
+/// server cvar "developer" is not 0.
+/// \param[in] team_num Team to print to. See NUM_TEAM constants.
+/// \param[in] text Text to print.
+void DebugPrintToChatTeam(int team_num, string text);
+
 void play_countdown(entity this, float finished, Sound samp);
 
 float CalcRotRegen(float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit);
 
 bool Spectate(entity this, entity pl);
 
+void ClientInit_Spawn();
+
+void PutObserverInServer(entity this);
+
+void SetSpectatee(entity this, entity spectatee);
+void SetSpectatee_status(entity this, int spectatee_num);
+
+void FixPlayermodel(entity player);
+
+void ClientInit_misc(entity this);
+
+void ClientKill_TeamChange(entity this, float targetteam);  // 0 = don't change, -1 = auto, -2 = spec
+
+bool joinAllowed(entity this);
+void Join(entity this);
+
 #define SPECTATE_COPY() ACCUMULATE void SpectateCopy(entity this, entity spectatee)
 #define SPECTATE_COPYFIELD(fld) SPECTATE_COPY() { this.(fld) = spectatee.(fld); }
index d6b1ae60f0f779511ca65dd6830323c134c91fe3..3cbaaf978e2950d7ad241a4147fab59953db3c11 100644 (file)
@@ -8,11 +8,6 @@
 #define GET_BAN_ARG(v, d) if (argc > reason_arg) { if ((v = stof(argv(reason_arg))) != 0) ++reason_arg; else v = d; } else { v = d; }
 #define GET_BAN_REASON(v, d) if (argc > reason_arg) v = substring(command, argv_start_index(reason_arg), strlen(command) - argv_start_index(reason_arg)); else v = d;
 
-void Ban_KickBanClient(entity client, float bantime, float masksize, string reason);
-void Ban_View();
-float Ban_Insert(string ip, float bantime, string reason, float dosync);
-float Ban_Delete(float i);
-
 // used by common/command/generic.qc:GenericCommand_dumpcommands to list all commands into a .txt file
 void BanCommand_macro_write_aliases(float fh);
 
index 291d8b178b2c5d0e450734918da7151546c60e6e..9b7a0f6574fb78cf3636d8325b4bb7479f59ae47 100644 (file)
 
 #include "../campaign.qh"
 #include "../cheats.qh"
+#include "../client.qh"
 #include "../player.qh"
 #include "../ipban.qh"
 #include "../mapvoting.qh"
 #include "../scores.qh"
 #include "../teamplay.qh"
 
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
+#include <common/gamemodes/_mod.qh>
 
 #ifdef SVQC
        #include <common/vehicles/all.qh>
@@ -40,8 +42,6 @@
 
 #include <lib/warpzone/common.qh>
 
-void ClientKill_TeamChange(entity this, float targetteam);  // 0 = don't change, -1 = auto, -2 = spec
-
 // =========================================================
 //  Server side networked commands code, reworked by Samual
 //  Last updated: December 28th, 2011
@@ -165,8 +165,6 @@ void ClientCommand_mv_getpicture(entity caller, float request, float argc)  // i
        }
 }
 
-bool joinAllowed(entity this);
-void Join(entity this);
 void ClientCommand_join(entity caller, float request)
 {
        switch (request)
index db822eb71f7ea45e2536677da8f50aab4d071bcf..cb8ab239fbebd1ac74b4781982d3d93ed14f43ac 100644 (file)
@@ -405,7 +405,7 @@ void CommonCommand_editmob(int request, entity caller, int argc)
                                        if (mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
                                        if (!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
 
-                                       Damage(mon, NULL, NULL, mon.health + mon.max_health + 200, DEATH_KILL.m_id, DMG_NOWEP, mon.origin, '0 0 0');
+                                       Damage(mon, NULL, NULL, GetResourceAmount(mon, RESOURCE_HEALTH) + mon.max_health + 200, DEATH_KILL.m_id, DMG_NOWEP, mon.origin, '0 0 0');
                                        print_to(caller, strcat("Your pet '", mon.monster_name, "' has been brutally mutilated"));
                                        return;
                                }
index 6c31af72c853d54cdef08fd5d723940efe6baecf..87bcef82f7124e1f001603129320b0684d46af5f 100644 (file)
@@ -4,7 +4,6 @@
 #include <common/command/_mod.qh>
 
 #include "../g_world.qh"
-#include "../g_subs.qh"
 
 #include <common/util.qh>
 
index 97af2d49460986ce79b2e30ae761da78c75b8e9e..1076225d82acaf1e89a00432927c4c95b8593c34 100644 (file)
@@ -19,7 +19,8 @@
 
 #include "../bot/api.qh"
 
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
+#include <common/gamemodes/_mod.qh>
 
 #include <common/constants.qh>
 #include <common/net_linked.qh>
 
 #include <common/monsters/sv_monsters.qh>
 
-
-void PutObserverInServer(entity this);
-
-// =====================================================
-//  Server side game commands code, reworked by Samual
-// =====================================================
-
 //  used by GameCommand_make_mapinfo()
 void make_mapinfo_Think(entity this)
 {
@@ -1496,10 +1490,13 @@ void GameCommand_trace(float request, float argc)
                                        if (argc == 4 || argc == 5)
                                        {
                                                e = nextent(NULL);
+                                               int dphitcontentsmask_save = e.dphitcontentsmask;
+                                               e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
                                                if (tracewalk(e, stov(argv(2)), e.mins, e.maxs, stov(argv(3)), stof(argv(4)), MOVE_NORMAL))
                                                        LOG_INFO("can walk");
                                                else
                                                        LOG_INFO("cannot walk");
+                                               e.dphitcontentsmask = dphitcontentsmask_save;
                                                return;
                                        }
                                }
index 638dbb1565eabf86df69239c58be3c40f77e9516..5f034f12f88534e9f644eeba88bed1351246ecb2 100644 (file)
@@ -14,7 +14,8 @@
 #include "../round_handler.qh"
 #include "../scores.qh"
 
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
+#include <common/gamemodes/_mod.qh>
 
 #include <common/constants.qh>
 #include <common/net_linked.qh>
index 7b10b37eb70bcf2a0e50a45fb105b14846d7b59c..d5c3bc0ad0e937d9569d1ec95cc539cd09c78897 100644 (file)
@@ -54,3 +54,4 @@ void reset_map(float dorespawn);
 void ReadyCount();
 void ReadyRestart_force();
 void VoteCount(float first_count);
+void Nagger_Init();
index 4f973bb9e75548e11bf67ddbed53aec3bb99b3ea..4511100581b7d74a89f45e955292f74ce45d04a7 100644 (file)
@@ -4,10 +4,10 @@
 #include <server/miscfunctions.qh>
 #include <server/items.qh>
 #include <server/resources.qh>
+#include <common/t_items.qh>
+#include <common/mapobjects/triggers.qh>
 #include <common/weapons/_all.qh>
 
-spawnfunc(target_items);
-
 //***********************
 //QUAKE 3 ENTITIES - So people can play quake3 maps with the xonotic weapons
 //***********************
@@ -141,33 +141,33 @@ void target_give_init(entity this)
        IL_EACH(g_items, it.targetname == this.target,
        {
                if (it.classname == "weapon_devastator") {
-                       this.ammo_rockets += it.count * WEP_CVAR(devastator, ammo);
+                       SetResourceAmountExplicit(this, RESOURCE_ROCKETS, GetResourceAmount(this, RESOURCE_ROCKETS) + it.count * WEP_CVAR_PRI(devastator, ammo)); // WEAPONTODO
                        this.netname = cons(this.netname, "devastator");
                }
                else if (it.classname == "weapon_vortex") {
-                       this.ammo_cells += it.count * WEP_CVAR_PRI(vortex, ammo); // WEAPONTODO
+                       SetResourceAmountExplicit(this, RESOURCE_CELLS, GetResourceAmount(this, RESOURCE_CELLS) + it.count * WEP_CVAR_PRI(vortex, ammo)); // WEAPONTODO
                        this.netname = cons(this.netname, "vortex");
                }
                else if (it.classname == "weapon_electro") {
-                       this.ammo_cells += it.count * WEP_CVAR_PRI(electro, ammo); // WEAPONTODO
+                       SetResourceAmountExplicit(this, RESOURCE_CELLS, GetResourceAmount(this, RESOURCE_CELLS) + it.count * WEP_CVAR_PRI(electro, ammo)); // WEAPONTODO
                        this.netname = cons(this.netname, "electro");
                }
                else if (it.classname == "weapon_hagar") {
-                       this.ammo_rockets += it.count * WEP_CVAR_PRI(hagar, ammo); // WEAPONTODO
+                       SetResourceAmountExplicit(this, RESOURCE_ROCKETS, GetResourceAmount(this, RESOURCE_ROCKETS) + it.count * WEP_CVAR_PRI(hagar, ammo)); // WEAPONTODO
                        this.netname = cons(this.netname, "hagar");
                }
                else if (it.classname == "weapon_crylink") {
-                       this.ammo_cells += it.count * WEP_CVAR_PRI(crylink, ammo);
+                       SetResourceAmountExplicit(this, RESOURCE_CELLS, GetResourceAmount(this, RESOURCE_CELLS) + it.count * WEP_CVAR_PRI(crylink, ammo)); // WEAPONTODO
                        this.netname = cons(this.netname, "crylink");
                }
                else if (it.classname == "weapon_mortar") {
-                       this.ammo_rockets += it.count * WEP_CVAR_PRI(mortar, ammo); // WEAPONTODO
+                       SetResourceAmountExplicit(this, RESOURCE_ROCKETS, GetResourceAmount(this, RESOURCE_ROCKETS) + it.count * WEP_CVAR_PRI(mortar, ammo)); // WEAPONTODO
                        this.netname = cons(this.netname, "mortar");
                }
                else if (it.classname == "item_armor_mega")
-                       this.armorvalue = 100;
+                       SetResourceAmountExplicit(this, RESOURCE_ARMOR, 100);
                else if (it.classname == "item_health_mega")
-                       this.health = 200;
+                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 200);
                //remove(it); // removing ents in init functions causes havoc, workaround:
         setthink(it, SUB_Remove);
         it.nextthink = time;
index 6f70f09beec2219624baeca92e2cd7deaa104fb4..342a829a187b12daa84a0b532bc0269aca79c16b 100644 (file)
@@ -1 +1,3 @@
 #pragma once
+
+bool DoesQ3ARemoveThisEntity(entity this);
index 1db5dd0c5b637244ef97e3a68271975bd393b8d0..4c23aaeb3b956f6706b8dcea57db48f1c4231f01 100644 (file)
@@ -36,6 +36,8 @@ float server_is_dedicated;
 
 .void(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) event_damage;
 
+.bool(entity targ, entity inflictor, float amount, float limit) event_heal;
+
 //.string      wad;
 //.string      map;
 
index e496404902dcd9e3b4daa2267de2d1f004ffd36f..cc0c71be5d56e32a1ad721a1e8fe1a9726645a43 100644 (file)
@@ -3,7 +3,7 @@
 #include <common/effects/all.qh>
 #include "bot/api.qh"
 #include "g_hook.qh"
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
 #include "scores.qh"
 #include "spawnpoints.qh"
 #include "../common/state.qh"
@@ -14,6 +14,7 @@
 #include "../common/items/_mod.qh"
 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
 #include "../common/mutators/mutator/instagib/sv_instagib.qh"
+#include "../common/mutators/mutator/buffs/buffs.qh"
 #include "weapons/accuracy.qh"
 #include "weapons/csqcprojectile.qh"
 #include "weapons/selection.qh"
@@ -24,6 +25,7 @@
 #include "../common/playerstats.qh"
 #include "../common/teams.qh"
 #include "../common/util.qh"
+#include <common/gamemodes/rules.qh>
 #include <common/weapons/_all.qh>
 #include "../lib/csqcmodel/sv_model.qh"
 #include "../lib/warpzone/common.qh"
@@ -220,7 +222,6 @@ bool frag_centermessage_override(entity attacker, entity targ, int deathtype, in
        return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
 }
 
-entity buff_FirstFromFlags(int _buffs);
 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
 {
        // Sanity check
@@ -592,9 +593,9 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
                // These are ALWAYS lethal
                // No damage modification here
                // Instead, prepare the victim for his death...
-               SetResourceAmount(targ, RESOURCE_ARMOR, 0);
+               SetResourceAmountExplicit(targ, RESOURCE_ARMOR, 0);
                targ.spawnshieldtime = 0;
-               SetResourceAmount(targ, RESOURCE_HEALTH, 0.9); // this is < 1
+               SetResourceAmountExplicit(targ, RESOURCE_HEALTH, 0.9); // this is < 1
                targ.flags -= targ.flags & FL_GODMODE;
                damage = 100000;
        }
@@ -635,7 +636,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
 
                                                        if(autocvar_g_mirrordamage_virtual)
                                                        {
-                                                               vector v  = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
+                                                               vector v  = healtharmor_applydamage(GetResourceAmount(attacker, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
                                                                attacker.dmg_take += v.x;
                                                                attacker.dmg_save += v.y;
                                                                attacker.dmg_inflictor = inflictor;
@@ -645,7 +646,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, int d
 
                                                        if(autocvar_g_friendlyfire_virtual)
                                                        {
-                                                               vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
+                                                               vector v = healtharmor_applydamage(GetResourceAmount(targ, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
                                                                targ.dmg_take += v.x;
                                                                targ.dmg_save += v.y;
                                                                targ.dmg_inflictor = inflictor;
@@ -1060,6 +1061,20 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e
        return RadiusDamageForSource (inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, false, forceintensity, deathtype, weaponentity, directhitentity);
 }
 
+bool Heal(entity targ, entity inflictor, float amount, float limit)
+{
+       if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
+               return false;
+
+       bool healed = false;
+       if(targ.event_heal)
+               healed = targ.event_heal(targ, inflictor, amount, limit);
+       // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
+       // TODO: healing fx!
+       // TODO: armor healing?
+       return healed;
+}
+
 float Fire_IsBurning(entity e)
 {
        return (time < e.fire_endtime);
index 9c4983fee1b7d34c28483d8743b75dee015e7a69..88fbf134422134492c3ee1756549d4b2131fa8ab 100644 (file)
@@ -19,7 +19,7 @@
     #include "defs.qh"
     #include <common/notifications/all.qh>
     #include <common/deathtypes/all.qh>
-    #include "mutators/_mod.qh"
+    #include <server/mutators/_mod.qh>
     #include <common/turrets/sv_turrets.qh>
     #include <common/vehicles/all.qh>
     #include <lib/csqcmodel/sv_model.qh>
@@ -96,6 +96,10 @@ float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector in
 
 float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity);
 
+// Calls .event_heal on the target so that they can handle healing themselves
+// a limit of RESOURCE_LIMIT_NONE should be handled by the entity as its max health (if applicable)
+bool Heal(entity targ, entity inflictor, float amount, float limit);
+
 .float fire_damagepersec;
 .float fire_endtime;
 .float fire_deathtype;
index 0d732e0ca4b6daba40fc5b33db54ade2db1c7501..68aa7154ecdc3928261ea584bd251be2c86d2c03 100644 (file)
@@ -110,7 +110,6 @@ void GrapplingHookReset(entity this)
        RemoveHook(this);
 }
 
-void GrapplingHookThink(entity this);
 void GrapplingHook_Stop(entity this)
 {
        Send_Effect(EFFECT_HOOK_IMPACT, this.origin, '0 0 0', 1);
@@ -328,7 +327,7 @@ void GrapplingHookTouch(entity this, entity toucher)
        GrapplingHook_Stop(this);
 
        if(toucher)
-               if(toucher.move_movetype != MOVETYPE_NONE)
+               //if(toucher.move_movetype != MOVETYPE_NONE)
                {
                        SetMovetypeFollow(this, toucher);
                        WarpZone_RefSys_BeginAddingIncrementally(this, this.aiment);
@@ -339,15 +338,15 @@ void GrapplingHookTouch(entity this, entity toucher)
 
 void GrapplingHook_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
 {
-       if(this.health <= 0)
+       if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
                return;
 
        if (!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
                return; // g_balance_projectiledamage says to halt
 
-       this.health = this.health - damage;
+       TakeResource(this, RESOURCE_HEALTH, damage);
 
-       if (this.health <= 0)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
        {
                if(attacker != this.realowner)
                {
@@ -364,19 +363,9 @@ void FireGrapplingHook(entity actor, .entity weaponentity)
        if(forbidWeaponUse(actor)) return;
        if(actor.vehicle) return;
 
-       makevectors(actor.v_angle);
-
-       int s = W_GunAlign(actor.(weaponentity), STAT(GUNALIGN, actor)) - 1;
-       vector vs = hook_shotorigin[s];
-
-       // UGLY WORKAROUND: play this on CH_WEAPON_B so it can't cut off fire sounds
-       sound (actor, CH_WEAPON_B, SND_HOOK_FIRE, VOL_BASE, ATTEN_NORM);
-       vector org = actor.origin + actor.view_ofs + v_forward * vs.x + v_right * -vs.y + v_up * vs.z;
-
-       tracebox(actor.origin + actor.view_ofs, '-3 -3 -3', '3 3 3', org, MOVE_NORMAL, actor);
-       org = trace_endpos;
-
-       Send_Effect(EFFECT_HOOK_MUZZLEFLASH, org, '0 0 0', 1);
+       // TODO: offhand hook shoots from eye
+       W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', true, 0, SND_HOOK_FIRE, CH_WEAPON_B, 0, WEP_HOOK.m_id);
+       Send_Effect(EFFECT_HOOK_MUZZLEFLASH, w_shotorg, '0 0 0', 1);
 
        entity missile = WarpZone_RefSys_SpawnSameRefSys(actor);
        missile.owner = missile.realowner = actor;
@@ -393,11 +382,11 @@ void FireGrapplingHook(entity actor, .entity weaponentity)
 
        //setmodel (missile, MDL_HOOK); // precision set below
        setsize (missile, '-3 -3 -3', '3 3 3');
-       setorigin(missile, org);
+       setorigin(missile, w_shotorg);
 
        missile.state = 0; // not latched onto anything
 
-       W_SetupProjVelocity_Explicit(missile, v_forward, v_up, autocvar_g_balance_grapplehook_speed_fly, 0, 0, 0, false);
+       W_SetupProjVelocity_Explicit(missile, w_shotdir, v_up, autocvar_g_balance_grapplehook_speed_fly, 0, 0, 0, false);
 
        missile.angles = vectoangles (missile.velocity);
        //missile.glow_color = 250; // 244, 250
@@ -408,7 +397,7 @@ void FireGrapplingHook(entity actor, .entity weaponentity)
 
        missile.effects = /*EF_FULLBRIGHT | EF_ADDITIVE |*/ EF_LOWPRECISION;
 
-       missile.health = autocvar_g_balance_grapplehook_health;//120
+       SetResourceAmountExplicit(missile, RESOURCE_HEALTH, autocvar_g_balance_grapplehook_health);
        missile.event_damage = GrapplingHook_Damage;
        missile.takedamage = DAMAGE_AIM;
        missile.damageforcescale = 0;
index 719bf5b605bdbfc1832761610f0f0ac8a3c88670..c0df31662a7d5cea6c95584dd7a13bfd7d969dad 100644 (file)
@@ -2,6 +2,7 @@
 
 // Wazat's grappling hook
 .entity                hook;
+void GrapplingHookThink(entity this);
 void RemoveGrapplingHooks(entity pl);
 void RemoveHook(entity this);
 // (note: you can change the hook impulse #'s to whatever you please)
diff --git a/qcsrc/server/g_subs.qc b/qcsrc/server/g_subs.qc
deleted file mode 100644 (file)
index dbbd6d2..0000000
+++ /dev/null
@@ -1,310 +0,0 @@
-#include "g_subs.qh"
-
-#include <server/defs.qh>
-#include <server/miscfunctions.qh>
-#include "antilag.qh"
-#include "command/common.qh"
-#include "../common/state.qh"
-#include "../lib/warpzone/common.qh"
-#include "../common/mapobjects/subs.qh"
-
-
-/*
-==================
-main
-
-unused but required by the engine
-==================
-*/
-void main ()
-{
-
-}
-
-// Misc
-
-/*
-==================
-traceline_antilag
-
-A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
-Additionally it moves players back into the past before the trace and restores them afterward.
-==================
-*/
-void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
-{
-       // check whether antilagged traces are enabled
-       if (lag < 0.001)
-               lag = 0;
-       if (!IS_REAL_CLIENT(forent))
-               lag = 0; // only antilag for clients
-
-       // change shooter to SOLID_BBOX so the shot can hit corpses
-       int oldsolid = source.dphitcontentsmask;
-       if(source)
-               source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
-
-       if (lag)
-               antilag_takeback_all(forent, lag);
-
-       // do the trace
-       if(wz)
-               WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
-       else
-               tracebox (v1, mi, ma, v2, nomonst, forent);
-
-       // restore players to current positions
-       if (lag)
-               antilag_restore_all(forent);
-
-       // restore shooter solid type
-       if(source)
-               source.dphitcontentsmask = oldsolid;
-}
-void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
-{
-       tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, false);
-}
-void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
-{
-       bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
-       if (autocvar_g_antilag != 2 || noantilag)
-               lag = 0;
-       traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
-}
-void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
-{
-       bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
-       if (autocvar_g_antilag != 2 || noantilag)
-               lag = 0;
-       tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, false);
-}
-void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
-{
-       tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, true);
-}
-void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
-{
-       bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
-       if (autocvar_g_antilag != 2 || noantilag)
-               lag = 0;
-       WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
-}
-void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
-{
-       bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
-       if (autocvar_g_antilag != 2 || noantilag)
-               lag = 0;
-       tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, true);
-}
-
-float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity) // returns the number of traces done, for benchmarking
-{
-       vector pos, dir, t;
-       float nudge;
-       entity stopentity;
-
-       //nudge = 2 * cvar("collision_impactnudge"); // why not?
-       nudge = 0.5;
-
-       dir = normalize(v2 - v1);
-
-       pos = v1 + dir * nudge;
-
-       float c;
-       c = 0;
-
-       for (;;)
-       {
-               if(pos * dir >= v2 * dir)
-               {
-                       // went too far
-                       trace_fraction = 1;
-                       trace_endpos = v2;
-                       return c;
-               }
-
-               tracebox(pos, mi, ma, v2, nomonsters, forent);
-               ++c;
-
-               if(c == 50)
-               {
-                       LOG_TRACE("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2));
-                       LOG_TRACE("  Nudging gets us nowhere at ", vtos(pos));
-                       LOG_TRACE("  trace_endpos is ", vtos(trace_endpos));
-                       LOG_TRACE("  trace distance is ", ftos(vlen(pos - trace_endpos)));
-               }
-
-               stopentity = trace_ent;
-
-               if(trace_startsolid)
-               {
-                       // we started inside solid.
-                       // then trace from endpos to pos
-                       t = trace_endpos;
-                       tracebox(t, mi, ma, pos, nomonsters, forent);
-                       ++c;
-                       if(trace_startsolid)
-                       {
-                               // t is still inside solid? bad
-                               // force advance, then, and retry
-                               pos = t + dir * nudge;
-
-                               // but if we hit an entity, stop RIGHT before it
-                               if(stopatentity && stopentity && stopentity != ignorestopatentity)
-                               {
-                                       trace_ent = stopentity;
-                                       trace_endpos = t;
-                                       trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
-                                       return c;
-                               }
-                       }
-                       else
-                       {
-                               // we actually LEFT solid!
-                               trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
-                               return c;
-                       }
-               }
-               else
-               {
-                       // pos is outside solid?!? but why?!? never mind, just return it.
-                       trace_endpos = pos;
-                       trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
-                       return c;
-               }
-       }
-}
-
-void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity)
-{
-       tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity);
-}
-
-/*
-==================
-findbetterlocation
-
-Returns a point at least 12 units away from walls
-(useful for explosion animations, although the blast is performed where it really happened)
-Ripped from DPMod
-==================
-*/
-vector findbetterlocation (vector org, float mindist)
-{
-       vector vec = mindist * '1 0 0';
-       int c = 0;
-       while (c < 6)
-       {
-               traceline (org, org + vec, true, NULL);
-               vec = vec * -1;
-               if (trace_fraction < 1)
-               {
-                       vector loc = trace_endpos;
-                       traceline (loc, loc + vec, true, NULL);
-                       if (trace_fraction >= 1)
-                               org = loc + vec;
-               }
-               if (c & 1)
-               {
-                       float h = vec.y;
-                       vec.y = vec.x;
-                       vec.x = vec.z;
-                       vec.z = h;
-               }
-               c = c + 1;
-       }
-
-       return org;
-}
-
-bool LOD_customize(entity this, entity client)
-{
-       if(autocvar_loddebug)
-       {
-               int d = autocvar_loddebug;
-               if(d == 1)
-                       this.modelindex = this.lodmodelindex0;
-               else if(d == 2 || !this.lodmodelindex2)
-                       this.modelindex = this.lodmodelindex1;
-               else // if(d == 3)
-                       this.modelindex = this.lodmodelindex2;
-               return true;
-       }
-
-       // TODO csqc network this so it only gets sent once
-       vector near_point = NearestPointOnBox(this, client.origin);
-       if(vdist(near_point - client.origin, <, this.loddistance1))
-               this.modelindex = this.lodmodelindex0;
-       else if(!this.lodmodelindex2 || vdist(near_point - client.origin, <, this.loddistance2))
-               this.modelindex = this.lodmodelindex1;
-       else
-               this.modelindex = this.lodmodelindex2;
-
-       return true;
-}
-
-void LOD_uncustomize(entity this)
-{
-       this.modelindex = this.lodmodelindex0;
-}
-
-void LODmodel_attach(entity this)
-{
-       entity e;
-
-       if(!this.loddistance1)
-               this.loddistance1 = 1000;
-       if(!this.loddistance2)
-               this.loddistance2 = 2000;
-       this.lodmodelindex0 = this.modelindex;
-
-       if(this.lodtarget1 != "")
-       {
-               e = find(NULL, targetname, this.lodtarget1);
-               if(e)
-               {
-                       this.lodmodel1 = e.model;
-                       delete(e);
-               }
-       }
-       if(this.lodtarget2 != "")
-       {
-               e = find(NULL, targetname, this.lodtarget2);
-               if(e)
-               {
-                       this.lodmodel2 = e.model;
-                       delete(e);
-               }
-       }
-
-       if(autocvar_loddebug < 0)
-       {
-               this.lodmodel1 = this.lodmodel2 = ""; // don't even initialize
-       }
-
-       if(this.lodmodel1 != "")
-       {
-               vector mi, ma;
-               mi = this.mins;
-               ma = this.maxs;
-
-               precache_model(this.lodmodel1);
-               _setmodel(this, this.lodmodel1);
-               this.lodmodelindex1 = this.modelindex;
-
-               if(this.lodmodel2 != "")
-               {
-                       precache_model(this.lodmodel2);
-                       _setmodel(this, this.lodmodel2);
-                       this.lodmodelindex2 = this.modelindex;
-               }
-
-               this.modelindex = this.lodmodelindex0;
-               setsize(this, mi, ma);
-       }
-
-       if(this.lodmodelindex1)
-               if (!getSendEntity(this))
-                       SetCustomizer(this, LOD_customize, LOD_uncustomize);
-}
diff --git a/qcsrc/server/g_subs.qh b/qcsrc/server/g_subs.qh
deleted file mode 100644 (file)
index 2528786..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-#pragma once
-
-void SUB_NullThink(entity this);
-
-/*
-==================
-SUB_Friction
-
-Applies some friction to this
-==================
-*/
-.float friction;
-void SUB_Friction (entity this);
-
-/*
-==================
-SUB_VanishOrRemove
-
-Makes client invisible or removes non-client
-==================
-*/
-void SUB_VanishOrRemove (entity ent);
-
-void SUB_SetFade_Think (entity this);
-
-/*
-==================
-SUB_SetFade
-
-Fade 'ent' out when time >= 'when'
-==================
-*/
-void SUB_SetFade (entity ent, float when, float fadetime);
-
-/*
-==================
-main
-
-unused but required by the engine
-==================
-*/
-void main ();
-
-// Misc
-
-/*
-==================
-traceline_antilag
-
-A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
-Additionally it moves players back into the past before the trace and restores them afterward.
-==================
-*/
-void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz);
-void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
-void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
-void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag);
-void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
-void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
-void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag);
-
-float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity); // returns the number of traces done, for benchmarking
-
-void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity);
-
-/*
-==================
-findbetterlocation
-
-Returns a point at least 12 units away from walls
-(useful for explosion animations, although the blast is performed where it really happened)
-Ripped from DPMod
-==================
-*/
-vector findbetterlocation (vector org, float mindist);
-
-
-.string lodtarget1;
-.string lodtarget2;
-.string lodmodel1;
-.string lodmodel2;
-.float lodmodelindex0;
-.float lodmodelindex1;
-.float lodmodelindex2;
-.float loddistance1;
-.float loddistance2;
-
-bool LOD_customize(entity this, entity client);
-
-void LOD_uncustomize(entity this);
-
-void LODmodel_attach(entity this);
index a8c2a76fc9bbb80375a2b5512e81ec7afa59319f..5c55e4ff50473ef065049d2b3ac25d1972f0f017 100644 (file)
@@ -13,7 +13,7 @@
 #include "g_hook.qh"
 #include "ipban.qh"
 #include "mapvoting.qh"
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
 #include "race.qh"
 #include "scores.qh"
 #include "teamplay.qh"
@@ -21,6 +21,7 @@
 #include "../common/constants.qh"
 #include <common/net_linked.qh>
 #include "../common/deathtypes/all.qh"
+#include "../common/gamemodes/sv_rules.qh"
 #include "../common/mapinfo.qh"
 #include "../common/monsters/_mod.qh"
 #include "../common/monsters/sv_monsters.qh"
@@ -91,9 +92,6 @@ const float SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS = 1;
 string redirection_target;
 float world_initialized;
 
-string GetGametype();
-void ShuffleMaplist();
-
 void SetDefaultAlpha()
 {
        if (!MUTATOR_CALLHOOK(SetDefaultAlpha))
@@ -362,6 +360,7 @@ void cvar_changes_init()
                BADPREFIX("gameversion_");
                BADPREFIX("g_chat_");
                BADPREFIX("g_ctf_captimerecord_");
+               BADPREFIX("g_hats_");
                BADPREFIX("g_maplist_");
                BADPREFIX("g_mod_");
                BADPREFIX("g_respawn_");
@@ -578,12 +577,7 @@ STATIC_INIT_EARLY(maxclients)
        }
 }
 
-void Map_MarkAsRecent(string m);
 float world_already_spawned;
-void Nagger_Init();
-void ClientInit_Spawn();
-void WeaponStats_Init();
-void WeaponStats_Shutdown();
 spawnfunc(worldspawn)
 {
        server_is_dedicated = boolean(stof(cvar_defstring("is_dedicated")));
@@ -1057,7 +1051,7 @@ void Map_Goto(float reinit)
 // return codes of map selectors:
 //   -1 = temporary failure (that is, try some method that is guaranteed to succeed)
 //   -2 = permanent failure
-float() MaplistMethod_Iterate = // usual method
+float MaplistMethod_Iterate() // usual method
 {
        float pass, i;
 
@@ -1076,7 +1070,7 @@ float() MaplistMethod_Iterate = // usual method
        return -1;
 }
 
-float() MaplistMethod_Repeat = // fallback method
+float MaplistMethod_Repeat() // fallback method
 {
        LOG_TRACE("Trying MaplistMethod_Repeat");
 
@@ -1085,7 +1079,7 @@ float() MaplistMethod_Repeat = // fallback method
        return -2;
 }
 
-float() MaplistMethod_Random = // random map selection
+float MaplistMethod_Random() // random map selection
 {
        float i, imax;
 
@@ -1103,7 +1097,7 @@ float() MaplistMethod_Random = // random map selection
        return -1;
 }
 
-float(float exponent) MaplistMethod_Shuffle = // more clever shuffling
+float MaplistMethod_Shuffle(float exponent) // more clever shuffling
 // the exponent sets a bias on the map selection:
 // the higher the exponent, the less likely "shortly repeated" same maps are
 {
@@ -1472,7 +1466,7 @@ void FixIntermissionClient(entity e)
        if(!e.autoscreenshot) // initial call
        {
                e.autoscreenshot = time + 0.8;  // used for autoscreenshot
-               e.health = -2342;
+               SetResourceAmountExplicit(e, RESOURCE_HEALTH, -2342);
                // first intermission phase; voting phase has positive health (used to decide whether to send SVC_FINALE or not)
                for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                {
@@ -2108,7 +2102,6 @@ float RedirectionThink()
        return true;
 }
 
-void TargetMusic_RestoreGame();
 void RestoreGame()
 {
        // Loaded from a save game
index 034407bc1f0f135722602b3ba8224e70ca98ea00..c0c35589a865f476ea08d006f5f43c2ab7996ac6 100644 (file)
@@ -16,11 +16,15 @@ void IntermissionThink(entity this);
 void GotoNextMap(float reinit);
 void ReadyRestart();
 
+string GetGametype();
+
 void DumpStats(float final);
 float Map_IsRecent(string m);
 string GetNextMap();
 void ShuffleMaplist();
 void Map_Goto_SetStr(string nextmapname);
 void Map_Goto(float reinit);
+void Map_MarkAsRecent(string m);
 float DoNextMapOverride(float reinit);
 void CheckRules_World();
+float RedirectionThink();
index cf0eb13b77b7e22a94e598937a24cc5b74b75a59..2c0af1c8f2ac12ab4d214bfc84e51dbc94388158 100644 (file)
@@ -31,8 +31,6 @@
 
 #define MAX_IPBAN_URIS (URI_GET_IPBAN_END - URI_GET_IPBAN + 1)
 
-float Ban_Insert(string ip, float bantime, string reason, float dosync);
-
 void OnlineBanList_SendBan(string ip, float bantime, string reason)
 {
        string uri;
index 946f44f9351a14b3be143cadf3bdecf5da0dc38f..330d8b7dff1e8c509ee6b419fddc14442423ec2e 100644 (file)
@@ -6,4 +6,9 @@ float Ban_MaybeEnforceBan(entity client);
 float Ban_MaybeEnforceBanOnce(entity client);
 float BanCommand(string command);
 
+float Ban_Insert(string ip, float bantime, string reason, float dosync);
+void Ban_KickBanClient(entity client, float bantime, float masksize, string reason);
+void Ban_View();
+float Ban_Delete(float i);
+
 void OnlineBanList_URI_Get_Callback(float id, float status, string data);
index 066fef34544c99d54cc4ffa28bb5784a3365d35f..2bfdc49b767b626aa66e778174914c54ddda9565 100644 (file)
@@ -1,6 +1,7 @@
 #include "item_key.qh"
 
 #include "../common/mapobjects/subs.qh"
+#include <common/mapobjects/triggers.qh>
 #include "../common/monsters/_mod.qh"
 #include "../common/notifications/all.qh"
 #include "../common/util.qh"
@@ -86,7 +87,7 @@ void item_key_touch(entity this, entity toucher)
        this.message = "";
        SUB_UseTargets(this, toucher, toucher); // TODO: should we be using toucher for the trigger here?
        this.message = oldmsg;
-};
+}
 
 /**
  * Spawn a key with given model, key code and color.
@@ -123,7 +124,7 @@ void spawn_item_key(entity this)
        }
 
        settouch(this, item_key_touch);
-};
+}
 
 
 /*QUAKED item_key (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
@@ -264,7 +265,7 @@ spawnfunc(item_key1)
        this.classname = "item_key";
        this.itemkeys = ITEM_KEY_BIT(1);
        spawnfunc_item_key(this);
-};
+}
 
 /*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
 GOLD key.
@@ -283,4 +284,4 @@ spawnfunc(item_key2)
        this.classname = "item_key";
        this.itemkeys = ITEM_KEY_BIT(0);
        spawnfunc_item_key(this);
-};
+}
index 04b0ba41d596e05d1c40addc88b06a3e8c2640c2..b21df78e3f247cb53b809446f95a555c25c86bcb 100644 (file)
@@ -5,9 +5,9 @@
 /// game items.
 /// \copyright GNU GPLv2 or any later version.
 
-#include "g_subs.qh"
-#include "mutators/events.qh"
+#include <server/mutators/_mod.qh>
 #include <common/weapons/all.qh>
+#include <common/mapobjects/subs.qh>
 
 .bool m_isloot; ///< Holds whether item is loot.
 /// \brief Holds whether strength, shield or superweapon timers expire while
index 816945d2ab0bc9bbdcb50ef78f11b6f813a50597..5c564d56db45fc7baeabeb9c6fd22240cc4263df 100644 (file)
@@ -591,9 +591,9 @@ void MapVote_Tick()
        int totalvotes = 0;
        FOREACH_CLIENT(IS_REAL_CLIENT(it), {
                // hide scoreboard again
-               if(it.health != 2342)
+               if(GetResourceAmount(it, RESOURCE_HEALTH) != 2342)
                {
-                       it.health = 2342;
+                       SetResourceAmountExplicit(it, RESOURCE_HEALTH, 2342);
                        CS(it).impulse = 0;
 
                        msg_entity = it;
index 4ba5efb09fb80261e3c61dd82eaf7480c6c22578..319b6f16ff7e3c7c333d8fb3a67e2b360eefbde8 100644 (file)
@@ -5,16 +5,18 @@
 #include "constants.qh"
 #include "g_hook.qh"
 #include "ipban.qh"
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
 #include "../common/t_items.qh"
 #include "resources.qh"
 #include "items.qh"
+#include "player.qh"
 #include "weapons/accuracy.qh"
 #include "weapons/csqcprojectile.qh"
 #include "weapons/selection.qh"
 #include "../common/command/_mod.qh"
 #include "../common/constants.qh"
 #include <common/net_linked.qh>
+#include <common/weapons/weapon/crylink.qh>
 #include "../common/deathtypes/all.qh"
 #include "../common/mapinfo.qh"
 #include "../common/notifications/all.qh"
@@ -66,6 +68,10 @@ void WarpZone_crosshair_trace(entity pl)
        WarpZone_traceline_antilag(pl, CS(pl).cursor_trace_start, CS(pl).cursor_trace_start + normalize(CS(pl).cursor_trace_endpos - CS(pl).cursor_trace_start) * max_shot_distance, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
 }
 
+void dedicated_print(string input)
+{
+       if (server_is_dedicated) print(input);
+}
 
 void GameLogEcho(string s)
 {
@@ -260,8 +266,8 @@ string formatmessage(entity this, string msg)
                        case "%": replacement = "%"; break;
                        case "\\":replacement = "\\"; break;
                        case "n": replacement = "\n"; break;
-                       case "a": replacement = ftos(floor(this.armorvalue)); break;
-                       case "h": replacement = ftos(floor(this.health)); break;
+                       case "a": replacement = ftos(floor(GetResourceAmount(this, RESOURCE_ARMOR))); break;
+                       case "h": replacement = ftos(floor(GetResourceAmount(this, RESOURCE_HEALTH))); break;
                        case "l": replacement = NearestLocation(this.origin); break;
                        case "y": replacement = NearestLocation(cursor); break;
                        case "d": replacement = NearestLocation(this.death_origin); break;
@@ -1054,7 +1060,7 @@ bool SUB_NoImpactCheck(entity this, entity toucher)
        if(trace_dphitcontents == 0)
        {
                LOG_TRACEF("A hit from a projectile happened with no hit contents! DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct. (edict: %i, classname: %s, origin: %v)", this, this.classname, this.origin);
-               checkclient(this);
+               checkclient(this); // TODO: .health is checked in the engine with this, possibly replace with a QC function?
        }
     if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
         return true;
@@ -1080,7 +1086,6 @@ bool SUB_NoImpactCheck(entity this, entity toucher)
 
 #define SUB_OwnerCheck(ent,oth) ((oth) && ((oth) == (ent).owner))
 
-void W_Crylink_Dequeue(entity e);
 bool WarpZone_Projectile_Touch_ImpactFilter_Callback(entity this, entity toucher)
 {
        if(SUB_OwnerCheck(this, toucher))
index 16c5a1d4d48dd1f738d4c20621dc61310ad90d23..1455054d2c5cad597ec87e84412d45b74ea6dc00 100644 (file)
@@ -1,10 +1,11 @@
 #pragma once
 
 #include <server/defs.qh>
+#include <server/g_world.qh>
 
 #include <common/t_items.qh>
 
-#include "mutators/events.qh"
+#include <server/mutators/_mod.qh>
 
 #include <common/constants.qh>
 #include <common/mapinfo.qh>
@@ -68,6 +69,9 @@ void follow_sameorigin(entity e, entity to);
 
 string formatmessage(entity this, string msg);
 
+/** print(), but only print if the server is not local */
+void dedicated_print(string input);
+
 void GameLogEcho(string s);
 
 void GameLogInit();
@@ -215,7 +219,6 @@ void readplayerstartcvars();
 float sv_autotaunt;
 float sv_taunt;
 
-string GetGametype(); // g_world.qc
 void readlevelcvars()
 {
        if(cvar("sv_allow_fullbright"))
index f0108dec37f6f31717305564d5678040008018d4..7b7cdf33dffba200b23b9b5ebcfb4c5fb04803d6 100644 (file)
@@ -1,4 +1,3 @@
 // generated file; do not modify
+#include <server/mutators/events.qc>
 #include <server/mutators/loader.qc>
-
-#include <server/mutators/mutator/_mod.inc>
index 9888c94666bfd085a8abe873054f7de7563ea469..6adf8e0db57a092f1b5b74e1a14bc81d1702bd8d 100644 (file)
@@ -1,4 +1,3 @@
 // generated file; do not modify
+#include <server/mutators/events.qh>
 #include <server/mutators/loader.qh>
-
-#include <server/mutators/mutator/_mod.qh>
diff --git a/qcsrc/server/mutators/events.qc b/qcsrc/server/mutators/events.qc
new file mode 100644 (file)
index 0000000..c2dbb70
--- /dev/null
@@ -0,0 +1 @@
+#include "events.qh"
index cf39f337bb15b48cbe9b8bc527ac5b1e822b1c23..de21a59035a5d4d913ab3ba33aa9b2e1ffbbef14 100644 (file)
@@ -27,6 +27,12 @@ MUTATOR_HOOKABLE(PutClientInServer, EV_PutClientInServer);
     /**/
 MUTATOR_HOOKABLE(ForbidSpawn, EV_ForbidSpawn);
 
+/** returns true if client should be put as player on connection */
+#define EV_AutoJoinOnConnection(i, o) \
+    /** player */ i(entity, MUTATOR_ARGV_0_entity) \
+    /**/
+MUTATOR_HOOKABLE(AutoJoinOnConnection, EV_AutoJoinOnConnection);
+
 /** called when player spawns to determine whether to give them random start weapons. Return true to forbid giving them. */
 #define EV_ForbidRandomStartWeapons(i, o) \
        /** player */ i(entity, MUTATOR_ARGV_0_entity) \
@@ -739,6 +745,30 @@ RESOURCE_* constants for resource types. Return true to forbid giving. */
        /**/
 MUTATOR_HOOKABLE(GiveResourceWithLimit, EV_GiveResourceWithLimit);
 
+/** Called when some resource is being taken from an entity. See RESOURCE_* constants
+for resource types. Return true to forbid giving. */
+#define EV_TakeResource(i, o) \
+    /** receiver */      i(entity, MUTATOR_ARGV_0_entity) \
+    /** resource type */ i(int, MUTATOR_ARGV_1_int) \
+    /**/                 o(int, MUTATOR_ARGV_1_int) \
+    /** amount */        i(float, MUTATOR_ARGV_2_float) \
+    /**/                 o(float, MUTATOR_ARGV_2_float) \
+    /**/
+MUTATOR_HOOKABLE(TakeResource, EV_TakeResource);
+
+/** Called when some resource is being taken from an entity, with a limit. See
+RESOURCE_* constants for resource types. Return true to forbid giving. */
+#define EV_TakeResourceWithLimit(i, o) \
+    /** receiver */      i(entity, MUTATOR_ARGV_0_entity) \
+    /** resource type */ i(int, MUTATOR_ARGV_1_int) \
+    /**/                 o(int, MUTATOR_ARGV_1_int) \
+    /** amount */        i(float, MUTATOR_ARGV_2_float) \
+    /**/                 o(float, MUTATOR_ARGV_2_float) \
+    /** limit */         i(float, MUTATOR_ARGV_3_float) \
+    /**/                 o(float, MUTATOR_ARGV_3_float) \
+    /**/
+MUTATOR_HOOKABLE(TakeResourceWithLimit, EV_TakeResourceWithLimit);
+
 /** called at when a player connect */
 #define EV_ClientConnect(i, o) \
     /** player */ i(entity, MUTATOR_ARGV_0_entity) \
diff --git a/qcsrc/server/mutators/gamemode.qh b/qcsrc/server/mutators/gamemode.qh
deleted file mode 100644 (file)
index b2c595f..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-#pragma once
-
-#include <server/miscfunctions.qh>
-#include <server/g_world.qh>
-#include <server/round_handler.qh>
-#include <server/scores.qh>
-#include <server/scores_rules.qh>
-#include <server/teamplay.qh>
-#include <common/gamemodes/rules.qh>
-
-#include "mutator.qh"
-
-// TODO: trim
-
-#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/scores.qh>
-#include <common/stats.qh>
-#include <common/teams.qh>
-#include <common/util.qh>
-#include <common/command/_mod.qh>
-#include <common/net_notice.qh>
-#include <common/animdecide.qh>
-#include <common/monsters/_mod.qh>
-#include <common/monsters/sv_monsters.qh>
-#include <common/monsters/sv_spawn.qh>
-#include <common/weapons/config.qh>
-#include <common/weapons/_all.qh>
-#include <server/weapons/accuracy.qh>
-#include <server/weapons/common.qh>
-#include <server/weapons/csqcprojectile.qh>
-#include <server/weapons/hitplot.qh>
-#include <server/weapons/selection.qh>
-#include <server/weapons/spawning.qh>
-#include <server/weapons/throwing.qh>
-#include <server/weapons/tracing.qh>
-#include <server/weapons/weaponstats.qh>
-#include <server/weapons/weaponsystem.qh>
-#include <common/t_items.qh>
-#include <server/autocvars.qh>
-#include <server/constants.qh>
-#include <server/defs.qh>
-#include <common/notifications/all.qh>
-#include <common/deathtypes/all.qh>
-#include <common/turrets/sv_turrets.qh>
-#include <common/vehicles/all.qh>
-#include <server/campaign.qh>
-#include <common/campaign_common.qh>
-#include <common/mapinfo.qh>
-#include <server/command/common.qh>
-#include <server/command/banning.qh>
-#include <server/command/radarmap.qh>
-#include <server/command/vote.qh>
-#include <server/command/getreplies.qh>
-#include <server/command/cmd.qh>
-#include <server/command/sv_cmd.qh>
-#include <common/csqcmodel_settings.qh>
-#include <lib/csqcmodel/common.qh>
-#include <lib/csqcmodel/sv_model.qh>
-#include <server/anticheat.qh>
-#include <server/cheats.qh>
-#include <common/playerstats.qh>
-#include <server/portals.qh>
-#include <server/g_hook.qh>
-#include <server/spawnpoints.qh>
-#include <server/mapvoting.qh>
-#include <server/ipban.qh>
-#include <server/antilag.qh>
-#include <server/item_key.qh>
-#include <server/pathlib/pathlib.qh>
-#include <common/vehicles/all.qh>
-
-#include <common/mutators/mutator/waypoints/waypointsprites.qh>
-
-#include <server/client.qh>
-#include <server/player.qh>
-#include <server/impulse.qh>
-#include <server/cheats.qh>
-#include <server/g_damage.qh>
-
-#include <server/bot/api.qh>
-
-#include <server/command/_mod.qh>
-
-#include <common/monsters/_mod.qh>
-
-#include <server/weapons/tracing.qh>
-#include <server/weapons/weaponsystem.qh>
-
-#include <common/physics/player.qh>
-#include <common/effects/qc/_mod.qh>
-#include <common/deathtypes/all.qh>
-#include <common/notifications/all.qh>
-#include <common/mapobjects/teleporters.qh>
-#include <common/mapobjects/subs.qh>
-#include <common/stats.qh>
-#include <common/teams.qh>
-
-#include <lib/warpzone/server.qh>
-#include <lib/warpzone/util_server.qh>
-
-.float lastground;
-float total_players;
-float redalive, bluealive, yellowalive, pinkalive;
diff --git a/qcsrc/server/mutators/mutator.qh b/qcsrc/server/mutators/mutator.qh
deleted file mode 100644 (file)
index bea16f7..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-#pragma once
-
-#include <common/mutators/base.qh>
-
-#include <server/client.qh>
-#include <server/player.qh>
-#include <server/impulse.qh>
-#include <server/cheats.qh>
-#include <server/g_damage.qh>
-#include <server/round_handler.qh>
-#include <server/scores.qh>
-#include <server/scores_rules.qh>
-
-#include <server/bot/api.qh>
-
-#include <server/command/_mod.qh>
-
-#include <server/weapons/common.qh>
-#include <server/weapons/tracing.qh>
-#include <server/weapons/throwing.qh>
-#include <server/weapons/weaponsystem.qh>
-
-#include <common/deathtypes/all.qh>
-#include <common/notifications/all.qh>
-#include <common/mapobjects/teleporters.qh>
-#include <common/mapobjects/subs.qh>
-#include <common/stats.qh>
-#include <common/teams.qh>
-
-#include <common/monsters/_mod.qh>
-
-#include <lib/warpzone/anglestransform.qh>
-#include <lib/warpzone/server.qh>
-#include <lib/warpzone/util_server.qh>
diff --git a/qcsrc/server/mutators/mutator/_mod.inc b/qcsrc/server/mutators/mutator/_mod.inc
deleted file mode 100644 (file)
index 6835f5d..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-// generated file; do not modify
-#include <server/mutators/mutator/gamemode_assault.qc>
-#include <server/mutators/mutator/gamemode_ca.qc>
-#include <server/mutators/mutator/gamemode_ctf.qc>
-#include <server/mutators/mutator/gamemode_cts.qc>
-#include <server/mutators/mutator/gamemode_deathmatch.qc>
-#include <server/mutators/mutator/gamemode_domination.qc>
-#include <server/mutators/mutator/gamemode_freezetag.qc>
-#include <server/mutators/mutator/gamemode_invasion.qc>
-#include <server/mutators/mutator/gamemode_keepaway.qc>
-#include <server/mutators/mutator/gamemode_keyhunt.qc>
-#include <server/mutators/mutator/gamemode_lms.qc>
-#include <server/mutators/mutator/gamemode_race.qc>
-#include <server/mutators/mutator/gamemode_tdm.qc>
diff --git a/qcsrc/server/mutators/mutator/_mod.qh b/qcsrc/server/mutators/mutator/_mod.qh
deleted file mode 100644 (file)
index aef0b33..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-// generated file; do not modify
-#include <server/mutators/mutator/gamemode_assault.qh>
-#include <server/mutators/mutator/gamemode_ca.qh>
-#include <server/mutators/mutator/gamemode_ctf.qh>
-#include <server/mutators/mutator/gamemode_cts.qh>
-#include <server/mutators/mutator/gamemode_deathmatch.qh>
-#include <server/mutators/mutator/gamemode_domination.qh>
-#include <server/mutators/mutator/gamemode_freezetag.qh>
-#include <server/mutators/mutator/gamemode_invasion.qh>
-#include <server/mutators/mutator/gamemode_keepaway.qh>
-#include <server/mutators/mutator/gamemode_keyhunt.qh>
-#include <server/mutators/mutator/gamemode_lms.qh>
-#include <server/mutators/mutator/gamemode_race.qh>
-#include <server/mutators/mutator/gamemode_tdm.qh>
diff --git a/qcsrc/server/mutators/mutator/gamemode_assault.qc b/qcsrc/server/mutators/mutator/gamemode_assault.qc
deleted file mode 100644 (file)
index d43dc99..0000000
+++ /dev/null
@@ -1,627 +0,0 @@
-#include "gamemode_assault.qh"
-
-#include <lib/float.qh>
-
-.entity sprite;
-#define AS_ROUND_DELAY 5
-
-IntrusiveList g_assault_destructibles;
-IntrusiveList g_assault_objectivedecreasers;
-IntrusiveList g_assault_objectives;
-STATIC_INIT(g_assault)
-{
-       g_assault_destructibles = IL_NEW();
-       g_assault_objectivedecreasers = IL_NEW();
-       g_assault_objectives = IL_NEW();
-}
-
-// random functions
-void assault_objective_use(entity this, entity actor, entity trigger)
-{
-       // activate objective
-       this.health = 100;
-       //print("^2Activated objective ", this.targetname, "=", etos(this), "\n");
-       //print("Activator is ", actor.classname, "\n");
-
-       IL_EACH(g_assault_objectivedecreasers, it.target == this.targetname,
-       {
-               target_objective_decrease_activate(it);
-       });
-}
-
-vector target_objective_spawn_evalfunc(entity this, entity player, entity spot, vector current)
-{
-       if(this.health < 0 || this.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(entity this)
-{
-       this.health = ASSAULT_VALUE_INACTIVE;
-}
-
-// decrease the health of targeted objectives
-void assault_objective_decrease_use(entity this, entity actor, entity trigger)
-{
-       if(actor.team != assault_attacker_team)
-       {
-               // wrong team triggered decrease
-               return;
-       }
-
-       if(trigger.assault_sprite)
-       {
-               WaypointSprite_Disown(trigger.assault_sprite, waypointsprite_deadlifetime);
-               if(trigger.classname == "func_assault_destructible")
-                       trigger.sprite = NULL; // TODO: just unsetting it?!
-       }
-       else
-               return; // already activated! cannot activate again!
-
-       if(this.enemy.health < ASSAULT_VALUE_INACTIVE)
-       {
-               if(this.enemy.health - this.dmg > 0.5)
-               {
-                       GameRules_scoring_add_team(actor, SCORE, this.dmg);
-                       this.enemy.health = this.enemy.health - this.dmg;
-               }
-               else
-               {
-                       GameRules_scoring_add_team(actor, SCORE, this.enemy.health);
-                       GameRules_scoring_add_team(actor, ASSAULT_OBJECTIVES, 1);
-                       this.enemy.health = -1;
-
-                       if(this.enemy.message)
-                               FOREACH_CLIENT(IS_PLAYER(it), { centerprint(it, this.enemy.message); });
-
-                       SUB_UseTargets(this.enemy, this, trigger);
-               }
-       }
-}
-
-void assault_setenemytoobjective(entity this)
-{
-       IL_EACH(g_assault_objectives, it.targetname == this.target,
-       {
-               if(this.enemy == NULL)
-                       this.enemy = it;
-               else
-                       objerror(this, "more than one objective as target - fix the map!");
-               break;
-       });
-
-       if(this.enemy == NULL)
-               objerror(this, "no objective as target - fix the map!");
-}
-
-bool assault_decreaser_sprite_visible(entity this, entity player, entity view)
-{
-       if(this.assault_decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE)
-               return false;
-
-       return true;
-}
-
-void target_objective_decrease_activate(entity this)
-{
-       entity spr;
-       this.owner = NULL;
-       FOREACH_ENTITY_STRING(target, this.targetname,
-       {
-               if(it.assault_sprite != NULL)
-               {
-                       WaypointSprite_Disown(it.assault_sprite, waypointsprite_deadlifetime);
-                       if(it.classname == "func_assault_destructible")
-                               it.sprite = NULL; // TODO: just unsetting it?!
-               }
-
-               spr = WaypointSprite_SpawnFixed(WP_AssaultDefend, 0.5 * (it.absmin + it.absmax), it, assault_sprite, RADARICON_OBJECTIVE);
-               spr.assault_decreaser = this;
-               spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
-               spr.classname = "sprite_waypoint";
-               WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
-               if(it.classname == "func_assault_destructible")
-               {
-                       WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
-                       WaypointSprite_UpdateMaxHealth(spr, it.max_health);
-                       WaypointSprite_UpdateHealth(spr, it.health);
-                       it.sprite = spr;
-               }
-               else
-                       WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
-       });
-}
-
-void target_objective_decrease_findtarget(entity this)
-{
-       assault_setenemytoobjective(this);
-}
-
-void target_assault_roundend_reset(entity this)
-{
-       //print("round end reset\n");
-       ++this.cnt; // up round counter
-       this.winning = false; // up round
-}
-
-void target_assault_roundend_use(entity this, entity actor, entity trigger)
-{
-       this.winning = 1; // round has been won by attackers
-}
-
-void assault_roundstart_use(entity this, entity actor, entity trigger)
-{
-       SUB_UseTargets(this, this, trigger);
-
-       //(Re)spawn all turrets
-       IL_EACH(g_turrets, true,
-       {
-               // Swap turret teams
-               if(it.team == NUM_TEAM_1)
-                       it.team = NUM_TEAM_2;
-               else
-                       it.team = NUM_TEAM_1;
-
-               // Doubles as teamchange
-               turret_respawn(it);
-       });
-}
-void assault_roundstart_use_this(entity this)
-{
-       assault_roundstart_use(this, NULL, NULL);
-}
-
-void assault_wall_think(entity this)
-{
-       if(this.enemy.health < 0)
-       {
-               this.model = "";
-               this.solid = SOLID_NOT;
-       }
-       else
-       {
-               this.model = this.mdl;
-               this.solid = SOLID_BSP;
-       }
-
-       this.nextthink = time + 0.2;
-}
-
-// trigger new round
-// reset objectives, toggle spawnpoints, reset triggers, ...
-void assault_new_round(entity this)
-{
-       //bprint("ASSAULT: new round\n");
-
-       // up round counter
-       this.winning = this.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;
-
-       IL_EACH(g_saved_team, !IS_CLIENT(it),
-       {
-               if(it.team_saved == NUM_TEAM_1)
-                       it.team_saved = NUM_TEAM_2;
-               else if(it.team_saved == NUM_TEAM_2)
-                       it.team_saved = NUM_TEAM_1;
-       });
-
-       // reset the level with a countdown
-       cvar_set("timelimit", ftos(ceil(time - AS_ROUND_DELAY - game_starttime) / 60));
-       ReadyRestart_force(); // sets game_starttime
-}
-
-entity as_round;
-.entity ent_winning;
-void as_round_think()
-{
-       game_stopped = false;
-       assault_new_round(as_round.ent_winning);
-       delete(as_round);
-       as_round = NULL;
-}
-
-// Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives)
-// they win. Otherwise the defending team wins once the timelimit passes.
-int WinningCondition_Assault()
-{
-       if(as_round)
-               return WINNING_NO;
-
-       WinningConditionHelper(NULL); // set worldstatus
-
-       int status = WINNING_NO;
-       // as the timelimit has not yet passed just assume the defending team will win
-       if(assault_attacker_team == NUM_TEAM_1)
-       {
-               SetWinners(team, NUM_TEAM_2);
-       }
-       else
-       {
-               SetWinners(team, NUM_TEAM_1);
-       }
-
-       entity ent;
-       ent = find(NULL, classname, "target_assault_roundend");
-       if(ent)
-       {
-               if(ent.winning) // round end has been triggered by attacking team
-               {
-                       bprint("Assault: round completed.\n");
-                       SetWinners(team, assault_attacker_team);
-
-                       TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 666 - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 0));
-
-                       if(ent.cnt == 1 || autocvar_g_campaign) // this was the second round
-                       {
-                               status = WINNING_YES;
-                       }
-                       else
-                       {
-                               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ASSAULT_OBJ_DESTROYED, ceil(time - game_starttime));
-                               as_round = new(as_round);
-                               as_round.think = as_round_think;
-                               as_round.ent_winning = ent;
-                               as_round.nextthink = time + AS_ROUND_DELAY;
-                               game_stopped = true;
-
-                               // make sure timelimit isn't hit while the game is blocked
-                               if(autocvar_timelimit > 0)
-                               if(time + AS_ROUND_DELAY >= game_starttime + autocvar_timelimit * 60)
-                                       cvar_set("timelimit", ftos(autocvar_timelimit + AS_ROUND_DELAY / 60));
-                       }
-               }
-       }
-
-       return status;
-}
-
-// spawnfuncs
-spawnfunc(info_player_attacker)
-{
-       if (!g_assault) { delete(this); return; }
-
-       this.team = NUM_TEAM_1; // red, gets swapped every round
-       spawnfunc_info_player_deathmatch(this);
-}
-
-spawnfunc(info_player_defender)
-{
-       if (!g_assault) { delete(this); return; }
-
-       this.team = NUM_TEAM_2; // blue, gets swapped every round
-       spawnfunc_info_player_deathmatch(this);
-}
-
-spawnfunc(target_objective)
-{
-       if (!g_assault) { delete(this); return; }
-
-       this.classname = "target_objective";
-       IL_PUSH(g_assault_objectives, this);
-       this.use = assault_objective_use;
-       this.reset = assault_objective_reset;
-       this.reset(this);
-       this.spawn_evalfunc = target_objective_spawn_evalfunc;
-}
-
-spawnfunc(target_objective_decrease)
-{
-       if (!g_assault) { delete(this); return; }
-
-       this.classname = "target_objective_decrease";
-       IL_PUSH(g_assault_objectivedecreasers, this);
-
-       if(!this.dmg)
-               this.dmg = 101;
-
-       this.use = assault_objective_decrease_use;
-       this.health = ASSAULT_VALUE_INACTIVE;
-       this.max_health = ASSAULT_VALUE_INACTIVE;
-       this.enemy = NULL;
-
-       InitializeEntity(this, 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) { delete(this); return; }
-
-       this.spawnflags = 3;
-       this.classname = "func_assault_destructible";
-       IL_PUSH(g_assault_destructibles, this);
-
-       if(assault_attacker_team == NUM_TEAM_1)
-               this.team = NUM_TEAM_2;
-       else
-               this.team = NUM_TEAM_1;
-
-       spawnfunc_func_breakable(this);
-}
-
-spawnfunc(func_assault_wall)
-{
-       if (!g_assault) { delete(this); return; }
-
-       this.classname = "func_assault_wall";
-       this.mdl = this.model;
-       _setmodel(this, this.mdl);
-       this.solid = SOLID_BSP;
-       setthink(this, assault_wall_think);
-       this.nextthink = time;
-       InitializeEntity(this, assault_setenemytoobjective, INITPRIO_FINDTARGET);
-}
-
-spawnfunc(target_assault_roundend)
-{
-       if (!g_assault) { delete(this); return; }
-
-       this.winning = 0; // round not yet won by attackers
-       this.classname = "target_assault_roundend";
-       this.use = target_assault_roundend_use;
-       this.cnt = 0; // first round
-       this.reset = target_assault_roundend_reset;
-}
-
-spawnfunc(target_assault_roundstart)
-{
-       if (!g_assault) { delete(this); return; }
-
-       assault_attacker_team = NUM_TEAM_1;
-       this.classname = "target_assault_roundstart";
-       this.use = assault_roundstart_use;
-       this.reset2 = assault_roundstart_use_this;
-       InitializeEntity(this, assault_roundstart_use_this, INITPRIO_FINDTARGET);
-}
-
-// legacy bot code
-void havocbot_goalrating_ast_targets(entity this, float ratingscale)
-{
-       IL_EACH(g_assault_destructibles, it.bot_attack,
-       {
-               if (it.target == "")
-                       continue;
-
-               bool found = false;
-               entity destr = it;
-               IL_EACH(g_assault_objectivedecreasers, it.targetname == destr.target,
-               {
-                       if(it.enemy.health > 0 && it.enemy.health < ASSAULT_VALUE_INACTIVE)
-                       {
-                               found = true;
-                               break;
-                       }
-               });
-
-               if(!found)
-                       continue;
-
-               vector p = 0.5 * (it.absmin + it.absmax);
-
-               // Find and rate waypoints around it
-               found = false;
-               entity best = NULL;
-               float bestvalue = 99999999999;
-               entity des = it;
-               for(float radius = 0; radius < 1500 && !found; radius += 500)
-               {
-                       FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
-                       {
-                               if(checkpvs(it.origin, des))
-                               {
-                                       found = true;
-                                       if(it.cnt < bestvalue)
-                                       {
-                                               best = it;
-                                               bestvalue = it.cnt;
-                                       }
-                               }
-                       });
-               }
-
-               if(best)
-               {
-               ///     dprint("waypoints around target were found\n");
-               //      te_lightning2(NULL, '0 0 0', best.origin);
-               //      te_knightspike(best.origin);
-
-                       navigation_routerating(this, best, ratingscale, 4000);
-                       best.cnt += 1;
-
-                       this.havocbot_attack_time = 0;
-
-                       if(checkpvs(this.origin + this.view_ofs, it))
-                       if(checkpvs(this.origin + this.view_ofs, best))
-                       {
-                       //      dprint("increasing attack time for this target\n");
-                               this.havocbot_attack_time = time + 2;
-                       }
-               }
-       });
-}
-
-void havocbot_role_ast_offense(entity this)
-{
-       if(IS_DEAD(this))
-       {
-               this.havocbot_attack_time = 0;
-               havocbot_ast_reset_role(this);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + 120;
-
-       if (time > this.havocbot_role_timeout)
-       {
-               havocbot_ast_reset_role(this);
-               return;
-       }
-
-       if(this.havocbot_attack_time>time)
-               return;
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
-               havocbot_goalrating_ast_targets(this, 20000);
-               havocbot_goalrating_items(this, 15000, this.origin, 10000);
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_ast_defense(entity this)
-{
-       if(IS_DEAD(this))
-       {
-               this.havocbot_attack_time = 0;
-               havocbot_ast_reset_role(this);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + 120;
-
-       if (time > this.havocbot_role_timeout)
-       {
-               havocbot_ast_reset_role(this);
-               return;
-       }
-
-       if(this.havocbot_attack_time>time)
-               return;
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 3000);
-               havocbot_goalrating_ast_targets(this, 20000);
-               havocbot_goalrating_items(this, 15000, this.origin, 10000);
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_ast_setrole(entity this, float role)
-{
-       switch(role)
-       {
-               case HAVOCBOT_AST_ROLE_DEFENSE:
-                       this.havocbot_role = havocbot_role_ast_defense;
-                       this.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE;
-                       this.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_AST_ROLE_OFFENSE:
-                       this.havocbot_role = havocbot_role_ast_offense;
-                       this.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE;
-                       this.havocbot_role_timeout = 0;
-                       break;
-       }
-}
-
-void havocbot_ast_reset_role(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if(this.team == assault_attacker_team)
-               havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_OFFENSE);
-       else
-               havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_DEFENSE);
-}
-
-// mutator hooks
-MUTATOR_HOOKFUNCTION(as, PlayerSpawn)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.team == assault_attacker_team)
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
-       else
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
-}
-
-MUTATOR_HOOKFUNCTION(as, TurretSpawn)
-{
-       entity turret = M_ARGV(0, entity);
-
-       if(!turret.team || turret.team == FLOAT_MAX)
-               turret.team = 5; // this gets reversed when match starts?
-}
-
-MUTATOR_HOOKFUNCTION(as, VehicleInit)
-{
-       entity veh = M_ARGV(0, entity);
-
-       veh.nextthink = time + 0.5;
-}
-
-MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole)
-{
-       entity bot = M_ARGV(0, entity);
-
-       havocbot_ast_reset_role(bot);
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, PlayHitsound)
-{
-       entity frag_victim = M_ARGV(0, entity);
-
-       return (frag_victim.classname == "func_assault_destructible");
-}
-
-MUTATOR_HOOKFUNCTION(as, CheckAllowedTeams)
-{
-       // assault always has 2 teams
-       c1 = c2 = 0;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, CheckRules_World)
-{
-       M_ARGV(0, float) = WinningCondition_Assault();
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, ReadLevelCvars)
-{
-       // incompatible
-       warmup_stage = 0;
-       sv_ready_restart_after_countdown = 0;
-}
-
-MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn)
-{
-    entity ent = M_ARGV(0, entity);
-
-       switch(ent.classname)
-       {
-               case "info_player_team1":
-               case "info_player_team2":
-               case "info_player_team3":
-               case "info_player_team4":
-                       return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(as, ReadyRestart_Deny)
-{
-       // readyrestart not supported (yet)
-       return true;
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_assault.qh b/qcsrc/server/mutators/mutator/gamemode_assault.qh
deleted file mode 100644 (file)
index 15d3a65..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma once
-
-#include "../gamemode.qh"
-
-const int ASSAULT_VALUE_INACTIVE = 1000;
-
-const int ST_ASSAULT_OBJECTIVES = 1;
-
-REGISTER_MUTATOR(as, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-        GameRules_teams(true);
-        int teams = BITS(2); // always red vs blue
-        GameRules_scoring(teams, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, {
-            field_team(ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
-            field(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
-        });
-       }
-       return 0;
-}
-
-// 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(entity this) havocbot_role_ast_defense;
-void(entity this) havocbot_role_ast_offense;
-
-void(entity bot) havocbot_ast_reset_role;
-
-void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_items;
-void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
-
-// predefined spawnfuncs
-void target_objective_decrease_activate(entity this);
diff --git a/qcsrc/server/mutators/mutator/gamemode_ca.qc b/qcsrc/server/mutators/mutator/gamemode_ca.qc
deleted file mode 100644 (file)
index 176661a..0000000
+++ /dev/null
@@ -1,488 +0,0 @@
-#include "gamemode_ca.qh"
-
-float autocvar_g_ca_damage2score_multiplier;
-bool autocvar_g_ca_spectate_enemies;
-
-void CA_count_alive_players()
-{
-       total_players = redalive = bluealive = yellowalive = pinkalive = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               switch(it.team)
-               {
-                       case NUM_TEAM_1: ++total_players; if(!IS_DEAD(it)) ++redalive; break;
-                       case NUM_TEAM_2: ++total_players; if(!IS_DEAD(it)) ++bluealive; break;
-                       case NUM_TEAM_3: ++total_players; if(!IS_DEAD(it)) ++yellowalive; break;
-                       case NUM_TEAM_4: ++total_players; if(!IS_DEAD(it)) ++pinkalive; break;
-               }
-       });
-       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
-               STAT(REDALIVE, it) = redalive;
-               STAT(BLUEALIVE, it) = bluealive;
-               STAT(YELLOWALIVE, it) = yellowalive;
-               STAT(PINKALIVE, it) = 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
-}
-
-void nades_Clear(entity player);
-
-#define CA_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
-#define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == NumTeams(ca_teams))
-float CA_CheckWinner()
-{
-       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
-               FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
-
-               allowed_to_spawn = false;
-               game_stopped = true;
-               round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
-               return 1;
-       }
-
-       CA_count_alive_players();
-       if(CA_ALIVE_TEAMS() > 1)
-               return 0;
-
-       int winner_team = CA_GetWinnerTeam();
-       if(winner_team > 0)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
-               TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1);
-       }
-       else if(winner_team == -1)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
-       }
-
-       allowed_to_spawn = false;
-       game_stopped = true;
-       round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
-
-       FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
-
-       return 1;
-}
-
-void CA_RoundStart()
-{
-    allowed_to_spawn = boolean(warmup_stage);
-}
-
-bool CA_CheckTeams()
-{
-       static int 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, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               return true;
-       }
-       if(total_players == 0)
-       {
-               if(prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               return false;
-       }
-       int missing_teams_mask = 0;
-       if(ca_teams & BIT(0))
-               missing_teams_mask += (!redalive) * 1;
-       if(ca_teams & BIT(1))
-               missing_teams_mask += (!bluealive) * 2;
-       if(ca_teams & BIT(2))
-               missing_teams_mask += (!yellowalive) * 4;
-       if(ca_teams & BIT(3))
-               missing_teams_mask += (!pinkalive) * 8;
-       if(prev_missing_teams_mask != missing_teams_mask)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
-               prev_missing_teams_mask = missing_teams_mask;
-       }
-       return false;
-}
-
-bool ca_isEliminated(entity e)
-{
-       if(e.caplayer == 1 && (IS_DEAD(e) || 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;
-       // continue from current player
-       for (entity e = start; (e = find(e, classname, STR_PLAYER)); )
-       {
-               if (SAME_TEAM(player, e)) return e;
-       }
-       // restart from begining
-       for (entity e = NULL; (e = find(e, classname, STR_PLAYER)); )
-       {
-               if (SAME_TEAM(player, e)) return e;
-       }
-       return start;
-}
-
-
-MUTATOR_HOOKFUNCTION(ca, PlayerSpawn)
-{
-    entity player = M_ARGV(0, entity);
-
-       player.caplayer = 1;
-       if (!warmup_stage)
-               eliminatedPlayers.SendFlags |= 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ForbidSpawn)
-{
-       entity player = M_ARGV(0, entity);
-
-       // spectators / observers that weren't playing can join; they are
-       // immediately forced to observe in the PutClientInServer hook
-       // this way they are put in a team and can play in the next round
-       if (!allowed_to_spawn && player.caplayer)
-               return true;
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
-{
-       entity player = M_ARGV(0, entity);
-
-       if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
-       {
-               TRANSMUTE(Observer, player);
-               if (CS(player).jointime != time && !player.caplayer) // not when connecting
-               {
-                       player.caplayer = 0.5;
-                       Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ca, reset_map_players)
-{
-       FOREACH_CLIENT(true, {
-               CS(it).killcount = 0;
-               if (!it.caplayer && IS_BOT_CLIENT(it))
-               {
-                       it.team = -1;
-                       it.caplayer = 1;
-               }
-               if (it.caplayer)
-               {
-                       TRANSMUTE(Player, it);
-                       it.caplayer = 1;
-                       PutClientInServer(it);
-               }
-       });
-       bot_relinkplayerlist();
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientConnect)
-{
-    entity player = M_ARGV(0, entity);
-
-       TRANSMUTE(Observer, player);
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, reset_map_global)
-{
-       allowed_to_spawn = true;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
-       M_ARGV(0, float) = ca_teams;
-}
-
-entity ca_LastPlayerForTeam(entity this)
-{
-       entity last_pl = NULL;
-       FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
-               if (!IS_DEAD(it))
-               if (SAME_TEAM(this, it))
-               if (!last_pl)
-                       last_pl = it;
-               else
-                       return NULL;
-       });
-       return last_pl;
-}
-
-void ca_LastPlayerForTeam_Notify(entity this)
-{
-       if (round_handler_IsActive())
-       if (round_handler_IsRoundStarted())
-       {
-               entity pl = ca_LastPlayerForTeam(this);
-               if (pl)
-                       Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerDies)
-{
-       entity frag_target = M_ARGV(2, entity);
-
-       ca_LastPlayerForTeam_Notify(frag_target);
-       if (!allowed_to_spawn)
-       {
-               frag_target.respawn_flags = RESPAWN_SILENT;
-               // prevent unwanted sudden rejoin as spectator and movement of spectator camera
-               frag_target.respawn_time = time + 2;
-       }
-       frag_target.respawn_flags |= RESPAWN_FORCE;
-       if (!warmup_stage)
-               eliminatedPlayers.SendFlags |= 1;
-       if(IS_BOT_CLIENT(frag_target))
-               bot_clear(frag_target);
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
-{
-    entity player = M_ARGV(0, entity);
-
-       if (player.caplayer == 1)
-               ca_LastPlayerForTeam_Notify(player);
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
-{
-    entity player = M_ARGV(0, entity);
-
-       if (!IS_DEAD(player))
-               ca_LastPlayerForTeam_Notify(player);
-       if (player.killindicator_teamchange == -2) // player wants to spectate
-               player.caplayer = 0;
-       if (player.caplayer)
-               player.frags = FRAGS_LMS_LOSER;
-       if (!warmup_stage)
-               eliminatedPlayers.SendFlags |= 1;
-       if (!player.caplayer)
-               return false;  // allow team reset
-       return true;  // prevent team reset
-}
-
-MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon)
-{
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST)
-{
-       M_ARGV(2, float) = 0; // score will be given to the winner team when the round ends
-       return true;
-}
-
-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");
-}
-
-MUTATOR_HOOKFUNCTION(ca, Damage_Calculate)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float frag_deathtype = M_ARGV(3, float);
-       float frag_damage = M_ARGV(4, float);
-       float frag_mirrordamage = M_ARGV(5, float);
-
-       if (IS_PLAYER(frag_target))
-       if (!IS_DEAD(frag_target))
-       if (frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id)
-               frag_damage = 0;
-
-       frag_mirrordamage = 0;
-
-       M_ARGV(4, float) = frag_damage;
-       M_ARGV(5, float) = frag_mirrordamage;
-}
-
-MUTATOR_HOOKFUNCTION(ca, FilterItem)
-{
-    entity item = M_ARGV(0, entity);
-
-       if (autocvar_g_powerups <= 0)
-       if (item.flags & FL_POWERUP)
-               return true;
-
-       if (autocvar_g_pickup_items <= 0)
-               return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float frag_damage = M_ARGV(7, float);
-       float damage_take = M_ARGV(4, float);
-       float damage_save = M_ARGV(5, float);
-
-       float excess = max(0, frag_damage - damage_take - damage_save);
-
-       if (frag_target != frag_attacker && IS_PLAYER(frag_attacker))
-               GameRules_scoring_add_team(frag_attacker, SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier);
-}
-
-MUTATOR_HOOKFUNCTION(ca, CalculateRespawnTime)
-{
-       // no respawn calculations needed, player is forced to spectate anyway
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerRegen)
-{
-       // no regeneration in CA
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
-{
-       // announce remaining frags
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectateSet)
-{
-    entity client = M_ARGV(0, entity);
-    entity targ = M_ARGV(1, entity);
-
-       if (!autocvar_g_ca_spectate_enemies && client.caplayer)
-       if (DIFF_TEAM(targ, client))
-               return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectateNext)
-{
-    entity client = M_ARGV(0, entity);
-
-       if (!autocvar_g_ca_spectate_enemies && client.caplayer)
-       {
-               entity targ = M_ARGV(1, entity);
-               M_ARGV(1, entity) = CA_SpectateNext(client, targ);
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectatePrev)
-{
-    entity client = M_ARGV(0, entity);
-    entity targ = M_ARGV(1, entity);
-    entity first = M_ARGV(2, entity);
-
-       if (!autocvar_g_ca_spectate_enemies && client.caplayer)
-       {
-               do { targ = targ.chain; }
-               while(targ && DIFF_TEAM(targ, client));
-
-               if (!targ)
-               {
-                       for (targ = first; targ && DIFF_TEAM(targ, client); targ = targ.chain);
-
-                       if (targ == client.enemy)
-                               return MUT_SPECPREV_RETURN;
-               }
-       }
-
-       M_ARGV(1, entity) = targ;
-
-       return MUT_SPECPREV_FOUND;
-}
-
-MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
-{
-       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
-               if (IS_PLAYER(it) || it.caplayer == 1)
-                       ++M_ARGV(0, int);
-               ++M_ARGV(1, int);
-       });
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate)
-{
-    entity player = M_ARGV(0, entity);
-
-       if (player.caplayer)
-       {
-               // they're going to spec, we can do other checks
-               if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
-                       Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
-               return MUT_SPECCMD_FORCE;
-       }
-
-       return MUT_SPECCMD_CONTINUE;
-}
-
-MUTATOR_HOOKFUNCTION(ca, WantWeapon)
-{
-       M_ARGV(2, bool) = true; // all weapons
-}
-
-MUTATOR_HOOKFUNCTION(ca, HideTeamNagger)
-{
-       return true; // doesn't work well with the whole spectator as player thing
-}
-
-MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus)
-{
-       entity player = M_ARGV(0, entity);
-
-       return player.caplayer == 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SetWeaponArena)
-{
-       // most weapons arena
-       if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "") M_ARGV(0, string) = "most";
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_ca.qh b/qcsrc/server/mutators/mutator/gamemode_ca.qh
deleted file mode 100644 (file)
index 0982fcc..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-#pragma once
-
-#include "../gamemode.qh"
-
-int autocvar_g_ca_point_limit;
-int autocvar_g_ca_point_leadlimit;
-float autocvar_g_ca_round_timelimit;
-bool autocvar_g_ca_team_spawns;
-//int autocvar_g_ca_teams;
-int autocvar_g_ca_teams_override;
-float autocvar_g_ca_warmup;
-
-
-int ca_teams;
-bool allowed_to_spawn;
-
-const int ST_CA_ROUNDS = 1;
-
-bool CA_CheckTeams();
-bool CA_CheckWinner();
-void CA_RoundStart();
-bool ca_isEliminated(entity e);
-
-REGISTER_MUTATOR(ca, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               GameRules_teams(true);
-        GameRules_spawning_teams(autocvar_g_ca_team_spawns);
-        GameRules_limit_score(autocvar_g_ca_point_limit);
-        GameRules_limit_lead(autocvar_g_ca_point_leadlimit);
-
-               ca_teams = autocvar_g_ca_teams_override;
-               if (ca_teams < 2)
-                       ca_teams = cvar("g_ca_teams"); // read the cvar directly as it gets written earlier in the same frame
-
-               ca_teams = BITS(bound(2, ca_teams, 4));
-        GameRules_scoring(ca_teams, SFL_SORT_PRIO_PRIMARY, 0, {
-            field_team(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY);
-        });
-
-               allowed_to_spawn = true;
-               round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart);
-               round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
-               EliminatedPlayers_Init(ca_isEliminated);
-       }
-       return 0;
-}
-
-// 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
diff --git a/qcsrc/server/mutators/mutator/gamemode_ctf.qc b/qcsrc/server/mutators/mutator/gamemode_ctf.qc
deleted file mode 100644 (file)
index 1847473..0000000
+++ /dev/null
@@ -1,2776 +0,0 @@
-#include "gamemode_ctf.qh"
-
-#include <common/effects/all.qh>
-#include <common/vehicles/all.qh>
-#include <server/teamplay.qh>
-
-#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;
-bool autocvar_g_ctf_flag_return_carrying;
-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;
-bool autocvar_g_ctf_score_ignore_fields;
-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 != NULL) ? ftos(actor.playerid) : "")));
-               //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (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, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
-       else if(!ctf_captimerecord)
-               Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time));
-       else if(cap_time < cap_record)
-               Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
-       else
-               Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
-
-       // 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, flag.ctf_pickuptime, cap_time);
-       }
-
-       if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
-               race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
-}
-
-bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
-{
-       int num_perteam = 0;
-       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
-
-       // automatically return if there's only 1 player on the team
-       return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
-               && flag.team);
-}
-
-bool ctf_Return_Customize(entity this, entity client)
-{
-       // only to the carrier
-       return boolean(client == this.owner);
-}
-
-void ctf_FlagcarrierWaypoints(entity player)
-{
-       WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 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));
-
-       if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
-       {
-               if(!player.wps_enemyflagcarrier)
-               {
-                       entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
-                       wp.colormod = WPCOLOR_ENEMYFC(player.team);
-                       setcefc(wp, ctf_Stalemate_Customize);
-
-                       if(IS_REAL_CLIENT(player) && !ctf_stalemate)
-                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
-               }
-
-               if(!player.wps_flagreturn)
-               {
-                       entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
-                       owp.colormod = '0 0.8 0.8';
-                       //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
-                       setcefc(owp, ctf_Return_Customize);
-               }
-       }
-}
-
-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;
-       int players_worseeq, players_total;
-
-       if(ctf_captureshield_max_ratio <= 0)
-               return false;
-
-       s  = GameRules_scoring_add(p, CTF_CAPS, 0);
-       s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0);
-       s3 = GameRules_scoring_add(p, CTF_RETURNS, 0);
-       s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0);
-
-       sr = ((s - s2) + (s3 + s4));
-
-       if(sr >= -ctf_captureshield_min_negscore)
-               return false;
-
-       players_total = players_worseeq = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               if(DIFF_TEAM(it, p))
-                       continue;
-               se  = GameRules_scoring_add(it, CTF_CAPS, 0);
-               se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0);
-               se3 = GameRules_scoring_add(it, CTF_RETURNS, 0);
-               se4 = GameRules_scoring_add(it, 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(entity this, entity client)
-{
-       if(!client.ctf_captureshielded) { return false; }
-       if(CTF_SAMETEAM(this, client)) { return false; }
-
-       return true;
-}
-
-void ctf_CaptureShield_Touch(entity this, entity toucher)
-{
-       if(!toucher.ctf_captureshielded) { return; }
-       if(CTF_SAMETEAM(this, toucher)) { return; }
-
-       vector mymid = (this.absmin + this.absmax) * 0.5;
-       vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
-
-       Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
-       if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
-}
-
-void ctf_CaptureShield_Spawn(entity flag)
-{
-       entity shield = new(ctf_captureshield);
-
-       shield.enemy = flag;
-       shield.team = flag.team;
-       settouch(shield, ctf_CaptureShield_Touch);
-       setcefc(shield, ctf_CaptureShield_Customize);
-       shield.effects = EF_ADDITIVE;
-       set_movetype(shield, MOVETYPE_NOCLIP);
-       shield.solid = SOLID_TRIGGER;
-       shield.avelocity = '7 0 11';
-       shield.scale = 0.5;
-
-       setorigin(shield, flag.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
-       set_movetype(flag, 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, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
-       _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
-       ctf_EventLog("dropped", player.team, player);
-
-       // scoring
-       GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
-       GameRules_scoring_add(player, CTF_DROPS, 1);
-
-       // waypoints
-       if(autocvar_g_ctf_flag_dropped_waypoint) {
-               entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL, ((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 = NULL;
-               flag.pass_target = NULL;
-       }
-}
-
-void ctf_Handle_Retrieve(entity flag, entity player)
-{
-       entity sender = flag.pass_sender;
-
-       // transfer flag to player
-       flag.owner = player;
-       flag.owner.flagcarried = flag;
-       GameRules_scoring_vip(player, true);
-
-       // 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);
-       }
-       set_movetype(flag, 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);
-
-       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
-               if(it == sender)
-                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
-               else if(it == player)
-                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
-               else if(SAME_TEAM(it, sender))
-                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), 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 = NULL;
-       flag.pass_target = NULL;
-}
-
-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, NULL, "");
-       setorigin(flag, player.origin + FLAG_DROP_OFFSET);
-       flag.owner.flagcarried = NULL;
-       GameRules_scoring_vip(flag.owner, false);
-       flag.owner = NULL;
-       flag.solid = SOLID_TRIGGER;
-       flag.ctf_dropper = player;
-       flag.ctf_droptime = time;
-       navigation_dynamicgoal_set(flag);
-
-       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
-                       set_movetype(flag, 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(NULL, _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, 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, 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);
-
-       if(player.wps_flagreturn)
-               WaypointSprite_Kill(player.wps_flagreturn);
-
-       // captureshield
-       ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
-}
-
-void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
-{
-       return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
-}
-
-// ==============
-// Event Handlers
-// ==============
-
-void nades_GiveBonus(entity player, float score);
-
-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 = NULL, 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((flag.cnt || enemy_flag.cnt) && flag.cnt != enemy_flag.cnt) { return; } // this should catch some edge cases (capturing grouped flag at ungrouped flag disallowed etc)
-
-       if (toucher.goalentity == flag.bot_basewaypoint)
-               toucher.goalentity_lock_timeout = 0;
-
-       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, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
-       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
-       float pscore = 0;
-       if(enemy_flag.score_capture || flag.score_capture)
-               pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
-       GameRules_scoring_add_team(player, SCORE, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
-       float capscore = 0;
-       if(enemy_flag.score_team_capture || flag.score_team_capture)
-               capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
-       GameRules_scoring_add_team(player, CTF_CAPS, ((capscore) ? capscore : 1));
-
-       old_time = GameRules_scoring_add(player, CTF_CAPTIME, 0);
-       new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
-       if(!old_time || new_time < old_time)
-               GameRules_scoring_add(player, 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))
-                       { GameRules_scoring_add_team(enemy_flag.ctf_dropper, SCORE, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
-       }
-
-       flag.enemy = toucher;
-
-       // 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, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
-       }
-       else if(flag.team)
-       {
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, 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))
-       {
-               GameRules_scoring_add_team(player, SCORE, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
-               GameRules_scoring_add(player, 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)
-       {
-               GameRules_scoring_add(flag.ctf_dropper, 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);
-
-       flag.enemy = player;
-
-       // 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
-
-       // attach the flag to the player
-       flag.owner = player;
-       player.flagcarried = flag;
-       GameRules_scoring_vip(player, true);
-       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
-       set_movetype(flag, 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, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), 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_NUM(flag.team, CENTER_CTF_PICKUP));
-       else
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
-
-       Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
-
-       if(!flag.team)
-               FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), { Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); });
-
-       if(flag.team)
-               FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
-                       if(CTF_SAMETEAM(flag, it))
-                       if(SAME_TEAM(player, it))
-                               Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
-                       else
-                               Send_Notification(NOTIF_ONE, it, 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
-       GameRules_scoring_add(player, CTF_PICKUPS, 1);
-       nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
-       switch(pickuptype)
-       {
-               case PICKUP_BASE:
-               {
-                       GameRules_scoring_add_team(player, SCORE, ((flag.score_pickup) ? flag.score_pickup : 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));
-                       GameRules_scoring_add_team(player, SCORE, 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, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
-                               case RETURN_DAMAGE:
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
-                               case RETURN_SPEEDRUN:
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
-                               case RETURN_NEEDKILL:
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
-                               default:
-                               case RETURN_TIMEOUT:
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
-                       }
-                       _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
-                       ctf_EventLog("returned", flag.team, NULL);
-                       flag.enemy = NULL;
-                       ctf_RespawnFlag(flag);
-               }
-       }
-}
-
-bool ctf_Stalemate_Customize(entity this, entity client)
-{
-       // make spectators see what the player would see
-       entity e = WaypointSprite_getviewentity(client);
-       entity wp_owner = this.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()
-{
-       // 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 = NULL; // 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, NULL, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
-                               wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
-                               setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
-                       }
-               }
-
-               if (!wpforenemy_announced)
-               {
-                       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); });
-
-                       wpforenemy_announced = true;
-               }
-       }
-}
-
-void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
-{
-       if(ITEM_DAMAGE_NEEDKILL(deathtype))
-       {
-               if(autocvar_g_ctf_flag_return_damage_delay)
-                       this.ctf_flagdamaged_byworld = true;
-               else
-               {
-                       this.health = 0;
-                       ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
-               }
-               return;
-       }
-       if(autocvar_g_ctf_flag_return_damage)
-       {
-               // reduce health and check if it should be returned
-               this.health = this.health - damage;
-               ctf_CheckFlagReturn(this, RETURN_DAMAGE);
-               return;
-       }
-}
-
-void ctf_FlagThink(entity this)
-{
-       // declarations
-       entity tmp_entity;
-
-       this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
-
-       // captureshield
-       if(this == ctf_worldflaglist) // only for the first flag
-               FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only
-
-       // sanity checks
-       if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
-               LOG_TRACE("wtf the flag got squashed?");
-               tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
-               if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
-                       setsize(this, this.m_mins, this.m_maxs);
-       }
-
-       // main think method
-       switch(this.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(vdist(this.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(this, tmp_entity, CAPTURE_DROPPED);
-                       }
-                       return;
-               }
-
-               case FLAG_DROPPED:
-               {
-                       this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
-
-                       if(autocvar_g_ctf_flag_dropped_floatinwater)
-                       {
-                               vector midpoint = ((this.absmin + this.absmax) * 0.5);
-                               if(pointcontents(midpoint) == CONTENT_WATER)
-                               {
-                                       this.velocity = this.velocity * 0.5;
-
-                                       if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
-                                               { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
-                                       else
-                                               { set_movetype(this, MOVETYPE_FLY); }
-                               }
-                               else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
-                       }
-                       if(autocvar_g_ctf_flag_return_dropped)
-                       {
-                               if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
-                               {
-                                       this.health = 0;
-                                       ctf_CheckFlagReturn(this, RETURN_DROPPED);
-                                       return;
-                               }
-                       }
-                       if(this.ctf_flagdamaged_byworld)
-                       {
-                               this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
-                               ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
-                               return;
-                       }
-                       else if(autocvar_g_ctf_flag_return_time)
-                       {
-                               this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
-                               ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
-                               return;
-                       }
-                       return;
-               }
-
-               case FLAG_CARRY:
-               {
-                       if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
-                       {
-                               this.health = 0;
-                               ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
-
-                               CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
-                               ImpulseCommands(this.owner);
-                       }
-                       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(this, this.owner) && this.team)
-                       {
-                               if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
-                                       ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
-                               else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
-                                       ctf_Handle_Return(this, this.owner);
-                       }
-                       return;
-               }
-
-               case FLAG_PASSING:
-               {
-                       vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
-                       targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
-                       WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
-
-                       if((this.pass_target == NULL)
-                               || (IS_DEAD(this.pass_target))
-                               || (this.pass_target.flagcarried)
-                               || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
-                               || ((trace_fraction < 1) && (trace_ent != this.pass_target))
-                               || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
-                       {
-                               // give up, pass failed
-                               ctf_Handle_Drop(this, NULL, DROP_PASS);
-                       }
-                       else
-                       {
-                               // still a viable target, go for it
-                               ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
-                       }
-                       return;
-               }
-
-               default: // this should never happen
-               {
-                       LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
-                       return;
-               }
-       }
-}
-
-METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
-{
-       return = false;
-       if(game_stopped) return;
-       if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
-
-       bool is_not_monster = (!IS_MONSTER(toucher));
-
-       // 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)
-               {
-                       flag.health = 0;
-                       ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
-               }
-               if(!flag.ctf_flagdamaged_byworld) { return; }
-       }
-
-       // special touch behaviors
-       if(STAT(FROZEN, toucher)) { 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 > flag.wait) // if we haven't in a while, play a sound/effect
-               {
-                       Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
-                       _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
-                       flag.wait = time + FLAG_TOUCHRATE;
-               }
-               return;
-       }
-       else if(IS_DEAD(toucher)) { return; }
-
-       switch(flag.ctf_status)
-       {
-               case FLAG_BASE:
-               {
-                       if(ctf_oneflag)
-                       {
-                               if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
-                                       ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
-                               else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
-                                       ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
-                       }
-                       else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
-                               ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
-                       else if(CTF_DIFFTEAM(toucher, flag) && (toucher.flagcarried) && CTF_SAMETEAM(toucher.flagcarried, toucher) && (!toucher.ctf_captureshielded) && autocvar_g_ctf_flag_return_carrying && (time > toucher.next_take_time) && is_not_monster)
-                       {
-                               ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
-                               ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
-                       }
-                       else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
-                               ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
-                       break;
-               }
-
-               case FLAG_DROPPED:
-               {
-                       if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
-                               ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
-                       else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
-                               ctf_Handle_Pickup(flag, 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?");
-                       break;
-               }
-
-               case FLAG_PASSING:
-               {
-                       if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
-                       {
-                               if(DIFF_TEAM(toucher, flag.pass_sender))
-                               {
-                                       if(ctf_Immediate_Return_Allowed(flag, toucher))
-                                               ctf_Handle_Return(flag, toucher);
-                                       else if(is_not_monster && (!toucher.flagcarried))
-                                               ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
-                               }
-                               else if(!toucher.flagcarried)
-                                       ctf_Handle_Retrieve(flag, 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.owner.wps_flagreturn);
-               WaypointSprite_Kill(flag.wps_flagcarrier);
-
-               flag.owner.flagcarried = NULL;
-               GameRules_scoring_vip(flag.owner, false);
-
-               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, NULL, "");
-       setorigin(flag, flag.ctf_spawnorigin);
-
-       set_movetype(flag, ((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 = NULL;
-       flag.pass_distance = 0;
-       flag.pass_sender = NULL;
-       flag.pass_target = NULL;
-       flag.ctf_dropper = NULL;
-       flag.ctf_pickuptime = 0;
-       flag.ctf_droptime = 0;
-       flag.ctf_flagdamaged_byworld = false;
-       navigation_dynamicgoal_unset(flag);
-
-       ctf_CheckStalemate();
-}
-
-void ctf_Reset(entity this)
-{
-       if(this.owner && IS_PLAYER(this.owner))
-               ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
-
-       this.enemy = NULL;
-       ctf_RespawnFlag(this);
-}
-
-bool ctf_FlagBase_Customize(entity this, entity client)
-{
-       entity e = WaypointSprite_getviewentity(client);
-       entity wp_owner = this.owner;
-       entity flag = e.flagcarried;
-       if(flag && CTF_SAMETEAM(e, flag))
-               return false;
-       if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
-               return false;
-       return true;
-}
-
-void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
-{
-       // bot waypoints
-       waypoint_spawnforitem_force(this, this.origin);
-       navigation_dynamicgoal_init(this, true);
-
-       // waypointsprites
-       entity basename;
-       switch (this.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, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
-       wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
-       WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
-       setcefc(wp, ctf_FlagBase_Customize);
-
-       // captureshield setup
-       ctf_CaptureShield_Spawn(this);
-}
-
-.bool pushable;
-
-void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
-{
-       // main setup
-       flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
-       ctf_worldflaglist = flag;
-
-       setattachment(flag, NULL, "");
-
-       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;
-       IL_PUSH(g_items, flag);
-       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;
-       flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
-       flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
-       if(flag.damagedbycontents)
-               IL_PUSH(g_damagedbycontents, flag);
-       flag.velocity = '0 0 0';
-       flag.mangle = flag.angles;
-       flag.reset = ctf_Reset;
-       settouch(flag, ctf_FlagTouch);
-       setthink(flag, ctf_FlagThink);
-       flag.nextthink = time + FLAG_THINKRATE;
-       flag.ctf_status = FLAG_BASE;
-
-       // crudely force them all to 0
-       if(autocvar_g_ctf_score_ignore_fields)
-               flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
-
-       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)); }
-       if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
-       if (flag.passeffect == "")      { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
-       if (flag.capeffect == "")       { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
-
-       // sounds
-#define X(s,b) \
-               if(flag.s == "") flag.s = b; \
-               precache_sound(flag.s);
-
-       X(snd_flag_taken,               strzone(SND(CTF_TAKEN(teamnumber))))
-       X(snd_flag_returned,    strzone(SND(CTF_RETURNED(teamnumber))))
-       X(snd_flag_capture,     strzone(SND(CTF_CAPTURE(teamnumber))))
-       X(snd_flag_dropped,     strzone(SND(CTF_DROPPED(teamnumber))))
-       X(snd_flag_respawn,     strzone(SND(CTF_RESPAWN)))
-       X(snd_flag_touch,               strzone(SND(CTF_TOUCH)))
-       X(snd_flag_pass,                strzone(SND(CTF_PASS)))
-#undef X
-
-       // precache
-       precache_model(flag.model);
-
-       // appearence
-       _setmodel(flag, flag.model); // precision set below
-       setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
-       flag.m_mins = flag.mins; // store these for squash checks
-       flag.m_maxs = flag.maxs;
-       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;
-               set_movetype(flag, MOVETYPE_NONE);
-       }
-       else // drop to floor, automatically find a platform and set that as spawn origin
-       {
-               flag.noalign = false;
-               droptofloor(flag);
-               set_movetype(flag, MOVETYPE_NONE);
-       }
-
-       InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-// NOTE: LEGACY CODE, needs to be re-written!
-
-void havocbot_ctf_calculate_middlepoint()
-{
-       entity f;
-       vector s = '0 0 0';
-       vector fo = '0 0 0';
-       int n = 0;
-
-       f = ctf_worldflaglist;
-       while (f)
-       {
-               fo = f.origin;
-               s = s + fo;
-               f = f.ctf_worldflagnext;
-               n++;
-       }
-       if(!n)
-               return;
-
-       havocbot_middlepoint = s / n;
-       havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
-
-       havocbot_symmetryaxis_equation = '0 0 0';
-       if(n == 2)
-       {
-               // for symmetrical editing of waypoints
-               entity f1 = ctf_worldflaglist;
-               entity f2 = f1.ctf_worldflagnext;
-               float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
-               float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
-               havocbot_symmetryaxis_equation.x = m;
-               havocbot_symmetryaxis_equation.y = q;
-       }
-       // store number of flags in this otherwise unused vector component
-       havocbot_symmetryaxis_equation.z = n;
-}
-
-
-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 NULL;
-}
-
-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 NULL;
-}
-
-int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
-{
-       if (!teamplay)
-               return 0;
-
-       int c = 0;
-
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
-                       continue;
-
-               if(vdist(it.origin - org, <, tc_radius))
-                       ++c;
-       });
-
-       return c;
-}
-
-// unused
-#if 0
-void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
-{
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               if (CTF_SAMETEAM(this, head))
-                       break;
-               head = head.ctf_worldflagnext;
-       }
-       if (head)
-               navigation_routerating(this, head, ratingscale, 10000);
-}
-#endif
-
-void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
-{
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               if (CTF_SAMETEAM(this, head))
-               {
-                       if (this.flagcarried)
-                       if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
-                       {
-                               head = head.ctf_worldflagnext; // skip base if it has a different group
-                               continue;
-                       }
-                       break;
-               }
-               head = head.ctf_worldflagnext;
-       }
-       if (!head)
-               return;
-
-       navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
-{
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               if(ctf_oneflag)
-               {
-                       if(CTF_DIFFTEAM(this, head))
-                       {
-                               if(head.team)
-                               {
-                                       if(this.flagcarried)
-                                               break;
-                               }
-                               else if(!this.flagcarried)
-                                       break;
-                       }
-               }
-               else if(CTF_DIFFTEAM(this, head))
-                       break;
-               head = head.ctf_worldflagnext;
-       }
-       if (head)
-               navigation_routerating(this, head, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
-{
-       if (!bot_waypoints_for_items)
-       {
-               havocbot_goalrating_ctf_enemyflag(this, ratingscale);
-               return;
-       }
-
-       entity head;
-
-       head = havocbot_ctf_find_enemy_flag(this);
-
-       if (!head)
-               return;
-
-       navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
-{
-       entity mf;
-
-       mf = havocbot_ctf_find_flag(this);
-
-       if(mf.ctf_status == FLAG_BASE)
-               return;
-
-       if(mf.tag_entity)
-               navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_droppedflags(entity this, 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==NULL)       // dropped
-               {
-                       if(df_radius)
-                       {
-                               if(vdist(org - head.origin, <, df_radius))
-                                       navigation_routerating(this, head, ratingscale, 10000);
-                       }
-                       else
-                               navigation_routerating(this, head, ratingscale, 10000);
-               }
-
-               head = head.ctf_worldflagnext;
-       }
-}
-
-void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
-{
-       IL_EACH(g_items, it.bot_pickup,
-       {
-               // gather health and armor only
-               if (it.solid)
-               if (it.health || it.armorvalue)
-               if (vdist(it.origin - org, <, sradius))
-               {
-                       // get the value of the item
-                       float t = it.bot_pickupevalfunc(this, it) * 0.0001;
-                       if (t > 0)
-                               navigation_routerating(this, it, t * ratingscale, 500);
-               }
-       });
-}
-
-void havocbot_ctf_reset_role(entity this)
-{
-       float cdefense, cmiddle, coffense;
-       entity mf, ef;
-       float c;
-
-       if(IS_DEAD(this))
-               return;
-
-       // Check ctf flags
-       if (this.flagcarried)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       mf = havocbot_ctf_find_flag(this);
-       ef = havocbot_ctf_find_enemy_flag(this);
-
-       // Retrieve stolen flag
-       if(mf.ctf_status!=FLAG_BASE)
-       {
-               havocbot_role_ctf_setrole(this, 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(this, HAVOCBOT_CTF_ROLE_MIDDLE);
-               return;
-       }
-
-       // if there is only me on the team switch to offense
-       c = 0;
-       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
-
-       if(c==1)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
-               return;
-       }
-
-       // Evaluate best position to take
-       // Count mates on middle position
-       cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
-
-       // Count mates on defense position
-       cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
-
-       // Count mates on offense position
-       coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
-
-       if(cdefense<=coffense)
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
-       else if(coffense<=cmiddle)
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
-       else
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
-}
-
-void havocbot_role_ctf_carrier(entity this)
-{
-       if(IS_DEAD(this))
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (this.flagcarried == NULL)
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-
-               if(ctf_oneflag)
-                       havocbot_goalrating_ctf_enemybase(this, 50000);
-               else
-                       havocbot_goalrating_ctf_ourbase(this, 50000);
-
-               if(this.health<100)
-                       havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-
-               entity head = ctf_worldflaglist;
-               while (head)
-               {
-                       if (this.goalentity == head.bot_basewaypoint)
-                       {
-                               this.goalentity_lock_timeout = time + 5;
-                               break;
-                       }
-                       head = head.ctf_worldflagnext;
-               }
-
-               if (this.goalentity)
-                       this.havocbot_cantfindflag = time + 10;
-               else if (time > this.havocbot_cantfindflag)
-               {
-                       // Can't navigate to my own base, suicide!
-                       // TODO: drop it and wander around
-                       Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
-                       return;
-               }
-       }
-}
-
-void havocbot_role_ctf_escort(entity this)
-{
-       entity mf, ef;
-
-       if(IS_DEAD(this))
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (this.flagcarried)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // If enemy flag is back on the base switch to previous role
-       ef = havocbot_ctf_find_enemy_flag(this);
-       if(ef.ctf_status==FLAG_BASE)
-       {
-               this.havocbot_role = this.havocbot_previous_role;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       // If the flag carrier reached the base switch to defense
-       mf = havocbot_ctf_find_flag(this);
-       if(mf.ctf_status!=FLAG_BASE)
-       if(vdist(ef.origin - mf.dropped_origin, <, 300))
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!this.havocbot_role_timeout)
-       {
-               this.havocbot_role_timeout = time + random() * 30 + 60;
-       }
-
-       // If nothing happened just switch to previous role
-       if (time > this.havocbot_role_timeout)
-       {
-               this.havocbot_role = this.havocbot_previous_role;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       // Chase the flag carrier
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-
-               havocbot_goalrating_ctf_enemyflag(this, 30000);
-               havocbot_goalrating_ctf_ourstolenflag(this, 40000);
-               havocbot_goalrating_items(this, 10000, this.origin, 10000);
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_ctf_offense(entity this)
-{
-       entity mf, ef;
-       vector pos;
-
-       if(IS_DEAD(this))
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (this.flagcarried)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // Check flags
-       mf = havocbot_ctf_find_flag(this);
-       ef = havocbot_ctf_find_enemy_flag(this);
-
-       // 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(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
-               {
-                       havocbot_role_ctf_setrole(this, 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(vdist(pos - mf.dropped_origin, >, 700))
-               {
-                       havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
-                       return;
-               }
-       }
-
-       // About to fail, switch to middlefield
-       if(this.health<50)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + 120;
-
-       if (time > this.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-
-               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
-               havocbot_goalrating_ctf_enemybase(this, 20000);
-               havocbot_goalrating_items(this, 5000, this.origin, 1000);
-               havocbot_goalrating_items(this, 1000, this.origin, 10000);
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-// Retriever (temporary role):
-void havocbot_role_ctf_retriever(entity this)
-{
-       entity mf;
-
-       if(IS_DEAD(this))
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (this.flagcarried)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // If flag is back on the base switch to previous role
-       mf = havocbot_ctf_find_flag(this);
-       if(mf.ctf_status==FLAG_BASE)
-       {
-               if (mf.enemy == this) // did this bot return the flag?
-                       navigation_goalrating_timeout_force(this);
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + 20;
-
-       if (time > this.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               float rt_radius;
-               rt_radius = 10000;
-
-               navigation_goalrating_start(this);
-
-               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
-               havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
-               havocbot_goalrating_ctf_enemybase(this, 30000);
-               havocbot_goalrating_items(this, 500, this.origin, rt_radius);
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_ctf_middle(entity this)
-{
-       entity mf;
-
-       if(IS_DEAD(this))
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (this.flagcarried)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       mf = havocbot_ctf_find_flag(this);
-       if(mf.ctf_status!=FLAG_BASE)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
-               return;
-       }
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + 10;
-
-       if (time > this.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               vector org;
-
-               org = havocbot_middlepoint;
-               org.z = this.origin.z;
-
-               navigation_goalrating_start(this);
-
-               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
-               havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
-               havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
-               havocbot_goalrating_items(this, 2500, this.origin, 10000);
-               havocbot_goalrating_ctf_enemybase(this, 2500);
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_ctf_defense(entity this)
-{
-       entity mf;
-
-       if(IS_DEAD(this))
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-
-       if (this.flagcarried)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // If own flag was captured
-       mf = havocbot_ctf_find_flag(this);
-       if(mf.ctf_status!=FLAG_BASE)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
-               return;
-       }
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + 30;
-
-       if (time > this.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(this);
-               return;
-       }
-       if (navigation_goalrating_timeout(this))
-       {
-               vector org = mf.dropped_origin;
-
-               navigation_goalrating_start(this);
-
-               // if enemies are closer to our base, go there
-               entity closestplayer = NULL;
-               float distance, bestdistance = 10000;
-               FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
-                       distance = vlen(org - it.origin);
-                       if(distance<bestdistance)
-                       {
-                               closestplayer = it;
-                               bestdistance = distance;
-                       }
-               });
-
-               if(closestplayer)
-               if(DIFF_TEAM(closestplayer, this))
-               if(vdist(org - this.origin, >, 1000))
-               if(checkpvs(this.origin,closestplayer)||random()<0.5)
-                       havocbot_goalrating_ctf_ourbase(this, 30000);
-
-               havocbot_goalrating_ctf_ourstolenflag(this, 20000);
-               havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
-               havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
-               havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
-               havocbot_goalrating_items(this, 5000, this.origin, 10000);
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_ctf_setrole(entity bot, int role)
-{
-       string s = "(null)";
-       switch(role)
-       {
-               case HAVOCBOT_CTF_ROLE_CARRIER:
-                       s = "carrier";
-                       bot.havocbot_role = havocbot_role_ctf_carrier;
-                       bot.havocbot_role_timeout = 0;
-                       bot.havocbot_cantfindflag = time + 10;
-                       if (bot.havocbot_previous_role != bot.havocbot_role)
-                               navigation_goalrating_timeout_force(bot);
-                       break;
-               case HAVOCBOT_CTF_ROLE_DEFENSE:
-                       s = "defense";
-                       bot.havocbot_role = havocbot_role_ctf_defense;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_MIDDLE:
-                       s = "middle";
-                       bot.havocbot_role = havocbot_role_ctf_middle;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_OFFENSE:
-                       s = "offense";
-                       bot.havocbot_role = havocbot_role_ctf_offense;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_RETRIEVER:
-                       s = "retriever";
-                       bot.havocbot_previous_role = bot.havocbot_role;
-                       bot.havocbot_role = havocbot_role_ctf_retriever;
-                       bot.havocbot_role_timeout = time + 10;
-                       if (bot.havocbot_previous_role != bot.havocbot_role)
-                               navigation_goalrating_timeout_expire(bot, 2);
-                       break;
-               case HAVOCBOT_CTF_ROLE_ESCORT:
-                       s = "escort";
-                       bot.havocbot_previous_role = bot.havocbot_role;
-                       bot.havocbot_role = havocbot_role_ctf_escort;
-                       bot.havocbot_role_timeout = time + 30;
-                       if (bot.havocbot_previous_role != bot.havocbot_role)
-                               navigation_goalrating_timeout_expire(bot, 2);
-                       break;
-       }
-       LOG_TRACE(bot.netname, " switched to ", s);
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
-{
-       entity player = M_ARGV(0, entity);
-
-       int t = 0, t2 = 0, t3 = 0;
-       bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC)
-
-       // initially clear items so they can be set as necessary later.
-       STAT(CTF_FLAGSTATUS, player) &= ~(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 | CTF_STALEMATE);
-
-       // scan through all the flags and notify the client about them
-       for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
-       {
-               if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING;              t2 = CTF_RED_FLAG_TAKEN;                t3 = CTF_RED_FLAG_LOST; }
-               if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING;             t2 = CTF_BLUE_FLAG_TAKEN;               t3 = CTF_BLUE_FLAG_LOST; }
-               if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING;   t2 = CTF_YELLOW_FLAG_TAKEN;             t3 = CTF_YELLOW_FLAG_LOST; }
-               if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING;             t2 = CTF_PINK_FLAG_TAKEN;               t3 = CTF_PINK_FLAG_LOST; }
-               if(flag.team == 0 && !b5)                  { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING;  t2 = CTF_NEUTRAL_FLAG_TAKEN;    t3 = CTF_NEUTRAL_FLAG_LOST; STAT(CTF_FLAGSTATUS, player) |= CTF_FLAG_NEUTRAL; }
-
-               switch(flag.ctf_status)
-               {
-                       case FLAG_PASSING:
-                       case FLAG_CARRY:
-                       {
-                               if((flag.owner == player) || (flag.pass_sender == player))
-                                       STAT(CTF_FLAGSTATUS, player) |= t; // carrying: player is currently carrying the flag
-                               else
-                                       STAT(CTF_FLAGSTATUS, player) |= t2; // taken: someone else is carrying the flag
-                               break;
-                       }
-                       case FLAG_DROPPED:
-                       {
-                               STAT(CTF_FLAGSTATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
-                               break;
-                       }
-               }
-       }
-
-       // item for stopping players from capturing the flag too often
-       if(player.ctf_captureshielded)
-               STAT(CTF_FLAGSTATUS, player) |= CTF_SHIELDED;
-
-       if(ctf_stalemate)
-               STAT(CTF_FLAGSTATUS, player) |= CTF_STALEMATE;
-
-       // update the health of the flag carrier waypointsprite
-       if(player.wps_flagcarrier)
-               WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
-}
-
-MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float frag_damage = M_ARGV(4, float);
-       vector frag_force = M_ARGV(6, vector);
-
-       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;
-               }
-
-               M_ARGV(4, float) = frag_damage;
-               M_ARGV(6, vector) = frag_force;
-       }
-       else if(frag_target.flagcarried && !IS_DEAD(frag_target) && 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?
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-
-       if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
-       {
-               GameRules_scoring_add_team(frag_attacker, SCORE, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
-               GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1);
-       }
-
-       if(frag_target.flagcarried)
-       {
-               entity tmp_entity = frag_target.flagcarried;
-               ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
-               tmp_entity.ctf_dropper = NULL;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
-{
-       M_ARGV(2, float) = 0; // frag score
-       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, NULL, DROP_NORMAL); }
-
-       for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
-       {
-               if(flag.pass_sender == player) { flag.pass_sender = NULL; }
-               if(flag.pass_target == player) { flag.pass_target = NULL; }
-               if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-
-       ctf_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       ctf_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
-{
-       if(!autocvar_g_ctf_leaderboard)
-               return;
-
-       entity player = M_ARGV(0, entity);
-
-       if(IS_REAL_CLIENT(player))
-       {
-               int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
-               race_send_rankings_cnt(MSG_ONE);
-               for (int i = 1; i <= m; ++i)
-               {
-                       race_SendRankings(i, 0, 0, MSG_ONE);
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
-{
-       if(!autocvar_g_ctf_leaderboard)
-               return;
-
-       entity player = M_ARGV(0, entity);
-
-       if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
-       {
-               if (!player.stored_netname)
-                       player.stored_netname = strzone(uid2name(player.crypto_idfp));
-               if(player.stored_netname != player.netname)
-               {
-                       db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
-                       strcpy(player.stored_netname, player.netname);
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.flagcarried)
-       if(!autocvar_g_ctf_portalteleport)
-               { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
-{
-       if(MUTATOR_RETURNVALUE || game_stopped) return;
-
-       entity player = M_ARGV(0, entity);
-
-       if((time > player.throw_antispam) && !IS_DEAD(player) && !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 = NULL;
-                       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) && !IS_DEAD(head))
-                               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 && !head.flagcarried)
-                                               {
-                                                       if(closest_target)
-                                                       {
-                                                               vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
-                                                               if(vlen2(passer_center - head_center) < vlen2(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, NULL, 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, NULL, DROP_THROW);
-                               return true;
-                       }
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
-       {
-               player.wps_helpme_time = time;
-               WaypointSprite_HelpMePing(player.wps_flagcarrier);
-       }
-       else // create a normal help me waypointsprite
-       {
-               WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
-               WaypointSprite_Ping(player.wps_helpme);
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
-{
-       entity player = M_ARGV(0, entity);
-       entity veh = M_ARGV(1, entity);
-
-       if(player.flagcarried)
-       {
-               if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
-               {
-                       ctf_Handle_Throw(player, NULL, DROP_NORMAL);
-               }
-               else
-               {
-                       player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
-                       setattachment(player.flagcarried, veh, "");
-                       setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
-                       player.flagcarried.scale = VEHICLE_FLAG_SCALE;
-                       //player.flagcarried.angles = '0 0 0';
-               }
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.flagcarried)
-       {
-               setattachment(player.flagcarried, player, "");
-               setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
-               player.flagcarried.scale = FLAG_SCALE;
-               player.flagcarried.angles = '0 0 0';
-               player.flagcarried.nodrawtoclient = NULL;
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.flagcarried)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
-               ctf_RespawnFlag(player.flagcarried);
-               return true;
-       }
-}
-
-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
-                               set_movetype(flag, 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;
-                       }
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
-{
-       entity bot = M_ARGV(0, entity);
-
-       havocbot_ctf_reset_role(bot);
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
-{
-       //M_ARGV(0, float) = ctf_teams;
-       M_ARGV(1, string) = "ctf_team";
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
-{
-       entity spectatee = M_ARGV(0, entity);
-       entity client = M_ARGV(1, entity);
-
-       STAT(CTF_FLAGSTATUS, client) = STAT(CTF_FLAGSTATUS, spectatee);
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GetRecords)
-{
-       int record_page = M_ARGV(0, int);
-       string ret_string = M_ARGV(1, string);
-
-       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");
-               }
-       }
-
-       M_ARGV(1, string) = ret_string;
-}
-
-bool superspec_Spectate(entity this, entity targ); // TODO
-void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
-MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
-{
-       entity player = M_ARGV(0, entity);
-       string cmd_name = M_ARGV(1, string);
-       int cmd_argc = M_ARGV(2, int);
-
-       if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
-
-       if(cmd_name == "followfc")
-       {
-               if(!g_ctf)
-                       return true;
-
-               int _team = 0;
-               bool found = false;
-
-               if(cmd_argc == 2)
-               {
-                       switch(argv(1))
-                       {
-                               case "red":    if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
-                               case "blue":   if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
-                               case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
-                               case "pink":   if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
-                       }
-               }
-
-               FOREACH_CLIENT(IS_PLAYER(it), {
-                       if(it.flagcarried && (it.team == _team || _team == 0))
-                       {
-                               found = true;
-                               if(_team == 0 && IS_SPEC(player) && player.enemy == it)
-                                       continue; // already spectating this fc, try another
-                               return superspec_Spectate(player, it);
-                       }
-               });
-
-               if(!found)
-                       superspec_msg("", "", player, "No active flag carrier\n", 1);
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
-{
-       entity frag_target = M_ARGV(0, entity);
-
-       if(frag_target.flagcarried)
-               ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
-}
-
-
-// ==========
-// 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) { delete(this); return; }
-
-       ctf_FlagSetup(NUM_TEAM_1, this);
-}
-
-/*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) { delete(this); return; }
-
-       ctf_FlagSetup(NUM_TEAM_2, this);
-}
-
-/*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) { delete(this); return; }
-
-       ctf_FlagSetup(NUM_TEAM_3, this);
-}
-
-/*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) { delete(this); return; }
-
-       ctf_FlagSetup(NUM_TEAM_4, this);
-}
-
-/*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) { delete(this); return; }
-       if(!cvar("g_ctf_oneflag")) { delete(this); return; }
-
-       ctf_FlagSetup(0, this);
-}
-
-/*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) { delete(this); return; }
-
-       this.classname = "ctf_team";
-       this.team = this.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);  }
-
-spawnfunc(team_CTF_neutralflag)        { spawnfunc_item_flag_neutral(this);  }
-spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this);  }
-
-// compatibility for wop maps
-spawnfunc(team_redplayer)      { spawnfunc_info_player_team1(this);  }
-spawnfunc(team_blueplayer)     { spawnfunc_info_player_team2(this);  }
-spawnfunc(team_ctl_redlolly)   { spawnfunc_item_flag_team1(this);    }
-spawnfunc(team_CTL_redlolly)   { spawnfunc_item_flag_team1(this);    }
-spawnfunc(team_ctl_bluelolly)  { spawnfunc_item_flag_team2(this);    }
-spawnfunc(team_CTL_bluelolly)  { spawnfunc_item_flag_team2(this);    }
-
-
-// ==============
-// Initialization
-// ==============
-
-// scoreboard setup
-void ctf_ScoreRules(int teams)
-{
-       CheckAllowedTeams(NULL);
-       GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
-        field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
-        field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
-        field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
-        field(SP_CTF_PICKUPS, "pickups", 0);
-        field(SP_CTF_FCKILLS, "fckills", 0);
-        field(SP_CTF_RETURNS, "returns", 0);
-        field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
-       });
-}
-
-// 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_pure(ctf_team);
-       this.netname = teamname;
-       this.cnt = teamcolor - 1;
-       this.spawnfunc_checked = true;
-       this.team = teamcolor;
-}
-
-void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
-{
-       ctf_teams = 0;
-
-       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); }
-
-               switch(tmp_entity.team)
-               {
-                       case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
-                       case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
-                       case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
-                       case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
-               }
-               if(tmp_entity.team == 0) { ctf_oneflag = true; }
-       }
-
-       havocbot_ctf_calculate_middlepoint();
-
-       if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
-       {
-               ctf_teams = 0; // so set the default red and blue teams
-               BITSET_ASSIGN(ctf_teams, BIT(0));
-               BITSET_ASSIGN(ctf_teams, BIT(1));
-       }
-
-       //ctf_teams = bound(2, ctf_teams, 4);
-
-       // if no teams are found, spawn defaults
-       if(find(NULL, classname, "ctf_team") == NULL)
-       {
-               LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
-               if(ctf_teams & BIT(0))
-                       ctf_SpawnTeam("Red", NUM_TEAM_1);
-               if(ctf_teams & BIT(1))
-                       ctf_SpawnTeam("Blue", NUM_TEAM_2);
-               if(ctf_teams & BIT(2))
-                       ctf_SpawnTeam("Yellow", NUM_TEAM_3);
-               if(ctf_teams & BIT(3))
-                       ctf_SpawnTeam("Pink", NUM_TEAM_4);
-       }
-
-       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;
-
-       InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_ctf.qh b/qcsrc/server/mutators/mutator/gamemode_ctf.qh
deleted file mode 100644 (file)
index 14bf281..0000000
+++ /dev/null
@@ -1,190 +0,0 @@
-#pragma once
-
-#ifdef SVQC
-
-#include "../gamemode.qh"
-
-void ctf_Initialize();
-
-REGISTER_MUTATOR(ctf, false)
-{
-    MUTATOR_STATIC();
-    MUTATOR_ONADD
-    {
-        GameRules_teams(true);
-        GameRules_limit_score(autocvar_capturelimit_override);
-        GameRules_limit_lead(autocvar_captureleadlimit_override);
-
-        ctf_Initialize();
-    }
-    return 0;
-}
-
-// used in cheats.qc
-void ctf_RespawnFlag(entity flag);
-
-// score rule declarations
-const int ST_CTF_CAPS = 1;
-
-CLASS(Flag, Pickup)
-    ATTRIB(Flag, m_mins, vector, (PL_MIN_CONST + '0 0 -13') * 1.4); // scaling be damned
-    ATTRIB(Flag, m_maxs, vector, (PL_MAX_CONST + '0 0 -13') * 1.4);
-ENDCLASS(Flag)
-Flag CTF_FLAG; STATIC_INIT(Flag) { CTF_FLAG = NEW(Flag); }
-void ctf_FlagTouch(entity this, entity toucher) { ITEM_HANDLE(Pickup, CTF_FLAG, this, toucher); }
-
-// flag constants // for most of these, there is just one question to be asked: WHYYYYY?
-
-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;
-
-// score fields
-.float score_assist;
-.float score_capture;
-.float score_drop; // note: negated
-.float score_pickup;
-.float score_return;
-.float score_team_capture; // shouldn't be too high
-
-// 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 wps_helpme;
-.entity wps_flagbase;
-.entity wps_flagcarrier;
-.entity wps_flagdropped;
-.entity wps_flagreturn;
-.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;
-
-bool ctf_Stalemate_Customize(entity this, entity client);
-
-void ctf_Handle_Throw(entity player, entity receiver, float droptype);
-
-// 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_byworld;
-int ctf_teams;
-.entity enemy; // when flag is back in the base, it remembers last player who carried/touched the flag, useful to bots
-
-// 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;
-
-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))
-#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;
-const int CTF_STALEMATE                                        = 8192;
diff --git a/qcsrc/server/mutators/mutator/gamemode_cts.qc b/qcsrc/server/mutators/mutator/gamemode_cts.qc
deleted file mode 100644 (file)
index 87830db..0000000
+++ /dev/null
@@ -1,432 +0,0 @@
-#include "gamemode_cts.qh"
-
-#include <server/race.qh>
-#include <server/items.qh>
-
-float autocvar_g_cts_finish_kill_delay;
-bool autocvar_g_cts_selfdamage;
-
-// legacy bot roles
-.float race_checkpoint;
-void havocbot_role_cts(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-
-               bool raw_touch_check = true;
-               int cp = this.race_checkpoint;
-
-               LABEL(search_racecheckpoints)
-               IL_EACH(g_racecheckpoints, true,
-               {
-                       if(it.cnt == cp || cp == -1)
-                       {
-                               // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
-                               // e.g. checkpoint in front of Stormkeep's warpzone
-                               // the same workaround is applied in Race game mode
-                               if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
-                               {
-                                       cp = race_NextCheckpoint(cp);
-                                       raw_touch_check = false;
-                                       goto search_racecheckpoints;
-                               }
-                               navigation_routerating(this, it, 1000000, 5000);
-                       }
-               });
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void cts_ScoreRules()
-{
-    GameRules_score_enabled(false);
-    GameRules_scoring(0, 0, 0, {
-        if (g_race_qualifying) {
-            field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-        } else {
-            field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
-            field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-            field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
-        }
-    });
-}
-
-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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void KillIndicator_Think(entity this);
-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;
-    setthink(e.killindicator, 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)
-{
-       entity player = M_ARGV(0, entity);
-       float dt = M_ARGV(1, float);
-
-       player.race_movetime_frac += dt;
-       float f = floor(player.race_movetime_frac);
-       player.race_movetime_frac -= f;
-       player.race_movetime_count += f;
-       player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
-
-#ifdef SVQC
-       if(IS_PLAYER(player))
-       {
-               if (player.race_penalty)
-                       if (time > player.race_penalty)
-                               player.race_penalty = 0;
-               if(player.race_penalty)
-               {
-                       player.velocity = '0 0 0';
-                       set_movetype(player, MOVETYPE_NONE);
-                       player.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(CS(player).movement.x);
-       wishvel.y = fabs(CS(player).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(CS(player).movement.x > 0)
-                               CS(player).movement_x = wishspeed;
-                       else
-                               CS(player).movement_x = -wishspeed;
-                       CS(player).movement_y = 0;
-               }
-               else if(wishvel.y >= 2 * wishvel.x)
-               {
-                       // pure Y motion
-                       CS(player).movement_x = 0;
-                       if(CS(player).movement.y > 0)
-                               CS(player).movement_y = wishspeed;
-                       else
-                               CS(player).movement_y = -wishspeed;
-               }
-               else
-               {
-                       // diagonal
-                       if(CS(player).movement.x > 0)
-                               CS(player).movement_x = M_SQRT1_2 * wishspeed;
-                       else
-                               CS(player).movement_x = -M_SQRT1_2 * wishspeed;
-                       if(CS(player).movement.y > 0)
-                               CS(player).movement_y = M_SQRT1_2 * wishspeed;
-                       else
-                               CS(player).movement_y = -M_SQRT1_2 * wishspeed;
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, reset_map_global)
-{
-       float s;
-
-       Score_NicePrint(NULL);
-
-       race_ClearRecords();
-       PlayerScore_Sort(race_place, 0, 1, 0);
-
-       FOREACH_CLIENT(true, {
-               if(it.race_place)
-               {
-                       s = GameRules_scoring_add(it, RACE_FASTEST, 0);
-                       if(!s)
-                               it.race_place = 0;
-               }
-               cts_EventLog(ftos(it.race_place), it);
-       });
-
-       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();
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, ClientConnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       race_PreparePlayer(player);
-       player.race_checkpoint = -1;
-
-       if(IS_REAL_CLIENT(player))
-       {
-               string rr = CTS_RECORD;
-
-               msg_entity = player;
-               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;
-               int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
-               race_send_rankings_cnt(MSG_ONE);
-               for (i = 1; i <= m; ++i)
-               {
-                       race_SendRankings(i, 0, 0, MSG_ONE);
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, AbortSpeedrun)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(autocvar_g_allow_checkpoints)
-               race_PreparePlayer(player); // nice try
-}
-
-MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(GameRules_scoring_add(player, RACE_FASTEST, 0))
-               player.frags = FRAGS_LMS_LOSER;
-       else
-               player.frags = FRAGS_SPECTATOR;
-
-       race_PreparePlayer(player);
-       player.race_checkpoint = -1;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerSpawn)
-{
-       entity player = M_ARGV(0, entity);
-       entity spawn_spot = M_ARGV(1, entity);
-
-       if(spawn_spot.target == "")
-               // Emergency: this wasn't a real spawnpoint. Can this ever happen?
-               race_PreparePlayer(player);
-
-       // if we need to respawn, do it right
-       player.race_respawn_checkpoint = player.race_checkpoint;
-       player.race_respawn_spotref = spawn_spot;
-
-       player.race_place = 0;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PutClientInServer)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(IS_PLAYER(player))
-       if(!game_stopped)
-       {
-               if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
-                       race_PreparePlayer(player);
-               else // respawn
-                       race_RetractPlayer(player);
-
-               race_AbandonRaceCheck(player);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerDies)
-{
-       entity frag_target = M_ARGV(2, entity);
-
-       frag_target.respawn_flags |= RESPAWN_FORCE;
-       race_AbandonRaceCheck(frag_target);
-}
-
-MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole)
-{
-       entity bot = M_ARGV(0, entity);
-
-       bot.havocbot_role = havocbot_role_cts;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(cts, GetPressedKeys)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
-       {
-               if (!player.stored_netname)
-                       player.stored_netname = strzone(uid2name(player.crypto_idfp));
-               if(player.stored_netname != player.netname)
-               {
-                       db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
-                       strcpy(player.stored_netname, player.netname);
-               }
-       }
-
-       if (!IS_OBSERVER(player))
-       {
-               if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
-               {
-                       speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
-                       speedaward_holder = player.netname;
-                       speedaward_uid = player.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);
-                       }
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon)
-{
-       // no weapon dropping in CTS
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(cts, FilterItem)
-{
-       entity item = M_ARGV(0, entity);
-
-       if (Item_IsLoot(item))
-       {
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, Damage_Calculate)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float frag_deathtype = M_ARGV(3, float);
-       float frag_damage = M_ARGV(4, float);
-
-       if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id)
-       if(!autocvar_g_cts_selfdamage)
-       {
-               frag_damage = 0;
-               M_ARGV(4, float) = frag_damage;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear)
-{
-       return true; // in CTS, you don't lose score by observing
-}
-
-MUTATOR_HOOKFUNCTION(cts, GetRecords)
-{
-       int record_page = M_ARGV(0, int);
-       string ret_string = M_ARGV(1, string);
-
-       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");
-               }
-       }
-
-       M_ARGV(1, string) = ret_string;
-}
-
-void ClientKill_Now(entity this);
-MUTATOR_HOOKFUNCTION(cts, ClientKill)
-{
-    entity player = M_ARGV(0, entity);
-
-       M_ARGV(1, float) = 0; // kill delay
-
-       if(player.killindicator && player.killindicator.health == 1) // player.killindicator.health == 1 means that the kill indicator was spawned by CTS_ClientKill
-       {
-               delete(player.killindicator);
-               player.killindicator = NULL;
-
-               ClientKill_Now(player); // allow instant kill in this case
-               return;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(autocvar_g_cts_finish_kill_delay)
-               CTS_ClientKill(player);
-}
-
-MUTATOR_HOOKFUNCTION(cts, HideTeamNagger)
-{
-       return true; // doesn't work so well (but isn't cts a teamless mode?)
-}
-
-MUTATOR_HOOKFUNCTION(cts, FixClientCvars)
-{
-       entity player = M_ARGV(0, entity);
-
-       stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
-}
-
-MUTATOR_HOOKFUNCTION(cts, WantWeapon)
-{
-       M_ARGV(1, float) = (M_ARGV(0, entity) == WEP_SHOTGUN); // want weapon = weapon info
-       M_ARGV(3, bool) = true; // want mutator blocked
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(cts, ForbidDropCurrentWeapon)
-{
-       return true;
-}
-
-void cts_Initialize()
-{
-       cts_ScoreRules();
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_cts.qh b/qcsrc/server/mutators/mutator/gamemode_cts.qh
deleted file mode 100644 (file)
index c90919e..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-#pragma once
-
-#include "../gamemode.qh"
-#include <server/race.qh>
-
-void cts_Initialize();
-
-REGISTER_MUTATOR(cts, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               g_race_qualifying = true;
-               independent_players = 1;
-        GameRules_limit_score(0);
-        GameRules_limit_lead(0);
-
-               cts_Initialize();
-       }
-       return 0;
-}
-
-// scores
-const float ST_CTS_LAPS = 1;
diff --git a/qcsrc/server/mutators/mutator/gamemode_deathmatch.qc b/qcsrc/server/mutators/mutator/gamemode_deathmatch.qc
deleted file mode 100644 (file)
index 9590027..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-#include "gamemode_deathmatch.qh"
-
-MUTATOR_HOOKFUNCTION(dm, Scores_CountFragsRemaining)
-{
-       // announce remaining frags
-       return true;
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_deathmatch.qh b/qcsrc/server/mutators/mutator/gamemode_deathmatch.qh
deleted file mode 100644 (file)
index f45b080..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-#pragma once
-
-#include "../gamemode.qh"
-
-REGISTER_MUTATOR(dm, false)
-{
-    MUTATOR_STATIC();
-       return 0;
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_domination.qc b/qcsrc/server/mutators/mutator/gamemode_domination.qc
deleted file mode 100644 (file)
index 38ef58b..0000000
+++ /dev/null
@@ -1,670 +0,0 @@
-#include "gamemode_domination.qh"
-
-#include <server/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;
-float autocvar_g_domination_round_timelimit;
-float autocvar_g_domination_warmup;
-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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void set_dom_state(entity e)
-{
-       STAT(DOM_TOTAL_PPS, e) = total_pps;
-       STAT(DOM_PPS_RED, e) = pps_red;
-       STAT(DOM_PPS_BLUE, e) = pps_blue;
-       if(domination_teams >= 3)
-               STAT(DOM_PPS_YELLOW, e) = pps_yellow;
-       if(domination_teams >= 4)
-               STAT(DOM_PPS_PINK, e) = pps_pink;
-}
-
-void dompoint_captured(entity this)
-{
-       float old_delay, old_team, real_team;
-
-       // now that the delay has expired, switch to the latest team to lay claim to this point
-       entity head = this.owner;
-
-       real_team = this.cnt;
-       this.cnt = -1;
-
-       dom_EventLog("taken", this.team, this.dmg_inflictor);
-       this.dmg_inflictor = NULL;
-
-       this.goalentity = head;
-       this.model = head.mdl;
-       this.modelindex = head.dmg;
-       this.skin = head.skin;
-
-       float points, wait_time;
-       if (autocvar_g_domination_point_amt)
-               points = autocvar_g_domination_point_amt;
-       else
-               points = this.frags;
-       if (autocvar_g_domination_point_rate)
-               wait_time = autocvar_g_domination_point_rate;
-       else
-               wait_time = this.wait;
-
-       if(domination_roundbased)
-               bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
-       else
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
-
-       if(this.enemy.playerid == this.enemy_playerid)
-               GameRules_scoring_add(this.enemy, DOM_TAKES, 1);
-       else
-               this.enemy = NULL;
-
-       if (head.noise != "")
-               if(this.enemy)
-                       _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
-               else
-                       _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
-       if (head.noise1 != "")
-               play2all(head.noise1);
-
-       this.delay = time + wait_time;
-
-       // do trigger work
-       old_delay = this.delay;
-       old_team = this.team;
-       this.team = real_team;
-       this.delay = 0;
-       SUB_UseTargets (this, this, NULL);
-       this.delay = old_delay;
-       this.team = old_team;
-
-       entity msg = WP_DomNeut;
-       switch(real_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(this.sprite, msg, WP_Null, WP_Null);
-
-       total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
-       IL_EACH(g_dompoints, true,
-       {
-               if (autocvar_g_domination_point_amt)
-                       points = autocvar_g_domination_point_amt;
-               else
-                       points = it.frags;
-               if (autocvar_g_domination_point_rate)
-                       wait_time = autocvar_g_domination_point_rate;
-               else
-                       wait_time = it.wait;
-               switch(it.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(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
-       WaypointSprite_Ping(this.sprite);
-
-       this.captime = time;
-
-       FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
-}
-
-void AnimateDomPoint(entity this)
-{
-       if(this.pain_finished > time)
-               return;
-       this.pain_finished = time + this.t_width;
-       if(this.nextthink > this.pain_finished)
-               this.nextthink = this.pain_finished;
-
-       this.frame = this.frame + 1;
-       if(this.frame > this.t_length)
-               this.frame = 0;
-}
-
-void dompointthink(entity this)
-{
-       float fragamt;
-
-       this.nextthink = time + 0.1;
-
-       //this.frame = this.frame + 1;
-       //if(this.frame > 119)
-       //      this.frame = 0;
-       AnimateDomPoint(this);
-
-       // give points
-
-       if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
-               return;
-
-       if(autocvar_g_domination_point_rate)
-               this.delay = time + autocvar_g_domination_point_rate;
-       else
-               this.delay = time + this.wait;
-
-       // give credit to the team
-       // NOTE: this defaults to 0
-       if (!domination_roundbased)
-       if (this.goalentity.netname != "")
-       {
-               if(autocvar_g_domination_point_amt)
-                       fragamt = autocvar_g_domination_point_amt;
-               else
-                       fragamt = this.frags;
-               TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
-               TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
-
-               // give credit to the individual player, if he is still there
-               if (this.enemy.playerid == this.enemy_playerid)
-               {
-                       GameRules_scoring_add(this.enemy, SCORE, fragamt);
-                       GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
-               }
-               else
-                       this.enemy = NULL;
-       }
-}
-
-void dompointtouch(entity this, entity toucher)
-{
-       if (!IS_PLAYER(toucher))
-               return;
-       if (toucher.health < 1)
-               return;
-
-       if(round_handler_IsActive() && !round_handler_IsRoundStarted())
-               return;
-
-       if(time < this.captime + 0.3)
-               return;
-
-       // only valid teams can claim it
-       entity head = find(NULL, classname, "dom_team");
-       while (head && head.team != toucher.team)
-               head = find(head, classname, "dom_team");
-       if (!head || head.netname == "" || head == this.goalentity)
-               return;
-
-       // delay capture
-
-       this.team = this.goalentity.team; // this stores the PREVIOUS team!
-
-       this.cnt = toucher.team;
-       this.owner = head; // team to switch to after the delay
-       this.dmg_inflictor = toucher;
-
-       // this.state = 1;
-       // this.delay = time + cvar("g_domination_point_capturetime");
-       //this.nextthink = time + cvar("g_domination_point_capturetime");
-       //this.think = dompoint_captured;
-
-       // go to neutral team in the mean time
-       head = find(NULL, classname, "dom_team");
-       while (head && head.netname != "")
-               head = find(head, classname, "dom_team");
-       if(head == NULL)
-               return;
-
-       WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
-       WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
-       WaypointSprite_Ping(this.sprite);
-
-       this.goalentity = head;
-       this.model = head.mdl;
-       this.modelindex = head.dmg;
-       this.skin = head.skin;
-
-       this.enemy = toucher; // individual player scoring
-       this.enemy_playerid = toucher.playerid;
-       dompoint_captured(this);
-}
-
-void dom_controlpoint_setup(entity this)
-{
-       entity head;
-       // find the spawnfunc_dom_team representing unclaimed points
-       head = find(NULL, classname, "dom_team");
-       while(head && head.netname != "")
-               head = find(head, classname, "dom_team");
-       if (!head)
-               objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
-
-       // copy important properties from spawnfunc_dom_team entity
-       this.goalentity = head;
-       _setmodel(this, head.mdl); // precision already set
-       this.skin = head.skin;
-
-       this.cnt = -1;
-
-       if(this.message == "")
-               this.message = " has captured a control point";
-
-       if(this.frags <= 0)
-               this.frags = 1;
-       if(this.wait <= 0)
-               this.wait = 5;
-
-       float points, waittime;
-       if (autocvar_g_domination_point_amt)
-               points = autocvar_g_domination_point_amt;
-       else
-               points = this.frags;
-       if (autocvar_g_domination_point_rate)
-               waittime = autocvar_g_domination_point_rate;
-       else
-               waittime = this.wait;
-
-       total_pps += points/waittime;
-
-       if(!this.t_width)
-               this.t_width = 0.02; // frame animation rate
-       if(!this.t_length)
-               this.t_length = 239; // maximum frame
-
-       setthink(this, dompointthink);
-       this.nextthink = time;
-       settouch(this, dompointtouch);
-       this.solid = SOLID_TRIGGER;
-       if(!this.flags & FL_ITEM)
-               IL_PUSH(g_items, this);
-       this.flags = FL_ITEM;
-       setsize(this, '-32 -32 -32', '32 32 32');
-       setorigin(this, this.origin + '0 0 20');
-       droptofloor(this);
-
-       waypoint_spawnforitem(this);
-       WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
-}
-
-float total_controlpoints;
-void Domination_count_controlpoints()
-{
-       total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
-       IL_EACH(g_dompoints, true,
-       {
-               ++total_controlpoints;
-               redowned += (it.goalentity.team == NUM_TEAM_1);
-               blueowned += (it.goalentity.team == NUM_TEAM_2);
-               yellowowned += (it.goalentity.team == NUM_TEAM_3);
-               pinkowned += (it.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, NULL, MSG_CENTER, CENTER_ROUND_OVER);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
-
-               game_stopped = true;
-               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, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
-               TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
-       }
-       else if(winner_team == -1)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
-       }
-
-       game_stopped = true;
-       round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
-
-       return 1;
-}
-
-float Domination_CheckPlayers()
-{
-       return 1;
-}
-
-void Domination_RoundStart()
-{
-       FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
-}
-
-//go to best items, or control points you don't own
-void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
-{
-       IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
-       {
-               if(it.cnt > -1) // this is just being fought
-                       navigation_routerating(this, it, ratingscale, 5000);
-               else if(it.goalentity.cnt == 0) // unclaimed
-                       navigation_routerating(this, it, ratingscale * 0.5, 5000);
-               else if(it.goalentity.team != this.team) // other team's point
-                       navigation_routerating(this, it, ratingscale * 0.2, 5000);
-       });
-}
-
-void havocbot_role_dom(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-               havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
-               havocbot_goalrating_items(this, 8000, this.origin, 8000);
-               //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
-               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(dom, CheckAllowedTeams)
-{
-       // fallback?
-       M_ARGV(0, float) = domination_teams;
-       string ret_string = "dom_team";
-
-       entity head = find(NULL, 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);
-       }
-
-       M_ARGV(1, string) = string_null;
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(dom, reset_map_players)
-{
-       total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               PutClientInServer(it);
-               if(domination_roundbased)
-                       it.player_blocked = 1;
-               if(IS_REAL_CLIENT(it))
-                       set_dom_state(it);
-       });
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(domination_roundbased)
-       if(!round_handler_IsRoundStarted())
-               player.player_blocked = 1;
-       else
-               player.player_blocked = 0;
-}
-
-MUTATOR_HOOKFUNCTION(dom, ClientConnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       set_dom_state(player);
-}
-
-MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
-{
-       entity bot = M_ARGV(0, entity);
-
-       bot.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)
-       {
-               delete(this);
-               return;
-       }
-       setthink(this, dom_controlpoint_setup);
-       this.nextthink = time + 0.1;
-       this.reset = dom_controlpoint_setup;
-
-       if(!this.scale)
-               this.scale = 0.6;
-
-       this.effects = this.effects | EF_LOWPRECISION;
-       if (autocvar_g_domination_point_fullbright)
-               this.effects |= EF_FULLBRIGHT;
-
-       IL_PUSH(g_dompoints, this);
-}
-
-/*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)
-       {
-               delete(this);
-               return;
-       }
-       precache_model(this.model);
-       if (this.noise != "")
-               precache_sound(this.noise);
-       if (this.noise1 != "")
-               precache_sound(this.noise1);
-       this.classname = "dom_team";
-       _setmodel(this, this.model); // precision not needed
-       this.mdl = this.model;
-       this.dmg = this.modelindex;
-       this.model = "";
-       this.modelindex = 0;
-       // this would have to be changed if used in quakeworld
-       if(this.cnt)
-               this.team = this.cnt + 1; // WHY are these different anyway?
-}
-
-// scoreboard setup
-void ScoreRules_dom(int teams)
-{
-       if(domination_roundbased)
-       {
-           GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
-            field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
-            field(SP_DOM_TAKES, "takes", 0);
-           });
-       }
-       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;
-               GameRules_scoring(teams, sp_score, sp_score, {
-            field_team(ST_DOM_TICKS, "ticks", sp_domticks);
-            field(SP_DOM_TICKS, "ticks", sp_domticks);
-            field(SP_DOM_TAKES, "takes", 0);
-               });
-       }
-}
-
-// 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, Sound capsound, string capnarration, string capmessage)
-{
-    TC(Sound, capsound);
-    entity e = new_pure(dom_team);
-       e.netname = strzone(teamname);
-       e.cnt = teamcolor;
-       e.model = pointmodel;
-       e.skin = pointskin;
-       e.noise = strzone(Sound_fixpath(capsound));
-       e.noise1 = strzone(capnarration);
-       e.message = strzone(capmessage);
-
-       // this code is identical to spawnfunc_dom_team
-       _setmodel(e, e.model); // precision not needed
-       e.mdl = e.model;
-       e.dmg = e.modelindex;
-       e.model = "";
-       e.modelindex = 0;
-       // this would have to be changed if used in quakeworld
-       e.team = e.cnt + 1;
-
-       //eprint(e);
-}
-
-void dom_spawnpoint(vector org)
-{
-       entity e = spawn();
-       e.classname = "dom_controlpoint";
-       setthink(e, spawnfunc_dom_controlpoint);
-       e.nextthink = time;
-       setorigin(e, org);
-       spawnfunc_dom_controlpoint(e);
-}
-
-// spawn some default teams if the map is not set up for domination
-void dom_spawnteams(int teams)
-{
-    TC(int, teams);
-       dom_spawnteam(Team_ColoredFullName(NUM_TEAM_1), NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND_DOM_CLAIM, "", "Red team has captured a control point");
-       dom_spawnteam(Team_ColoredFullName(NUM_TEAM_2), 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(Team_ColoredFullName(NUM_TEAM_3), 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(Team_ColoredFullName(NUM_TEAM_4), 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, SND_Null, "", "");
-}
-
-void dom_DelayedInit(entity this) // 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(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
-       {
-               LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
-               domination_teams = autocvar_g_domination_teams_override;
-               if (domination_teams < 2)
-                       domination_teams = autocvar_g_domination_default_teams;
-               domination_teams = bound(2, domination_teams, 4);
-               dom_spawnteams(domination_teams);
-       }
-
-       CheckAllowedTeams(NULL);
-       //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
-
-       int teams = 0;
-       if(c1 >= 0) teams |= BIT(0);
-       if(c2 >= 0) teams |= BIT(1);
-       if(c3 >= 0) teams |= BIT(2);
-       if(c4 >= 0) teams |= BIT(3);
-       domination_teams = teams;
-
-       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(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_domination.qh b/qcsrc/server/mutators/mutator/gamemode_domination.qh
deleted file mode 100644 (file)
index 95311c9..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-#pragma once
-
-#include "../gamemode.qh"
-
-#define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
-bool autocvar_g_domination_roundbased;
-int autocvar_g_domination_roundbased_point_limit;
-int autocvar_g_domination_point_leadlimit;
-
-void dom_Initialize();
-
-REGISTER_MUTATOR(dom, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               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;
-
-               GameRules_teams(true);
-        GameRules_limit_score(fraglimit_override);
-        GameRules_limit_lead(autocvar_g_domination_point_leadlimit);
-
-               dom_Initialize();
-       }
-       return 0;
-}
-
-// score rule declarations
-const float ST_DOM_TICKS = 1;
-const float ST_DOM_CAPS = 1;
-
-// pps: points per second
-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;
-
-void AnimateDomPoint(entity this);
-
-IntrusiveList g_dompoints;
-STATIC_INIT(g_dompoints) { g_dompoints = IL_NEW(); }
diff --git a/qcsrc/server/mutators/mutator/gamemode_freezetag.qc b/qcsrc/server/mutators/mutator/gamemode_freezetag.qc
deleted file mode 100644 (file)
index 36546c4..0000000
+++ /dev/null
@@ -1,585 +0,0 @@
-#include "gamemode_freezetag.qh"
-
-float autocvar_g_freezetag_frozen_maxtime;
-float autocvar_g_freezetag_revive_clearspeed;
-float autocvar_g_freezetag_round_timelimit;
-//int autocvar_g_freezetag_teams;
-int autocvar_g_freezetag_teams_override;
-float autocvar_g_freezetag_warmup;
-
-void freezetag_count_alive_players()
-{
-       total_players = redalive = bluealive = yellowalive = pinkalive = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               switch(it.team)
-               {
-                       case NUM_TEAM_1: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++redalive; break;
-                       case NUM_TEAM_2: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++bluealive; break;
-                       case NUM_TEAM_3: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++yellowalive; break;
-                       case NUM_TEAM_4: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++pinkalive; break;
-               }
-       });
-       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
-               STAT(REDALIVE, it) = redalive;
-               STAT(BLUEALIVE, it) = bluealive;
-               STAT(YELLOWALIVE, it) = yellowalive;
-               STAT(PINKALIVE, it) = pinkalive;
-       });
-
-       eliminatedPlayers.SendFlags |= 1;
-}
-#define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
-#define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == NumTeams(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, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               return 1;
-       }
-       if(total_players == 0)
-       {
-               if(prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               return 0;
-       }
-       int missing_teams_mask = 0;
-       if(freezetag_teams & BIT(0))
-               missing_teams_mask += (!redalive) * 1;
-       if(freezetag_teams & BIT(1))
-               missing_teams_mask += (!bluealive) * 2;
-       if(freezetag_teams & BIT(2))
-               missing_teams_mask += (!yellowalive) * 4;
-       if(freezetag_teams & BIT(3))
-               missing_teams_mask += (!pinkalive) * 8;
-       if(prev_missing_teams_mask != missing_teams_mask)
-       {
-               Send_Notification(NOTIF_ALL, NULL, 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
-}
-
-void nades_Clear(entity);
-void nades_GiveBonus(entity player, float score);
-
-float freezetag_CheckWinner()
-{
-       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
-               FOREACH_CLIENT(IS_PLAYER(it), {
-                       it.freezetag_frozen_timeout = 0;
-                       nades_Clear(it);
-               });
-               game_stopped = true;
-               round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
-               return 1;
-       }
-
-       if(FREEZETAG_ALIVE_TEAMS() > 1)
-               return 0;
-
-       int winner_team = freezetag_getWinnerTeam();
-       if(winner_team > 0)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
-               TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
-       }
-       else if(winner_team == -1)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
-       }
-
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               it.freezetag_frozen_timeout = 0;
-               nades_Clear(it);
-       });
-
-       game_stopped = true;
-       round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
-       return 1;
-}
-
-entity freezetag_LastPlayerForTeam(entity this)
-{
-       entity last_pl = NULL;
-       FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
-               if(it.health >= 1)
-               if(!STAT(FROZEN, it))
-               if(SAME_TEAM(it, this))
-               if(!last_pl)
-                       last_pl = it;
-               else
-                       return NULL;
-       });
-       return last_pl;
-}
-
-void freezetag_LastPlayerForTeam_Notify(entity this)
-{
-       if(round_handler_IsActive())
-       if(round_handler_IsRoundStarted())
-       {
-               entity pl = freezetag_LastPlayerForTeam(this);
-               if(pl)
-                       Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
-       }
-}
-
-void freezetag_Add_Score(entity targ, entity attacker)
-{
-       if(attacker == targ)
-       {
-               // you froze your own dumb targ
-               // counted as "suicide" already
-               GameRules_scoring_add(targ, SCORE, -1);
-       }
-       else if(IS_PLAYER(attacker))
-       {
-               // got frozen by an enemy
-               // counted as "kill" and "death" already
-               GameRules_scoring_add(targ, SCORE, -1);
-               GameRules_scoring_add(attacker, SCORE, +1);
-       }
-       // else nothing - got frozen by the game type rules themselves
-}
-
-void freezetag_Freeze(entity targ, entity attacker)
-{
-       if(STAT(FROZEN, targ))
-               return;
-
-       if(autocvar_g_freezetag_frozen_maxtime > 0)
-               targ.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
-
-       Freeze(targ, 0, 1, true);
-
-       freezetag_count_alive_players();
-
-       freezetag_Add_Score(targ, attacker);
-}
-
-void freezetag_Unfreeze(entity this)
-{
-       this.freezetag_frozen_time = 0;
-       this.freezetag_frozen_timeout = 0;
-
-       Unfreeze(this);
-}
-
-float freezetag_isEliminated(entity e)
-{
-       if(IS_PLAYER(e) && (STAT(FROZEN, e) == 1 || IS_DEAD(e)))
-               return true;
-       return false;
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-void(entity this) havocbot_role_ft_freeing;
-void(entity this) havocbot_role_ft_offense;
-
-void havocbot_goalrating_freeplayers(entity this, float ratingscale, vector org, float sradius)
-{
-       float t;
-       FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
-               if (STAT(FROZEN, it) == 1)
-               {
-                       if(vdist(it.origin - org, >, sradius))
-                               continue;
-                       navigation_routerating(this, it, ratingscale, 2000);
-               }
-               else if(vdist(it.origin - org, >, 400)) // avoid gathering all teammates in one place
-               {
-                       // If teamate is not frozen still seek them out as fight better
-                       // in a group.
-                       t = 0.2 * 150 / (this.health + this.armorvalue);
-                       navigation_routerating(this, it, t * ratingscale, 2000);
-               }
-       });
-}
-
-void havocbot_role_ft_offense(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + random() * 10 + 20;
-
-       // Count how many players on team are unfrozen.
-       int unfrozen = 0;
-       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !(STAT(FROZEN, it) != 1), { unfrozen++; });
-
-       // If only one left on team or if role has timed out then start trying to free players.
-       if (((unfrozen == 0) && (!STAT(FROZEN, this))) || (time > this.havocbot_role_timeout))
-       {
-               LOG_TRACE("changing role to freeing");
-               this.havocbot_role = havocbot_role_ft_freeing;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-               havocbot_goalrating_items(this, 10000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
-               havocbot_goalrating_freeplayers(this, 9000, this.origin, 10000);
-               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_ft_freeing(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + random() * 10 + 20;
-
-       if (time > this.havocbot_role_timeout)
-       {
-               LOG_TRACE("changing role to offense");
-               this.havocbot_role = havocbot_role_ft_offense;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-               havocbot_goalrating_items(this, 8000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
-               havocbot_goalrating_freeplayers(this, 20000, this.origin, 10000);
-               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-void ft_RemovePlayer(entity this)
-{
-       this.health = 0; // neccessary to update correctly alive stats
-       if(!STAT(FROZEN, this))
-               freezetag_LastPlayerForTeam_Notify(this);
-       freezetag_Unfreeze(this);
-       freezetag_count_alive_players();
-}
-
-MUTATOR_HOOKFUNCTION(ft, ClientDisconnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       ft_RemovePlayer(player);
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-
-       ft_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerDies)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float frag_deathtype = M_ARGV(3, float);
-
-       if(round_handler_IsActive())
-       if(round_handler_CountdownRunning())
-       {
-               if(STAT(FROZEN, frag_target))
-                       freezetag_Unfreeze(frag_target);
-               freezetag_count_alive_players();
-               return true; // 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(STAT(FROZEN, frag_target) != 1)
-               {
-                       freezetag_Add_Score(frag_target, frag_attacker);
-                       freezetag_count_alive_players();
-                       freezetag_LastPlayerForTeam_Notify(frag_target);
-               }
-               else
-                       freezetag_Unfreeze(frag_target); // remove ice
-               frag_target.health = 0; // Unfreeze resets health
-               frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
-               return true;
-       }
-
-       if(STAT(FROZEN, frag_target))
-               return true;
-
-       freezetag_Freeze(frag_target, frag_attacker);
-       freezetag_LastPlayerForTeam_Notify(frag_target);
-
-       if(frag_attacker == frag_target || frag_attacker == NULL)
-       {
-               if(IS_PLAYER(frag_target))
-                       Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
-       }
-       else
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
-               return true; // do nothing, round is starting right now
-
-       if(player.freezetag_frozen_timeout == -2) // player was dead
-       {
-               freezetag_Freeze(player, NULL);
-               return true;
-       }
-
-       freezetag_count_alive_players();
-
-       if(round_handler_IsActive())
-       if(round_handler_IsRoundStarted())
-       {
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
-               freezetag_Freeze(player, NULL);
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, reset_map_players)
-{
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               CS(it).killcount = 0;
-               it.freezetag_frozen_timeout = -1;
-               PutClientInServer(it);
-               it.freezetag_frozen_timeout = 0;
-       });
-       freezetag_count_alive_players();
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST)
-{
-       M_ARGV(2, float) = 0; // no frags counted in Freeze Tag
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST)
-{
-       if(game_stopped)
-               return true;
-
-       if(round_handler_IsActive())
-       if(!round_handler_IsRoundStarted())
-               return true;
-
-       int n;
-       entity o = NULL;
-       entity player = M_ARGV(0, entity);
-       //if(STAT(FROZEN, player))
-       //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout)
-               //player.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (player.freezetag_frozen_timeout - time) / (player.freezetag_frozen_timeout - player.freezetag_frozen_time);
-
-       if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
-               n = -1;
-       else
-       {
-               vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
-               n = 0;
-               FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
-                       if(STAT(FROZEN, it) == 0)
-                       if(!IS_DEAD(it))
-                       if(SAME_TEAM(it, player))
-                       if(boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
-                       {
-                               if(!o)
-                                       o = it;
-                               if(STAT(FROZEN, player) == 1)
-                                       it.reviving = true;
-                               ++n;
-                       }
-               });
-
-       }
-
-       if(n && STAT(FROZEN, player) == 1) // OK, there is at least one teammate reviving us
-       {
-               STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
-               player.health = max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health));
-
-               if(STAT(REVIVE_PROGRESS, player) >= 1)
-               {
-                       freezetag_Unfreeze(player);
-                       freezetag_count_alive_players();
-
-                       if(n == -1)
-                       {
-                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
-                               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, player.netname, autocvar_g_freezetag_frozen_maxtime);
-                               return true;
-                       }
-
-                       // EVERY team mate nearby gets a point (even if multiple!)
-                       FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
-                               GameRules_scoring_add(it, FREEZETAG_REVIVALS, +1);
-                               GameRules_scoring_add(it, SCORE, +1);
-                               nades_GiveBonus(it,autocvar_g_nades_bonus_score_low);
-                       });
-
-                       Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
-                       Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
-                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED, player.netname, o.netname);
-               }
-
-               FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
-                       STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
-                       it.reviving = false;
-               });
-       }
-       else if(!n && STAT(FROZEN, player) == 1) // only if no teammate is nearby will we reset
-       {
-               STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
-               player.health = max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health));
-       }
-       else if(!n && !STAT(FROZEN, player))
-       {
-               STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody
-       }
-
-       return true;
-}
-
-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");
-}
-
-MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
-{
-       entity bot = M_ARGV(0, entity);
-
-       if (!IS_DEAD(bot))
-       {
-               if (random() < 0.5)
-                       bot.havocbot_role = havocbot_role_ft_freeing;
-               else
-                       bot.havocbot_role = havocbot_role_ft_offense;
-       }
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
-       M_ARGV(0, float) = freezetag_teams;
-}
-
-MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
-{
-       // most weapons arena
-       if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
-               M_ARGV(0, string) = "most";
-}
-
-MUTATOR_HOOKFUNCTION(ft, FragCenterMessage)
-{
-       entity frag_attacker = M_ARGV(0, entity);
-       entity frag_target = M_ARGV(1, entity);
-       //float frag_deathtype = M_ARGV(2, float);
-       int kill_count_to_attacker = M_ARGV(3, int);
-       int kill_count_to_target = M_ARGV(4, int);
-
-       if(STAT(FROZEN, frag_target))
-               return; // target was already frozen, so this is just pushing them off the cliff
-
-       Send_Notification(NOTIF_ONE, frag_attacker, MSG_CHOICE, CHOICE_FRAG_FREEZE, frag_target.netname, kill_count_to_attacker, (IS_BOT_CLIENT(frag_target) ? -1 : CS(frag_target).ping));
-       Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target, frag_attacker.health, frag_attacker.armorvalue, (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping));
-
-       return true;
-}
-
-void freezetag_Initialize()
-{
-       freezetag_teams = autocvar_g_freezetag_teams_override;
-       if(freezetag_teams < 2)
-               freezetag_teams = cvar("g_freezetag_teams"); // read the cvar directly as it gets written earlier in the same frame
-
-       freezetag_teams = BITS(bound(2, freezetag_teams, 4));
-       GameRules_scoring(freezetag_teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
-           field(SP_FREEZETAG_REVIVALS, "revivals", 0);
-       });
-
-       round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
-       round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
-
-       EliminatedPlayers_Init(freezetag_isEliminated);
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_freezetag.qh b/qcsrc/server/mutators/mutator/gamemode_freezetag.qh
deleted file mode 100644 (file)
index a258d82..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-#pragma once
-
-#include "../gamemode.qh"
-
-int autocvar_g_freezetag_point_limit;
-int autocvar_g_freezetag_point_leadlimit;
-bool autocvar_g_freezetag_team_spawns;
-void freezetag_Initialize();
-
-REGISTER_MUTATOR(ft, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               GameRules_teams(true);
-        GameRules_spawning_teams(autocvar_g_freezetag_team_spawns);
-        GameRules_limit_score(autocvar_g_freezetag_point_limit);
-        GameRules_limit_lead(autocvar_g_freezetag_point_leadlimit);
-
-               freezetag_Initialize();
-       }
-       return 0;
-}
-
-.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
-
-float autocvar_g_freezetag_revive_extra_size;
-float autocvar_g_freezetag_revive_speed;
-bool autocvar_g_freezetag_revive_nade;
-float autocvar_g_freezetag_revive_nade_health;
diff --git a/qcsrc/server/mutators/mutator/gamemode_invasion.qc b/qcsrc/server/mutators/mutator/gamemode_invasion.qc
deleted file mode 100644 (file)
index 777b1b1..0000000
+++ /dev/null
@@ -1,604 +0,0 @@
-#include "gamemode_invasion.qh"
-
-#include <common/monsters/sv_spawn.qh>
-#include <common/monsters/sv_monsters.qh>
-
-#include <server/teamplay.qh>
-
-IntrusiveList g_invasion_roundends;
-IntrusiveList g_invasion_waves;
-IntrusiveList g_invasion_spawns;
-STATIC_INIT(g_invasion)
-{
-       g_invasion_roundends = IL_NEW();
-       g_invasion_waves = IL_NEW();
-       g_invasion_spawns = IL_NEW();
-}
-
-float autocvar_g_invasion_round_timelimit;
-float autocvar_g_invasion_spawnpoint_spawn_delay;
-float autocvar_g_invasion_warmup;
-int autocvar_g_invasion_monster_count;
-bool autocvar_g_invasion_zombies_only;
-float autocvar_g_invasion_spawn_delay;
-
-bool victent_present;
-.bool inv_endreached;
-
-bool inv_warning_shown; // spammy
-
-.string spawnmob;
-
-void target_invasion_roundend_use(entity this, entity actor, entity trigger)
-{
-       if(!IS_PLAYER(actor)) { return; }
-
-       actor.inv_endreached = true;
-
-       int plnum = 0;
-       int realplnum = 0;
-       // let's not count bots
-       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
-               ++realplnum;
-               if(it.inv_endreached)
-                       ++plnum;
-       });
-       if(plnum < ceil(realplnum * min(1, this.count))) // 70% of players
-               return;
-
-       this.winning = true;
-}
-
-spawnfunc(target_invasion_roundend)
-{
-       if(!g_invasion) { delete(this); return; }
-
-       victent_present = true; // a victory entity is present, we don't need to rely on monster count TODO: merge this with the intrusive list (can check empty)
-
-       if(!this.count) { this.count = 0.7; } // require at least 70% of the players to reach the end before triggering victory
-
-       this.use = target_invasion_roundend_use;
-
-       IL_PUSH(g_invasion_roundends, this);
-}
-
-spawnfunc(invasion_wave)
-{
-       if(!g_invasion) { delete(this); return; }
-
-       IL_PUSH(g_invasion_waves, this);
-}
-
-spawnfunc(invasion_spawnpoint)
-{
-       if(!g_invasion) { delete(this); return; }
-
-       this.classname = "invasion_spawnpoint";
-       IL_PUSH(g_invasion_spawns, this);
-}
-
-void ClearWinners();
-
-// Invasion stage mode winning condition: If the attackers triggered a round end (by fulfilling all objectives)
-// they win.
-int WinningCondition_Invasion()
-{
-       WinningConditionHelper(NULL); // set worldstatus
-
-       int status = WINNING_NO;
-
-       if(autocvar_g_invasion_type == INV_TYPE_STAGE)
-       {
-               SetWinners(inv_endreached, true);
-
-               int found = 0;
-               IL_EACH(g_invasion_roundends, true,
-               {
-                       ++found;
-                       if(it.winning)
-                       {
-                               bprint("Invasion: round completed.\n");
-                               // winners already set (TODO: teamplay support)
-
-                               status = WINNING_YES;
-                               break;
-                       }
-               });
-
-               if(!found)
-                       status = WINNING_YES; // just end it? TODO: should warn mapper!
-       }
-       else if(autocvar_g_invasion_type == INV_TYPE_HUNT)
-       {
-               ClearWinners();
-
-               int found = 0; // NOTE: this ends the round if no monsters are placed
-               IL_EACH(g_monsters, !(it.spawnflags & MONSTERFLAG_RESPAWNED),
-               {
-                       ++found;
-               });
-
-               if(found <= 0)
-               {
-                       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
-                       {
-                               it.winning = true;
-                       });
-                       status = WINNING_YES;
-               }
-       }
-
-       return status;
-}
-
-Monster invasion_PickMonster(int supermonster_count)
-{
-       RandomSelection_Init();
-
-       FOREACH(Monsters, it != MON_Null,
-       {
-               if((it.spawnflags & MON_FLAG_HIDDEN) || (it.spawnflags & MONSTER_TYPE_PASSIVE) || (it.spawnflags & MONSTER_TYPE_FLY) || (it.spawnflags & MONSTER_TYPE_SWIM) ||
-                       (it.spawnflags & MONSTER_SIZE_QUAKE) || ((it.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
-                       continue;
-               if(autocvar_g_invasion_zombies_only && !(it.spawnflags & MONSTER_TYPE_UNDEAD))
-                       continue;
-        RandomSelection_AddEnt(it, 1, 1);
-       });
-
-       return RandomSelection_chosen_ent;
-}
-
-entity invasion_PickSpawn()
-{
-       RandomSelection_Init();
-
-       IL_EACH(g_invasion_spawns, true,
-       {
-               RandomSelection_AddEnt(it, 1, ((time < it.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
-               it.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
-       });
-
-       return RandomSelection_chosen_ent;
-}
-
-entity invasion_GetWaveEntity(int wavenum)
-{
-       IL_EACH(g_invasion_waves, it.cnt == wavenum,
-       {
-               return it; // found one
-       });
-
-       // if no specific one is found, find the last existing wave ent
-       entity best = NULL;
-       IL_EACH(g_invasion_waves, it.cnt <= wavenum,
-       {
-               if(!best || it.cnt > best.cnt)
-                       best = it;
-       });
-
-       return best;
-}
-
-void invasion_SpawnChosenMonster(Monster mon)
-{
-       entity monster;
-       entity spawn_point = invasion_PickSpawn();
-       entity wave_ent = invasion_GetWaveEntity(inv_roundcnt);
-
-       string tospawn = "";
-       if(wave_ent && wave_ent.spawnmob && wave_ent.spawnmob != "")
-       {
-               RandomSelection_Init();
-               FOREACH_WORD(wave_ent.spawnmob, true,
-               {
-                       RandomSelection_AddString(it, 1, 1);
-               });
-
-               tospawn = RandomSelection_chosen_string;
-       }
-
-       if(spawn_point == NULL)
-       {
-               if(!inv_warning_shown)
-               {
-                       inv_warning_shown = true;
-                       LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations");
-               }
-               entity e = spawn();
-               setsize(e, mon.m_mins, mon.m_maxs);
-
-               if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
-                       monster = spawnmonster(e, tospawn, mon.monsterid, NULL, NULL, e.origin, false, false, 2);
-               else
-               {
-                       delete(e);
-                       return;
-               }
-       }
-       else // if spawnmob field falls through (unset), fallback to mon (relying on spawnmonster for that behaviour)
-               monster = spawnmonster(spawn(), ((spawn_point.spawnmob && spawn_point.spawnmob != "") ? spawn_point.spawnmob : tospawn), mon.monsterid, spawn_point, spawn_point, spawn_point.origin, false, false, 2);
-
-       if(!monster)
-               return;
-
-       monster.spawnshieldtime = time;
-
-       if(spawn_point)
-       {
-               if(spawn_point.target_range)
-                       monster.target_range = spawn_point.target_range;
-               monster.target2 = spawn_point.target2;
-       }
-
-       if(teamplay)
-       {
-               if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0)
-                       monster.team = spawn_point.team;
-               else
-               {
-                       RandomSelection_Init();
-                       if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_AddFloat(NUM_TEAM_1, 1, 1);
-                       if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_AddFloat(NUM_TEAM_2, 1, 1);
-                       if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_AddFloat(NUM_TEAM_3, 1, 1); }
-                       if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_AddFloat(NUM_TEAM_4, 1, 1); }
-
-                       monster.team = RandomSelection_chosen_float;
-               }
-
-               monster_setupcolors(monster);
-
-               if(monster.sprite)
-               {
-                       WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0'));
-
-                       monster.sprite.team = 0;
-                       monster.sprite.SendFlags |= 1;
-               }
-       }
-
-       if(monster.monster_attack)
-               IL_REMOVE(g_monster_targets, monster);
-       monster.monster_attack = false; // it's the player's job to kill all the monsters
-
-       if(inv_roundcnt >= inv_maxrounds)
-               monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses
-}
-
-void invasion_SpawnMonsters(int supermonster_count)
-{
-       Monster chosen_monster = invasion_PickMonster(supermonster_count);
-
-       invasion_SpawnChosenMonster(chosen_monster);
-}
-
-bool Invasion_CheckWinner()
-{
-       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
-       {
-               IL_EACH(g_monsters, true,
-               {
-                       Monster_Remove(it);
-               });
-               IL_CLEAR(g_monsters);
-
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
-               Send_Notification(NOTIF_ALL, NULL, 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;
-
-       IL_EACH(g_monsters, it.health > 0,
-       {
-               if((get_monsterinfo(it.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
-                       ++supermonster_count;
-               ++total_alive_monsters;
-
-               if(teamplay)
-               switch(it.team)
-               {
-                       case NUM_TEAM_1: ++red_alive; break;
-                       case NUM_TEAM_2: ++blue_alive; break;
-                       case NUM_TEAM_3: ++yellow_alive; break;
-                       case NUM_TEAM_4: ++pink_alive; break;
-               }
-       });
-
-       if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
-       {
-               if(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 = NULL;
-       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
-       {
-               FOREACH_CLIENT(IS_PLAYER(it), {
-                       float cs = GameRules_scoring_add(it, KILLS, 0);
-                       if(cs > winning_score)
-                       {
-                               winning_score = cs;
-                               winner = it;
-                       }
-               });
-       }
-
-       IL_EACH(g_monsters, true,
-       {
-               Monster_Remove(it);
-       });
-       IL_CLEAR(g_monsters);
-
-       if(teamplay)
-       {
-               if(winner_team)
-               {
-                       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
-                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
-               }
-       }
-       else if(winner)
-       {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
-       }
-
-       round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
-
-       return 1;
-}
-
-bool Invasion_CheckPlayers()
-{
-       return true;
-}
-
-void Invasion_RoundStart()
-{
-       int numplayers = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               it.player_blocked = false;
-               ++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)
-{
-       entity frag_target = M_ARGV(0, entity);
-       entity frag_attacker = M_ARGV(1, entity);
-
-       if(!(frag_target.spawnflags & MONSTERFLAG_RESPAWNED))
-       {
-               if(autocvar_g_invasion_type == INV_TYPE_ROUND)
-               {
-                       inv_numkilled += 1;
-                       inv_maxcurrent -= 1;
-               }
-               if(teamplay) { inv_monsters_perteam[frag_target.team] -= 1; }
-
-               if(IS_PLAYER(frag_attacker))
-               if(SAME_TEAM(frag_attacker, frag_target)) // in non-teamplay modes, same team = same player, so this works
-                       GameRules_scoring_add(frag_attacker, KILLS, -1);
-               else
-               {
-                       GameRules_scoring_add(frag_attacker, KILLS, +1);
-                       if(teamplay)
-                               TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(inv, MonsterSpawn)
-{
-       entity mon = M_ARGV(0, entity);
-       mon.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
-
-       if(autocvar_g_invasion_type == INV_TYPE_HUNT)
-               return false; // allowed
-
-       if(!(mon.spawnflags & MONSTERFLAG_SPAWNED))
-               return true;
-
-       if(!(mon.spawnflags & MONSTERFLAG_RESPAWNED))
-       {
-               inv_numspawned += 1;
-               inv_maxcurrent += 1;
-       }
-
-       mon.monster_skill = inv_monsterskill;
-
-       if((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, mon.monster_name);
-}
-
-MUTATOR_HOOKFUNCTION(inv, SV_StartFrame)
-{
-       if(autocvar_g_invasion_type != INV_TYPE_ROUND)
-               return; // uses map spawned monsters
-
-       monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
-       monsters_killed = inv_numkilled;
-}
-
-MUTATOR_HOOKFUNCTION(inv, PlayerRegen)
-{
-       // no regeneration in invasion, regardless of the game type
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, PlayerSpawn)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.bot_attack)
-               IL_REMOVE(g_bot_targets, player);
-       player.bot_attack = false;
-}
-
-MUTATOR_HOOKFUNCTION(inv, Damage_Calculate)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float frag_damage = M_ARGV(4, float);
-       vector frag_force = M_ARGV(6, vector);
-
-       if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target)
-       {
-               frag_damage = 0;
-               frag_force = '0 0 0';
-
-               M_ARGV(4, float) = frag_damage;
-               M_ARGV(6, vector) = frag_force;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(inv, BotShouldAttack)
-{
-       entity targ = M_ARGV(1, entity);
-
-       if(!IS_MONSTER(targ))
-               return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, SetStartItems)
-{
-       if(autocvar_g_invasion_type == INV_TYPE_ROUND)
-       {
-               start_health = 200;
-               start_armorvalue = 200;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid)
-{
-       entity frag_target = M_ARGV(1, entity);
-
-       if(IS_MONSTER(frag_target))
-               return MUT_ACCADD_INVALID;
-       return MUT_ACCADD_INDIFFERENT;
-}
-
-MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning)
-{
-       // monster spawning disabled during an invasion
-       M_ARGV(1, string) = "You cannot spawn monsters during an invasion!";
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, CheckRules_World)
-{
-       if(autocvar_g_invasion_type == INV_TYPE_ROUND)
-               return false;
-
-       M_ARGV(0, float) = WinningCondition_Invasion();
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
-       M_ARGV(0, float) = invasion_teams;
-}
-
-MUTATOR_HOOKFUNCTION(inv, AllowMobButcher)
-{
-       M_ARGV(0, string) = "This command does not work during an invasion!";
-       return true;
-}
-
-void invasion_ScoreRules(int inv_teams)
-{
-       if(inv_teams) { CheckAllowedTeams(NULL); }
-       GameRules_score_enabled(false);
-       GameRules_scoring(inv_teams, 0, 0, {
-           if (inv_teams) {
-            field_team(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
-           }
-           field(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
-       });
-}
-
-void invasion_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
-{
-       if(autocvar_g_invasion_type == INV_TYPE_HUNT || autocvar_g_invasion_type == INV_TYPE_STAGE)
-               cvar_set("fraglimit", "0");
-
-       if(autocvar_g_invasion_teams)
-       {
-               invasion_teams = BITS(bound(2, autocvar_g_invasion_teams, 4));
-       }
-       else
-               invasion_teams = 0;
-
-       independent_players = 1; // to disable extra useless scores
-
-       invasion_ScoreRules(invasion_teams);
-
-       independent_players = 0;
-
-       if(autocvar_g_invasion_type == INV_TYPE_ROUND)
-       {
-               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()
-{
-       InitializeEntity(NULL, invasion_DelayedInit, INITPRIO_GAMETYPE);
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_invasion.qh b/qcsrc/server/mutators/mutator/gamemode_invasion.qh
deleted file mode 100644 (file)
index 0ea0e82..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-#pragma once
-
-#include "../gamemode.qh"
-
-#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
-int autocvar_g_invasion_teams;
-int autocvar_g_invasion_type;
-bool autocvar_g_invasion_team_spawns;
-bool g_invasion;
-void invasion_Initialize();
-
-REGISTER_MUTATOR(inv, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               if (autocvar_g_invasion_teams >= 2) {
-                       GameRules_teams(true);
-                       GameRules_spawning_teams(autocvar_g_invasion_team_spawns);
-               }
-        GameRules_limit_score(autocvar_g_invasion_point_limit);
-
-               g_invasion = true;
-               cvar_settemp("g_monsters", "1");
-               invasion_Initialize();
-       }
-       return 0;
-}
-
-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;
-
-const int INV_TYPE_ROUND = 0; // round-based waves of enemies
-const int INV_TYPE_HUNT = 1; // clear the map of placed enemies
-const int INV_TYPE_STAGE = 2; // reach the end of the level
diff --git a/qcsrc/server/mutators/mutator/gamemode_keepaway.qc b/qcsrc/server/mutators/mutator/gamemode_keepaway.qc
deleted file mode 100644 (file)
index 567f24b..0000000
+++ /dev/null
@@ -1,472 +0,0 @@
-#include "gamemode_keepaway.qh"
-
-#include <common/effects/all.qh>
-
-.entity ballcarried;
-
-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;
-
-bool ka_ballcarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs on waypoints which are attached to ballcarriers, updates once per frame
-{
-       if(view.ballcarried)
-               if(IS_SPEC(player))
-                       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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void ka_TouchEvent(entity this, entity toucher);
-void ka_RespawnBall(entity this) // runs whenever the ball needs to be relocated
-{
-       if(game_stopped) return;
-       vector oldballorigin = this.origin;
-
-       if(!MoveToRandomMapLocation(this, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
-       {
-               entity spot = SelectSpawnPoint(this, true);
-               setorigin(this, spot.origin);
-               this.angles = spot.angles;
-       }
-
-       makevectors(this.angles);
-       set_movetype(this, MOVETYPE_BOUNCE);
-       this.velocity = '0 0 200';
-       this.angles = '0 0 0';
-       this.effects = autocvar_g_keepawayball_effects;
-       settouch(this, ka_TouchEvent);
-       setthink(this, ka_RespawnBall);
-       this.nextthink = time + autocvar_g_keepawayball_respawntime;
-       navigation_dynamicgoal_set(this);
-
-       Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1);
-       Send_Effect(EFFECT_ELECTRO_COMBO, this.origin, '0 0 0', 1);
-
-       WaypointSprite_Spawn(WP_KaBall, 0, 0, this, '0 0 64', NULL, this.team, this, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
-       WaypointSprite_Ping(this.waypointsprite_attachedforcarrier);
-
-       sound(this, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-}
-
-void ka_TimeScoring(entity this)
-{
-       if(this.owner.ballcarried)
-       { // add points for holding the ball after a certain amount of time
-               if(autocvar_g_keepaway_score_timepoints)
-                       GameRules_scoring_add(this.owner, SCORE, autocvar_g_keepaway_score_timepoints);
-
-               GameRules_scoring_add(this.owner, KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds"
-               this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
-       }
-}
-
-void ka_TouchEvent(entity this, entity toucher) // runs any time that the ball comes in contact with something
-{
-       if(game_stopped) return;
-       if(!this) return;
-       if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
-       { // The ball fell off the map, respawn it since players can't get to it
-               ka_RespawnBall(this);
-               return;
-       }
-       if(IS_DEAD(toucher)) { return; }
-       if(STAT(FROZEN, toucher)) { return; }
-       if (!IS_PLAYER(toucher))
-       {  // The ball just touched an object, most likely the world
-               Send_Effect(EFFECT_BALL_SPARKS, this.origin, '0 0 0', 1);
-               sound(this, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM);
-               return;
-       }
-       else if(this.wait > time) { return; }
-
-       // attach the ball to the player
-       this.owner = toucher;
-       toucher.ballcarried = this;
-       GameRules_scoring_vip(toucher, true);
-       setattachment(this, toucher, "");
-       setorigin(this, '0 0 0');
-
-       // make the ball invisible/unable to do anything/set up time scoring
-       this.velocity = '0 0 0';
-       set_movetype(this, MOVETYPE_NONE);
-       this.effects |= EF_NODRAW;
-       settouch(this, func_null);
-       setthink(this, ka_TimeScoring);
-       this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
-       this.takedamage = DAMAGE_NO;
-       navigation_dynamicgoal_unset(this);
-
-       // apply effects to player
-       toucher.glow_color = autocvar_g_keepawayball_trail_color;
-       toucher.glow_trail = true;
-       toucher.effects |= autocvar_g_keepaway_ballcarrier_effects;
-
-       // messages and sounds
-       ka_EventLog("pickup", toucher);
-       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_PICKUP, toucher.netname);
-       Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, toucher.netname);
-       Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF);
-       sound(this.owner, CH_TRIGGER, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-
-       // scoring
-       GameRules_scoring_add(toucher, KEEPAWAY_PICKUPS, 1);
-
-       // waypoints
-       WaypointSprite_AttachCarrier(WP_KaBallCarrier, toucher, RADARICON_FLAGCARRIER);
-       toucher.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
-       WaypointSprite_UpdateRule(toucher.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
-       WaypointSprite_Ping(toucher.waypointsprite_attachedforcarrier);
-       WaypointSprite_Kill(this.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, NULL, "");
-       set_movetype(ball, MOVETYPE_BOUNCE);
-       ball.wait = time + 1;
-       settouch(ball, ka_TouchEvent);
-       setthink(ball, 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();
-       entity e = ball.owner; ball.owner = NULL;
-       e.ballcarried = NULL;
-       GameRules_scoring_vip(e, false);
-       navigation_dynamicgoal_set(ball);
-
-       // 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, NULL, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname);
-       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname);
-       sound(NULL, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-
-       // scoring
-       // GameRules_scoring_add(plyr, 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', NULL, 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);
-}
-
-/** used to clear the ballcarrier whenever the match switches from warmup to normal */
-void ka_Reset(entity this)
-{
-       if((this.owner) && (IS_PLAYER(this.owner)))
-               ka_DropEvent(this.owner);
-
-       if(time < game_starttime)
-       {
-               setthink(this, ka_RespawnBall);
-               settouch(this, func_null);
-               this.nextthink = game_starttime;
-       }
-       else
-               ka_RespawnBall(this);
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-void havocbot_goalrating_ball(entity this, float ratingscale, vector org)
-{
-       float t;
-       entity ball_owner;
-       ball_owner = ka_ball.owner;
-
-       if (ball_owner == this)
-               return;
-
-       // If ball is carried by player then hunt them down.
-       if (ball_owner)
-       {
-               t = (this.health + this.armorvalue) / (ball_owner.health + ball_owner.armorvalue);
-               navigation_routerating(this, ball_owner, t * ratingscale, 2000);
-       }
-       else // Ball has been dropped so collect.
-               navigation_routerating(this, ka_ball, ratingscale, 2000);
-}
-
-void havocbot_role_ka_carrier(entity this)
-{
-       if (IS_DEAD(this))
-               return;
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-               havocbot_goalrating_items(this, 10000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
-               havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-
-       if (!this.ballcarried)
-       {
-               this.havocbot_role = havocbot_role_ka_collector;
-               navigation_goalrating_timeout_expire(this, 2);
-       }
-}
-
-void havocbot_role_ka_collector(entity this)
-{
-       if (IS_DEAD(this))
-               return;
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-               havocbot_goalrating_items(this, 10000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 1000, this.origin, 10000);
-               havocbot_goalrating_ball(this, 20000, this.origin);
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-
-       if (this.ballcarried)
-       {
-               this.havocbot_role = havocbot_role_ka_carrier;
-               navigation_goalrating_timeout_expire(this, 2);
-       }
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-MUTATOR_HOOKFUNCTION(ka, PlayerDies)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-
-       if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)))
-       {
-               if(frag_target.ballcarried) { // add to amount of times killing carrier
-                       GameRules_scoring_add(frag_attacker, KEEPAWAY_CARRIERKILLS, 1);
-                       if(autocvar_g_keepaway_score_bckill) // add bckills to the score
-                               GameRules_scoring_add(frag_attacker, 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
-                       GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_killac);
-       }
-
-       if(frag_target.ballcarried) { ka_DropEvent(frag_target); } // a player with the ball has died, drop it
-}
-
-MUTATOR_HOOKFUNCTION(ka, GiveFragsForKill)
-{
-       M_ARGV(2, float) = 0; // no frags counted in keepaway
-       return true; // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPreThink)
-{
-       entity player = M_ARGV(0, entity);
-
-       // clear the item used for the ball in keepaway
-       player.items &= ~IT_KEY1;
-
-       // if the player has the ball, make sure they have the item for it (Used for HUD primarily)
-       if(player.ballcarried)
-               player.items |= IT_KEY1;
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerUseKey)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(MUTATOR_RETURNVALUE == 0)
-       if(player.ballcarried)
-       {
-               ka_DropEvent(player);
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(ka, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float frag_damage = M_ARGV(4, float);
-       vector frag_force = M_ARGV(6, vector);
-
-       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;
-               }
-       }
-
-       M_ARGV(4, float) = frag_damage;
-       M_ARGV(6, vector) = frag_force;
-}
-
-MUTATOR_HOOKFUNCTION(ka, ClientDisconnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
-}
-
-MUTATOR_HOOKFUNCTION(ka, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPowerups)
-{
-       entity player = M_ARGV(0, entity);
-
-       // 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()
-
-       player.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
-
-       if(player.ballcarried)
-               player.effects |= autocvar_g_keepaway_ballcarrier_effects;
-}
-
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPhysics_UpdateStats)
-{
-       entity player = M_ARGV(0, entity);
-       // these automatically reset, no need to worry
-
-       if(player.ballcarried)
-               STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_keepaway_ballcarrier_highspeed;
-}
-
-MUTATOR_HOOKFUNCTION(ka, BotShouldAttack)
-{
-       entity bot = M_ARGV(0, entity);
-       entity targ = M_ARGV(1, entity);
-
-       // if neither player has ball then don't attack unless the ball is on the ground
-       if(!targ.ballcarried && !bot.ballcarried && ka_ball.owner)
-               return true;
-}
-
-MUTATOR_HOOKFUNCTION(ka, HavocBot_ChooseRole)
-{
-       entity bot = M_ARGV(0, entity);
-
-       if (bot.ballcarried)
-               bot.havocbot_role = havocbot_role_ka_carrier;
-       else
-               bot.havocbot_role = havocbot_role_ka_collector;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(ka, DropSpecialItems)
-{
-       entity frag_target = M_ARGV(0, entity);
-
-       if(frag_target.ballcarried)
-               ka_DropEvent(frag_target);
-}
-
-.bool pushable;
-
-// ==============
-// Initialization
-// ==============
-
-MODEL(KA_BALL, "models/orbs/orbblue.md3");
-
-void ka_SpawnBall() // loads various values for the ball, runs only once at start of match
-{
-       entity e = new(keepawayball);
-       setmodel(e, MDL_KA_BALL);
-       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.damageforcescale = autocvar_g_keepawayball_damageforcescale;
-       e.takedamage = DAMAGE_YES;
-       e.solid = SOLID_TRIGGER;
-       set_movetype(e, MOVETYPE_BOUNCE);
-       e.glow_color = autocvar_g_keepawayball_trail_color;
-       e.glow_trail = true;
-       e.flags = FL_ITEM;
-       IL_PUSH(g_items, e);
-       e.pushable = true;
-       e.reset = ka_Reset;
-       settouch(e, ka_TouchEvent);
-       e.owner = NULL;
-       ka_ball = e;
-       navigation_dynamicgoal_init(ka_ball, false);
-
-       InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So.
-}
-
-void ka_Initialize() // run at the start of a match, initiates game mode
-{
-       ka_SpawnBall();
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_keepaway.qh b/qcsrc/server/mutators/mutator/gamemode_keepaway.qh
deleted file mode 100644 (file)
index abbabbd..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#pragma once
-
-#include "../gamemode.qh"
-
-void ka_Initialize();
-
-REGISTER_MUTATOR(ka, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-           GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
-            field(SP_KEEPAWAY_PICKUPS, "pickups", 0);
-            field(SP_KEEPAWAY_CARRIERKILLS, "bckills", 0);
-            field(SP_KEEPAWAY_BCTIME, "bctime", SFL_SORT_PRIO_SECONDARY);
-        });
-
-               ka_Initialize();
-       }
-       return false;
-}
-
-
-entity ka_ball;
-
-void(entity this) havocbot_role_ka_carrier;
-void(entity this) havocbot_role_ka_collector;
-
-void ka_DropEvent(entity plyr);
diff --git a/qcsrc/server/mutators/mutator/gamemode_keyhunt.qc b/qcsrc/server/mutators/mutator/gamemode_keyhunt.qc
deleted file mode 100644 (file)
index 0457648..0000000
+++ /dev/null
@@ -1,1321 +0,0 @@
-#include "gamemode_keyhunt.qh"
-
-float autocvar_g_balance_keyhunt_damageforcescale;
-float autocvar_g_balance_keyhunt_delay_collect;
-float autocvar_g_balance_keyhunt_delay_damage_return;
-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_return_when_unreachable;
-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_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;
-
-bool 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 siren_time;  //  time delay the siren
-//.float stuff_time;  //  time delay to stuffcmd a cvar
-
-int 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";
-
-int kh_Team_ByID(int 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;
-//bool kh_tracking_enabled;
-int kh_teams;
-int kh_interferemsg_team;
-float kh_interferemsg_time;
-.entity kh_next, kh_prev; // linked list
-.float kh_droptime;
-.int kh_dropperteam;
-.entity kh_previous_owner;
-.int kh_previous_owner_playerid;
-
-int kh_key_dropped, kh_key_carried;
-
-int kh_Key_AllOwnedByWhichTeam();
-
-const int ST_KH_CAPS = 1;
-void kh_ScoreRules(int teams)
-{
-       GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
-        field_team(ST_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
-        field(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
-        field(SP_KH_PUSHES, "pushes", 0);
-        field(SP_KH_DESTROYS, "destroyed", SFL_LOWER_IS_BETTER);
-        field(SP_KH_PICKUPS, "pickups", 0);
-        field(SP_KH_KCKILLS, "kckills", 0);
-        field(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER);
-       });
-}
-
-bool kh_KeyCarrier_waypointsprite_visible_for_player(entity this, entity player, entity view)  // runs all the time
-{
-       if(!IS_PLAYER(view) || DIFF_TEAM(this, view))
-               if(!kh_tracking_enabled)
-                       return false;
-
-       return true;
-}
-
-bool kh_Key_waypointsprite_visible_for_player(entity this, entity player, entity view)
-{
-       if(!kh_tracking_enabled)
-               return false;
-       if(!this.owner)
-               return true;
-       if(!this.owner.owner)
-               return true;
-       return false;  // draw only when key is not owned
-}
-
-void kh_update_state()
-{
-       entity key;
-       int f;
-       int s = 0;
-       FOR_EACH_KH_KEY(key)
-       {
-               if(key.owner)
-                       f = key.team;
-               else
-                       f = 30;
-               s |= (32 ** key.count) * f;
-       }
-
-       FOREACH_CLIENT(true, { STAT(KH_KEYS, it) = s; });
-
-       FOR_EACH_KH_KEY(key)
-       {
-               if(key.owner)
-                       STAT(KH_KEYS, key.owner) |= (32 ** key.count) * 31;
-       }
-       //print(ftos((nextent(NULL)).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(entity this)  // called a lot
-{
-       if(game_stopped)
-               return;
-       if(this.cnt > 0)
-       {
-               if(getthink(this) != kh_WaitForPlayers)
-                       this.cnt -= 1;
-       }
-       else if(this.cnt == 0)
-       {
-               this.cnt -= 1;
-               kh_Controller_Thinkfunc();
-       }
-       this.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(game_stopped)
-               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 = 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;
-       if(IL_CONTAINS(g_items, key))
-               IL_REMOVE(g_items, key);
-       key.solid = SOLID_NOT;
-       set_movetype(key, MOVETYPE_NONE);
-       key.team = key.owner.team;
-       key.nextthink = time;
-       key.damageforcescale = 0;
-       key.takedamage = DAMAGE_NO;
-       key.modelindex = kh_key_carried;
-       navigation_dynamicgoal_unset(key);
-}
-
-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 = 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, NULL, "");
-       setorigin(key, key.owner.origin + '0 0 1' * (STAT(PL_MIN, key.owner).z - KH_KEY_MIN_z));
-       key.angles = key.owner.angles;
-#else
-       setorigin(key, key.owner.origin + key.origin.z * '0 0 1');
-       setattachment(key, NULL, "");
-       key.angles_y += key.owner.angles.y;
-#endif
-       key.flags = FL_ITEM;
-       if(!IL_CONTAINS(g_items, key))
-               IL_PUSH(g_items, key);
-       key.solid = SOLID_TRIGGER;
-       set_movetype(key, 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;
-       navigation_dynamicgoal_set(key);
-       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
-{
-       if(key.owner == player)
-               return;
-
-       int 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 = NULL;
-               key.kh_prev = NULL;
-
-               if(key.owner.kh_next == NULL)
-               {
-                       // 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 == NULL)
-               {
-                       // 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 = NULL;
-
-       int ownerteam = kh_Key_AllOwnedByWhichTeam();
-       if(ownerteam != ownerteam0)
-       {
-               entity k;
-               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, { first = it; break; });
-                               entity third = WP_Null;
-                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { 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 "Key Carrier"
-                       FOR_EACH_KH_KEY(k)
-                       {
-                               if (!k.owner) continue;
-                               entity first = WP_Null;
-                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; });
-                               entity third = WP_Null;
-                               FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; });
-                               WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third);
-                       }
-               }
-       }
-}
-
-void kh_Key_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
-{
-       if(this.owner)
-               return;
-       if(ITEM_DAMAGE_NEEDKILL(deathtype))
-       {
-               this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
-               return;
-       }
-       if(force == '0 0 0')
-               return;
-       if(time > this.pushltime)
-               if(IS_PLAYER(attacker))
-                       this.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);
-               GameRules_scoring_add(player, KH_PICKUPS, 1);
-       }
-       key.kh_dropperteam = 0;
-       int realteam = kh_Team_ByID(key.count);
-       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PICKUP), player.netname);
-
-       kh_Key_AssignTo(key, player); // this also updates .kh_state
-}
-
-void kh_Key_Touch(entity this, entity toucher)  // runs many, many times when a key has been dropped and can be picked up
-{
-       if(game_stopped)
-               return;
-
-       if(this.owner) // already carried
-               return;
-
-       if(ITEM_TOUCH_NEEDKILL())
-       {
-               this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
-               return;
-       }
-
-       if (!IS_PLAYER(toucher))
-               return;
-       if(IS_DEAD(toucher))
-               return;
-       if(toucher == this.enemy)
-               if(time < this.kh_droptime + autocvar_g_balance_keyhunt_delay_collect)
-                       return;  // you just dropped it!
-       kh_Key_Collect(this, toucher);
-}
-
-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 = key.owner;
-       kh_Key_AssignTo(key, NULL);
-       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;
-               }
-       }
-
-       delete(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, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
-       kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
-}
-
-void nades_GiveBonus(entity player, float score);
-
-void kh_WinnerTeam(int winner_team)  // runs when a team wins
-{
-       // all key carriers get some points
-       entity key;
-       float score = (NumTeams(kh_teams) - 1) * autocvar_g_balance_keyhunt_score_capture;
-       DistributeEvenly_Init(score, NumTeams(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 = DistributeEvenly_Get(1);
-               kh_Scores_Event(key.owner, key, "capture", f, 0);
-               GameRules_scoring_add_team(key.owner, KH_CAPS, 1);
-               nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high);
-       }
-
-       bool 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, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
-       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_KEYHUNT_CAPTURE), keyowner);
-
-       first = true;
-       vector firstorigin = '0 0 0', lastorigin = '0 0 0', midpoint = '0 0 0';
-       FOR_EACH_KH_KEY(key)
-       {
-               vector thisorigin = kh_AttachedOrigin(key);
-               //dprint("Key origin: ", vtos(thisorigin), "\n");
-               midpoint += thisorigin;
-
-               if(!first)
-                       te_lightning2(NULL, lastorigin, thisorigin);
-               lastorigin = thisorigin;
-               if(first)
-                       firstorigin = thisorigin;
-               first = false;
-       }
-       if(NumTeams(kh_teams) > 2)
-       {
-               te_lightning2(NULL, lastorigin, firstorigin);
-       }
-       midpoint = midpoint * (1 / NumTeams(kh_teams));
-       te_customflash(midpoint, 1000, 1, Team_ColorRGB(winner_team) * 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(int loser_team, entity lostkey)  // runs when a player pushes a flag carrier off the map
-{
-       float f;
-       entity attacker = NULL;
-       if(lostkey.pusher)
-               if(lostkey.pusher.team != loser_team)
-                       if(IS_PLAYER(lostkey.pusher))
-                               attacker = lostkey.pusher;
-
-       if(attacker)
-       {
-               if(lostkey.kh_previous_owner)
-                       kh_Scores_Event(lostkey.kh_previous_owner, NULL, "pushed", 0, -autocvar_g_balance_keyhunt_score_push);
-                       // don't actually GIVE him the -nn points, just log
-               kh_Scores_Event(attacker, NULL, "push", autocvar_g_balance_keyhunt_score_push, 0);
-               GameRules_scoring_add(attacker, KH_PUSHES, 1);
-               //centerprint(attacker, "Your push is the best!"); // does this really need to exist?
-       }
-       else
-       {
-               int players = 0;
-               float of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
-
-               FOREACH_CLIENT(IS_PLAYER(it) && it.team != loser_team, { ++players; });
-
-               entity key;
-               int keys = 0;
-               FOR_EACH_KH_KEY(key)
-                       if(key.owner && key.team != loser_team)
-                               ++keys;
-
-               if(lostkey.kh_previous_owner)
-                       kh_Scores_Event(lostkey.kh_previous_owner, NULL, "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)
-                       GameRules_scoring_add(lostkey.kh_previous_owner, KH_DESTROYS, 1);
-
-               DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players);
-
-               FOR_EACH_KH_KEY(key)
-                       if(key.owner && key.team != loser_team)
-                       {
-                               f = DistributeEvenly_Get(of);
-                               kh_Scores_Event(key.owner, NULL, "destroyed_holdingkey", f, 0);
-                       }
-
-               int fragsleft = DistributeEvenly_Get(players);
-
-               // Now distribute these among all other teams...
-               int j = NumTeams(kh_teams) - 1;
-               for(int i = 0; i < NumTeams(kh_teams); ++i)
-               {
-                       int thisteam = kh_Team_ByID(i);
-                       if(thisteam == loser_team) // bad boy, no cookie - this WILL happen
-                               continue;
-
-                       players = 0;
-                       FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, { ++players; });
-
-                       DistributeEvenly_Init(fragsleft, j);
-                       fragsleft = DistributeEvenly_Get(j - 1);
-                       DistributeEvenly_Init(DistributeEvenly_Get(1), players);
-
-                       FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, {
-                               f = DistributeEvenly_Get(1);
-                               kh_Scores_Event(it, NULL, "destroyed", f, 0);
-                       });
-
-                       --j;
-               }
-       }
-
-       int realteam = kh_Team_ByID(lostkey.count);
-       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(loser_team, CENTER_ROUND_TEAM_LOSS));
-       if(attacker)
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PUSHED), attacker.netname, lostkey.kh_previous_owner.netname);
-       else
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DESTROYED), lostkey.kh_previous_owner.netname);
-
-       play2all(SND(KH_DESTROY));
-       te_tarexplosion(lostkey.origin);
-
-       kh_FinishRound();
-}
-
-void kh_Key_Think(entity this)  // runs all the time
-{
-       if(game_stopped)
-               return;
-
-       if(this.owner)
-       {
-#ifndef KH_PLAYER_USE_ATTACHMENT
-               makevectors('0 1 0' * (this.cnt + (time % 360) * KH_KEY_XYSPEED));
-               setorigin(this, v_forward * KH_KEY_XYDIST + '0 0 1' * this.origin.z);
-#endif
-       }
-
-       // if in nodrop or time over, end the round
-       if(!this.owner)
-               if(time > this.pain_finished)
-                       kh_LoserTeam(this.team, this);
-
-       if(this.owner)
-       if(kh_Key_AllOwnedByWhichTeam() != -1)
-       {
-               if(this.siren_time < time)
-               {
-                       sound(this.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM);  // play a simple alarm
-                       this.siren_time = time + 2.5;  // repeat every 2.5 seconds
-               }
-
-               entity key;
-               vector p = this.owner.origin;
-               FOR_EACH_KH_KEY(key)
-                       if(vdist(key.owner.origin - p, >, autocvar_g_balance_keyhunt_maxdist))
-                               goto not_winning;
-               kh_WinnerTeam(this.team);
-LABEL(not_winning)
-       }
-
-       if(kh_interferemsg_time && time > kh_interferemsg_time)
-       {
-               kh_interferemsg_time = 0;
-               FOREACH_CLIENT(IS_PLAYER(it), {
-                       if(it.team == kh_interferemsg_team)
-                               if(it.kh_next)
-                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_MEET);
-                               else
-                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_HELP);
-                       else
-                               Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE));
-               });
-       }
-
-       this.nextthink = time + 0.05;
-}
-
-void key_reset(entity this)
-{
-       kh_Key_AssignTo(this, NULL);
-       kh_Key_Remove(this);
-}
-
-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 = spawn();
-       key.count = i;
-       key.classname = STR_ITEM_KH_KEY;
-       settouch(key, kh_Key_Touch);
-       setthink(key, 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.damagedbytriggers = autocvar_g_balance_keyhunt_return_when_unreachable;
-       key.damagedbycontents = autocvar_g_balance_keyhunt_return_when_unreachable;
-       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;
-       navigation_dynamicgoal_init(key, false);
-
-       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(initial_owner.team, CENTER_KEYHUNT_START));
-
-       WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, NULL, 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
-int kh_Key_AllOwnedByWhichTeam()  // constantly called. check to see if all the keys are owned by the same team
-{
-       entity key;
-       int teem = -1;
-       int keys = NumTeams(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 = key.owner;
-
-       key.kh_droptime = time;
-       key.enemy = player;
-
-       kh_Scores_Event(player, key, "dropkey", 0, 0);
-       GameRules_scoring_add(player, KH_LOSSES, 1);
-       int realteam = kh_Team_ByID(key.count);
-       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DROP), player.netname);
-
-       kh_Key_AssignTo(key, NULL);
-       makevectors(player.v_angle);
-       key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false);
-       key.pusher = NULL;
-       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
-{
-       if(player.kh_next)
-       {
-               entity mypusher = NULL;
-               if(player.pusher)
-                       if(time < player.pushltime)
-                               mypusher = player.pusher;
-
-               entity key;
-               while((key = player.kh_next))
-               {
-                       kh_Scores_Event(player, key, "losekey", 0, 0);
-                       GameRules_scoring_add(player, KH_LOSSES, 1);
-                       int realteam = kh_Team_ByID(key.count);
-                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_LOST), player.netname);
-                       kh_Key_AssignTo(key, NULL);
-                       makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random());
-                       key.velocity = W_CalculateProjectileVelocity(player, 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);
-       }
-}
-
-int kh_GetMissingTeams()
-{
-       int missing_teams = 0;
-       for(int i = 0; i < NumTeams(kh_teams); ++i)
-       {
-               int teem = kh_Team_ByID(i);
-               int players = 0;
-               FOREACH_CLIENT(IS_PLAYER(it), {
-                       if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
-                               ++players;
-               });
-               if (!players)
-                       missing_teams |= (2 ** i);
-       }
-       return missing_teams;
-}
-
-void kh_WaitForPlayers()  // delay start of the round until enough players are present
-{
-       static int prev_missing_teams_mask;
-       if(time < game_starttime)
-       {
-               if (prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
-               return;
-       }
-
-       int missing_teams_mask = kh_GetMissingTeams();
-       if(!missing_teams_mask)
-       {
-               if(prev_missing_teams_mask > 0)
-                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
-               prev_missing_teams_mask = -1;
-               Send_Notification(NOTIF_ALL, NULL, 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, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
-                       prev_missing_teams_mask = -1;
-               }
-               else
-               {
-                       if(prev_missing_teams_mask != missing_teams_mask)
-                       {
-                               Send_Notification(NOTIF_ALL, NULL, 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, NULL, MSG_CENTER, CPID_KEYHUNT);
-       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
-
-       kh_tracking_enabled = true;
-}
-
-void kh_StartRound()  // runs at the start of each round
-{
-       if(time < game_starttime)
-       {
-               kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
-               return;
-       }
-
-       if(kh_GetMissingTeams())
-       {
-               kh_Controller_SetThink(1, kh_WaitForPlayers);
-               return;
-       }
-
-       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT);
-       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
-
-       for(int i = 0; i < NumTeams(kh_teams); ++i)
-       {
-               int teem = kh_Team_ByID(i);
-               int players = 0;
-               entity my_player = NULL;
-               FOREACH_CLIENT(IS_PLAYER(it), {
-                       if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
-                       {
-                               ++players;
-                               if(random() * players <= 1)
-                                       my_player = it;
-                       }
-               });
-               kh_Key_Spawn(my_player, 360 * i / NumTeams(kh_teams), i);
-       }
-
-       kh_tracking_enabled = false;
-       Send_Notification(NOTIF_ALL, NULL, 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)
-               {
-                       int nk = 0;
-                       for(entity k = targ.kh_next; k != NULL; 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);
-                       GameRules_scoring_add(attacker, 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 = cvar("g_keyhunt_teams"); // read the cvar directly as it gets written earlier in the same frame
-       kh_teams = BITS(bound(2, kh_teams, 4));
-
-       // make a KH entity for controlling the game
-       kh_controller = spawn();
-       setthink(kh_controller, 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;
-
-       kh_ScoreRules(kh_teams);
-}
-
-void kh_finalize()
-{
-       // to be called before intermission
-       kh_FinishRound();
-       delete(kh_controller);
-       kh_controller = NULL;
-}
-
-// legacy bot role
-
-void(entity this) havocbot_role_kh_carrier;
-void(entity this) havocbot_role_kh_defense;
-void(entity this) havocbot_role_kh_offense;
-void(entity this) havocbot_role_kh_freelancer;
-
-
-void havocbot_goalrating_kh(entity this, float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
-{
-       entity head;
-       for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
-       {
-               if(head.owner == this)
-                       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 != this.team)
-                       {
-                               traceline(this.origin + this.view_ofs, head.origin, MOVE_NOMONSTERS, this);
-                               if (trace_fraction < 1 && trace_ent != head)
-                                       continue; // skip what I can't see
-                       }
-               }
-               if(!head.owner)
-                       navigation_routerating(this, head, ratingscale_dropped * 10000, 100000);
-               else if(head.team == this.team)
-                       navigation_routerating(this, head.owner, ratingscale_team * 10000, 100000);
-               else
-                       navigation_routerating(this, head.owner, ratingscale_enemy * 10000, 100000);
-       }
-
-       havocbot_goalrating_items(this, 1, this.origin, 10000);
-}
-
-void havocbot_role_kh_carrier(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (!(this.kh_next))
-       {
-               LOG_TRACE("changing role to freelancer");
-               this.havocbot_role = havocbot_role_kh_freelancer;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-
-               if(kh_Key_AllOwnedByWhichTeam() == this.team)
-                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // bring home
-               else
-                       havocbot_goalrating_kh(this, 4, 4, 1); // play defensively
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_kh_defense(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (this.kh_next)
-       {
-               LOG_TRACE("changing role to carrier");
-               this.havocbot_role = havocbot_role_kh_carrier;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + random() * 10 + 20;
-       if (time > this.havocbot_role_timeout)
-       {
-               LOG_TRACE("changing role to freelancer");
-               this.havocbot_role = havocbot_role_kh_freelancer;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               float key_owner_team;
-               navigation_goalrating_start(this);
-
-               key_owner_team = kh_Key_AllOwnedByWhichTeam();
-               if(key_owner_team == this.team)
-                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend key carriers
-               else if(key_owner_team == -1)
-                       havocbot_goalrating_kh(this, 4, 1, 0.1); // play defensively
-               else
-                       havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_kh_offense(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (this.kh_next)
-       {
-               LOG_TRACE("changing role to carrier");
-               this.havocbot_role = havocbot_role_kh_carrier;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + random() * 10 + 20;
-       if (time > this.havocbot_role_timeout)
-       {
-               LOG_TRACE("changing role to freelancer");
-               this.havocbot_role = havocbot_role_kh_freelancer;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               float key_owner_team;
-
-               navigation_goalrating_start(this);
-
-               key_owner_team = kh_Key_AllOwnedByWhichTeam();
-               if(key_owner_team == this.team)
-                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
-               else if(key_owner_team == -1)
-                       havocbot_goalrating_kh(this, 0.1, 1, 4); // play offensively
-               else
-                       havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK! EMERGENCY!
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void havocbot_role_kh_freelancer(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (this.kh_next)
-       {
-               LOG_TRACE("changing role to carrier");
-               this.havocbot_role = havocbot_role_kh_carrier;
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (!this.havocbot_role_timeout)
-               this.havocbot_role_timeout = time + random() * 10 + 10;
-       if (time > this.havocbot_role_timeout)
-       {
-               if (random() < 0.5)
-               {
-                       LOG_TRACE("changing role to offense");
-                       this.havocbot_role = havocbot_role_kh_offense;
-               }
-               else
-               {
-                       LOG_TRACE("changing role to defense");
-                       this.havocbot_role = havocbot_role_kh_defense;
-               }
-               this.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-
-               int key_owner_team = kh_Key_AllOwnedByWhichTeam();
-               if(key_owner_team == this.team)
-                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
-               else if(key_owner_team == -1)
-                       havocbot_goalrating_kh(this, 1, 10, 4); // prefer dropped keys
-               else
-                       havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-
-// register this as a mutator
-
-MUTATOR_HOOKFUNCTION(kh, ClientDisconnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       kh_Key_DropAll(player, true);
-}
-
-MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-
-       kh_Key_DropAll(player, true);
-}
-
-MUTATOR_HOOKFUNCTION(kh, PlayerDies)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-
-       if(frag_target == frag_attacker)
-               kh_Key_DropAll(frag_target, true);
-       else if(IS_PLAYER(frag_attacker))
-               kh_Key_DropAll(frag_target, false);
-       else
-               kh_Key_DropAll(frag_target, true);
-}
-
-MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST)
-{
-       entity frag_attacker = M_ARGV(0, entity);
-       entity frag_target = M_ARGV(1, entity);
-       float frag_score = M_ARGV(2, float);
-       M_ARGV(2, float) = kh_HandleFrags(frag_attacker, frag_target, frag_score);
-}
-
-MUTATOR_HOOKFUNCTION(kh, MatchEnd)
-{
-       kh_finalize();
-}
-
-MUTATOR_HOOKFUNCTION(kh, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
-       M_ARGV(0, float) = kh_teams;
-}
-
-MUTATOR_HOOKFUNCTION(kh, SpectateCopy)
-{
-       entity spectatee = M_ARGV(0, entity);
-       entity client = M_ARGV(1, entity);
-
-       STAT(KH_KEYS, client) = STAT(KH_KEYS, spectatee);
-}
-
-MUTATOR_HOOKFUNCTION(kh, PlayerUseKey)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(MUTATOR_RETURNVALUE == 0)
-       {
-               entity k = player.kh_next;
-               if(k)
-               {
-                       kh_Key_DropOne(k);
-                       return true;
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole)
-{
-    entity bot = M_ARGV(0, entity);
-
-       if(IS_DEAD(bot))
-               return true;
-
-       float r = random() * 3;
-       if (r < 1)
-               bot.havocbot_role = havocbot_role_kh_offense;
-       else if (r < 2)
-               bot.havocbot_role = havocbot_role_kh_defense;
-       else
-               bot.havocbot_role = havocbot_role_kh_freelancer;
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(kh, DropSpecialItems)
-{
-       entity frag_target = M_ARGV(0, entity);
-
-       kh_Key_DropAll(frag_target, false);
-}
-
-MUTATOR_HOOKFUNCTION(kh, reset_map_global)
-{
-       kh_WaitForPlayers(); // takes care of killing the "missing teams" message
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_keyhunt.qh b/qcsrc/server/mutators/mutator/gamemode_keyhunt.qh
deleted file mode 100644 (file)
index 77d7c06..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-#pragma once
-
-#include "../gamemode.qh"
-
-#define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit")
-int autocvar_g_keyhunt_point_leadlimit;
-bool autocvar_g_keyhunt_team_spawns;
-void kh_Initialize();
-
-REGISTER_MUTATOR(kh, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               GameRules_teams(true);
-        GameRules_spawning_teams(autocvar_g_keyhunt_team_spawns);
-        GameRules_limit_score(autocvar_g_keyhunt_point_limit);
-        GameRules_limit_lead(autocvar_g_keyhunt_point_leadlimit);
-
-               kh_Initialize();
-       }
-       return 0;
-}
-
-#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:
-bool kh_tracking_enabled;
-.entity kh_next;
-
-USING(kh_Think_t, void());
-void kh_StartRound();
-void kh_Controller_SetThink(float t, kh_Think_t func);
diff --git a/qcsrc/server/mutators/mutator/gamemode_lms.qc b/qcsrc/server/mutators/mutator/gamemode_lms.qc
deleted file mode 100644 (file)
index a57b2ae..0000000
+++ /dev/null
@@ -1,429 +0,0 @@
-#include "gamemode_lms.qh"
-
-#include <common/mutators/mutator/instagib/items.qh>
-#include <server/campaign.qh>
-#include <server/command/_mod.qh>
-
-int autocvar_g_lms_extra_lives;
-bool autocvar_g_lms_join_anytime;
-int autocvar_g_lms_last_join;
-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);
-}
-
-void ClearWinners();
-
-// LMS winning condition: game terminates if and only if there's at most one
-// one player who's living lives. Top two scores being equal cancels the time
-// limit.
-int WinningCondition_LMS()
-{
-       entity first_player = NULL;
-       int total_players = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               if (!total_players)
-                       first_player = it;
-               ++total_players;
-       });
-
-       if (total_players)
-       {
-               if (total_players > 1)
-               {
-                       // two or more active players - continue with the game
-
-                       if (autocvar_g_campaign)
-                       {
-                               FOREACH_CLIENT(IS_REAL_CLIENT(it), {
-                                       float pl_lives = GameRules_scoring_add(it, LMS_LIVES, 0);
-                                       if (!pl_lives)
-                                               return WINNING_YES; // human player lost, game over
-                                       break;
-                               });
-                       }
-               }
-               else
-               {
-                       // exactly one player?
-
-                       ClearWinners();
-                       SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
-
-                       if (LMS_NewPlayerLives())
-                       {
-                               // game still running (that is, nobody got removed from the game by a frag yet)? then continue
-                               return WINNING_NO;
-                       }
-                       else
-                       {
-                               // a winner!
-                               // and assign him his first place
-                               GameRules_scoring_add(first_player, LMS_RANK, 1);
-                               if(warmup_stage)
-                                       return WINNING_NO;
-                               else
-                                       return WINNING_YES;
-                       }
-               }
-       }
-       else
-       {
-               // nobody is playing at all...
-               if (LMS_NewPlayerLives())
-               {
-                       // wait for players...
-               }
-               else
-               {
-                       // SNAFU (maybe a draw game?)
-                       ClearWinners();
-                       LOG_TRACE("No players, ending game.");
-                       return WINNING_YES;
-               }
-       }
-
-       // When we get here, we have at least two players who are actually LIVING,
-       // now check if the top two players have equal score.
-       WinningConditionHelper(NULL);
-
-       ClearWinners();
-       if(WinningConditionHelper_winner)
-               WinningConditionHelper_winner.winning = true;
-       if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore)
-               return WINNING_NEVER;
-
-       // Top two have different scores? Way to go for our beloved TIMELIMIT!
-       return WINNING_NO;
-}
-
-// mutator hooks
-MUTATOR_HOOKFUNCTION(lms, reset_map_global)
-{
-       lms_lowest_lives = 999;
-}
-
-MUTATOR_HOOKFUNCTION(lms, reset_map_players)
-{
-       FOREACH_CLIENT(true, {
-               TRANSMUTE(Player, it);
-               it.frags = FRAGS_PLAYER;
-               GameRules_scoring_add(it, LMS_LIVES, LMS_NewPlayerLives());
-               PutClientInServer(it);
-       });
-}
-
-MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.frags == FRAGS_SPECTATOR)
-               TRANSMUTE(Observer, player);
-       else
-       {
-               float tl = GameRules_scoring_add(player, LMS_LIVES, 0);
-               if(tl < lms_lowest_lives)
-                       lms_lowest_lives = tl;
-               if(tl <= 0)
-                       TRANSMUTE(Observer, player);
-               if(warmup_stage)
-                       GameRules_scoring_add(player, LMS_RANK, -GameRules_scoring_add(player, LMS_RANK, 0));
-       }
-}
-
-MUTATOR_HOOKFUNCTION(lms, ForbidSpawn)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(warmup_stage)
-               return false;
-       if(player.frags == FRAGS_SPECTATOR)
-               return true;
-       if(GameRules_scoring_add(player, LMS_LIVES, 0) <= 0)
-       {
-               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES);
-               return true;
-       }
-       return false;
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerDies)
-{
-       entity frag_target = M_ARGV(2, entity);
-
-       frag_target.respawn_flags |= RESPAWN_FORCE;
-}
-
-void lms_RemovePlayer(entity player)
-{
-       static int quitters = 0;
-       float player_rank = GameRules_scoring_add(player, LMS_RANK, 0);
-       if (!player_rank)
-       {
-               int pl_cnt = 0;
-               FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
-               if (player.lms_spectate_warning != 2)
-               {
-                       if(IS_BOT_CLIENT(player))
-                               bot_clear(player);
-                       player.frags = FRAGS_LMS_LOSER;
-                       GameRules_scoring_add(player, LMS_RANK, pl_cnt + 1);
-               }
-               else
-               {
-                       lms_lowest_lives = 999;
-                       FOREACH_CLIENT(true, {
-                               if (it.frags == FRAGS_LMS_LOSER)
-                               {
-                                       float it_rank = GameRules_scoring_add(it, LMS_RANK, 0);
-                                       if (it_rank > player_rank && it_rank <= 256)
-                                               GameRules_scoring_add(it, LMS_RANK, -1);
-                                       lms_lowest_lives = 0;
-                               }
-                               else if (it.frags != FRAGS_SPECTATOR)
-                               {
-                                       float tl = GameRules_scoring_add(it, LMS_LIVES, 0);
-                                       if(tl < lms_lowest_lives)
-                                               lms_lowest_lives = tl;
-                               }
-                       });
-                       GameRules_scoring_add(player, LMS_RANK, 665 - quitters); // different from 666
-                       if(!warmup_stage)
-                       {
-                               GameRules_scoring_add(player, LMS_LIVES, -GameRules_scoring_add(player, LMS_LIVES, 0));
-                               ++quitters;
-                       }
-                       player.frags = FRAGS_LMS_LOSER;
-                       TRANSMUTE(Observer, player);
-               }
-               if (pl_cnt == 2 && !warmup_stage) // a player is forfeiting leaving only one player
-                       lms_lowest_lives = 0; // end the game now!
-       }
-
-       if(CS(player).killcount != FRAGS_SPECTATOR)
-               if(GameRules_scoring_add(player, LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
-                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
-               else
-                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       lms_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
-{
-    entity player = M_ARGV(0, entity);
-
-       lms_RemovePlayer(player);
-       return true;  // prevent team reset
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientConnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       TRANSMUTE(Player, player);
-       campaign_bots_may_start = true;
-
-       if(GameRules_scoring_add(player, LMS_LIVES, LMS_NewPlayerLives()) <= 0)
-       {
-               GameRules_scoring_add(player, LMS_RANK, 666); // mark as forced spectator for the hud code
-               player.frags = FRAGS_SPECTATOR;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(player.deadflag == DEAD_DYING)
-               player.deadflag = DEAD_RESPAWNING;
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
-{
-       if(autocvar_g_lms_regenerate)
-               return false;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
-{
-       // forbode!
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
-{
-       entity frag_target = M_ARGV(1, entity);
-
-       if (!warmup_stage)
-       {
-               // remove a life
-               int tl = GameRules_scoring_add(frag_target, LMS_LIVES, -1);
-               if(tl < lms_lowest_lives)
-                       lms_lowest_lives = tl;
-               if(tl <= 0)
-               {
-                       int pl_cnt = 0;
-                       FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
-                       if(IS_BOT_CLIENT(frag_target))
-                               bot_clear(frag_target);
-                       frag_target.frags = FRAGS_LMS_LOSER;
-                       GameRules_scoring_add(frag_target, LMS_RANK, pl_cnt);
-               }
-       }
-       M_ARGV(2, float) = 0; // frag score
-
-       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");
-}
-
-MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
-{
-       // don't clear player score
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, FilterItemDefinition)
-{
-       entity definition = M_ARGV(0, entity);
-
-       if (autocvar_g_lms_extra_lives && definition == ITEM_ExtraLife)
-       {
-               return false;
-       }
-       return true;
-}
-
-void lms_extralife(entity this)
-{
-       StartItem(this, ITEM_ExtraLife);
-}
-
-MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
-{
-       if (!autocvar_g_powerups) return false;
-       if (!autocvar_g_lms_extra_lives) return false;
-
-       entity ent = M_ARGV(0, entity);
-
-       // Can't use .itemdef here
-       if (ent.classname != "item_health_mega") return false;
-
-       entity e = spawn();
-       setthink(e, lms_extralife);
-
-       e.nextthink = time + 0.1;
-       e.spawnflags = ent.spawnflags;
-       e.noalign = ent.noalign;
-       setorigin(e, ent.origin);
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ItemTouch)
-{
-       entity item = M_ARGV(0, entity);
-       entity toucher = M_ARGV(1, entity);
-
-       if(item.itemdef == ITEM_ExtraLife)
-       {
-               Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
-               GameRules_scoring_add(toucher, LMS_LIVES, autocvar_g_lms_extra_lives);
-               return MUT_ITEMTOUCH_PICKUP;
-       }
-
-       return MUT_ITEMTOUCH_CONTINUE;
-}
-
-MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
-{
-       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
-               ++M_ARGV(0, int); // activerealplayers
-               ++M_ARGV(1, int); // realplayers
-       });
-
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
-{
-    entity player = M_ARGV(0, entity);
-
-       if(warmup_stage || player.lms_spectate_warning)
-       {
-               // for the forfeit message...
-               player.lms_spectate_warning = 2;
-       }
-       else
-       {
-               if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER)
-               {
-                       player.lms_spectate_warning = 1;
-                       sprint(player, "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)
-{
-       M_ARGV(0, float) = WinningCondition_LMS();
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, WantWeapon)
-{
-       M_ARGV(2, bool) = true; // all weapons
-}
-
-MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
-{
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
-{
-       if(game_stopped)
-       if(M_ARGV(0, entity) == SP_LMS_RANK) // score field
-               return true; // allow writing to this field in intermission as it is needed for newly joining players
-}
-
-void lms_Initialize()
-{
-       lms_lowest_lives = 9999;
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_lms.qh b/qcsrc/server/mutators/mutator/gamemode_lms.qh
deleted file mode 100644 (file)
index c69113a..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#pragma once
-
-#include "../gamemode.qh"
-
-.float lms_spectate_warning;
-#define autocvar_g_lms_lives_override cvar("g_lms_lives_override")
-void lms_Initialize();
-
-REGISTER_MUTATOR(lms, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-        GameRules_limit_score(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override));
-        GameRules_limit_lead(0);
-        GameRules_score_enabled(false);
-        GameRules_scoring(0, 0, 0, {
-            field(SP_LMS_LIVES, "lives", SFL_SORT_PRIO_SECONDARY);
-            field(SP_LMS_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
-        });
-
-               lms_Initialize();
-       }
-       return 0;
-}
-
-// lives related defs
-float lms_lowest_lives;
-float LMS_NewPlayerLives();
diff --git a/qcsrc/server/mutators/mutator/gamemode_race.qc b/qcsrc/server/mutators/mutator/gamemode_race.qc
deleted file mode 100644 (file)
index 65541fc..0000000
+++ /dev/null
@@ -1,489 +0,0 @@
-#include "gamemode_race.qh"
-
-#include <server/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(entity this)
-{
-       if(IS_DEAD(this))
-               return;
-
-       if (navigation_goalrating_timeout(this))
-       {
-               navigation_goalrating_start(this);
-
-               bool raw_touch_check = true;
-               int cp = this.race_checkpoint;
-
-               LABEL(search_racecheckpoints)
-               IL_EACH(g_racecheckpoints, true,
-               {
-                       if(it.cnt == cp || cp == -1)
-                       {
-                               // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
-                               // e.g. checkpoint in front of Stormkeep's warpzone
-                               // the same workaround is applied in CTS game mode
-                               if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
-                               {
-                                       cp = race_NextCheckpoint(cp);
-                                       raw_touch_check = false;
-                                       goto search_racecheckpoints;
-                               }
-                               navigation_routerating(this, it, 1000000, 5000);
-                       }
-               });
-
-               navigation_goalrating_end(this);
-
-               navigation_goalrating_timeout_set(this);
-       }
-}
-
-void race_ScoreRules()
-{
-    GameRules_score_enabled(false);
-       GameRules_scoring(race_teams, 0, 0, {
-        if (race_teams) {
-            field_team(ST_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
-            field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
-            field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-            field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
-        } else if (g_race_qualifying) {
-            field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-        } else {
-            field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
-            field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
-            field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
-        }
-       });
-}
-
-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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-float WinningCondition_Race(float fraglimit)
-{
-       float wc;
-       float n, c;
-
-       n = 0;
-       c = 0;
-       FOREACH_CLIENT(IS_PLAYER(it), {
-               ++n;
-               if(CS(it).race_completed)
-                       ++c;
-       });
-       if(n && (n == c))
-               return WINNING_YES;
-       wc = WinningCondition_Scores(fraglimit, 0);
-
-       // ALWAYS initiate overtime, unless EVERYONE has finished the race!
-       if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
-       // do NOT support equality when the laps are all raced!
-               return WINNING_STARTSUDDENDEATHOVERTIME;
-       else
-               return WINNING_NEVER;
-}
-
-float WinningCondition_QualifyingThenRace(float limit)
-{
-       float wc;
-       wc = WinningCondition_Scores(limit, 0);
-
-       // NEVER initiate overtime
-       if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
-       {
-               return WINNING_YES;
-       }
-
-       return wc;
-}
-
-MUTATOR_HOOKFUNCTION(rc, ClientKill)
-{
-       if(g_race_qualifying)
-               M_ARGV(1, float) = 0; // killtime
-}
-
-MUTATOR_HOOKFUNCTION(rc, AbortSpeedrun)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(autocvar_g_allow_checkpoints)
-               race_PreparePlayer(player); // nice try
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerPhysics)
-{
-       entity player = M_ARGV(0, entity);
-       float dt = M_ARGV(1, float);
-
-       player.race_movetime_frac += dt;
-       float f = floor(player.race_movetime_frac);
-       player.race_movetime_frac -= f;
-       player.race_movetime_count += f;
-       player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
-
-#ifdef SVQC
-       if(IS_PLAYER(player))
-       {
-               if (player.race_penalty)
-                       if (time > player.race_penalty)
-                               player.race_penalty = 0;
-               if(player.race_penalty)
-               {
-                       player.velocity = '0 0 0';
-                       set_movetype(player, MOVETYPE_NONE);
-                       player.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(CS(player).movement.x);
-       wishvel.y = fabs(CS(player).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(CS(player).movement.x > 0)
-                               CS(player).movement_x = wishspeed;
-                       else
-                               CS(player).movement_x = -wishspeed;
-                       CS(player).movement_y = 0;
-               }
-               else if(wishvel.y >= 2 * wishvel.x)
-               {
-                       // pure Y motion
-                       CS(player).movement_x = 0;
-                       if(CS(player).movement.y > 0)
-                               CS(player).movement_y = wishspeed;
-                       else
-                               CS(player).movement_y = -wishspeed;
-               }
-               else
-               {
-                       // diagonal
-                       if(CS(player).movement.x > 0)
-                               CS(player).movement_x = M_SQRT1_2 * wishspeed;
-                       else
-                               CS(player).movement_x = -M_SQRT1_2 * wishspeed;
-                       if(CS(player).movement.y > 0)
-                               CS(player).movement_y = M_SQRT1_2 * wishspeed;
-                       else
-                               CS(player).movement_y = -M_SQRT1_2 * wishspeed;
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(rc, reset_map_global)
-{
-       float s;
-
-       Score_NicePrint(NULL);
-
-       race_ClearRecords();
-       PlayerScore_Sort(race_place, 0, 1, 0);
-
-       FOREACH_CLIENT(true, {
-               if(it.race_place)
-               {
-                       s = GameRules_scoring_add(it, RACE_FASTEST, 0);
-                       if(!s)
-                               it.race_place = 0;
-               }
-               race_EventLog(ftos(it.race_place), it);
-       });
-
-       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();
-       }
-}
-
-MUTATOR_HOOKFUNCTION(rc, ClientConnect)
-{
-       entity player = M_ARGV(0, entity);
-
-       race_PreparePlayer(player);
-       player.race_checkpoint = -1;
-
-       string rr = RACE_RECORD;
-
-       if(IS_REAL_CLIENT(player))
-       {
-               msg_entity = player;
-               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;
-               int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
-               race_send_rankings_cnt(MSG_ONE);
-               for (i = 1; i <= m; ++i)
-               {
-                       race_SendRankings(i, 0, 0, MSG_ONE);
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(g_race_qualifying)
-       if(GameRules_scoring_add(player, RACE_FASTEST, 0))
-               player.frags = FRAGS_LMS_LOSER;
-       else
-               player.frags = FRAGS_SPECTATOR;
-
-       race_PreparePlayer(player);
-       player.race_checkpoint = -1;
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerSpawn)
-{
-       entity player = M_ARGV(0, entity);
-       entity spawn_spot = M_ARGV(1, entity);
-
-       if(spawn_spot.target == "")
-               // Emergency: this wasn't a real spawnpoint. Can this ever happen?
-               race_PreparePlayer(player);
-
-       // if we need to respawn, do it right
-       player.race_respawn_checkpoint = player.race_checkpoint;
-       player.race_respawn_spotref = spawn_spot;
-
-       player.race_place = 0;
-}
-
-MUTATOR_HOOKFUNCTION(rc, PutClientInServer)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(IS_PLAYER(player))
-       if(!game_stopped)
-       {
-               if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
-                       race_PreparePlayer(player);
-               else // respawn
-                       race_RetractPlayer(player);
-
-               race_AbandonRaceCheck(player);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerDies)
-{
-       entity frag_target = M_ARGV(2, entity);
-
-       frag_target.respawn_flags |= RESPAWN_FORCE;
-       race_AbandonRaceCheck(frag_target);
-}
-
-MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole)
-{
-       entity bot = M_ARGV(0, entity);
-
-       bot.havocbot_role = havocbot_role_race;
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(rc, GetPressedKeys)
-{
-       entity player = M_ARGV(0, entity);
-
-       if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
-       {
-               if (!player.stored_netname)
-                       player.stored_netname = strzone(uid2name(player.crypto_idfp));
-               if(player.stored_netname != player.netname)
-               {
-                       db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
-                       strcpy(player.stored_netname, player.netname);
-               }
-       }
-
-       if (!IS_OBSERVER(player))
-       {
-               if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
-               {
-                       speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
-                       speedaward_holder = player.netname;
-                       speedaward_uid = player.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);
-                       }
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear)
-{
-       if(g_race_qualifying)
-               return true; // in qualifying, you don't lose score by observing
-}
-
-MUTATOR_HOOKFUNCTION(rc, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
-       M_ARGV(0, float) = race_teams;
-}
-
-MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
-{
-       // announce remaining frags if not in qualifying mode
-       if(!g_race_qualifying)
-               return true;
-}
-
-MUTATOR_HOOKFUNCTION(rc, GetRecords)
-{
-       int record_page = M_ARGV(0, int);
-       string ret_string = M_ARGV(1, string);
-
-       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");
-               }
-       }
-
-       M_ARGV(1, string) = ret_string;
-}
-
-MUTATOR_HOOKFUNCTION(rc, HideTeamNagger)
-{
-       return true; // doesn't work so well
-}
-
-MUTATOR_HOOKFUNCTION(rc, FixClientCvars)
-{
-       entity player = M_ARGV(0, entity);
-
-       stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
-}
-
-MUTATOR_HOOKFUNCTION(rc, CheckRules_World)
-{
-       float checkrules_timelimit = M_ARGV(1, float);
-       float checkrules_fraglimit = M_ARGV(2, float);
-
-       if(checkrules_timelimit >= 0)
-       {
-               if(!g_race_qualifying)
-               {
-                       M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit);
-                       return true;
-               }
-               else if(g_race_qualifying == 2)
-               {
-                       M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
-                       return true;
-               }
-       }
-}
-
-MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
-{
-       if(g_race_qualifying == 2)
-               warmup_stage = 0;
-}
-
-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)
-       {
-               GameRules_teams(true);
-               race_teams = BITS(bound(2, autocvar_g_race_teams, 4));
-       }
-       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 = autocvar_timelimit_override;
-
-       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(want_qualifying)
-       {
-               g_race_qualifying = 2;
-               independent_players = 1;
-               race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit;
-               race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit;
-               race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit;
-               qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit;
-               fraglimit_override = 0;
-               leadlimit_override = 0;
-               timelimit_override = qualifying_override;
-       }
-       else
-               g_race_qualifying = 0;
-    GameRules_limit_score(fraglimit_override);
-    GameRules_limit_lead(leadlimit_override);
-    GameRules_limit_time(timelimit_override);
-    GameRules_limit_time_qualifying(qualifying_override);
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_race.qh b/qcsrc/server/mutators/mutator/gamemode_race.qh
deleted file mode 100644 (file)
index 1e475e3..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#pragma once
-
-#include "../gamemode.qh"
-
-void rc_SetLimits();
-void race_Initialize();
-
-REGISTER_MUTATOR(rc, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               rc_SetLimits();
-
-               race_Initialize();
-       }
-       return 0;
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_tdm.qc b/qcsrc/server/mutators/mutator/gamemode_tdm.qc
deleted file mode 100644 (file)
index aad3193..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-#include "gamemode_tdm.qh"
-
-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 || !this.cnt) { delete(this); return; }
-
-       this.classname = "tdm_team";
-       this.team = this.cnt + 1;
-}
-
-// code from here on is just to support maps that don't have team entities
-void tdm_SpawnTeam (string teamname, int teamcolor)
-{
-       entity this = new_pure(tdm_team);
-       this.netname = teamname;
-       this.cnt = teamcolor - 1;
-       this.team = teamcolor;
-       this.spawnfunc_checked = true;
-       //spawnfunc_tdm_team(this);
-}
-
-void tdm_DelayedInit(entity this)
-{
-       // if no teams are found, spawn defaults
-       if(find(NULL, classname, "tdm_team") == NULL)
-       {
-               LOG_TRACE("No \"tdm_team\" entities found on this map, creating them anyway.");
-
-               int numteams = autocvar_g_tdm_teams_override;
-               if(numteams < 2) { numteams = autocvar_g_tdm_teams; }
-
-               int teams = BITS(bound(2, numteams, 4));
-               if(teams & BIT(0))
-                       tdm_SpawnTeam("Red", NUM_TEAM_1);
-               if(teams & BIT(1))
-                       tdm_SpawnTeam("Blue", NUM_TEAM_2);
-               if(teams & BIT(2))
-                       tdm_SpawnTeam("Yellow", NUM_TEAM_3);
-               if(teams & BIT(3))
-                       tdm_SpawnTeam("Pink", NUM_TEAM_4);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(tdm, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
-       M_ARGV(1, string) = "tdm_team";
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining)
-{
-       // announce remaining frags
-       return true;
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_tdm.qh b/qcsrc/server/mutators/mutator/gamemode_tdm.qh
deleted file mode 100644 (file)
index c163962..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-
-#include "../gamemode.qh"
-
-int autocvar_g_tdm_point_limit;
-int autocvar_g_tdm_point_leadlimit;
-bool autocvar_g_tdm_team_spawns;
-void tdm_DelayedInit(entity this);
-
-REGISTER_MUTATOR(tdm, false)
-{
-    MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               GameRules_teams(true);
-        GameRules_spawning_teams(autocvar_g_tdm_team_spawns);
-               GameRules_limit_score(autocvar_g_tdm_point_limit);
-        GameRules_limit_lead(autocvar_g_tdm_point_leadlimit);
-
-               InitializeEntity(NULL, tdm_DelayedInit, INITPRIO_GAMETYPE);
-       }
-       return 0;
-}
index b84ae6414e20ba637d8f81c86bf946040bf37cb5..13dbda5200b7fbbb48a7329a8d4b1407a4922892 100644 (file)
@@ -8,9 +8,7 @@ MODEL(SQUARE_BAD,   "models/pathlib/badsquare.md3");
 MODEL(EDGE,         "models/pathlib/edge.md3");
 
 #ifdef TURRET_DEBUG
-void mark_error(vector where,float lifetime);
-void mark_info(vector where,float lifetime);
-entity mark_misc(vector where,float lifetime);
+#include <common/turrets/util.qh>
 #endif
 
 void pathlib_showpath(entity start)
index 811c031aff3d968a056e6dffa974179a409ce321..6f1bd989502c7b3ac086655f74a6f5806d3fb33f 100644 (file)
@@ -1,2 +1,8 @@
 #pragma once
 #include "pathlib.qh"
+
+#if DEBUGPATHING
+void pathlib_showpath(entity start);
+void pathlib_showpath2(entity path);
+void pathlib_showsquare(vector where,float goodsquare,float _lifetime);
+#endif
index c2f33260302c7899dbba85212c3df2eeece69172..1aeae109c34610f31e05c6a6e174f684d2223e9f 100644 (file)
@@ -30,9 +30,7 @@ void dumpnode(entity n)
 }
 
 #if DEBUGPATHING
-void pathlib_showpath(entity start);
-void pathlib_showpath2(entity path);
-void pathlib_showsquare(vector where,float goodsquare,float _lifetime);
+#include "debug.qh"
 #endif
 
 
index 21ef8b3cbc5286f50a89ca72dde12a3454c7f7a1..d1bafe392a892e2fae67b165eb9c8b32d1a41a9a 100644 (file)
@@ -15,11 +15,6 @@ const vector PLIB_FORWARD = '0 1 0';
 const vector PLIB_RIGHT = '1 0 0';
 //#define PLIB_LEFT    '-1 0 0'
 
-#if DEBUGPATHING
-void pathlib_showpath(entity start);
-void pathlib_showpath2(entity path);
-#endif
-
 entity openlist;
 entity closedlist;
 
index fd7bad4f8d7616c4918d763cb91a60f16a71c363..cc58cdc6d7ca7e2bc493b9c2bd1518a3aaa3bbe2 100644 (file)
@@ -5,7 +5,6 @@
 #include "cheats.qh"
 #include "g_damage.qh"
 #include "handicap.qh"
-#include "g_subs.qh"
 #include "miscfunctions.qh"
 #include "portals.qh"
 #include "teamplay.qh"
@@ -15,6 +14,7 @@
 #include "../common/anim.qh"
 #include "../common/animdecide.qh"
 #include "../common/csqcmodel_settings.qh"
+#include "../common/gamemodes/sv_rules.qh"
 #include "../common/deathtypes/all.qh"
 #include "../common/mapobjects/subs.qh"
 #include "../common/playerstats.qh"
@@ -74,6 +74,7 @@ void CopyBody(entity this, float keepvelocity)
        clone.effects = this.effects;
        clone.glowmod = this.glowmod;
        clone.event_damage = this.event_damage;
+       clone.event_heal = this.event_heal;
        clone.anim_state = this.anim_state;
        clone.anim_time = this.anim_time;
        clone.anim_lower_action = this.anim_lower_action;
@@ -89,8 +90,8 @@ void CopyBody(entity this, float keepvelocity)
        clone.dphitcontentsmask = this.dphitcontentsmask;
        clone.death_time = this.death_time;
        clone.pain_finished = this.pain_finished;
-       clone.health = this.health;
-       clone.armorvalue = this.armorvalue;
+       SetResourceAmountExplicit(clone, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH));
+       SetResourceAmountExplicit(clone, RESOURCE_ARMOR, GetResourceAmount(this, RESOURCE_ARMOR));
        clone.armortype = this.armortype;
        clone.model = this.model;
        clone.modelindex = this.modelindex;
@@ -173,7 +174,7 @@ void PlayerCorpseDamage(entity this, entity inflictor, entity attacker, float da
        vector v;
        Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
 
-       v = healtharmor_applydamage(this.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
+       v = healtharmor_applydamage(GetResourceAmount(this, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
        take = v.x;
        save = v.y;
 
@@ -192,8 +193,8 @@ void PlayerCorpseDamage(entity this, entity inflictor, entity attacker, float da
        if (take > 100)
                Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker);
 
-       this.armorvalue = this.armorvalue - save;
-       this.health = this.health - take;
+       TakeResource(this, RESOURCE_ARMOR, save);
+       TakeResource(this, RESOURCE_HEALTH, take);
        // pause regeneration for 5 seconds
        this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen);
 
@@ -201,7 +202,7 @@ void PlayerCorpseDamage(entity this, entity inflictor, entity attacker, float da
        this.dmg_take = this.dmg_take + take;//max(take - 10, 0);
        this.dmg_inflictor = inflictor;
 
-       if (this.health <= -autocvar_sv_gibhealth && this.alpha >= 0)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) <= -autocvar_sv_gibhealth && this.alpha >= 0)
        {
                // don't use any animations as a gib
                this.frame = 0;
@@ -310,8 +311,8 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
        vector v;
        float excess;
 
-       dh = max(this.health, 0);
-       da = max(this.armorvalue, 0);
+       dh = max(GetResourceAmount(this, RESOURCE_HEALTH), 0);
+       da = max(GetResourceAmount(this, RESOURCE_ARMOR), 0);
 
        if(!DEATH_ISSPECIAL(deathtype))
        {
@@ -359,7 +360,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
        else
                Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
 
-       v = healtharmor_applydamage(this.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
+       v = healtharmor_applydamage(GetResourceAmount(this, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
        take = v.x;
        save = v.y;
 
@@ -388,8 +389,8 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
        }
 
        MUTATOR_CALLHOOK(PlayerDamage_SplitHealthArmor, inflictor, attacker, this, force, take, save, deathtype, damage);
-       take = bound(0, M_ARGV(4, float), this.health);
-       save = bound(0, M_ARGV(5, float), this.armorvalue);
+       take = bound(0, M_ARGV(4, float), GetResourceAmount(this, RESOURCE_HEALTH));
+       save = bound(0, M_ARGV(5, float), GetResourceAmount(this, RESOURCE_ARMOR));
        excess = max(0, damage - take - save);
 
        if(sound_allowed(MSG_BROADCAST, attacker))
@@ -411,8 +412,8 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
        {
                if (!(this.flags & FL_GODMODE))
                {
-                       this.armorvalue = this.armorvalue - save;
-                       this.health = this.health - take;
+                       TakeResource(this, RESOURCE_ARMOR, save);
+                       TakeResource(this, RESOURCE_HEALTH, take);
                        // pause regeneration for 5 seconds
                        if(take)
                                this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen);
@@ -432,19 +433,19 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
                                                                animdecide_setaction(this, ANIMACTION_PAIN2, true);
                                                }
                                        }
-
+                                       float myhp = GetResourceAmount(this, RESOURCE_HEALTH);
+                                       if(myhp > 1)
+                                       if(myhp < 25 || !(DEATH_WEAPONOF(deathtype).spawnflags & WEP_FLAG_CANCLIMB) || take > 20 || attacker != this)
                                        if(sound_allowed(MSG_BROADCAST, attacker))
-                                       if(this.health < 25 || !(DEATH_WEAPONOF(deathtype).spawnflags & WEP_FLAG_CANCLIMB) || take > 20 || attacker != this)
-                                       if(this.health > 1)
                                        // exclude pain sounds for laserjumps as long as you aren't REALLY low on health and would die of the next two
                                        {
                                                if(deathtype == DEATH_FALL.m_id)
                                                        PlayerSound(this, playersound_fall, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
-                                               else if(this.health > 75)
+                                               else if(myhp > 75)
                                                        PlayerSound(this, playersound_pain100, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
-                                               else if(this.health > 50)
+                                               else if(myhp > 50)
                                                        PlayerSound(this, playersound_pain75, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
-                                               else if(this.health > 25)
+                                               else if(myhp > 25)
                                                        PlayerSound(this, playersound_pain50, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
                                                else
                                                        PlayerSound(this, playersound_pain25, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
@@ -454,7 +455,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
 
                        // throw off bot aim temporarily
                        float shake;
-                       if(IS_BOT_CLIENT(this) && this.health >= 1)
+                       if(IS_BOT_CLIENT(this) && GetResourceAmount(this, RESOURCE_HEALTH) >= 1)
                        {
                                shake = damage * 5 / (bound(0,skill,100) + 1);
                                this.v_angle_x = this.v_angle.x + (random() * 2 - 1) * shake;
@@ -497,8 +498,8 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
                valid_damage_for_weaponstats = true;
        }
 
-       dh = dh - max(this.health, 0);
-       da = da - max(this.armorvalue, 0);
+       dh = dh - max(GetResourceAmount(this, RESOURCE_HEALTH), 0);
+       da = da - max(GetResourceAmount(this, RESOURCE_ARMOR), 0);
        if(valid_damage_for_weaponstats)
        {
                WeaponStats_LogDamage(awep.m_id, abot, this.(weaponentity).m_weapon.m_id, vbot, dh + da);
@@ -506,7 +507,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
 
        MUTATOR_CALLHOOK(PlayerDamaged, attacker, this, dh, da, hitloc, deathtype, damage);
 
-       if (this.health < 1)
+       if (GetResourceAmount(this, RESOURCE_HEALTH) < 1)
        {
                float defer_ClientKill_Now_TeamChange;
                defer_ClientKill_Now_TeamChange = false;
@@ -580,7 +581,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
 
                // player could have been miraculously resuscitated ;)
                // e.g. players in freezetag get frozen, they don't really die
-               if(this.health >= 1 || !(IS_PLAYER(this) || this.classname == "body"))
+               if(GetResourceAmount(this, RESOURCE_HEALTH) >= 1 || !(IS_PLAYER(this) || this.classname == "body"))
                        return;
 
                if (!this.respawn_time) // can be set in the mutator hook PlayerDies
@@ -589,7 +590,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
                // when we get here, player actually dies
 
                Unfreeze(this); // remove any icy remains
-               this.health = 0; // Unfreeze resets health, so we need to set it back
+               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // Unfreeze resets health, so we need to set it back
 
                // clear waypoints
                WaypointSprite_PlayerDead(this);
@@ -629,6 +630,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
 
                // set damage function to corpse damage
                this.event_damage = PlayerCorpseDamage;
+               this.event_heal = func_null;
                // call the corpse damage function just in case it wants to gib
                this.event_damage(this, inflictor, attacker, excess, deathtype, weaponentity, hitloc, force);
 
@@ -664,6 +666,15 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
        }
 }
 
+bool PlayerHeal(entity targ, entity inflictor, float amount, float limit)
+{
+       if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= limit)
+               return false;
+
+       GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, limit);
+       return true;
+}
+
 bool MoveToTeam(entity client, int team_colour, int type)
 {
        int lockteams_backup = lockteams;  // backup any team lock
@@ -679,60 +690,6 @@ bool MoveToTeam(entity client, int team_colour, int type)
        return true;
 }
 
-/** print(), but only print if the server is not local */
-void dedicated_print(string input)
-{
-       if (server_is_dedicated) print(input);
-}
-
-void PrintToChat(entity player, string text)
-{
-       text = strcat("\{1}^7", text, "\n");
-       sprint(player, text);
-}
-
-void DebugPrintToChat(entity player, string text)
-{
-       if (autocvar_developer)
-       {
-               PrintToChat(player, text);
-       }
-}
-
-void PrintToChatAll(string text)
-{
-       text = strcat("\{1}^7", text, "\n");
-       bprint(text);
-}
-
-void DebugPrintToChatAll(string text)
-{
-       if (autocvar_developer)
-       {
-               PrintToChatAll(text);
-       }
-}
-
-void PrintToChatTeam(int teamnum, string text)
-{
-       text = strcat("\{1}^7", text, "\n");
-       FOREACH_CLIENT(IS_REAL_CLIENT(it),
-       {
-               if (it.team == teamnum)
-               {
-                       sprint(it, text);
-               }
-       });
-}
-
-void DebugPrintToChatTeam(int teamnum, string text)
-{
-       if (autocvar_developer)
-       {
-               PrintToChatTeam(teamnum, text);
-       }
-}
-
 /**
  * message "": do not say, just test flood control
  * return value:
index ee073ccb4498a960b757409fd055cec5d3c8062d..8c9bac9b62aaa8e94ea4a594e33f6b680f67d74c 100644 (file)
@@ -9,45 +9,6 @@
 void CopyBody_Think(entity this);
 void CopyBody(entity this, float keepvelocity);
 
-void dedicated_print(string input);
-
-/// \brief Print the string to player's chat.
-/// \param[in] player Player to print to.
-/// \param[in] text Text to print.
-/// \return No return.
-void PrintToChat(entity player, string text);
-
-/// \brief Print the string to player's chat if the server cvar "developer" is
-/// not 0.
-/// \param[in] player Player to print to.
-/// \param[in] text Text to print.
-/// \return No return.
-void DebugPrintToChat(entity player, string text);
-
-/// \brief Prints the string to all players' chat.
-/// \param[in] text Text to print.
-/// \return No return.
-void PrintToChatAll(string text);
-
-/// \brief Prints the string to all players' chat if the server cvar "developer"
-/// is not 0.
-/// \param[in] text Text to print.
-/// \return No return.
-void DebugPrintToChatAll(string text);
-
-/// \brief Print the string to chat of all players of the specified team.
-/// \param[in] teamnum Team to print to. See NUM_TEAM constants.
-/// \param[in] text Text to print.
-/// \return No return.
-void PrintToChatTeam(int teamnum, string text);
-
-/// \brief Print the string to chat of all players of the specified team if the
-/// server cvar "developer" is not 0.
-/// \param[in] teamnum Team to print to. See NUM_TEAM constants.
-/// \param[in] text Text to print.
-/// \return No return.
-void DebugPrintToChatTeam(int teamnum, string text);
-
 void player_setupanimsformodel(entity this);
 
 void player_anim(entity this);
@@ -78,4 +39,6 @@ bool MoveToTeam(entity client, float team_colour, float type);
 
 void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force);
 
+bool PlayerHeal(entity targ, entity inflictor, float amount, float limit);
+
 int Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol);
index 758c69bccd4c2f03e3347fca67b367cf6c1087d1..162026a169e98843d259e2717228984fb06905b8 100644 (file)
@@ -199,8 +199,8 @@ float Portal_TeleportPlayer(entity teleporter, entity player)
        // reset fade counter
        teleporter.portal_wants_to_vanish = 0;
        teleporter.fade_time = time + autocvar_g_balance_portal_lifetime;
-       teleporter.health = autocvar_g_balance_portal_health;
-       teleporter.enemy.health = autocvar_g_balance_portal_health;
+       SetResourceAmountExplicit(teleporter, RESOURCE_HEALTH, autocvar_g_balance_portal_health);
+       SetResourceAmountExplicit(teleporter.enemy, RESOURCE_HEALTH, autocvar_g_balance_portal_health);
 
        return 1;
 }
@@ -323,7 +323,6 @@ void Portal_Touch(entity this, entity toucher)
                                toucher.effects += EF_BLUE - EF_RED;
 }
 
-void Portal_Think(entity this);
 void Portal_MakeBrokenPortal(entity portal)
 {
        portal.skin = 2;
@@ -436,8 +435,8 @@ void Portal_Damage(entity this, entity inflictor, entity attacker, float damage,
        if(attacker != this.aiment)
                if(IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(this.aiment))
                        return;
-       this.health -= damage;
-       if(this.health < 0)
+       TakeResource(this, RESOURCE_HEALTH, damage);
+       if(GetResourceAmount(this, RESOURCE_HEALTH) < 0)
                Portal_Remove(this, 1);
 }
 
@@ -640,7 +639,7 @@ entity Portal_Spawn(entity own, vector org, vector ang)
        portal.takedamage = DAMAGE_AIM;
        portal.event_damage = Portal_Damage;
        portal.fade_time = time + autocvar_g_balance_portal_lifetime;
-       portal.health = autocvar_g_balance_portal_health;
+       SetResourceAmountExplicit(portal, RESOURCE_HEALTH, autocvar_g_balance_portal_health);
        setmodel(portal, MDL_PORTAL);
        portal.savemodelindex = portal.modelindex;
        setcefc(portal, Portal_Customize);
index f3528d081072ce7e4314a5ca80b4a6a03bdff8ee..20a2bd930c6f23b4e40b76159943f9fb6d12d24f 100644 (file)
@@ -11,3 +11,5 @@ void Portal_ClearWithID(entity own, float id);
 
 vector Portal_ApplyTransformToPlayerAngle(vector transform, vector vangle);
 void Portal_ClearAll_PortalsOnly(entity own);
+
+void Portal_Think(entity this);
index c1b272ceaffe360c9ee4b617bcc15761a28b0c8a..5286032fb5715900836945008816d06f2e987241 100644 (file)
@@ -14,7 +14,9 @@
 #include <common/gamemodes/rules.qh>
 #include <common/net_linked.qh>
 #include <common/state.qh>
+#include <common/weapons/weapon/porto.qh>
 #include "../common/mapobjects/subs.qh"
+#include <common/mapobjects/triggers.qh>
 #include "../lib/warpzone/util_server.qh"
 #include "../lib/warpzone/common.qh"
 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
@@ -29,8 +31,6 @@ void race_InitSpectator()
                        race_SendNextCheckpoint(msg_entity.enemy, 1);
 }
 
-void W_Porto_Fail(entity this, float failhard);
-
 float race_readTime(string map, float pos)
 {
        string rr = ((g_cts) ? CTS_RECORD : ((g_ctf) ? CTF_RECORD : RACE_RECORD));
@@ -104,8 +104,6 @@ string race_readName(string map, float pos)
 
 const float MAX_CHECKPOINTS = 255;
 
-spawnfunc(target_checkpoint);
-
 .float race_penalty;
 .float race_penalty_accumulator;
 .string race_penalty_reason;
index 32edca781768165a32e3309433c9e8774e9c238f..4402e22568ce1f027c26f93e6cba1a0e339f278c 100644 (file)
@@ -70,3 +70,5 @@ void race_SendRankings(float pos, float prevpos, float del, float msg);
 void race_RetractPlayer(entity this);
 
 void race_InitSpectator();
+
+spawnfunc(target_checkpoint);
index 4ad66bb98ed2169380e7074ea23147a503e652cf..b3b19095ae8b14e7832b386d1f5614c891a3d93f 100644 (file)
@@ -10,6 +10,9 @@
 
 float GetResourceLimit(entity e, int resource_type)
 {
+       if(!IS_PLAYER(e))
+               return RESOURCE_LIMIT_NONE; // no limits on non-players
+
        float limit;
        switch (resource_type)
        {
@@ -74,6 +77,17 @@ float GetResourceAmount(entity e, int resource_type)
        return e.(resource_field);
 }
 
+bool SetResourceAmountExplicit(entity e, int resource_type, float amount)
+{
+       .float resource_field = GetResourceField(resource_type);
+       if (e.(resource_field) != amount)
+       {
+               e.(resource_field) = amount;
+               return true;
+       }
+       return false;
+}
+
 void SetResourceAmount(entity e, int resource_type, float amount)
 {
        bool forbid = MUTATOR_CALLHOOK(SetResourceAmount, e, resource_type, amount);
@@ -83,17 +97,16 @@ void SetResourceAmount(entity e, int resource_type, float amount)
        }
        resource_type = M_ARGV(1, int);
        amount = M_ARGV(2, float);
-       float max_amount = GetResourceLimit(e, resource_type);
+       float max_amount = GetResourceLimit(e, resource_type); // TODO: should allow overriding these limits if cheats are enabled!
        float amount_wasted = 0;
-       if (amount > max_amount)
+       if (amount > max_amount && max_amount != RESOURCE_LIMIT_NONE)
        {
                amount_wasted = amount - max_amount;
                amount = max_amount;
        }
-       .float resource_field = GetResourceField(resource_type);
-       if (e.(resource_field) != amount)
+       bool changed = SetResourceAmountExplicit(e, resource_type, amount);
+       if (changed)
        {
-               e.(resource_field) = amount;
                MUTATOR_CALLHOOK(ResourceAmountChanged, e, resource_type, amount);
        }
        if (amount_wasted == 0)
@@ -105,7 +118,7 @@ void SetResourceAmount(entity e, int resource_type, float amount)
 
 void GiveResource(entity receiver, int resource_type, float amount)
 {
-       if (amount == 0)
+       if (amount <= 0)
        {
                return;
        }
@@ -151,7 +164,7 @@ void GiveResource(entity receiver, int resource_type, float amount)
 void GiveResourceWithLimit(entity receiver, int resource_type, float amount,
        float limit)
 {
-       if (amount == 0)
+       if (amount <= 0)
        {
                return;
        }
@@ -164,18 +177,68 @@ void GiveResourceWithLimit(entity receiver, int resource_type, float amount,
        resource_type = M_ARGV(1, int);
        amount = M_ARGV(2, float);
        limit = M_ARGV(3, float);
-       if (amount == 0)
+       if (amount <= 0)
        {
                return;
        }
        float current_amount = GetResourceAmount(receiver, resource_type);
-       if (current_amount + amount > limit)
+       if (current_amount + amount > limit && limit != RESOURCE_LIMIT_NONE)
        {
                amount = limit - current_amount;
        }
        GiveResource(receiver, resource_type, amount);
 }
 
+void TakeResource(entity receiver, int resource_type, float amount)
+{
+       if (amount <= 0)
+       {
+               return;
+       }
+       bool forbid = MUTATOR_CALLHOOK(TakeResource, receiver, resource_type,
+               amount);
+       if (forbid)
+       {
+               return;
+       }
+       resource_type = M_ARGV(1, int);
+       amount = M_ARGV(2, float);
+       if (amount <= 0)
+       {
+               return;
+       }
+       SetResourceAmount(receiver, resource_type,
+               GetResourceAmount(receiver, resource_type) - amount);
+}
+
+void TakeResourceWithLimit(entity receiver, int resource_type, float amount,
+       float limit)
+{
+       if (amount <= 0)
+       {
+               return;
+       }
+       bool forbid = MUTATOR_CALLHOOK(TakeResourceWithLimit, receiver,
+               resource_type, amount, limit);
+       if (forbid)
+       {
+               return;
+       }
+       resource_type = M_ARGV(1, int);
+       amount = M_ARGV(2, float);
+       limit = M_ARGV(3, float);
+       if (amount <= 0)
+       {
+               return;
+       }
+       float current_amount = GetResourceAmount(receiver, resource_type);
+       if (current_amount - amount < limit)
+       {
+               amount = limit + current_amount;
+       }
+       TakeResource(receiver, resource_type, amount);
+}
+
 int GetResourceType(.float resource_field)
 {
        switch (resource_field)
index 6ff3cea67917973eab045526b76d0c6d97b05890..e4631ab5605ad30d7953826cdaddf6ef78ea7bd7 100644 (file)
@@ -7,9 +7,6 @@
 
 #include <common/resources.qh>
 
-/// \brief Unconditional maximum amount of resources the entity can have.
-const int RESOURCE_AMOUNT_HARD_LIMIT = 999;
-
 // ============================ Public API ====================================
 
 /// \brief Returns the maximum amount of the given resource.
@@ -24,6 +21,13 @@ float GetResourceLimit(entity e, int resource_type);
 /// \return Current amount of resource the given entity has.
 float GetResourceAmount(entity e, int resource_type);
 
+/// \brief Sets the resource amount of an entity without calling any hooks.
+/// \param[in,out] e Entity to adjust.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to set.
+/// \return Boolean for whether the ammo amount was changed
+bool SetResourceAmountExplicit(entity e, int resource_type, float amount);
+
 /// \brief Sets the current amount of resource the given entity will have.
 /// \param[in,out] e Entity to adjust.
 /// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
@@ -47,6 +51,22 @@ void GiveResource(entity receiver, int resource_type, float amount);
 void GiveResourceWithLimit(entity receiver, int resource_type, float amount,
        float limit);
 
+/// \brief Takes an entity some resource.
+/// \param[in,out] receiver Entity to take resource from.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to take.
+/// \return No return.
+void TakeResource(entity receiver, int resource_type, float amount);
+
+/// \brief Takes an entity some resource but not less than a limit.
+/// \param[in,out] receiver Entity to take resource from.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to take.
+/// \param[in] limit Limit of resources to take.
+/// \return No return.
+void TakeResourceWithLimit(entity receiver, int resource_type, float amount,
+       float limit);
+
 // ===================== Legacy and/or internal API ===========================
 
 /// \brief Converts an entity field to resource type.
index 67c115c8a56ff7dfc2135e973c693f6476762efa..2cb40a83492d88f454622f2c6dc9a6a47b7a7d15 100644 (file)
@@ -1,11 +1,18 @@
 #include "scores.qh"
 
 #include "command/common.qh"
-#include "mutators/_mod.qh"
+#include "defs.qh"
+#include <server/g_world.qh>
+#include <server/miscfunctions.qh>
+#include <server/mutators/_mod.qh>
 #include <common/net_linked.qh>
 #include "../common/playerstats.qh"
 #include "../common/teams.qh"
+#include <common/mapinfo.qh>
+#include <common/mutators/base.qh>
 #include <common/scores.qh>
+#include <common/state.qh>
+#include <common/stats.qh>
 
 .entity scorekeeper;
 entity teamscorekeepers[16];
index 64c94001fbc5bcd9ab42caffd6d8c86604368a32..160b5df5e4cfb21c279b96c39d28dbe6965b27d4 100644 (file)
@@ -9,8 +9,6 @@
 
 int ScoreRules_teams;
 
-void CheckAllowedTeams (entity for_whom);
-
 int NumTeams(int teams)
 {
        return boolean(teams & BIT(0)) + boolean(teams & BIT(1)) + boolean(teams & BIT(2)) + boolean(teams & BIT(3));
index 3a6d36d31695afaa7102e557d5639960f61e031c..dcf6016c5e326b902e8c59f06519ab86215ee707 100644 (file)
@@ -1,8 +1,9 @@
 #include "spawnpoints.qh"
 
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
 #include "g_world.qh"
 #include "race.qh"
+#include "defs.qh"
 #include "../common/constants.qh"
 #include <common/net_linked.qh>
 #include "../common/teams.qh"
@@ -11,6 +12,7 @@
 #include "../common/util.qh"
 #include "../lib/warpzone/common.qh"
 #include "../lib/warpzone/util_server.qh"
+#include <server/utils.qh>
 
 bool SpawnPoint_Send(entity this, entity to, int sf)
 {
index 0a4dd6095ee915f4c846b4d46162792b49849a19..92c918f00ca2281d360282312cfc3b3b9e1fc5df 100644 (file)
@@ -29,11 +29,8 @@ vector steerlib_push(entity this, vector point)
 **/
 vector steerlib_arrive(entity this, vector point, float maximal_distance)
 {
-    float distance;
-    vector direction;
-
-    distance = bound(0.001,vlen(this.origin - point),maximal_distance);
-    direction = normalize(point - this.origin);
+    float distance = bound(0.001,vlen(this.origin - point),maximal_distance);
+    vector direction = normalize(point - this.origin);
     return  direction * (distance / maximal_distance);
 }
 
@@ -42,25 +39,18 @@ vector steerlib_arrive(entity this, vector point, float maximal_distance)
 **/
 vector steerlib_attract(entity this, vector point, float maximal_distance)
 {
-    float distance;
-    vector direction;
-
-    distance = bound(0.001,vlen(this.origin - point),maximal_distance);
-    direction = normalize(point - this.origin);
+    float distance = bound(0.001,vlen(this.origin - point),maximal_distance);
+    vector direction = normalize(point - this.origin);
 
     return  direction * (1-(distance / maximal_distance));
 }
 
 vector steerlib_attract2(entity this, vector point, float min_influense,float max_distance,float max_influense)
 {
-    float distance;
-    vector direction;
-    float influense;
-
-    distance  = bound(0.00001,vlen(this.origin - point),max_distance);
-    direction = normalize(point - this.origin);
+    float distance  = bound(0.00001,vlen(this.origin - point),max_distance);
+    vector direction = normalize(point - this.origin);
 
-    influense = 1 - (distance / max_distance);
+    float influense = 1 - (distance / max_distance);
     influense = min_influense + (influense * (max_influense - min_influense));
 
     return  direction * influense;
index 487ab1fc533821590ab49c18fdeffb7cf24dd686..539b30d294ce0c68e43e80d81f9b0d7732353199 100644 (file)
@@ -2,14 +2,16 @@
 
 #include "anticheat.qh"
 #include "g_hook.qh"
+#include "g_damage.qh"
 #include "g_world.qh"
 
 #include "bot/api.qh"
 
 #include "command/common.qh"
 
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
 #include "weapons/csqcprojectile.qh"
+#include <server/compat/quake3.qh>
 
 #include "../common/constants.qh"
 #include "../common/deathtypes/all.qh"
@@ -18,6 +20,7 @@
 #include "../common/util.qh"
 
 #include "../common/vehicles/all.qh"
+#include <common/monsters/sv_monsters.qh>
 #include <common/weapons/_all.qh>
 
 #include "../lib/csqcmodel/sv_model.qh"
@@ -165,7 +168,6 @@ Called before each frame by the server
 bool game_delay_last;
 
 bool autocvar_sv_autopause = false;
-float RedirectionThink();
 void systems_update();
 void sys_phys_update(entity this, float dt);
 void StartFrame()
@@ -246,7 +248,6 @@ void StartFrame()
 .float anglejitter;
 .string gametypefilter;
 .string cvarfilter;
-bool DoesQ3ARemoveThisEntity(entity this);
 
 /**
  * Evaluate an expression of the form: [+ | -]? [var[op]val | [op]var | val | var] ...
@@ -383,3 +384,15 @@ void WarpZone_PostInitialize_Callback()
        }
        delete(tracetest_ent);
 }
+
+/*
+==================
+main
+
+unused but required by the engine
+==================
+*/
+void main ()
+{
+
+}
index 7f86d19c01a47bc83cfd2870c0f0c170a0e93b99..93480cf282ec6c5377ac13197eea0dbe009ddfd2 100644 (file)
@@ -1,3 +1,12 @@
 #pragma once
 
 bool expr_evaluate(string s);
+
+/*
+==================
+main
+
+unused but required by the engine
+==================
+*/
+void main ();
index a137f4150e1546ccf3dc8ffee26824399085683d..b0d9e0992eb921c204451f24b48877010c082369 100644 (file)
@@ -9,10 +9,10 @@
 
 #include "command/vote.qh"
 
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
 
 #include "../common/deathtypes/all.qh"
-#include "../common/gamemodes/_mod.qh"
+#include <common/gamemodes/_mod.qh>
 #include "../common/teams.qh"
 
 void TeamchangeFrags(entity e)
@@ -116,7 +116,7 @@ string getwelcomemessage(entity this)
        if(g_weapon_stay && !g_cts)
                modifications = strcat(modifications, ", Weapons stay");
        if(g_jetpack)
-               modifications = strcat(modifications, ", Jet pack");
+               modifications = strcat(modifications, ", Jetpack");
        if(autocvar_g_powerups == 0)
                modifications = strcat(modifications, ", No powerups");
        if(autocvar_g_powerups > 0)
index 50dc5a35bca7cd130b7732fe141819539bc64e01..e52d4fcf1111c67ddd6c17f53b95feed83d605b7 100644 (file)
@@ -2,7 +2,7 @@
 
 void test_weapons_hurt(entity this)
 {
-    EXPECT_NE(100, this.health);
+    EXPECT_NE(100, GetResourceAmount(this, RESOURCE_HEALTH));
     delete(this.enemy);
     delete(this);
 }
index 1eda4e25b1066c86831ef2cb679bccd7993a6ce8..8cd7ab66c346f5bc2041c747f116c8d15b412664 100644 (file)
@@ -1,6 +1,6 @@
 #include "accuracy.qh"
 
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
 #include <common/constants.qh>
 #include <common/net_linked.qh>
 #include <common/teams.qh>
@@ -94,6 +94,7 @@ bool accuracy_isgooddamage(entity attacker, entity targ)
        int mutator_check = MUTATOR_CALLHOOK(AccuracyTargetValid, attacker, targ);
 
        if (warmup_stage) return false;
+       if (game_stopped) return false;
 
        // damage to dead/frozen players is good only if it happens in the frame they get killed / frozen
        // so that stats for weapons that shoot multiple projectiles per shot are properly counted
index e6bdd00d2b8e3163f2e51f090584fbd37008dd80..e79e9ddb6413bb2b86e98e84e4e04793fa1ca5a5 100644 (file)
@@ -3,7 +3,6 @@
 #include <server/defs.qh>
 #include <server/miscfunctions.qh>
 #include "../antilag.qh"
-#include "../g_subs.qh"
 #include <common/weapons/_all.qh>
 #include <common/state.qh>
 #include <common/wepent.qh>
index 17f2ddeeee2bb567271f1f689c8376494168a5a6..204b5a7633669ffeac43adcf5c6da6013939d703 100644 (file)
@@ -2,7 +2,7 @@
 
 #include "weaponsystem.qh"
 #include "../resources.qh"
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
 #include <common/t_items.qh>
 #include <server/items.qh>
 #include <common/weapons/_all.qh>
index dcf9ee41b33f2805c2364b4c909bda592ff4feb7..462af14d8e0ca151f366f9daa611f1caf3c00cc7 100644 (file)
@@ -3,7 +3,7 @@
 #include "weaponsystem.qh"
 #include "../resources.qh"
 #include "../items.qh"
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
 #include <common/t_items.qh>
 #include "../g_damage.qh"
 #include <common/items/item.qh>
index 46c26b2e866182fbaaa332000e366c0794317424..ddf1ff26248066b0fa719c18ab543802fc866d4b 100644 (file)
@@ -8,7 +8,6 @@
 #include "weaponsystem.qh"
 
 #include "../g_damage.qh"
-#include "../g_subs.qh"
 #include "../antilag.qh"
 
 #include <common/constants.qh>
index 011d5cbc11b19c13ca5b946d4dd75fabd4e9f51c..bf81f7044187d3ec532ea0f44ab968c328bf211f 100644 (file)
@@ -3,9 +3,9 @@
 #include "selection.qh"
 
 #include "../command/common.qh"
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
 #include "../round_handler.qh"
-#include "../resources.qh"
+#include <server/resources.qh>
 #include <common/t_items.qh>
 #include <common/animdecide.qh>
 #include <common/constants.qh>
@@ -332,8 +332,6 @@ bool weapon_prepareattack(Weapon thiswep, entity actor, .entity weaponentity, bo
        return false;
 }
 
-void wframe_send(entity actor, entity weaponentity, vector a, bool restartanim);
-
 /**
  * @param t defer thinking until time + t
  * @param func next think function
@@ -435,7 +433,7 @@ void W_WeaponFrame(Player actor, .entity weaponentity)
        entity this = actor.(weaponentity);
        if (frametime) this.weapon_frametime = frametime;
 
-       if (!this || actor.health < 1) return;  // Dead player can't use weapons and injure impulse commands
+       if (!this || GetResourceAmount(actor, RESOURCE_HEALTH) < 1) return;  // Dead player can't use weapons and injure impulse commands
 
        int button_atck = PHYS_INPUT_BUTTON_ATCK(actor);
        int button_atck2 = PHYS_INPUT_BUTTON_ATCK2(actor);
diff --git a/randomitems-overkill.cfg b/randomitems-overkill.cfg
new file mode 100644 (file)
index 0000000..e2a2617
--- /dev/null
@@ -0,0 +1,21 @@
+// Random items mutator config for Overkill ruleset
+
+// Map items
+
+set g_random_items_item_health_mega_probability 1 "Probability of random mega health spawning in the map during overkill."
+set g_random_items_item_armor_small_probability 10 "Probability of random small armor spawning in the map during overkill."
+set g_random_items_item_armor_medium_probability 4 "Probability of random medium armor spawning in the map during overkill."
+set g_random_items_item_armor_big_probability 2 "Probability of random big armor spawning in the map during overkill."
+set g_random_items_item_armor_mega_probability 1 "Probability of random mega armor spawning in the map during overkill."
+set g_random_items_weapon_okhmg_probability 0.5 "Probability of random overkill HMG spawning in the map during overkill."
+set g_random_items_weapon_okrpc_probability 0.5 "Probability of random overkill RPC spawning in the map during overkill."
+
+// Loot
+
+set g_random_loot_item_health_mega_probability 1 "Probability of random mega health spawning as loot during overkill."
+set g_random_loot_item_armor_small_probability 10 "Probability of random small armor spawning as loot during overkill."
+set g_random_loot_item_armor_medium_probability 4 "Probability of random medium armor spawning as loot during overkill."
+set g_random_loot_item_armor_big_probability 2 "Probability of random big armor spawning as loot during overkill."
+set g_random_loot_item_armor_mega_probability 1 "Probability of random mega armor spawning as loot during overkill."
+set g_random_loot_weapon_okhmg_probability 1 "Probability of random overkill HMG spawning as loot during overkill."
+set g_random_loot_weapon_okrpc_probability 1 "Probability of random overkill RPC spawning as loot during overkill."
index 1491ffd0c9ea0284b83e6556e37dca69592a4170..47611a9e23a5120f20b3663a4abcbd501ec6e58e 100644 (file)
@@ -91,6 +91,11 @@ set g_random_items_weapon_hlac_probability 0 "Probability of random HLAC spawnin
 set g_random_items_weapon_rifle_probability 0 "Probability of random rifle spawning in the map."
 set g_random_items_weapon_seeker_probability 0 "Probability of random TAG seeker spawning in the map."
 set g_random_items_weapon_vaporizer_probability 0 "Probability of random vaporizer spawning in the map."
+set g_random_items_weapon_okshotgun_probability 0 "Probability of random overkill shotgun spawning in the map."
+set g_random_items_weapon_okmachinegun_probability 0 "Probability of random overkill machinegun spawning in the map."
+set g_random_items_weapon_oknex_probability 0 "Probability of random overkill nex spawning in the map."
+set g_random_items_weapon_okhmg_probability 0 "Probability of random overkill HMG spawning in the map."
+set g_random_items_weapon_okrpc_probability 0 "Probability of random overkill RPC spawning in the map."
 set g_random_items_item_strength_probability 1 "Probability of random strength spawning in the map."
 set g_random_items_item_shield_probability 1 "Probability of random shield spawning in the map."
 set g_random_items_item_fuel_regen_probability 0 "Probability of random fuel regeneration spawning in the map."
@@ -99,13 +104,6 @@ set g_random_items_item_vaporizer_cells_probability 20 "Probability of random va
 set g_random_items_item_invisibility_probability 1 "Probability of random invisibility spawning in the map."
 set g_random_items_item_extralife_probability 1 "Probability of random extra life spawning in the map."
 set g_random_items_item_speed_probability 1 "Probability of random speed spawning in the map."
-set g_random_items_overkill_item_health_mega_probability 1 "Probability of random mega health spawning in the map during overkill."
-set g_random_items_overkill_item_armor_small_probability 10 "Probability of random small armor spawning in the map during overkill."
-set g_random_items_overkill_item_armor_medium_probability 4 "Probability of random medium armor spawning in the map during overkill."
-set g_random_items_overkill_item_armor_big_probability 2 "Probability of random big armor spawning in the map during overkill."
-set g_random_items_overkill_item_armor_mega_probability 1 "Probability of random mega armor spawning in the map during overkill."
-set g_random_items_overkill_weapon_okhmg_probability 0.5 "Probability of random overkill HMG spawning in the map during overkill."
-set g_random_items_overkill_weapon_okrpc_probability 0.5 "Probability of random overkill RPC spawning in the map during overkill."
 
 // Loot
 
@@ -155,6 +153,11 @@ set g_random_loot_weapon_hlac_probability 0 "Probability of random HLAC spawning
 set g_random_loot_weapon_rifle_probability 0 "Probability of random rifle spawning as loot."
 set g_random_loot_weapon_seeker_probability 0 "Probability of random TAG seeker spawning as loot."
 set g_random_loot_weapon_vaporizer_probability 0 "Probability of random vaporizer spawning as loot."
+set g_random_loot_weapon_okshotgun_probability 0 "Probability of random overkill shotgun spawning as loot."
+set g_random_loot_weapon_okmachinegun_probability 0 "Probability of random overkill machinegun spawning as loot."
+set g_random_loot_weapon_oknex_probability 0 "Probability of random overkill nex spawning as loot."
+set g_random_loot_weapon_okhmg_probability 0 "Probability of random overkill HMG spawning as loot."
+set g_random_loot_weapon_okrpc_probability 0 "Probability of random overkill RPC spawning as loot."
 set g_random_loot_item_strength_probability 1 "Probability of random strength spawning as loot."
 set g_random_loot_item_shield_probability 1 "Probability of random shield spawning as loot."
 set g_random_loot_item_fuel_regen_probability 0 "Probability of random fuel regeneration spawning as loot."
@@ -163,10 +166,3 @@ set g_random_loot_item_vaporizer_cells_probability 20 "Probability of random vap
 set g_random_loot_item_invisibility_probability 1 "Probability of random invisibility spawning as loot."
 set g_random_loot_item_extralife_probability 1 "Probability of random extra life spawning as loot."
 set g_random_loot_item_speed_probability 1 "Probability of random speed spawning as loot."
-set g_random_loot_overkill_item_health_mega_probability 1 "Probability of random mega health spawning as loot during overkill."
-set g_random_loot_overkill_item_armor_small_probability 10 "Probability of random small armor spawning as loot during overkill."
-set g_random_loot_overkill_item_armor_medium_probability 4 "Probability of random medium armor spawning as loot during overkill."
-set g_random_loot_overkill_item_armor_big_probability 2 "Probability of random big armor spawning as loot during overkill."
-set g_random_loot_overkill_item_armor_mega_probability 1 "Probability of random mega armor spawning as loot during overkill."
-set g_random_loot_overkill_weapon_okhmg_probability 1 "Probability of random overkill HMG spawning as loot during overkill."
-set g_random_loot_overkill_weapon_okrpc_probability 1 "Probability of random overkill RPC spawning as loot during overkill."
index b37f01dcff30513ddf050bc88b2e93e3309aa76f..20740d9a86482beef2dfb4c14e375453ffc71ec0 100644 (file)
@@ -7,7 +7,7 @@ exec balance-xdf.cfg
 exec physicsXDF.cfg
 
 // general gameplay
-set g_jump_grunt 1 // make enemies even easier to hear when they're jumping around
+// set g_jump_grunt 1 // just no
 set g_shootfromcenter 1 // hit where you point at with the crosshair (almost so, no shooteye because it's really ugly)
 set g_balance_kill_antispam 0
 set g_forced_respawn 1
index f3607bb2f86cbc696f06eb2ffe46154b21f38279..eb682aa55ce7a2b2fb3880753146d9833e716a27 100644 (file)
@@ -24,3 +24,4 @@ set g_monsters 0
 set g_turrets 0
 set g_vehicles 0
 set sv_showspectators 0
+set sv_taunt 0
index 49eb730c93d2c19c4a6b22eb3313ca547eace313..cc512e0c40dee4247caae0f4dcd920f52c6aa2e7 100644 (file)
@@ -5,6 +5,7 @@
 exec xonotic-server.cfg
 exec balance-overkill.cfg
 exec physicsOverkill.cfg
+exec randomitems-overkill.cfg
 
 // general gameplay
 set g_overkill 1
index b8d865d4df142e4c9158e87853e4d9aac3f7ace6..5ac5a1ec8ddbce6451b641424d456c54db1df56b 100644 (file)
@@ -677,8 +677,8 @@ set cl_effects_lightningarc_branchfactor_add 0.1
 
 set menu_updatecheck_getpacks 1 "get update packs from update server"
 
-set cl_loddistance1 1024
-set cl_loddistance2 3072
+seta cl_loddistance1 1024
+seta cl_loddistance2 3072
 seta cl_playerdetailreduction 4        "the higher, the less detailed player models are displayed (LOD)"
 seta cl_modeldetailreduction 1 "the higher, the less detailed certain map models are displayed (LOD)"
 
index 97e061b1c511e36d58960911c3d6c85c2bd8fc6c..13e38ebe049428928e1b4c57ce3f88125afb4df9 100644 (file)
@@ -43,7 +43,7 @@ set sv_timeout_number 2       "how many timeouts one player is allowed to call (gets r
 set sv_timeout_leadtime 4      "how long the players will be informed that a timeout was called before it starts, in seconds"
 set sv_timeout_resumetime 3    "how long the remaining timeout-time will be after a player called the timein command"
 
-set g_allow_oldvortexbeam 0 "If enabled, clients are allowed to use old v2.3 Vortex beam"
+set g_allow_oldvortexbeam 1 "If enabled, clients are allowed to use old v2.3 Vortex beam"
 
 set g_telefrags 1 "telefragging, i.e. killing someone who stands in the way of someone who is teleporting"
 set g_telefrags_teamplay 1 "never telefrag team mates"
@@ -345,7 +345,7 @@ set g_maplist_votable_screenshot_dir "maps levelshots"      "where to look for map sc
 
 set sv_vote_gametype 0 "show a vote screen for gametypes before map vote screen"
 set sv_vote_gametype_keeptwotime 10 "show only 2 options after this amount of time during gametype vote screen"
-set sv_vote_gametype_options "dm ctf ca lms tdm ft"
+set sv_vote_gametype_options "dm tdm ctf"
 set sv_vote_gametype_timeout 20
 set sv_vote_gametype_default_current 1 "Keep the current gametype if no one votes"