]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'Lyberta/GenmodFix' into Lyberta/master
authorLyberta <lyberta@lyberta.net>
Thu, 28 Sep 2017 06:52:20 +0000 (09:52 +0300)
committerLyberta <lyberta@lyberta.net>
Thu, 28 Sep 2017 06:52:20 +0000 (09:52 +0300)
51 files changed:
bal-wep-overkill-nerfed.cfg [new file with mode: 0644]
bal-wep-overkill.cfg
bal-wep-xonotic.cfg
defaultClient.cfg
defaultServer.cfg
gamemodes-server.cfg
player-template-example.cfg [new file with mode: 0644]
qcsrc/client/hud/panel/modicons.qc
qcsrc/client/view.qc
qcsrc/common/gamemodes/gamemode/_mod.inc
qcsrc/common/gamemodes/gamemode/_mod.qh
qcsrc/common/gamemodes/gamemode/gungame/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/gungame/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/gungame/sv_gungame.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/gungame/sv_gungame.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh [new file with mode: 0644]
qcsrc/common/mapinfo.qh
qcsrc/common/mutators/mutator/overkill/_mod.inc
qcsrc/common/mutators/mutator/overkill/_mod.qh
qcsrc/common/mutators/mutator/overkill/cl_overkill.qc
qcsrc/common/mutators/mutator/overkill/hmg.qc
qcsrc/common/mutators/mutator/overkill/hmg.qh
qcsrc/common/mutators/mutator/overkill/okmachinegun.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/overkill/okmachinegun.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/overkill/okshotgun.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/overkill/okshotgun.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/overkill/okvortex.qc [new file with mode: 0644]
qcsrc/common/mutators/mutator/overkill/okvortex.qh [new file with mode: 0644]
qcsrc/common/mutators/mutator/overkill/rpc.qc
qcsrc/common/mutators/mutator/overkill/rpc.qh
qcsrc/common/mutators/mutator/overkill/sv_overkill.qc
qcsrc/common/mutators/mutator/waypoints/all.inc
qcsrc/common/notifications/all.inc
qcsrc/common/stats.qh
qcsrc/common/weapons/weapon/shotgun.qc
qcsrc/common/wepent.qc
qcsrc/common/wepent.qh
qcsrc/menu/xonotic/dialog_multiplayer_create.qc
qcsrc/menu/xonotic/util.qc
qcsrc/server/_mod.inc
qcsrc/server/_mod.qh
qcsrc/server/client.qc
qcsrc/server/defs.qh
qcsrc/server/g_world.qc
qcsrc/server/playertemplates.qc [new file with mode: 0644]
qcsrc/server/playertemplates.qh [new file with mode: 0644]
sound/misc/kill.ogg
survival.cfg [new file with mode: 0644]

diff --git a/bal-wep-overkill-nerfed.cfg b/bal-wep-overkill-nerfed.cfg
new file mode 100644 (file)
index 0000000..da0689e
--- /dev/null
@@ -0,0 +1,116 @@
+// This config file is for overkill weapons that were nerfed to have the same
+// stats as vanilla weapons, secondary attack uses stats of vanilla blaster.
+
+// {{{ Overkill Shotgun
+set g_balance_okshotgun_primary_ammo 1
+set g_balance_okshotgun_primary_animtime 0.2
+set g_balance_okshotgun_primary_bot_range 512
+set g_balance_okshotgun_primary_bullets 12
+set g_balance_okshotgun_primary_damage 4
+set g_balance_okshotgun_primary_force 15
+set g_balance_okshotgun_primary_refire 0.75
+set g_balance_okshotgun_primary_solidpenetration 3.8
+set g_balance_okshotgun_primary_spread 0.12
+set g_balance_okshotgun_reload_ammo 0
+set g_balance_okshotgun_reload_time 2
+set g_balance_okshotgun_secondary_animtime 0.2
+set g_balance_okshotgun_secondary_damage 20
+set g_balance_okshotgun_secondary_delay 0
+set g_balance_okshotgun_secondary_edgedamage 10
+set g_balance_okshotgun_secondary_force 300
+set g_balance_okshotgun_secondary_lifetime 5
+set g_balance_okshotgun_secondary_radius 60
+set g_balance_okshotgun_secondary_refire 0.7
+set g_balance_okshotgun_secondary_refire_type 0
+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 10
+set g_balance_okmachinegun_primary_force 3
+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.02
+set g_balance_okmachinegun_reload_ammo 60
+set g_balance_okmachinegun_reload_time 2
+set g_balance_okmachinegun_secondary_animtime 0.2
+set g_balance_okmachinegun_secondary_damage 20
+set g_balance_okmachinegun_secondary_delay 0
+set g_balance_okmachinegun_secondary_edgedamage 10
+set g_balance_okmachinegun_secondary_force 300
+set g_balance_okmachinegun_secondary_lifetime 5
+set g_balance_okmachinegun_secondary_radius 60
+set g_balance_okmachinegun_secondary_refire 0.7
+set g_balance_okmachinegun_secondary_refire_type 0
+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 Vortex
+set g_balance_okvortex_charge 1
+set g_balance_okvortex_charge_animlimit 0.5
+set g_balance_okvortex_charge_limit 1
+set g_balance_okvortex_charge_maxspeed 800
+set g_balance_okvortex_charge_mindmg 40
+set g_balance_okvortex_charge_minspeed 400
+set g_balance_okvortex_charge_rate 0.6
+set g_balance_okvortex_charge_rot_pause 0
+set g_balance_okvortex_charge_rot_rate 0
+set g_balance_okvortex_charge_shot_multiplier 0
+set g_balance_okvortex_charge_start 0.5
+set g_balance_okvortex_charge_velocity_rate 0
+set g_balance_okvortex_primary_ammo 6
+set g_balance_okvortex_primary_animtime 0.4
+set g_balance_okvortex_primary_damage 80
+set g_balance_okvortex_primary_damagefalloff_forcehalflife 0
+set g_balance_okvortex_primary_damagefalloff_halflife 0
+set g_balance_okvortex_primary_damagefalloff_maxdist 0
+set g_balance_okvortex_primary_damagefalloff_mindist 0
+set g_balance_okvortex_primary_force 400
+set g_balance_okvortex_primary_refire 1.5
+set g_balance_okvortex_reload_ammo 0
+set g_balance_okvortex_reload_time 2
+set g_balance_okvortex_secondary 0
+set g_balance_okvortex_secondary_ammo 2
+set g_balance_okvortex_secondary_animtime 0
+set g_balance_okvortex_secondary_chargepool 0
+set g_balance_okvortex_secondary_chargepool_pause_regen 1
+set g_balance_okvortex_secondary_chargepool_regen 0.15
+set g_balance_okvortex_secondary_damage 0
+set g_balance_okvortex_secondary_damagefalloff_forcehalflife 0
+set g_balance_okvortex_secondary_damagefalloff_halflife 0
+set g_balance_okvortex_secondary_damagefalloff_maxdist 0
+set g_balance_okvortex_secondary_damagefalloff_mindist 0
+set g_balance_okvortex_secondary_force 0
+set g_balance_okvortex_secondary_refire 0
+set g_balance_okvortex_secondary_refire_type 0
+set g_balance_okvortex_secondary_delay 0
+set g_balance_okvortex_secondary_edgedamage 10
+set g_balance_okvortex_secondary_lifetime 5
+set g_balance_okvortex_secondary_radius 60
+set g_balance_okvortex_secondary_shotangle 0
+set g_balance_okvortex_secondary_speed 6000
+set g_balance_okvortex_secondary_spread 0
+set g_balance_okvortex_switchdelay_drop 0.2
+set g_balance_okvortex_switchdelay_raise 0.2
+set g_balance_okvortex_weaponreplace ""
+set g_balance_okvortex_weaponstart 0
+set g_balance_okvortex_weaponstartoverride -1
+set g_balance_okvortex_weaponthrowable 1
+// }}}
index 9cfffed10b02114e478653d4ea7ea117ad744c0e..fcee3d5e81f1e30bda996ecf9cde6d39e2ac7b42 100644 (file)
@@ -778,16 +778,16 @@ set g_balance_arc_weaponstartoverride -1
 set g_balance_arc_weaponthrowable 1
 // }}}
 // {{{ #21: Heavy Machine Gun
-set g_balance_hmg_ammo 1
-set g_balance_hmg_damage 30
-set g_balance_hmg_force 10
-set g_balance_hmg_refire 0.05
+set g_balance_hmg_primary_ammo 1
+set g_balance_hmg_primary_damage 30
+set g_balance_hmg_primary_force 10
+set g_balance_hmg_primary_refire 0.05
+set g_balance_hmg_primary_solidpenetration 32
+set g_balance_hmg_primary_spread_add 0.005
+set g_balance_hmg_primary_spread_max 0.06
+set g_balance_hmg_primary_spread_min 0.01
 set g_balance_hmg_reload_ammo 120
 set g_balance_hmg_reload_time 1
-set g_balance_hmg_solidpenetration 32
-set g_balance_hmg_spread_add 0.005
-set g_balance_hmg_spread_max 0.06
-set g_balance_hmg_spread_min 0.01
 set g_balance_hmg_switchdelay_drop 0.2
 set g_balance_hmg_switchdelay_raise 0.2
 set g_balance_hmg_weaponreplace ""
@@ -796,21 +796,21 @@ set g_balance_hmg_weaponstartoverride 0
 set g_balance_hmg_weaponthrowable 0
 // }}}
 // {{{ #22: Rocket Propelled Chainsaw
-set g_balance_rpc_ammo 10
-set g_balance_rpc_animtime 1
-set g_balance_rpc_damage 150
-set g_balance_rpc_damage2 500
-set g_balance_rpc_damageforcescale 2
-set g_balance_rpc_edgedamage 50
-set g_balance_rpc_force 400
-set g_balance_rpc_health 25
-set g_balance_rpc_lifetime 30
-set g_balance_rpc_radius 300
-set g_balance_rpc_refire 1
+set g_balance_rpc_primary_ammo 10
+set g_balance_rpc_primary_animtime 1
+set g_balance_rpc_primary_damage 150
+set g_balance_rpc_primary_damage2 500
+set g_balance_rpc_primary_damageforcescale 2
+set g_balance_rpc_primary_edgedamage 50
+set g_balance_rpc_primary_force 400
+set g_balance_rpc_primary_health 25
+set g_balance_rpc_primary_lifetime 30
+set g_balance_rpc_primary_radius 300
+set g_balance_rpc_primary_refire 1
+set g_balance_rpc_primary_speed 2500
+set g_balance_rpc_primary_speedaccel 5000
 set g_balance_rpc_reload_ammo 10
 set g_balance_rpc_reload_time 1
-set g_balance_rpc_speed 2500
-set g_balance_rpc_speedaccel 5000
 set g_balance_rpc_switchdelay_drop 0.2
 set g_balance_rpc_switchdelay_raise 0.2
 set g_balance_rpc_weaponreplace ""
@@ -818,3 +818,116 @@ set g_balance_rpc_weaponstart 0
 set g_balance_rpc_weaponstartoverride 0
 set g_balance_rpc_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 Vortex
+set g_balance_okvortex_charge 0
+set g_balance_okvortex_charge_animlimit 0.5
+set g_balance_okvortex_charge_limit 1
+set g_balance_okvortex_charge_maxspeed 800
+set g_balance_okvortex_charge_mindmg 40
+set g_balance_okvortex_charge_minspeed 400
+set g_balance_okvortex_charge_rate 0.6
+set g_balance_okvortex_charge_rot_pause 0
+set g_balance_okvortex_charge_rot_rate 0
+set g_balance_okvortex_charge_shot_multiplier 0
+set g_balance_okvortex_charge_start 0.5
+set g_balance_okvortex_charge_velocity_rate 0
+set g_balance_okvortex_primary_ammo 10
+set g_balance_okvortex_primary_animtime 0.65
+set g_balance_okvortex_primary_damage 100
+set g_balance_okvortex_primary_damagefalloff_forcehalflife 0
+set g_balance_okvortex_primary_damagefalloff_halflife 0
+set g_balance_okvortex_primary_damagefalloff_maxdist 0
+set g_balance_okvortex_primary_damagefalloff_mindist 0
+set g_balance_okvortex_primary_force 500
+set g_balance_okvortex_primary_refire 1
+set g_balance_okvortex_reload_ammo 50
+set g_balance_okvortex_reload_time 2
+set g_balance_okvortex_secondary 2
+set g_balance_okvortex_secondary_ammo 0
+set g_balance_okvortex_secondary_animtime 0.2
+set g_balance_okvortex_secondary_chargepool 0
+set g_balance_okvortex_secondary_chargepool_pause_regen 1
+set g_balance_okvortex_secondary_chargepool_regen 0.15
+set g_balance_okvortex_secondary_damage 25
+set g_balance_okvortex_secondary_damagefalloff_forcehalflife 0
+set g_balance_okvortex_secondary_damagefalloff_halflife 0
+set g_balance_okvortex_secondary_damagefalloff_maxdist 0
+set g_balance_okvortex_secondary_damagefalloff_mindist 0
+set g_balance_okvortex_secondary_force 300
+set g_balance_okvortex_secondary_refire 0.7
+set g_balance_okvortex_secondary_refire_type 1
+set g_balance_okvortex_secondary_delay 0
+set g_balance_okvortex_secondary_edgedamage 12.5
+set g_balance_okvortex_secondary_lifetime 5
+set g_balance_okvortex_secondary_radius 70
+set g_balance_okvortex_secondary_shotangle 0
+set g_balance_okvortex_secondary_speed 6000
+set g_balance_okvortex_secondary_spread 0
+set g_balance_okvortex_switchdelay_drop 0.2
+set g_balance_okvortex_switchdelay_raise 0.2
+set g_balance_okvortex_weaponreplace ""
+set g_balance_okvortex_weaponstart 0
+set g_balance_okvortex_weaponstartoverride -1
+set g_balance_okvortex_weaponthrowable 1
+// }}}
index ac5be34f3783b822759d7b26922629c1adedd52b..0e9f45baf8c1d2cfef74feea04db4b2268a6b726 100644 (file)
@@ -778,16 +778,29 @@ set g_balance_arc_weaponstartoverride -1
 set g_balance_arc_weaponthrowable 1
 // }}}
 // {{{ #21: Heavy Machine Gun
-set g_balance_hmg_ammo 1
-set g_balance_hmg_damage 30
-set g_balance_hmg_force 10
-set g_balance_hmg_refire 0.05
+set g_balance_hmg_primary_ammo 1
+set g_balance_hmg_primary_damage 30
+set g_balance_hmg_primary_force 10
+set g_balance_hmg_primary_refire 0.05
+set g_balance_hmg_primary_solidpenetration 32
+set g_balance_hmg_primary_spread_add 0.005
+set g_balance_hmg_primary_spread_max 0.06
+set g_balance_hmg_primary_spread_min 0.01
 set g_balance_hmg_reload_ammo 120
 set g_balance_hmg_reload_time 1
-set g_balance_hmg_solidpenetration 32
-set g_balance_hmg_spread_add 0.005
-set g_balance_hmg_spread_max 0.06
-set g_balance_hmg_spread_min 0.01
+set g_balance_hmg_secondary_ammo 0
+set g_balance_hmg_secondary_animtime 0.2
+set g_balance_hmg_secondary_damage 25
+set g_balance_hmg_secondary_delay 0
+set g_balance_hmg_secondary_edgedamage 12.5
+set g_balance_hmg_secondary_force 300
+set g_balance_hmg_secondary_lifetime 5
+set g_balance_hmg_secondary_radius 70
+set g_balance_hmg_secondary_refire 0.7
+set g_balance_hmg_secondary_refire_type 1
+set g_balance_hmg_secondary_shotangle 0
+set g_balance_hmg_secondary_speed 6000
+set g_balance_hmg_secondary_spread 0
 set g_balance_hmg_switchdelay_drop 0.2
 set g_balance_hmg_switchdelay_raise 0.2
 set g_balance_hmg_weaponreplace ""
@@ -796,21 +809,34 @@ set g_balance_hmg_weaponstartoverride 0
 set g_balance_hmg_weaponthrowable 0
 // }}}
 // {{{ #22: Rocket Propelled Chainsaw
-set g_balance_rpc_ammo 10
-set g_balance_rpc_animtime 1
-set g_balance_rpc_damage 150
-set g_balance_rpc_damage2 500
-set g_balance_rpc_damageforcescale 2
-set g_balance_rpc_edgedamage 50
-set g_balance_rpc_force 400
-set g_balance_rpc_health 25
-set g_balance_rpc_lifetime 30
-set g_balance_rpc_radius 300
-set g_balance_rpc_refire 1
+set g_balance_rpc_primary_ammo 10
+set g_balance_rpc_primary_animtime 1
+set g_balance_rpc_primary_damage 150
+set g_balance_rpc_primary_damage2 500
+set g_balance_rpc_primary_damageforcescale 2
+set g_balance_rpc_primary_edgedamage 50
+set g_balance_rpc_primary_force 400
+set g_balance_rpc_primary_health 25
+set g_balance_rpc_primary_lifetime 30
+set g_balance_rpc_primary_radius 300
+set g_balance_rpc_primary_refire 1
+set g_balance_rpc_primary_speed 2500
+set g_balance_rpc_primary_speedaccel 5000
 set g_balance_rpc_reload_ammo 10
 set g_balance_rpc_reload_time 1
-set g_balance_rpc_speed 2500
-set g_balance_rpc_speedaccel 5000
+set g_balance_rpc_secondary_ammo 0
+set g_balance_rpc_secondary_animtime 0.2
+set g_balance_rpc_secondary_damage 25
+set g_balance_rpc_secondary_delay 0
+set g_balance_rpc_secondary_edgedamage 12.5
+set g_balance_rpc_secondary_force 300
+set g_balance_rpc_secondary_lifetime 5
+set g_balance_rpc_secondary_radius 70
+set g_balance_rpc_secondary_refire 0.7
+set g_balance_rpc_secondary_refire_type 1
+set g_balance_rpc_secondary_shotangle 0
+set g_balance_rpc_secondary_speed 6000
+set g_balance_rpc_secondary_spread 0
 set g_balance_rpc_switchdelay_drop 0.2
 set g_balance_rpc_switchdelay_raise 0.2
 set g_balance_rpc_weaponreplace ""
@@ -818,3 +844,116 @@ set g_balance_rpc_weaponstart 0
 set g_balance_rpc_weaponstartoverride 0
 set g_balance_rpc_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 Vortex
+set g_balance_okvortex_charge 0
+set g_balance_okvortex_charge_animlimit 0.5
+set g_balance_okvortex_charge_limit 1
+set g_balance_okvortex_charge_maxspeed 800
+set g_balance_okvortex_charge_mindmg 40
+set g_balance_okvortex_charge_minspeed 400
+set g_balance_okvortex_charge_rate 0.6
+set g_balance_okvortex_charge_rot_pause 0
+set g_balance_okvortex_charge_rot_rate 0
+set g_balance_okvortex_charge_shot_multiplier 0
+set g_balance_okvortex_charge_start 0.5
+set g_balance_okvortex_charge_velocity_rate 0
+set g_balance_okvortex_primary_ammo 10
+set g_balance_okvortex_primary_animtime 0.65
+set g_balance_okvortex_primary_damage 100
+set g_balance_okvortex_primary_damagefalloff_forcehalflife 0
+set g_balance_okvortex_primary_damagefalloff_halflife 0
+set g_balance_okvortex_primary_damagefalloff_maxdist 0
+set g_balance_okvortex_primary_damagefalloff_mindist 0
+set g_balance_okvortex_primary_force 500
+set g_balance_okvortex_primary_refire 1
+set g_balance_okvortex_reload_ammo 50
+set g_balance_okvortex_reload_time 2
+set g_balance_okvortex_secondary 2
+set g_balance_okvortex_secondary_ammo 0
+set g_balance_okvortex_secondary_animtime 0.2
+set g_balance_okvortex_secondary_chargepool 0
+set g_balance_okvortex_secondary_chargepool_pause_regen 1
+set g_balance_okvortex_secondary_chargepool_regen 0.15
+set g_balance_okvortex_secondary_damage 25
+set g_balance_okvortex_secondary_damagefalloff_forcehalflife 0
+set g_balance_okvortex_secondary_damagefalloff_halflife 0
+set g_balance_okvortex_secondary_damagefalloff_maxdist 0
+set g_balance_okvortex_secondary_damagefalloff_mindist 0
+set g_balance_okvortex_secondary_force 300
+set g_balance_okvortex_secondary_refire 0.7
+set g_balance_okvortex_secondary_refire_type 1
+set g_balance_okvortex_secondary_delay 0
+set g_balance_okvortex_secondary_edgedamage 12.5
+set g_balance_okvortex_secondary_lifetime 5
+set g_balance_okvortex_secondary_radius 70
+set g_balance_okvortex_secondary_shotangle 0
+set g_balance_okvortex_secondary_speed 6000
+set g_balance_okvortex_secondary_spread 0
+set g_balance_okvortex_switchdelay_drop 0.2
+set g_balance_okvortex_switchdelay_raise 0.2
+set g_balance_okvortex_weaponreplace ""
+set g_balance_okvortex_weaponstart 0
+set g_balance_okvortex_weaponstartoverride -1
+set g_balance_okvortex_weaponthrowable 1
+// }}}
index e4904ff58134dea57a745398fb65123d1f3b035c..f08874c1247ce69c3d930161dc7f1620b70ea217 100644 (file)
@@ -608,13 +608,13 @@ seta menu_mouse_speed 1 "speed multiplier for the mouse in the menu (does not af
 set menu_use_default_hostname 1
 alias sethostname "set menu_use_default_hostname 0; hostname $*"
 
-seta cl_weaponpriority "vaporizer hmg rpc vortex fireball mortar machinegun hagar rifle arc electro devastator crylink minelayer shotgun shockwave hlac tuba blaster porto seeker hook" "weapon priority list"
+seta cl_weaponpriority "vaporizer hmg rpc okvortex vortex fireball mortar okmachinegun machinegun hagar rifle arc electro devastator crylink minelayer okshotgun shotgun shockwave hlac tuba blaster porto seeker hook" "weapon priority list"
 seta cl_weaponpriority_useforcycling 0 "when set, weapon cycling by the mouse wheel makes use of the weapon priority list (the special value 2 uses the weapon ID list for cycling)"
 seta cl_weaponpriority0 "rpc devastator mortar hagar seeker fireball"                   "use weapon_priority_0_prev for prev gun from this list, weapon_priority_0_best for best gun, weapon_priority_0_next for next gun.  Default value: explosives"
-seta cl_weaponpriority1 "vaporizer vortex crylink hlac arc electro blaster shockwave"   "use weapon_priority_1_prev for prev gun from this list, weapon_priority_1_best for best gun, weapon_priority_1_next for next gun.  Default value: energy"
-seta cl_weaponpriority2 "vaporizer vortex rifle"                                        "use weapon_priority_2_prev for prev gun from this list, weapon_priority_2_best for best gun, weapon_priority_2_next for next gun.  Default value: hitscan exact"
-seta cl_weaponpriority3 "vaporizer hmg vortex rifle machinegun shotgun shockwave"       "use weapon_priority_3_prev for prev gun from this list, weapon_priority_3_best for best gun, weapon_priority_3_next for next gun.  Default value: hitscan all"
-seta cl_weaponpriority4 "mortar minelayer hlac hagar crylink seeker shotgun shockwave"  "use weapon_priority_4_prev for prev gun from this list, weapon_priority_4_best for best gun, weapon_priority_4_next for next gun.  Default value: spam weapons"
+seta cl_weaponpriority1 "vaporizer okvortex vortex crylink hlac arc electro blaster shockwave"   "use weapon_priority_1_prev for prev gun from this list, weapon_priority_1_best for best gun, weapon_priority_1_next for next gun.  Default value: energy"
+seta cl_weaponpriority2 "vaporizer okvortex vortex rifle"                                        "use weapon_priority_2_prev for prev gun from this list, weapon_priority_2_best for best gun, weapon_priority_2_next for next gun.  Default value: hitscan exact"
+seta cl_weaponpriority3 "vaporizer hmg okvortex vortex rifle okmachinegun machinegun okshotgun shotgun shockwave"       "use weapon_priority_3_prev for prev gun from this list, weapon_priority_3_best for best gun, weapon_priority_3_next for next gun.  Default value: hitscan all"
+seta cl_weaponpriority4 "mortar minelayer hlac hagar crylink seeker okshotgun shotgun shockwave"  "use weapon_priority_4_prev for prev gun from this list, weapon_priority_4_best for best gun, weapon_priority_4_next for next gun.  Default value: spam weapons"
 seta cl_weaponpriority5 "blaster shockwave hook porto"                                  "use weapon_priority_5_prev for prev gun from this list, weapon_priority_5_best for best gun, weapon_priority_5_next for next gun.  Default value: weapons for moving"
 seta cl_weaponpriority6 ""                                                              "use weapon_priority_6_prev for prev gun from this list, weapon_priority_6_best for best gun, weapon_priority_6_next for next gun"
 seta cl_weaponpriority7 ""                                                              "use weapon_priority_7_prev for prev gun from this list, weapon_priority_7_best for best gun, weapon_priority_7_next for next gun"
index 47037c069b62ca6aea1538be444c949192e3f721..f029976ebd70e2ff8b965b9b8ff585837fb26fc6 100644 (file)
@@ -133,9 +133,9 @@ set bot_ai_keyboard_threshold 0.57
 set bot_ai_aimskill_offset 0.3 "Amount of error induced to the bots aim"
 set bot_ai_aimskill_think 1 "Aiming velocity. Use values below 1 for slower aiming"
 set bot_ai_custom_weapon_priority_distances "300 850"  "Define close and far distances in any order. Based on the distance to the enemy bots will choose different weapons"
-set bot_ai_custom_weapon_priority_far   "vaporizer vortex rifle electro devastator mortar hagar hlac crylink blaster machinegun fireball seeker shotgun shockwave tuba minelayer"      "Desired weapons for far distances ordered by priority"
-set bot_ai_custom_weapon_priority_mid   "vaporizer devastator vortex fireball seeker mortar electro machinegun arc crylink hlac hagar shotgun shockwave blaster rifle tuba minelayer"  "Desired weapons for middle distances ordered by priority"
-set bot_ai_custom_weapon_priority_close "vaporizer vortex shotgun shockwave machinegun arc hlac tuba seeker hagar crylink mortar electro devastator blaster fireball rifle minelayer"  "Desired weapons for close distances ordered by priority"
+set bot_ai_custom_weapon_priority_far   "vaporizer okvortex vortex rifle electro devastator mortar hagar hlac crylink blaster okmachinegun machinegun fireball seeker okshotgun shotgun shockwave tuba minelayer"      "Desired weapons for far distances ordered by priority"
+set bot_ai_custom_weapon_priority_mid   "vaporizer devastator okvortex vortex fireball seeker mortar electro okmachinegun machinegun arc crylink hlac hagar okshotgun shotgun shockwave blaster rifle tuba minelayer"  "Desired weapons for middle distances ordered by priority"
+set bot_ai_custom_weapon_priority_close "vaporizer okvortex vortex okshotgun shotgun shockwave okmachinegun machinegun arc hlac tuba seeker hagar crylink mortar electro devastator blaster fireball rifle minelayer"  "Desired weapons for close distances ordered by priority"
 set bot_ai_weapon_combo 1      "Enable bots to do weapon combos"
 set bot_ai_weapon_combo_threshold 0.4  "Try to make a combo N seconds after the last attack"
 set bot_ai_friends_aware_pickup_radius "500"   "Bots will not pickup items if a team mate is this distance near the item"
index 6790a3b4fb9575ab91e070c57bc984ac97609ba2..c11ecacbf0673f7fe1e57216ee66b3b0c3391a1b 100644 (file)
@@ -28,6 +28,7 @@ alias sv_hook_gamestart_cts
 alias sv_hook_gamestart_ka
 alias sv_hook_gamestart_ft
 alias sv_hook_gamestart_inv
+alias sv_hook_gamestart_gg
 alias sv_hook_gamerestart
 alias sv_hook_gameend
 
@@ -47,6 +48,7 @@ alias sv_vote_gametype_hook_cts
 alias sv_vote_gametype_hook_dm
 alias sv_vote_gametype_hook_dom
 alias sv_vote_gametype_hook_ft
+alias sv_vote_gametype_hook_gg
 alias sv_vote_gametype_hook_inv
 alias sv_vote_gametype_hook_ka
 alias sv_vote_gametype_hook_kh
@@ -196,7 +198,13 @@ set g_inv_respawn_delay_large_count 0
 set g_inv_respawn_delay_max 0
 set g_inv_respawn_waves 0
 set g_inv_weapon_stay 0
-
+set g_gg_respawn_delay_small 0
+set g_gg_respawn_delay_small_count 0
+set g_gg_respawn_delay_large 0
+set g_gg_respawn_delay_large_count 0
+set g_gg_respawn_delay_max 0
+set g_gg_respawn_waves 0
+set g_gg_weapon_stay 0
 
 // =========
 //  assault
@@ -522,3 +530,12 @@ set g_invasion_spawnpoint_spawn_delay 0.5
 set g_invasion_teams 0 "number of teams in invasion (note: use mapinfo to set this)"
 set g_invasion_team_spawns 1 "use team spawns in teamplay invasion mode"
 set g_invasion_type 0 "type of invasion mode - 0: round-based, 1: hunting, 2: complete the stage (note: use mapinfo to set this)"
+
+// =========
+//  gungame
+// =========
+set g_gg 0 "GunGame: Kill players with all weapons"
+set g_gg_weapons "vortex mortar machinegun hagar arc electro devastator crylink shotgun blaster"
+set g_gg_kills_per_weapon 3 "Number of kills needed to advance to the next weapon"
+
+exec survival.cfg // Lyberta: surv
diff --git a/player-template-example.cfg b/player-template-example.cfg
new file mode 100644 (file)
index 0000000..408f18d
--- /dev/null
@@ -0,0 +1,80 @@
+// Player template example
+// This config file defines a player template named "example" that has vanilla
+// behavior. It is intended to be a starting point for making your own player
+// templates. Just copy the variables and replace "example" with your own name.
+
+// Spawn section
+// Variables in this section define what happens during spawn.
+set g_player_template_example_start_health "default" "How much health does player get during spawn"
+set g_player_template_example_start_armor "default" "How much armor does player get during spawn"
+set g_player_template_example_unlimited_ammo "default" "Whether to give player unlimited ammo"
+set g_player_template_example_start_ammo_shells "default" "How much shells does player get during spawn"
+set g_player_template_example_start_ammo_bullets "default" "How much bullets does player get during spawn"
+set g_player_template_example_start_ammo_rockets "default" "How much rockets does player get during spawn"
+set g_player_template_example_start_ammo_cells "default" "How much cells does player get during spawn"
+// These weapons are given to all players by default. They are usually blaster and shotgun.
+set g_player_template_example_default_start_weapons "default" "Whether to give player default start weapons"
+// These weapons will be given unconditionally.
+set g_player_template_example_start_weapons "default" "Which weapons does player get during spawn"
+// These weapons will be given randomly. The player will get the exact amount of
+// random weapons. The code will make sure that they are not duplicate.
+set g_player_template_example_random_start_weapons_count "default" "Number of weapons that can be randomly given to player during spawn"
+set g_player_template_example_random_start_weapons "default" "Weapons that can be randomly given to player during spawn"
+set g_player_template_example_random_start_shells "default" "How much shells does the player get with random start shell-based weapon"
+set g_player_template_example_random_start_bullets "default" "How much bullets does the player get with random start bullet-based weapon"
+set g_player_template_example_random_start_rockets "default" "How much rockets does the player get with random start rocket-based weapon"
+set g_player_template_example_random_start_cells "default" "How much cells does the player get with random start cell-based weapon"
+
+set g_player_template_example_drop_weapons "default" "Whether the player can drop weapons by throwing them or by dying"
+
+// Item pickup section
+// Variables in this section define what happens during item pickup.
+// Player can get health, armor and/or ammo on top of or instead of an item.
+// The format of the value is:
+// "<add|override> [health] [armor] [shells] [bullets] [rockets] [cells] [plasma] [fuel]"
+// For example "add 50" will give 50 health and the original item.
+// "override 0 0 5" will give 5 shells instead of the item.
+set g_player_template_example_pickup_health_small "default" "What items does player get when they pickup small health"
+set g_player_template_example_pickup_health_medium "default" "What items does player get when they pickup medium health"
+set g_player_template_example_pickup_health_big "default" "What items does player get when they pickup big health"
+set g_player_template_example_pickup_health_mega "default" "What items does player get when they pickup mega health"
+set g_player_template_example_pickup_armor_small "default" "What items does player get when they pickup small armor"
+set g_player_template_example_pickup_armor_medium "default" "What items does player get when they pickup medium armor"
+set g_player_template_example_pickup_armor_big "default" "What items does player get when they pickup big armor"
+set g_player_template_example_pickup_armor_mega "default" "What items does player get when they pickup mega armor"
+set g_player_template_example_pickup_item_shells "default" "What items does player get when they pickup shells"
+set g_player_template_example_pickup_item_bullets "default" "What items does player get when they pickup bullets"
+set g_player_template_example_pickup_item_rockets "default" "What items does player get when they pickup rockets"
+set g_player_template_example_pickup_item_cells "default" "What items does player get when they pickup cells"
+set g_player_template_example_pickup_weapon_machinegun "default" "What items does player get when they pickup a machinegun"
+set g_player_template_example_pickup_weapon_mortar "default" "What items does player get when they pickup a mortar"
+set g_player_template_example_pickup_weapon_electro "default" "What items does player get when they pickup an electro"
+set g_player_template_example_pickup_weapon_crylink "default" "What items does player get when they pickup a crylink"
+set g_player_template_example_pickup_weapon_vortex "default" "What items does player get when they pickup a vortex"
+set g_player_template_example_pickup_weapon_hagar "default" "What items does player get when they pickup a hagar"
+set g_player_template_example_pickup_weapon_devastator "default" "What items does player get when they pickup a devastator"
+set g_player_template_example_pickup_weapon_minelayer "default" "What items does player get when they pickup a mine layer"
+set g_player_template_example_pickup_weapon_hlac "default" "What items does player get when they pickup a HLAC"
+set g_player_template_example_pickup_weapon_rifle "default" "What items does player get when they pickup a rifle"
+set g_player_template_example_pickup_weapon_seeker "default" "What items does player get when they pickup a TAG seeker"
+// These weapons were dropped by other players.
+set g_player_template_example_pickup_weapon_dropped_shotgun "default" "What items does player get when they pickup a dropped shotgun"
+set g_player_template_example_pickup_weapon_dropped_machinegun "default" "What items does player get when they pickup a dropped machinegun"
+set g_player_template_example_pickup_weapon_dropped_mortar "default" "What items does player get when they pickup a dropped mortar"
+set g_player_template_example_pickup_weapon_dropped_electro "default" "What items does player get when they pickup a dropped electro"
+set g_player_template_example_pickup_weapon_dropped_crylink "default" "What items does player get when they pickup a dropped crylink"
+set g_player_template_example_pickup_weapon_dropped_vortex "default" "What items does player get when they pickup a dropped vortex"
+set g_player_template_example_pickup_weapon_dropped_hagar "default" "What items does player get when they pickup a dropped hagar"
+set g_player_template_example_pickup_weapon_dropped_devastator "default" "What items does player get when they pickup a dropped devastator"
+set g_player_template_example_pickup_weapon_dropped_minelayer "default" "What items does player get when they pickup a dropped mine layer"
+set g_player_template_example_pickup_weapon_dropped_hlac "default" "What items does player get when they pickup a dropped HLAC"
+set g_player_template_example_pickup_weapon_dropped_rifle "default" "What items does player get when they pickup a dropped rifle"
+set g_player_template_example_pickup_weapon_dropped_seeker "default" "What items does player get when they pickup a dropped TAG seeker"
+set g_player_template_example_pickup_item_strength "default" "What items does player get when they pickup strength"
+set g_player_template_example_pickup_item_shield "default" "What items does player get when they pickup shield"
+
+// Damage section
+// Variables in this section define what happens during damage.
+set g_player_template_example_attack_scale "default" "How much player damages others. Higher values mean more damage"
+set g_player_template_example_defense_scale "default" "How much player gets damaged. Higher values mean less damage"
+set g_player_template_example_blaster_self_damage "default" "Whether player gets damaged with their own blaster"
index 65682b3ec7d27006a653449ad69e7b725ba69516..2513a803f344cc13c5fc35062920a8a3a085bf93 100644 (file)
@@ -711,6 +711,135 @@ void HUD_Mod_Dom(vector myPos, vector mySize)
        }
 }
 
+void HUD_Mod_GG(vector pos, vector mySize)
+{
+       mod_active = 1; // required in each mod function that always shows something
+       int stat_weapon = STAT(GUNGAME_LEADING_WEAPON);
+       vector pic_pos, pic_size;
+       if (mySize.x > mySize.y)
+       {
+               pic_pos = pos + eX * 0.25 * mySize.x;
+               pic_size = vec2(0.5 * mySize.x, mySize.y);
+       }
+       else
+       {
+               pic_pos = pos + eY * 0.25 * mySize.y;
+               pic_size = vec2(mySize.x, 0.5 * mySize.y);
+       }
+       string weapon_pic = string_null;
+       FOREACH(Weapons, it != WEP_Null,
+       {
+               if (it.m_id == stat_weapon)
+               {
+                       weapon_pic = it.model2;
+                       break;
+               }
+       });
+       if (!weapon_pic)
+       {
+               return;
+       }
+       drawpic_aspect_skin(pic_pos, weapon_pic, pic_size, '1 1 1', 1,
+               DRAWFLAG_NORMAL);
+}
+
+// Lyberta: surv
+void HUD_Mod_SURV(vector mypos, vector mysize)
+{
+       mod_active = 1; // required in each mod function that always shows something
+       float defenderhealth = STAT(SURV_DEFENDER_HEALTH);
+       // Draw a health bar
+       float margin = mysize.y / 10; // Leave a small margin to be stylish
+       vector healthbarpos = mypos;
+       healthbarpos.x += margin;
+       healthbarpos.y += margin;
+       vector healthbarsize = mysize;
+       healthbarsize.x -= margin * 2;
+       healthbarsize.y -= margin * 2;
+       vector healthbarcolor;
+       healthbarcolor.z = 0;
+       if (defenderhealth > 0.5)
+       {
+               healthbarcolor.x = defenderhealth * -2 + 2;
+               healthbarcolor.y = 1;
+       }
+       else
+       {
+               healthbarcolor.x = 1;
+               healthbarcolor.y = defenderhealth;
+       }
+       HUD_Panel_DrawProgressBar(healthbarpos, healthbarsize, "progressbar",
+               defenderhealth, false, 0, healthbarcolor, 0.50, DRAWFLAG_NORMAL);
+       // Draw defender picture
+       int defenderteam = STAT(SURV_DEFENDER_TEAM);
+       string defenderpic = "";
+       vector defendercolor;
+       switch (defenderteam)
+       {
+               case 1:
+               {
+                       defenderpic = "player_red";
+                       defendercolor = '1 0 0';
+                       break;
+               }
+               case 2:
+               {
+                       defenderpic = "player_blue";
+                       defendercolor = '0 0 1';
+                       break;
+               }
+               default:
+               {
+                       defendercolor = '1 1 1';
+                       break;
+               }
+       }
+       vector picpos = mypos;
+       vector picsize = mysize;
+       picsize.x = picsize.y;
+       drawpic_aspect_skin(picpos, defenderpic, picsize, '1 1 1', 1,
+               DRAWFLAG_NORMAL);
+       // Draw number of defenders
+       int numalive = STAT(SURV_DEFENDERS_ALIVE);
+       vector alivepos = mypos;
+       alivepos.x += picsize.x;
+       vector alivesize = picsize;
+       drawstring_aspect(alivepos, ftos(numalive), alivesize, defendercolor, 1,
+               DRAWFLAG_NORMAL);
+       // Draw the time left
+       float roundtime = STAT(SURV_ROUND_TIME);
+       if (roundtime < 0)
+       {
+               roundtime = 0;
+       }
+       vector roundtimepos = mypos;
+       roundtimepos.x += picsize.x * 2;
+       vector roundtimesize = mysize;
+       roundtimesize.x = mysize.x - picsize.x * 2;
+       drawstring_aspect(roundtimepos, seconds_tostring(roundtime), roundtimesize,
+               '1 1 1', 1, DRAWFLAG_NORMAL);
+       if (autocvar_developer == 0)
+       {
+               return;
+       }
+       // Debug info below
+       int redalive = STAT(REDALIVE);
+       int bluealive = STAT(BLUEALIVE);
+       int yellowalive = STAT(YELLOWALIVE);
+       int pinkalive = STAT(PINKALIVE);
+       string message = strcat(ftos(redalive), "/", ftos(yellowalive));
+       vector redpos = mypos;
+       redpos.y += mysize.y;
+       vector statsize = mysize;
+       statsize.x /= 2;
+       drawstring_aspect(redpos, message, statsize, '1 0 0', 1, DRAWFLAG_NORMAL);
+       message = strcat(ftos(bluealive), "/", ftos(pinkalive));
+       vector bluepos = mypos;
+       bluepos.x += mysize.x / 2;
+       bluepos.y += mysize.y;
+       drawstring_aspect(bluepos, message, statsize, '0 0 1', 1, DRAWFLAG_NORMAL);
+}
+
 void HUD_ModIcons_SetFunc()
 {
        HUD_ModIcons_GameType = gametype.m_modicons;
index 7a054606313f1e64d90b61105c2ae98683a909b9..e2aadf18d7808a6313771cbaccd8dba08dc63074 100644 (file)
@@ -29,6 +29,7 @@
 
 #include <common/vehicles/all.qh>
 #include <common/weapons/_all.qh>
+#include <common/mutators/mutator/overkill/okvortex.qh>
 #include <common/viewloc.qh>
 #include <common/minigames/cl_minigames.qh>
 #include <common/minigames/cl_minigames_hud.qh>
@@ -470,7 +471,7 @@ vector GetCurrentFov(float fov)
                {
                        entity wepent = viewmodels[slot];
                        if(wepent.switchweapon == wepent.activeweapon)
-                       if((wepent.activeweapon == WEP_VORTEX && !WEP_CVAR(vortex, secondary)) || (wepent.activeweapon == WEP_RIFLE && !WEP_CVAR(rifle, secondary))) // do NOT use switchweapon here
+                       if((wepent.activeweapon == WEP_VORTEX && !WEP_CVAR(vortex, secondary)) || (wepent.activeweapon == WEP_RIFLE && !WEP_CVAR(rifle, secondary)) || (wepent.activeweapon == WEP_OVERKILL_VORTEX && !WEP_CVAR(okvortex, secondary))) // do NOT use switchweapon here
                                zoomdir += button_attack2;
                }
        }
@@ -662,6 +663,7 @@ float TrueAimCheck(entity wepent)
                case WEP_MORTAR: // toss curve
                        return SHOTTYPE_HITWORLD;
                case WEP_VORTEX:
+               case WEP_OVERKILL_VORTEX:
                case WEP_VAPORIZER:
                        mv = MOVE_NORMAL;
                        break;
@@ -1186,6 +1188,10 @@ void HUD_Crosshair(entity this)
                                vortex_charge = STAT(VORTEX_CHARGE);
                                vortex_chargepool = STAT(VORTEX_CHARGEPOOL);
 
+                               float okvortex_charge, okvortex_chargepool;
+                               okvortex_charge = STAT(OVERKILL_VORTEX_CHARGE);
+                               okvortex_chargepool = STAT(OVERKILL_VORTEX_CHARGEPOOL);
+
                                float arc_heat = STAT(ARC_HEAT);
 
                                if(vortex_charge_movingavg == 0) // this should only happen if we have just loaded up the game
@@ -1214,6 +1220,26 @@ void HUD_Crosshair(entity this)
                                        ring_rgb = wcross_color;
                                        ring_image = "gfx/crosshair_ring_nexgun.tga";
                                }
+                               else if (autocvar_crosshair_ring && (wepent.activeweapon == WEP_OVERKILL_VORTEX) && okvortex_charge && autocvar_crosshair_ring_vortex)
+                               {
+                                       if (okvortex_chargepool || use_vortex_chargepool) {
+                                               use_vortex_chargepool = 1;
+                                               ring_inner_value = okvortex_chargepool;
+                                       } else {
+                                               vortex_charge_movingavg = (1 - autocvar_crosshair_ring_vortex_currentcharge_movingavg_rate) * vortex_charge_movingavg + autocvar_crosshair_ring_vortex_currentcharge_movingavg_rate * okvortex_charge;
+                                               ring_inner_value = bound(0, autocvar_crosshair_ring_vortex_currentcharge_scale * (okvortex_charge - vortex_charge_movingavg), 1);
+                                       }
+
+                                       ring_inner_alpha = autocvar_crosshair_ring_vortex_inner_alpha;
+                                       ring_inner_rgb = eX * autocvar_crosshair_ring_vortex_inner_color_red + eY * autocvar_crosshair_ring_vortex_inner_color_green + eZ * autocvar_crosshair_ring_vortex_inner_color_blue;
+                                       ring_inner_image = "gfx/crosshair_ring_inner.tga";
+
+                                       // draw the outer ring to show the current charge of the weapon
+                                       ring_value = okvortex_charge;
+                                       ring_alpha = autocvar_crosshair_ring_vortex_alpha;
+                                       ring_rgb = wcross_color;
+                                       ring_image = "gfx/crosshair_ring_nexgun.tga";
+                               }
                                else if (autocvar_crosshair_ring && wepent.activeweapon == WEP_MINE_LAYER && WEP_CVAR(minelayer, limit) && autocvar_crosshair_ring_minelayer)
                                {
                                        ring_value = bound(0, STAT(LAYED_MINES) / WEP_CVAR(minelayer, limit), 1); // if you later need to use the count of bullets in another place, then add a float for it. For now, no need to.
index 2fc2c404678883117dcd9205365e36317f5e997e..866e16227928c9e3c25826a8284c71b85652acaa 100644 (file)
@@ -1,4 +1,6 @@
 // generated file; do not modify
 
+#include <common/gamemodes/gamemode/gungame/_mod.inc>
 #include <common/gamemodes/gamemode/nexball/_mod.inc>
 #include <common/gamemodes/gamemode/onslaught/_mod.inc>
+#include <common/gamemodes/gamemode/survival/_mod.inc>
index d79957012609493478bdf9e0a03ea2116fec63c3..f4c598f697af8f0cb85c1eeec5ff5d2be982c4db 100644 (file)
@@ -1,4 +1,6 @@
 // generated file; do not modify
 
+#include <common/gamemodes/gamemode/gungame/_mod.qh>
 #include <common/gamemodes/gamemode/nexball/_mod.qh>
 #include <common/gamemodes/gamemode/onslaught/_mod.qh>
+#include <common/gamemodes/gamemode/survival/_mod.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/gungame/_mod.inc b/qcsrc/common/gamemodes/gamemode/gungame/_mod.inc
new file mode 100644 (file)
index 0000000..a0bb673
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/gungame/sv_gungame.qc>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/gungame/_mod.qh b/qcsrc/common/gamemodes/gamemode/gungame/_mod.qh
new file mode 100644 (file)
index 0000000..d97ca6f
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/gungame/sv_gungame.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/gungame/sv_gungame.qc b/qcsrc/common/gamemodes/gamemode/gungame/sv_gungame.qc
new file mode 100644 (file)
index 0000000..b2db39b
--- /dev/null
@@ -0,0 +1,232 @@
+/// \file
+/// \brief Source file that contains implementation of the GunGame gamemode.
+/// \author Lyberta
+/// \copyright GNU GPLv2 or any later version.
+
+#include "sv_gungame.qh"
+
+//============================ Constants ======================================
+
+const string GUNGAME_WEAPONS = "g_gg_weapons";
+
+//======================= Global variables ====================================
+
+/// \brief Number of kills needed to advance to the next weapon.
+int autocvar_g_gg_kills_per_weapon;
+
+int gungame_maxlevel; ///< Player who reaches this level wins.
+string gungame_weapons; ///< Holds weapons corresponding to levels.
+
+entity gungame_leading_player; ///< Holds the leading player.
+int gungame_leading_level; ///< Holds the leading level.
+entity gungame_leading_weapon; ///< Holds the leading weapon.
+
+//====================== Forward declarations =================================
+
+/// \brief Resets the state to initial one.
+/// \return No return.
+void GunGame_Reset();
+
+/// \brief Returns the weapon that corresponds to the given level.
+/// \param[in] level Level of the weapon.
+/// \return Weapon corresponding to the given level.
+entity GunGame_GetWeapon(int level);
+
+/// \brief Updates stats of all players.
+/// \return No return.
+void GunGame_UpdateStats();
+
+//========================= Free functions ====================================
+
+void GunGame_Initialize()
+{
+       GunGame_Reset();
+}
+
+void GunGame_Reset()
+{
+       if (gungame_weapons)
+       {
+               strunzone(gungame_weapons);
+       }
+       gungame_weapons = strzone(cvar_string(GUNGAME_WEAPONS));
+       gungame_maxlevel = tokenize_console(gungame_weapons) *
+               autocvar_g_gg_kills_per_weapon;
+       if (gungame_maxlevel == 0)
+       {
+               error("GunGame: Invalid weapon configuration.");
+       }
+       GameRules_limit_score(gungame_maxlevel);
+       gungame_leading_player = NULL;
+       gungame_leading_level = 0;
+       gungame_leading_weapon = GunGame_GetWeapon(0);
+       GunGame_UpdateStats();
+}
+
+entity GunGame_GetWeapon(int level)
+{
+       if (level >= gungame_maxlevel)
+       {
+               return NULL;
+       }
+       tokenize_console(gungame_weapons);
+       string weapon = argv(floor(level / autocvar_g_gg_kills_per_weapon));
+       FOREACH(Weapons, it != WEP_Null,
+       {
+               if (it.netname == weapon)
+               {
+                       return it;
+               }
+       });
+       error("GunGame_GetWeapon: Invalid level or weapon name");
+       return NULL;
+}
+
+/// \brief Returns the player level.
+/// \param[in] player Player to check.
+/// \return Level of the player.
+int GunGame_GetPlayerLevel(entity player)
+{
+       return PlayerScore_Get(player, SP_SCORE);
+}
+
+/// \brief Updates the information about the leading player.
+/// \return No return.
+void GunGame_UpdateLeadingPlayer()
+{
+       entity previous_leader = gungame_leading_player;
+       FOREACH_CLIENT(true,
+       {
+               if (gungame_leading_player == NULL)
+               {
+                       gungame_leading_player = it;
+                       continue;
+               }
+               if (GunGame_GetPlayerLevel(it) > GunGame_GetPlayerLevel(
+                       gungame_leading_player))
+               {
+                       gungame_leading_player = it;
+               }
+       });
+       if (gungame_leading_player == NULL)
+       {
+               return;
+       }
+       if ((gungame_leading_player == previous_leader) &&
+               (GunGame_GetPlayerLevel(gungame_leading_player) ==
+               gungame_leading_level))
+       {
+               return;
+       }
+       gungame_leading_level = GunGame_GetPlayerLevel(gungame_leading_player);
+       gungame_leading_weapon = GunGame_GetWeapon(gungame_leading_level);
+       GunGame_UpdateStats();
+       //PrintToChatAll(strcat(gungame_leading_player.netname,
+       //      " is leading with level ", ftos(gungame_leading_level)));
+}
+
+void GunGame_UpdateStats()
+{
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               STAT(GUNGAME_LEADING_WEAPON, it) = gungame_leading_weapon.m_id;
+       });
+}
+
+/// \brief Gives the player a weapon that corresponds to their level.
+/// \param[in,out] player Player to give weapon to.
+/// \return No return.
+void GunGame_GivePlayerWeapon(entity player)
+{
+       int level = GunGame_GetPlayerLevel(player);
+       if (level >= gungame_maxlevel)
+       {
+               return;
+       }
+       entity weapon = GunGame_GetWeapon(level);
+       player.weapons |= weapon.m_wepset;
+       centerprint(player, strcat("^3Level ", ftos(level + 1), ": ^2",
+               weapon.m_name));
+}
+
+//============================= Hooks ========================================
+
+/// \brief Hook that is called to determine if there is a weapon arena.
+MUTATOR_HOOKFUNCTION(gg, SetWeaponArena)
+{
+       //PrintToChatAll("SetWeaponArena");
+       M_ARGV(0, string) = "off";
+}
+
+/// \brief Hook that is called to determine start items of all players.
+MUTATOR_HOOKFUNCTION(gg, SetStartItems)
+{
+       //PrintToChatAll("SetStartItems");
+       start_weapons = WEPSET(Null);
+       warmup_start_weapons = WEPSET(Null);
+}
+
+/// \brief Hook that is called when an item is about to spawn.
+MUTATOR_HOOKFUNCTION(gg, FilterItem)
+{
+       //PrintToChatAll("FilterItem");
+       entity item = M_ARGV(0, entity);
+       if (item.itemdef.instanceOfWeaponPickup)
+       {
+               // Block weapons from spawning.
+               return true;
+       }
+}
+
+/// \brief Hook that is called when player connects to the server.
+MUTATOR_HOOKFUNCTION(gg, ClientConnect)
+{
+       entity player = M_ARGV(0, entity);
+       if (!IS_REAL_CLIENT(player))
+       {
+               return true;
+       }
+       STAT(GUNGAME_LEADING_WEAPON, player) = gungame_leading_weapon.m_id;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(gg, reset_map_global)
+{
+       GunGame_Reset();
+}
+
+/// \brief Hook that is called when player spawns.
+MUTATOR_HOOKFUNCTION(gg, PlayerSpawn, CBC_ORDER_LAST)
+{
+       entity player = M_ARGV(0, entity);
+       player.weapons = WEPSET(Null);
+       GunGame_GivePlayerWeapon(player);
+       player.items |= IT_UNLIMITED_AMMO;
+}
+
+/// \brief Hook which is called when the player tries to throw their weapon.
+MUTATOR_HOOKFUNCTION(gg, ForbidThrowCurrentWeapon)
+{
+       return true;
+}
+
+/// \brief Hook that is called when player dies.
+MUTATOR_HOOKFUNCTION(gg, PlayerDies)
+{
+       GunGame_UpdateLeadingPlayer();
+       entity attacker = M_ARGV(1, entity);
+       if (!IS_PLAYER(attacker) || IS_DEAD(attacker) || (GunGame_GetPlayerLevel(
+               attacker) >= gungame_maxlevel))
+       {
+               return;
+       }
+       attacker.weapons = WEPSET(Null);
+       GunGame_GivePlayerWeapon(attacker);
+}
+
+/// \brief Hook that determines whether remaining frags are announced.
+MUTATOR_HOOKFUNCTION(gg, Scores_CountFragsRemaining)
+{
+       // announce remaining frags
+       return true;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/gungame/sv_gungame.qh b/qcsrc/common/gamemodes/gamemode/gungame/sv_gungame.qh
new file mode 100644 (file)
index 0000000..44df2e3
--- /dev/null
@@ -0,0 +1,20 @@
+/// \file
+/// \brief Header file that describes the GunGame gamemode.
+/// \author Lyberta
+/// \copyright GNU GPLv2 or any later version.
+
+#pragma once
+
+/// \brief Initializes global data for the gametype.
+/// \return No return.
+void GunGame_Initialize();
+
+REGISTER_MUTATOR(gg, false)
+{
+       MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               GunGame_Initialize();
+       }
+       return 0;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/survival/_mod.inc b/qcsrc/common/gamemodes/gamemode/survival/_mod.inc
new file mode 100644 (file)
index 0000000..006e93f
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/survival/sv_survival.qc>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/survival/_mod.qh b/qcsrc/common/gamemodes/gamemode/survival/_mod.qh
new file mode 100644 (file)
index 0000000..5ace954
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/survival/sv_survival.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc b/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc
new file mode 100644 (file)
index 0000000..0860778
--- /dev/null
@@ -0,0 +1,2273 @@
+#include "sv_survival.qh"
+
+#include <common/mutators/mutator/overkill/hmg.qh>
+#include <common/mutators/mutator/overkill/rpc.qh>
+
+//============================ Constants ======================================
+
+const int SURVIVAL_TEAM_1_BIT = BIT(0); ///< Bitmask of the first team.
+const int SURVIVAL_TEAM_2_BIT = BIT(1); ///< Bitmask of the second team.
+
+/// \brief Used when bitfield team count is requested.
+const int SURVIVAL_TEAM_BITS = 3;
+
+enum
+{
+       SURVIVAL_TYPE_COOP, ///< All humans are on the defender team.
+       SURVIVAL_TYPE_VERSUS ///< Humans take turns between attackers and defenders.
+};
+
+const string SURVIVAL_TYPE_COOP_VALUE = "coop"; ///< Cvar value for coop.
+const string SURVIVAL_TYPE_VERSUS_VALUE = "versus"; ///< Cvar value for versus.
+
+enum
+{
+       /// \brief First round where there is timelimit set by the server.
+       SURVIVAL_ROUND_FIRST,
+       /// \brief Second round where defender team tries to survive for the first
+       /// round's time.
+       SURVIVAL_ROUND_SECOND
+};
+
+enum
+{
+       /// \brief Player is spectating and has no intention of playing.
+       SURVIVAL_STATE_NOT_PLAYING,
+       /// \brief Player is playing the game.
+       SURVIVAL_STATE_PLAYING = 1
+};
+
+enum
+{
+       SURVIVAL_ROLE_NONE, ///< Player is not playing.
+       SURVIVAL_ROLE_PLAYER, ///< Player is an attacker or defender.
+       SURVIVAL_ROLE_CANNON_FODDER ///< Player is a cannon fodder.
+};
+
+SOUND(SURV_3_FRAGS_LEFT, "announcer/default/3fragsleft");
+SOUND(SURV_2_FRAGS_LEFT, "announcer/default/2fragsleft");
+SOUND(SURV_1_FRAG_LEFT, "announcer/default/1fragleft");
+
+SOUND(SURV_RED_SCORES, "ctf/red_capture");
+SOUND(SURV_BLUE_SCORES, "ctf/blue_capture");
+
+//======================= Global variables ====================================
+
+float autocvar_g_surv_warmup; ///< Warmup time in seconds.
+float autocvar_g_surv_round_timelimit; ///< First round time limit in seconds.
+
+int autocvar_g_surv_point_limit; ///< Maximum number of points.
+int autocvar_g_surv_point_leadlimit; ///< Maximum lead of a single team.
+
+/// \brief How much players are allowed in teams (excluding cannon fodder).
+int autocvar_g_surv_team_size;
+/// \brief If set, defenders will not be shown on the radar.
+int autocvar_g_surv_stealth;
+/// \brief Whether to allow spectating enemy players while dead.
+bool autocvar_g_surv_spectate_enemies;
+
+/// \brief Whether to force overkill player models for attackers.
+int autocvar_g_surv_attacker_force_overkill_models;
+/// \brief Whether to force overkill player models for defenders.
+int autocvar_g_surv_defender_force_overkill_models;
+/// \brief Whether to force overkill player models for cannon fodder.
+int autocvar_g_surv_cannon_fodder_force_overkill_models;
+
+/// \brief How much score attackers gain per 1 point of damage.
+float autocvar_g_surv_attacker_damage_score;
+
+/// \brief How much score attackers get for fragging defenders.
+float autocvar_g_surv_attacker_frag_score;
+
+/// \brief How much health do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_health;
+/// \brief How much armor do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_armor;
+/// \brief How many shells do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_shells;
+/// \brief How many bullets do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_bullets;
+/// \brief How many rockets do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_rockets;
+/// \brief How many cells do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_cells;
+/// \brief How much plasma do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_plasma;
+/// \brief How much fuel do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_fuel;
+/// \brief How much health do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_health;
+/// \brief How much armor do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_armor;
+/// \brief How many shells do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_shells;
+/// \brief How many bullets do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_bullets;
+/// \brief How many rockets do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_rockets;
+/// \brief How many cells do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_cells;
+/// \brief How much plasma do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_plasma;
+/// \brief How much fuel do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_fuel;
+
+/// \brief Holds the state of the player. See SURVIVAL_STATE constants.
+.int surv_state;
+/// \brief Holds the role of the player. See SURVIVAL_ROLE constants.
+.int surv_role;
+.string surv_savedplayermodel; ///< Initial player model.
+/// \brief Player state used during replacement of bot player with real player.
+.entity surv_savedplayerstate;
+.string surv_playermodel; ///< Player model forced by the game.
+
+.entity surv_attack_sprite; ///< Holds the sprite telling attackers to attack.
+.entity surv_defend_sprite; ///< Holds the sprite telling defenders to defend.
+
+int surv_type; ///< Holds the type of survival. See SURVIVAL_TYPE constants.
+bool surv_warmup; ///< Holds whether warmup is active.
+/// \brief Holds the type of the current round. See SURVIVAL_ROUND constants.
+int surv_roundtype;
+bool surv_isroundactive; ///< Holds whether the round is active.
+float surv_roundstarttime; ///< Holds the time of the round start.
+/// \brief Holds the time needed to survive in the second round.
+float surv_timetobeat;
+
+int surv_attackerteam; ///< Holds the attacker team.
+int surv_defenderteam; ///< Holds the defender team.
+
+int surv_attackerteambit; ///< Hols the attacker team bitmask.
+int surv_defenderteambit; ///< Holds the defender team bitmask.
+
+int surv_numattackers; ///< Holds the number of players in attacker team.
+int surv_numdefenders; ///< Holds the number of players in defender team.
+
+/// \brief Holds the number of humans in attacker team.
+int surv_numattackerhumans;
+/// \brief Holds the number of humans in defender team.
+int surv_numdefenderhumans;
+
+/// \brief Holds the number of attacker players that are alive.
+int surv_numattackersalive;
+/// \brief Holds the number of defender players that are alive.
+int surv_numdefendersalive;
+
+bool surv_autobalance; ///< Holds whether autobalance is active.
+bool surv_announcefrags; ///< Holds whether remaining frags must be announced.
+bool surv_allowed_to_spawn; ///< Holds whether players are allowed to spawn.
+
+//====================== Forward declarations =================================
+
+/// \brief Determines whether the round can start.
+/// \return True if the round can start, false otherwise.
+bool Surv_CanRoundStart();
+
+/// \brief Determines whether the round can end.
+/// \return True if the round can end, false otherwise.
+bool Surv_CanRoundEnd();
+
+/// \brief Called when the round starts.
+/// \return No return.
+void Surv_RoundStart();
+
+/// \brief Returns whether player has been eliminated.
+/// \param[in] player Player to check.
+/// \return True if player is eliminated, false otherwise.
+bool Surv_IsEliminated(entity player);
+
+/// \brief Updates stats of team count on HUD.
+/// \return No return.
+void Surv_UpdateTeamStats();
+
+/// \brief Updates stats of alive players on HUD.
+/// \return No return.
+void Surv_UpdateAliveStats();
+
+/// \brief Updates defender health on the HUD.
+/// \return No return.
+void Surv_UpdateDefenderHealthStat();
+
+/// \brief Updates the health of defender sprite.
+/// \param[in,out] player Player that has the sprite.
+/// \return No return.
+void Surv_UpdateWaypointSpriteHealth(entity player);
+
+//========================= Free functions ====================================
+
+void Surv_Initialize()
+{
+       switch (cvar_string("g_surv_type"))
+       {
+               case SURVIVAL_TYPE_COOP_VALUE:
+               {
+                       surv_type = SURVIVAL_TYPE_COOP;
+                       break;
+               }
+               case SURVIVAL_TYPE_VERSUS_VALUE:
+               {
+                       surv_type = SURVIVAL_TYPE_VERSUS;
+                       break;
+               }
+               default:
+               {
+                       error("Invalid survival type.");
+               }
+       }
+       surv_roundtype = SURVIVAL_ROUND_FIRST;
+       surv_isroundactive = false;
+       surv_roundstarttime = time;
+       surv_timetobeat = autocvar_g_surv_round_timelimit;
+       if (random() < 0.5)
+       {
+               surv_attackerteam = NUM_TEAM_1;
+               surv_defenderteam = NUM_TEAM_2;
+               surv_attackerteambit = SURVIVAL_TEAM_1_BIT;
+               surv_defenderteambit = SURVIVAL_TEAM_2_BIT;
+       }
+       else
+       {
+               surv_attackerteam = NUM_TEAM_2;
+               surv_defenderteam = NUM_TEAM_1;
+               surv_attackerteambit = SURVIVAL_TEAM_2_BIT;
+               surv_defenderteambit = SURVIVAL_TEAM_1_BIT;
+       }
+       surv_numattackers = 0;
+       surv_numdefenders = 0;
+       surv_numattackerhumans = 0;
+       surv_numdefenderhumans = 0;
+       surv_numattackersalive = 0;
+       surv_numdefendersalive = 0;
+       surv_autobalance = true;
+       surv_announcefrags = true;
+       surv_allowed_to_spawn = true;
+       precache_all_playermodels("models/ok_player/*.dpm");
+       ScoreRules_basics(SURVIVAL_TEAM_BITS, SFL_SORT_PRIO_PRIMARY, 0, true);
+       ScoreInfo_SetLabel_TeamScore(1, "rounds", SFL_SORT_PRIO_PRIMARY);
+       ScoreRules_basics_end();
+       round_handler_Spawn(Surv_CanRoundStart, Surv_CanRoundEnd, Surv_RoundStart);
+       round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
+       EliminatedPlayers_Init(Surv_IsEliminated);
+       GameRules_teams(true);
+       GameRules_limit_score(autocvar_g_surv_point_limit);
+       GameRules_limit_lead(autocvar_g_surv_point_leadlimit);
+}
+
+/// \brief Returns the name of the template of the given player.
+/// \param[in] player Player to inspect.
+/// \return Name of the template of the given player.
+string Surv_GetPlayerTemplate(entity player)
+{
+       switch (player.team)
+       {
+               case surv_attackerteam:
+               {
+                       switch (player.surv_role)
+                       {
+                               case SURVIVAL_ROLE_NONE:
+                               case SURVIVAL_ROLE_PLAYER:
+                               {
+                                       return cvar_string("g_surv_attacker_template");
+                               }
+                               case SURVIVAL_ROLE_CANNON_FODDER:
+                               {
+                                       return cvar_string("g_surv_cannon_fodder_template");
+                               }
+                       }
+               }
+               case surv_defenderteam:
+               {
+                       return cvar_string("g_surv_defender_template");
+               }
+       }
+       return "default";
+}
+
+/// \brief Saves the player state. Used to seamlessly swap bots with humans.
+/// \param[in] player Player to save the state of.
+/// \return Entity containing the player state.
+entity Surv_SavePlayerState(entity player)
+{
+       entity state = spawn();
+       state.origin = player.origin;
+       state.velocity = player.velocity;
+       state.angles = player.angles;
+       state.health = player.health;
+       state.armorvalue = player.armorvalue;
+       state.ammo_shells = player.ammo_shells;
+       state.ammo_nails = player.ammo_nails;
+       state.ammo_rockets = player.ammo_rockets;
+       state.ammo_cells = player.ammo_cells;
+       state.weapons = player.weapons;
+       state.items = player.items;
+       state.superweapons_finished = player.superweapons_finished;
+       return state;
+}
+
+/// \brief Restores a saved player state.
+/// \param[in] player Player to restore the state of.
+/// \param[in] st State to restore.
+/// \return No return.
+void Surv_RestorePlayerState(entity player, entity st)
+{
+       player.origin = st.origin;
+       player.velocity = st.velocity;
+       player.angles = st.angles;
+       player.health = st.health;
+       player.armorvalue = st.armorvalue;
+       player.ammo_shells = st.ammo_shells;
+       player.ammo_nails = st.ammo_nails;
+       player.ammo_rockets = st.ammo_rockets;
+       player.ammo_cells = st.ammo_cells;
+       player.weapons = st.weapons;
+       player.items = st.items;
+       player.superweapons_finished = st.superweapons_finished;
+}
+
+/// \brief Returns the attacker with the lowest score.
+/// \param[in] bot Whether to search only for bots.
+/// \return Attacker with the lowest score or NULL if not found.
+entity Surv_FindLowestAttacker(bool bot)
+{
+       entity player = NULL;
+       float score = FLOAT_MAX;
+       FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
+       {
+               if ((it.team == surv_attackerteam) && (it.surv_role ==
+                       SURVIVAL_ROLE_PLAYER))
+               {
+                       float tempscore = PlayerScore_Get(it, SP_SCORE);
+                       if (tempscore < score)
+                       {
+                               player = it;
+                               score = tempscore;
+                       }
+               }
+       });
+       return player;
+}
+
+/// \brief Returns the defender with the lowest score.
+/// \param[in] bot Whether to search only for bots.
+/// \param[in] alive Whether to search only for alive players.
+/// \return Defender with the lowest score or NULL if not found.
+entity Surv_FindLowestDefender(bool bot, bool alive)
+{
+       entity player = NULL;
+       float score = FLOAT_MAX;
+       FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
+       {
+               if ((it.team == surv_defenderteam) && (alive ? !IS_DEAD(it) : true))
+               {
+                       float tempscore = PlayerScore_Get(it, SP_SCORE);
+                       if (tempscore < score)
+                       {
+                               player = it;
+                               score = tempscore;
+                       }
+               }
+       });
+       return player;
+}
+
+/// \brief Returns the cannon fodder.
+/// \return Cannon fodder or NULL if not found.
+entity Surv_FindCannonFodder()
+{
+       FOREACH_CLIENT(IS_BOT_CLIENT(it),
+       {
+               if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+               {
+                       return it;
+               }
+       });
+       return NULL;
+}
+
+/// \brief Changes the number of players in a team.
+/// \param[in] teamnum Team to adjust.
+/// \param[in] delta Amount to adjust by.
+/// \return No return.
+void Surv_ChangeNumberOfPlayers(int teamnum, int delta)
+{
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       surv_numattackers += delta;
+                       LOG_TRACE("Number of attackers = ", ftos(surv_numattackers),
+                               " was = ", ftos(surv_numattackers - delta));
+                       Surv_UpdateTeamStats();
+                       return;
+               }
+               case surv_defenderteam:
+               {
+                       surv_numdefenders += delta;
+                       LOG_TRACE("Number of defenders = ", ftos(surv_numdefenders),
+                               " was = ", ftos(surv_numdefenders - delta));
+                       Surv_UpdateTeamStats();
+                       return;
+               }
+       }
+}
+
+/// \brief Changes the number of alive players in a team.
+/// \param[in] teamnum Team to adjust.
+/// \param[in] delta Amount to adjust by.
+/// \return No return.
+void Surv_ChangeNumberOfAlivePlayers(int teamnum, int delta)
+{
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       surv_numattackersalive += delta;
+                       LOG_TRACE("Number of alive attackers = ", ftos(
+                               surv_numattackersalive), " was = ", ftos(surv_numattackersalive
+                               - delta));
+                       break;
+               }
+               case surv_defenderteam:
+               {
+                       surv_numdefendersalive += delta;
+                       LOG_TRACE("Number of alive defenders = ", ftos(
+                               surv_numdefendersalive), " was = ", ftos(surv_numdefendersalive
+                               - delta));
+                       break;
+               }
+       }
+       Surv_UpdateAliveStats();
+       eliminatedPlayers.SendFlags |= 1;
+}
+
+/// \brief Sets the player role.
+/// \param[in,out] player Player to adjust.
+/// \param[in] role Role to set.
+/// \return No return.
+void Surv_SetPlayerRole(entity player, int role)
+{
+       if (player.surv_role == role)
+       {
+               return;
+       }
+       player.surv_role = role;
+       switch (role)
+       {
+               case SURVIVAL_ROLE_NONE:
+               {
+                       LOG_TRACE(player.netname, " now has no role.");
+                       break;
+               }
+               case SURVIVAL_ROLE_PLAYER:
+               {
+                       LOG_TRACE(player.netname, " is now a player.");
+                       break;
+               }
+               case SURVIVAL_ROLE_CANNON_FODDER:
+               {
+                       LOG_TRACE(player.netname, " is now a cannon fodder.");
+                       break;
+               }
+       }
+}
+
+/// \brief Adds player to team. Handles bookkeeping information.
+/// \param[in] player Player to add.
+/// \param[in] teamnum Team to add to.
+/// \return True on success, false otherwise.
+bool Surv_AddPlayerToTeam(entity player, int teamnum)
+{
+       LOG_TRACE("Survival: AddPlayerToTeam, player: ", player.netname);
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       LOG_TRACE("Attacker team");
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               LOG_TRACE("Cannon fodder is switching team");
+                               return true;
+                       }
+                       if (IS_BOT_CLIENT(player))
+                       {
+                               LOG_TRACE("Client is bot");
+                               LOG_TRACE("Attackers = ", ftos(surv_numattackers));
+                               if (surv_numattackers < autocvar_g_surv_team_size)
+                               {
+                                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+                                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                                       return true;
+                               }
+                               Surv_SetPlayerRole(player, SURVIVAL_ROLE_CANNON_FODDER);
+                               return true;
+                       }
+                       LOG_TRACE("Client is not a bot");
+                       LOG_TRACE("Attackers = ", ftos(surv_numattackers));
+                       if (surv_numattackers >= autocvar_g_surv_team_size)
+                       {
+                               LOG_TRACE("Removing bot");
+                               // Remove bot to make space for human.
+                               entity bot = Surv_FindLowestAttacker(true);
+                               if (bot == NULL)
+                               {
+                                       LOG_TRACE("No valid bot to remove");
+                                       // No space in team, denying team change.
+                                       TRANSMUTE(Spectator, player);
+                                       return false;
+                               }
+                               LOG_TRACE("Changing ", bot.netname,
+                                       " from attacker to cannon fodder.");
+                               Surv_SetPlayerRole(bot, SURVIVAL_ROLE_CANNON_FODDER);
+                               if (!IS_DEAD(bot))
+                               {
+                                       Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
+                               }
+                               Surv_ChangeNumberOfPlayers(teamnum, -1);
+                               LOG_TRACE("Removed bot");
+                       }
+                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                       ++surv_numattackerhumans;                       
+                       LOG_TRACE("Human attackers = ", ftos(surv_numattackerhumans));
+                       if ((surv_autobalance == false) || (surv_numattackers -
+                               surv_numdefenders) < 2)
+                       {
+                               return true;
+                       }
+                       entity lowestplayer = Surv_FindLowestAttacker(true);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                               surv_autobalance = savedautobalance;
+                               return true;
+                       }
+                       lowestplayer = Surv_FindLowestAttacker(false);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                               surv_autobalance = savedautobalance;
+                       }
+                       return true;
+               }
+               case surv_defenderteam:
+               {
+                       LOG_TRACE("Defender team");
+                       if (IS_BOT_CLIENT(player))
+                       {
+                               LOG_TRACE("Client is bot");
+                               LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
+                               if (surv_numdefenders < autocvar_g_surv_team_size)
+                               {
+                                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+                                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                                       return true;
+                               }
+                               LOG_TRACE("No space for defender, switching to attacker");
+                               SetPlayerTeamSimple(player, surv_attackerteam);
+                               return false;
+                       }
+                       LOG_TRACE("Client is not a bot");
+                       LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
+                       if (surv_numdefenders >= autocvar_g_surv_team_size)
+                       {
+                               LOG_TRACE("Removing bot");
+                               // Remove bot to make space for human.
+                               entity bot = Surv_FindLowestDefender(true, true);
+                               if (bot == NULL)
+                               {
+                                       bot = Surv_FindLowestDefender(true, false);
+                               }
+                               if (bot == NULL)
+                               {
+                                       LOG_TRACE("No valid bot to remove");
+                                       // No space in team, denying team change.
+                                       TRANSMUTE(Spectator, player);
+                                       return false;
+                               }
+                               LOG_TRACE("Changing ", bot.netname,
+                                       " from defender to cannon fodder.");
+                               if (!IS_DEAD(bot))
+                               {
+                                       player.surv_savedplayerstate = Surv_SavePlayerState(bot);
+                               }
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               surv_announcefrags = false;
+                               SetPlayerTeamSimple(bot, surv_attackerteam);
+                               surv_autobalance = savedautobalance;
+                               surv_announcefrags = true;
+                               LOG_TRACE("Removed bot");
+                       }
+                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                       ++surv_numdefenderhumans;
+                       LOG_TRACE("Human defenders = ", ftos(surv_numdefenderhumans));
+                       if ((surv_autobalance == false) || (surv_numdefenders -
+                               surv_numattackers) < 2)
+                       {
+                               return true;
+                       }
+                       entity lowestplayer = Surv_FindLowestDefender(true, false);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
+                               surv_autobalance = savedautobalance;
+                               return true;
+                       }
+                       lowestplayer = Surv_FindLowestDefender(false, false);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
+                               surv_autobalance = savedautobalance;
+                       }
+                       return true;
+               }
+               case -1:
+               {
+                       LOG_TRACE("Spectator team");
+                       player.surv_role = SURVIVAL_ROLE_NONE;                  
+                       return false;
+               }
+       }
+       LOG_TRACE("Invalid team");
+       player.surv_role = SURVIVAL_ROLE_NONE;
+       return false;
+}
+
+/// \brief Removes player from team. Handles bookkeeping information.
+/// \param[in] player Player to remove.
+/// \param[in] Team to remove from.
+/// \return No return.
+void Surv_RemovePlayerFromTeam(entity player, int teamnum)
+{
+       LOG_TRACE("Survival: RemovePlayerFromTeam, player: ", player.netname);
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       LOG_TRACE("Attacker team");
+                       if (player.surv_role == SURVIVAL_ROLE_NONE)
+                       {
+                               string message = strcat("RemovePlayerFromTeam: ",
+                                       player.netname, " has invalid role.");
+                               DebugPrintToChatAll(message);
+                               return;
+                       }
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
+                               return;
+                       }
+                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
+                       Surv_ChangeNumberOfPlayers(teamnum, -1);
+                       if (!IS_BOT_CLIENT(player))
+                       {
+                               --surv_numattackerhumans;
+                       }
+                       if ((surv_autobalance == false) || (surv_numattackers >=
+                               surv_numdefenders))
+                       {
+                               return;
+                       }
+                       // Add bot to keep teams balanced.
+                       entity lowestplayer = Surv_FindCannonFodder();
+                       if (lowestplayer != NULL)
+                       {
+                               LOG_TRACE("Changing ", lowestplayer.netname,
+                                       " from cannon fodder to attacker.");
+                               Surv_SetPlayerRole(lowestplayer, SURVIVAL_ROLE_PLAYER);
+                               Surv_ChangeNumberOfPlayers(teamnum, +1);
+                               if (!IS_DEAD(lowestplayer))
+                               {
+                                       Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
+                               }
+                               return;
+                       }
+                       lowestplayer = Surv_FindLowestDefender(true, false);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
+                               surv_autobalance = savedautobalance;
+                               return;
+                       }
+                       lowestplayer = Surv_FindLowestDefender(false, false);
+                       if (lowestplayer == NULL)
+                       {
+                               return;
+                       }
+                       bool savedautobalance = surv_autobalance;
+                       surv_autobalance = false;
+                       SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
+                       surv_autobalance = savedautobalance;
+                       return;
+               }
+               case surv_defenderteam:
+               {
+                       LOG_TRACE("Defender team");
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               // This happens during team switch. We don't need to change
+                               // anything.
+                               LOG_TRACE("Cannon fodder. Assuming team switch");
+                               return;
+                       }
+                       if (player.surv_role != SURVIVAL_ROLE_PLAYER)
+                       {
+                               string message = strcat("RemovePlayerFromTeam: ",
+                                       player.netname, " has invalid role.");
+                               DebugPrintToChatAll(message);
+                               return;
+                       }
+                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
+                       Surv_ChangeNumberOfPlayers(teamnum, -1);
+                       if (!IS_BOT_CLIENT(player))
+                       {
+                               --surv_numdefenderhumans;
+                       }
+                       if ((surv_autobalance == false) || (surv_numdefenders >=
+                               surv_numattackers))
+                       {
+                               return;
+                       }
+                       // Add bot to keep teams balanced.
+                       entity lowestplayer = Surv_FindCannonFodder();
+                       if (lowestplayer != NULL)
+                       {
+                               LOG_TRACE("Changing ", lowestplayer.netname,
+                                       " from cannon fodder to defender.");
+                               if (!IS_DEAD(player))
+                               {
+                                       lowestplayer.surv_savedplayerstate =
+                                               Surv_SavePlayerState(player);
+                               }
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               surv_announcefrags = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                               surv_autobalance = savedautobalance;
+                               surv_announcefrags = true;                                      
+                               return;
+                       }
+                       lowestplayer = Surv_FindLowestAttacker(true);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               surv_announcefrags = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                               surv_autobalance = savedautobalance;
+                               surv_announcefrags = true;
+                               return;
+                       }
+                       lowestplayer = Surv_FindLowestAttacker(false);
+                       if (lowestplayer == NULL)
+                       {
+                               return;
+                       }
+                       bool savedautobalance = surv_autobalance;
+                       surv_autobalance = false;
+                       surv_announcefrags = false;
+                       SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                       surv_autobalance = savedautobalance;
+                       surv_announcefrags = true;
+                       return;
+               }
+               case -1:
+               {
+                       LOG_TRACE("Spectator team");
+                       return;
+               }
+               default:
+               {
+                       LOG_TRACE("Invalid team");
+                       return;
+               }
+       }
+}
+
+/// \brief Updates stats of team count on HUD.
+/// \return No return.
+void Surv_UpdateTeamStats()
+{
+       // Debug stuff
+       if (surv_attackerteam == NUM_TEAM_1)
+       {
+               yellowalive = surv_numattackers;
+               pinkalive = surv_numdefenders;
+       }
+       else
+       {
+               pinkalive = surv_numattackers;
+               yellowalive = surv_numdefenders;
+       }
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               it.yellowalive_stat = yellowalive;
+               it.pinkalive_stat = pinkalive;
+       });
+}
+
+/// \brief Adds player to alive list. Handles bookkeeping information.
+/// \param[in] player Player to add.
+/// \param[in] teamnum Team of the player.
+/// \return No return.
+void Surv_AddPlayerToAliveList(entity player, int teamnum)
+{
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_PLAYER)
+                       {
+                               Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
+                       }
+                       return;
+               }
+               case surv_defenderteam:
+               {
+                       Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
+                       return;
+               }
+       }
+}
+
+/// \brief Removes player from alive list. Handles bookkeeping information.
+/// \param[in] player Player to remove.
+/// \param[in] teamnum Team of the player.
+/// \return No return.
+void Surv_RemovePlayerFromAliveList(entity player, int teamnum)
+{
+       if (player.surv_attack_sprite)
+       {
+               WaypointSprite_Kill(player.surv_attack_sprite);
+       }
+       if (player.surv_defend_sprite)
+       {
+               WaypointSprite_Kill(player.surv_defend_sprite);
+       }
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_PLAYER)
+                       {
+                               Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
+                       }
+                       return;
+               }
+               case surv_defenderteam:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               // This happens during team switch. We don't need to change
+                               // anything.
+                               return;
+                       }
+                       Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
+                       if (warmup_stage || surv_allowed_to_spawn || !surv_announcefrags)
+                       {
+                               return;
+                       }
+                       switch (surv_numdefendersalive)
+                       {
+                               case 1:
+                               {
+                                       sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT,
+                                               VOL_BASE, ATTEN_NONE);
+                                       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+                                       {
+                                               if (it.team == surv_defenderteam)
+                                               {
+                                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER,
+                                                               CENTER_ALONE);
+                                                       return;
+                                               }
+                                       });
+                                       return;
+                               }
+                               case 2:
+                               {
+                                       sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT,
+                                               VOL_BASE, ATTEN_NONE);
+                                       return;
+                               }
+                               case 3:
+                               {
+                                       sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT,
+                                               VOL_BASE, ATTEN_NONE);
+                                       return;
+                               }
+                       }
+                       return;
+               }
+       }
+}
+
+/// \brief Counts alive players.
+/// \return No return.
+/// \note In a perfect world this function shouldn't exist. However, since QC
+/// code is so bad and spurious mutators can really mess with your code, this
+/// function is called as a last resort.
+void Surv_CountAlivePlayers()
+{
+       int savednumdefenders = surv_numdefendersalive;
+       surv_numattackersalive = 0;
+       surv_numdefendersalive = 0;
+       FOREACH_CLIENT(IS_PLAYER(it),
+       {
+               switch (it.team)
+               {
+                       case surv_attackerteam:
+                       {
+                               if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
+                               {
+                                       ++surv_numattackersalive;
+                               }
+                               break;
+                       }
+                       case surv_defenderteam:
+                       {
+                               if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
+                               {
+                                       ++surv_numdefendersalive;
+                               }
+                               break;
+                       }
+               }
+       });
+       Surv_UpdateAliveStats();
+       eliminatedPlayers.SendFlags |= 1;
+       if (warmup_stage || surv_allowed_to_spawn || (savednumdefenders <=
+               surv_numdefendersalive))
+       {
+               return;
+       }
+       switch (surv_numdefendersalive)
+       {
+               case 1:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT, VOL_BASE, ATTEN_NONE);
+                       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+                       {
+                               if (it.team == surv_defenderteam)
+                               {
+                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ALONE);
+                                       return;
+                               }
+                       });
+                       return;
+               }
+               case 2:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT, VOL_BASE,
+                               ATTEN_NONE);
+                       return;
+               }
+               case 3:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT, VOL_BASE,
+                               ATTEN_NONE);
+                       return;
+               }
+       }
+}
+
+/// \brief Updates stats of alive players on HUD.
+/// \return No return.
+void Surv_UpdateAliveStats()
+{
+       // Debug stuff
+       if (surv_attackerteam == NUM_TEAM_1)
+       {
+               redalive = surv_numattackersalive;
+               bluealive = surv_numdefendersalive;
+       }
+       else
+       {
+               bluealive = surv_numattackersalive;
+               redalive = surv_numdefendersalive;
+       }
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               STAT(SURV_DEFENDERS_ALIVE, it) = surv_numdefendersalive;
+               it.redalive_stat = redalive;
+               it.bluealive_stat = bluealive;
+       });
+       Surv_UpdateDefenderHealthStat();
+}
+
+/// \brief Updates defender health on the HUD.
+/// \return No return.
+void Surv_UpdateDefenderHealthStat()
+{
+       float maxhealth;
+       float totalhealth = 0;
+       if (autocvar_g_instagib == 1)
+       {
+               maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
+                       "surv_defender", "start_armor") + 1);
+               FOREACH_CLIENT(IS_PLAYER(it),
+               {
+                       if (it.team == surv_defenderteam)
+                       {
+                               totalhealth += GetResourceAmount(it, RESOURCE_ARMOR) + 1;
+                       }
+               });
+       }
+       else
+       {
+               maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
+                       "surv_defender", "start_health") + PlayerTemplate_GetFloatValue(
+                       "surv_defender", "start_armor"));
+               FOREACH_CLIENT(IS_PLAYER(it),
+               {
+                       if (it.team == surv_defenderteam)
+                       {
+                               totalhealth += GetResourceAmount(it, RESOURCE_HEALTH);
+                               totalhealth += GetResourceAmount(it, RESOURCE_ARMOR);
+                       }
+               });
+       }
+       float healthratio;
+       if (maxhealth == 0)
+       {
+               healthratio = 0;
+       }
+       else
+       {
+               healthratio = totalhealth / maxhealth;
+       }
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               STAT(SURV_DEFENDER_HEALTH, it) = healthratio;
+       });
+}
+
+/// \brief Returns whether the player can spawn.
+/// \param[in] player Player to check.
+/// \return True if the player can spawn, false otherwise.
+bool Surv_CanPlayerSpawn(entity player)
+{
+       if ((player.team == surv_attackerteam) ||
+               (player.surv_savedplayerstate != NULL))
+       {
+               return true;
+       }
+       return surv_allowed_to_spawn;
+}
+
+/// \brief Switches the round type.
+/// \return No return.
+void Surv_SwitchRoundType()
+{
+       switch (surv_roundtype)
+       {
+               case SURVIVAL_ROUND_FIRST:
+               {
+                       surv_roundtype = SURVIVAL_ROUND_SECOND;
+                       return;
+               }
+               case SURVIVAL_ROUND_SECOND:
+               {
+                       surv_roundtype = SURVIVAL_ROUND_FIRST;
+                       return;
+               }
+       }
+}
+
+/// \brief Cleans up the mess after the round has finished.
+/// \return No return.
+void Surv_RoundCleanup()
+{
+       surv_allowed_to_spawn = false;
+       surv_isroundactive = false;
+       game_stopped = true;
+       FOREACH_CLIENT(true,
+       {
+               if (it.surv_attack_sprite)
+               {
+                       WaypointSprite_Kill(it.surv_attack_sprite);
+               }
+               if (it.surv_defend_sprite)
+               {
+                       WaypointSprite_Kill(it.surv_defend_sprite);
+               }
+               if (it.surv_savedplayerstate)
+               {
+                       delete(it.surv_savedplayerstate);
+                       it.surv_savedplayerstate = NULL;
+               }
+       });
+       if (surv_type == SURVIVAL_TYPE_VERSUS)
+       {
+               Surv_SwitchRoundType();
+               round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
+               return;
+       }
+       round_handler_Init(5, autocvar_g_surv_warmup,
+               autocvar_g_surv_round_timelimit);
+}
+
+/// \brief Swaps attacker and defender teams.
+/// \return No return.
+void Surv_SwapTeams()
+{
+       int temp = surv_attackerteam;
+       surv_attackerteam = surv_defenderteam;
+       surv_defenderteam = temp;
+       temp = surv_attackerteambit;
+       surv_attackerteambit = surv_defenderteambit;
+       surv_defenderteambit = temp;
+       temp = surv_numattackers;
+       surv_numattackers = surv_numdefenders;
+       surv_numdefenders = temp;
+       temp = surv_numattackerhumans;
+       surv_numattackerhumans = surv_numdefenderhumans;
+       surv_numdefenderhumans = temp;
+       FOREACH_CLIENT(true,
+       {
+               if ((it.team == surv_defenderteam) && (it.surv_role ==
+                       SURVIVAL_ROLE_CANNON_FODDER))
+               {
+                       SetPlayerTeamSimple(it, surv_attackerteam);
+               }
+       });
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               STAT(SURV_DEFENDER_TEAM, it) = Team_TeamToNumber(surv_defenderteam);
+       });
+}
+
+/// \brief Forces the overkill model for specific player.
+/// \param[in,out] player Player to force the model of.
+/// \return No return.
+void Surv_ForceOverkillPlayerModel(entity player)
+{
+       switch (player.team)
+       {
+               case NUM_TEAM_1:
+               {
+                       switch (floor(random() * 4))
+                       {
+                               case 0:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okrobot1.dpm";
+                                       return;
+                               }
+                               case 1:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okrobot2.dpm";
+                                       return;
+                               }
+                               case 2:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okrobot3.dpm";
+                                       return;
+                               }
+                               case 3:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okrobot4.dpm";
+                                       return;
+                               }
+                       }
+                       return;
+               }
+               case NUM_TEAM_2:
+               {
+                       switch (floor(random() * 4))
+                       {
+                               case 0:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okmale1.dpm";
+                                       return;
+                               }
+                               case 1:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okmale2.dpm";
+                                       return;
+                               }
+                               case 2:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okmale3.dpm";
+                                       return;
+                               }
+                               case 3:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okmale4.dpm";
+                                       return;
+                               }
+                       }
+                       return;
+               }
+       }
+}
+
+/// \brief Determines the player model to the one configured for the gamemode.
+/// \param[in,out] player Player to determine the model of.
+/// \return No return.
+void Surv_DeterminePlayerModel(entity player)
+{
+       switch (player.team)
+       {
+               case surv_attackerteam:
+               {
+                       switch (player.surv_role)
+                       {
+                               case SURVIVAL_ROLE_PLAYER:
+                               {
+                                       if (!autocvar_g_surv_attacker_force_overkill_models)
+                                       {
+                                               player.surv_playermodel = player.surv_savedplayermodel;
+                                               return;
+                                       }
+                                       Surv_ForceOverkillPlayerModel(player);
+                                       return;
+                               }
+                               case SURVIVAL_ROLE_CANNON_FODDER:
+                               {
+                                       if (!autocvar_g_surv_cannon_fodder_force_overkill_models)
+                                       {
+                                               player.surv_playermodel = player.surv_savedplayermodel;
+                                               return;
+                                       }
+                                       Surv_ForceOverkillPlayerModel(player);
+                                       return;
+                               }
+                       }
+               }
+               case surv_defenderteam:
+               {
+                       if (!autocvar_g_surv_defender_force_overkill_models)
+                       {
+                               player.surv_playermodel = player.surv_savedplayermodel;
+                               return;
+                       }
+                       Surv_ForceOverkillPlayerModel(player);
+                       return;
+               }
+       }
+}
+
+/// \brief Setups a waypoint sprite used to track defenders.
+/// \param[in] player Player to attach sprite too.
+/// \return No return.
+void Surv_SetupWaypointSprite(entity player)
+{
+       WaypointSprite_Spawn(WP_SurvivalKill, 0, 0, player, '0 0 64', NULL,
+               surv_attackerteam, player, surv_attack_sprite, false,
+               RADARICON_OBJECTIVE);
+       WaypointSprite_Spawn(WP_SurvivalDefend, 0, 0, player, '0 0 64', NULL,
+               surv_defenderteam, player, surv_defend_sprite, false,
+               RADARICON_OBJECTIVE);
+       //player.surv_attack_sprite.colormod = colormapPaletteColor(player.team - 1,
+       //      false);
+       //player.surv_defend_sprite.colormod = colormapPaletteColor(player.team - 1,
+       //      false);
+       float max_hp;
+       if (autocvar_g_instagib == 1)
+       {
+               max_hp = PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(player),
+                       "start_armor") + 1;
+               WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite, max_hp);
+               WaypointSprite_UpdateMaxHealth(player.surv_defend_sprite, max_hp);
+               Surv_UpdateWaypointSpriteHealth(player);
+               return;
+       }
+       max_hp = PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(player),
+               "start_health") + PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(
+               player), "start_armor");
+       WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite, max_hp);
+       WaypointSprite_UpdateMaxHealth(player.surv_defend_sprite, max_hp);
+       Surv_UpdateWaypointSpriteHealth(player);
+}
+
+void Surv_UpdateWaypointSpriteHealth(entity player)
+{
+       float hp;
+       if (autocvar_g_instagib == 1)
+       {
+               hp = GetResourceAmount(player, RESOURCE_ARMOR) + 1;
+       }
+       else
+       {
+               hp = GetResourceAmount(player, RESOURCE_HEALTH) + GetResourceAmount(
+                       player, RESOURCE_ARMOR);
+       }
+       WaypointSprite_UpdateHealth(player.surv_attack_sprite, hp);
+       WaypointSprite_UpdateHealth(player.surv_defend_sprite, hp);
+}
+
+//=============================== Callbacks ===================================
+
+bool Surv_CanRoundStart()
+{
+       return (surv_numattackersalive > 0) && (surv_numdefendersalive > 0);
+}
+
+bool Surv_CanRoundEnd()
+{
+       if (warmup_stage)
+       {
+               return false;
+       }
+       if((round_handler_GetEndTime() > 0) && (round_handler_GetEndTime() -
+               time <= 0))
+       {
+               if (surv_roundtype == SURVIVAL_ROUND_FIRST)
+               {
+                       surv_timetobeat = time - surv_roundstarttime;
+                       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
+                               CENTER_SURVIVAL_DEFENDERS_SURVIVED);
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
+                               INFO_SURVIVAL_DEFENDERS_SURVIVED);
+                       Surv_RoundCleanup();
+                       return true;
+               }
+               surv_timetobeat = autocvar_g_surv_round_timelimit;
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
+                       CENTER_SURVIVAL_DEFENDERS_SURVIVED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
+                       INFO_SURVIVAL_DEFENDERS_SURVIVED);
+               switch (surv_defenderteam)
+               {
+                       case NUM_TEAM_1:
+                       {
+                               sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
+                                       ATTEN_NONE);
+                               break;
+                       }
+                       case NUM_TEAM_2:
+                       {
+                               sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
+                                       ATTEN_NONE);
+                               break;
+                       }
+               }
+               TeamScore_AddToTeam(surv_defenderteam, 1, 1);
+               Surv_RoundCleanup();
+               return true;
+       }
+       if (surv_numdefendersalive > 0)
+       {
+               return false;
+       }
+       if (surv_roundtype == SURVIVAL_ROUND_FIRST)
+       {
+               surv_timetobeat = time - surv_roundstarttime;
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
+                       CENTER_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
+                       INFO_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
+               Surv_RoundCleanup();
+               return true;
+       }
+       surv_timetobeat = autocvar_g_surv_round_timelimit;
+       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
+               CENTER_SURVIVAL_DEFENDERS_ELIMINATED);
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
+               INFO_SURVIVAL_DEFENDERS_ELIMINATED);
+       switch (surv_attackerteam)
+       {
+               case NUM_TEAM_1:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
+                               ATTEN_NONE);
+                       break;
+               }
+               case NUM_TEAM_2:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
+                               ATTEN_NONE);
+                       break;
+               }
+       }
+       TeamScore_AddToTeam(surv_attackerteam, 1, 1);
+       Surv_RoundCleanup();
+       return true;
+}
+
+void Surv_RoundStart()
+{
+       if (warmup_stage)
+       {
+               surv_allowed_to_spawn = true;
+               return;
+       }
+       surv_isroundactive = true;
+       surv_roundstarttime = time;
+       surv_allowed_to_spawn = false;
+       switch (surv_roundtype)
+       {
+               case SURVIVAL_ROUND_FIRST:
+               {
+                       FOREACH_CLIENT(IS_PLAYER(it),
+                       {
+                               if (it.team == surv_attackerteam)
+                               {
+                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                               CENTER_SURVIVAL_1ST_ROUND_ATTACKER);
+                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                               INFO_SURVIVAL_1ST_ROUND_ATTACKER);
+                                       break;
+                               }
+                       });
+                       FOREACH_CLIENT(IS_PLAYER(it),
+                       {
+                               if (it.team == surv_defenderteam)
+                               {
+                                       if (surv_type == SURVIVAL_TYPE_COOP)
+                                       {
+                                               Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                                       CENTER_SURVIVAL_COOP_DEFENDER);
+                                               Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                                       INFO_SURVIVAL_COOP_DEFENDER);
+                                               break;
+                                       }
+                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                               CENTER_SURVIVAL_1ST_ROUND_DEFENDER);
+                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                               INFO_SURVIVAL_1ST_ROUND_DEFENDER);
+                                       break;
+                               }
+                       });
+                       break;
+               }
+               case SURVIVAL_ROUND_SECOND:
+               {
+                       FOREACH_CLIENT(IS_PLAYER(it),
+                       {
+                               if (it.team == surv_attackerteam)
+                               {
+                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                               CENTER_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
+                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                               INFO_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
+                                       break;
+                               }
+                       });
+                       FOREACH_CLIENT(IS_PLAYER(it),
+                       {
+                               if (it.team == surv_defenderteam)
+                               {
+                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                               CENTER_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
+                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                               INFO_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
+                                       break;
+                               }
+                       });
+                       break;
+               }
+       }
+       if (autocvar_g_surv_stealth)
+       {
+               return;
+       }
+       FOREACH_CLIENT(IS_PLAYER(it),
+       {
+               switch (it.team)
+               {
+                       case surv_defenderteam:
+                       {
+                               if (it.surv_role == SURVIVAL_ROLE_PLAYER)
+                               {
+                                       Surv_SetupWaypointSprite(it);
+                               }
+                               break;
+                       }
+               }
+       });
+}
+
+bool Surv_IsEliminated(entity player)
+{
+       switch (player.surv_state)
+       {
+               case SURVIVAL_STATE_NOT_PLAYING:
+               {
+                       return true;
+               }
+               case SURVIVAL_STATE_PLAYING:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               // A hack until proper scoreboard is done.
+                               return true;
+                       }
+                       if ((player.team == surv_defenderteam) && (IS_DEAD(player) ||
+                               IS_OBSERVER(player)))
+                       {
+                               return true;
+                       }
+                       return false;
+               }
+       }
+       // Should never reach here
+       return true;
+}
+
+//============================= Hooks ========================================
+
+/// \brief Hook that is called to determine general rules of the game. 
+MUTATOR_HOOKFUNCTION(surv, ReadLevelCvars)
+{
+       surv_warmup = warmup_stage;
+}
+
+/// \brief Hook that is called to determine if there is a weapon arena.
+MUTATOR_HOOKFUNCTION(surv, SetWeaponArena)
+{
+       // Removing any weapon arena.
+       M_ARGV(0, string) = "off";
+}
+
+/// \brief Hook that is called to determine start items of all players.
+MUTATOR_HOOKFUNCTION(surv, SetStartItems)
+{
+       if (autocvar_g_instagib == 1)
+       {
+               return;
+       }
+       start_weapons = WEPSET(Null);
+       warmup_start_weapons = WEPSET(Null);
+}
+
+/// \brief Hook that is called on every frame.
+MUTATOR_HOOKFUNCTION(surv, SV_StartFrame)
+{
+       if (game_stopped || !surv_isroundactive)
+       {
+               return;
+       }
+       float roundtime = 0;
+       switch (surv_roundtype)
+       {
+               case SURVIVAL_ROUND_FIRST:
+               {
+                       roundtime = time - surv_roundstarttime;
+                       break;
+               }
+               case SURVIVAL_ROUND_SECOND:
+               {
+                       roundtime = round_handler_GetEndTime() - time;
+                       break;
+               }
+       }
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               STAT(SURV_ROUND_TIME, it) = roundtime;
+       });
+}
+
+/// \brief Hook that determines which team player can join. This is called
+/// before ClientConnect.
+MUTATOR_HOOKFUNCTION(surv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       entity player = M_ARGV(2, entity);
+       LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
+       if (player == NULL)
+       {
+               return SURVIVAL_TEAM_BITS;
+       }
+       if (IS_BOT_CLIENT(player))
+       {
+               int teambits = surv_attackerteambit;
+               if ((player.team == surv_defenderteam) || (surv_numdefenders <
+                       autocvar_g_surv_team_size))
+               {
+                       teambits |= surv_defenderteambit;
+               }
+               M_ARGV(0, float) = teambits;
+               return;
+       }
+       if (surv_type == SURVIVAL_TYPE_COOP)
+       {
+               if (surv_numdefenderhumans < autocvar_g_surv_team_size)
+               {
+                       M_ARGV(0, float) = surv_defenderteambit;
+                       return;
+               }
+               M_ARGV(0, float) = 0;
+               return;
+       }
+       int teambits = 0;
+       if (surv_numattackerhumans < autocvar_g_surv_team_size)
+       {
+               LOG_TRACE("Player can join attackers");
+               teambits |= surv_attackerteambit;
+       }
+       if (surv_numdefenderhumans < autocvar_g_surv_team_size)
+       {
+               LOG_TRACE("Player can join defenders");
+               teambits |= surv_defenderteambit;
+       }
+       M_ARGV(0, float) = teambits;
+       return;
+}
+
+/// \brief Hook that override team counts.
+MUTATOR_HOOKFUNCTION(surv, GetTeamCounts, CBC_ORDER_EXCLUSIVE)
+{
+       return true;
+}
+
+/// \brief Hook that sets the team count.
+MUTATOR_HOOKFUNCTION(surv, GetTeamCount, CBC_ORDER_EXCLUSIVE)
+{
+       float teamnum = M_ARGV(0, float);
+       entity ignore = M_ARGV(1, entity);
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       M_ARGV(2, float) = surv_numattackers;
+                       M_ARGV(3, float) = surv_numattackers - surv_numattackerhumans;
+                       if (ignore.team == surv_attackerteam)
+                       {
+                               --M_ARGV(2, float);
+                               if (IS_BOT_CLIENT(ignore))
+                               {
+                                       --M_ARGV(3, float);
+                               }
+                       }
+                       entity lowestplayer = NULL;
+                       float lowestplayerscore = FLOAT_MAX;
+                       entity lowestbot = NULL;
+                       float lowestbotscore = FLOAT_MAX;
+                       FOREACH_CLIENT((it.team == surv_attackerteam) && (it.surv_role ==
+                               SURVIVAL_ROLE_PLAYER),
+                       {
+                               if (it == ignore)
+                               {
+                                       continue;
+                               }
+                               if (IS_BOT_CLIENT(it))
+                               {
+                                       float tempscore = PlayerScore_Get(it, SP_SCORE);
+                                       if (tempscore < lowestbotscore)
+                                       {
+                                               lowestbot = it;
+                                               lowestbotscore = tempscore;
+                                               continue;
+                                       }
+                               }
+                               float tempscore = PlayerScore_Get(it, SP_SCORE);
+                               if (tempscore < lowestplayerscore)
+                               {
+                                       lowestplayer = it;
+                                       lowestplayerscore = tempscore;
+                               }
+                       });
+                       M_ARGV(4, entity) = lowestplayer;
+                       M_ARGV(5, entity) = lowestbot;
+                       break;
+               }
+               case surv_defenderteam:
+               {
+                       M_ARGV(2, float) = surv_numdefenders;
+                       M_ARGV(3, float) = surv_numdefenders - surv_numdefenderhumans;
+                       if (ignore.team == surv_defenderteam)
+                       {
+                               --M_ARGV(2, float);
+                               if (IS_BOT_CLIENT(ignore))
+                               {
+                                       --M_ARGV(3, float);
+                               }
+                       }
+                       entity lowestplayer = NULL;
+                       float lowestplayerscore = FLOAT_MAX;
+                       entity lowestbot = NULL;
+                       float lowestbotscore = FLOAT_MAX;
+                       FOREACH_CLIENT((it.team == surv_defenderteam),
+                       {
+                               if (it == ignore)
+                               {
+                                       continue;
+                               }
+                               if (IS_BOT_CLIENT(it))
+                               {
+                                       float tempscore = PlayerScore_Get(it, SP_SCORE);
+                                       if (tempscore < lowestbotscore)
+                                       {
+                                               lowestbot = it;
+                                               lowestbotscore = tempscore;
+                                               continue;
+                                       }
+                               }
+                               float tempscore = PlayerScore_Get(it, SP_SCORE);
+                               if (tempscore < lowestplayerscore)
+                               {
+                                       lowestplayer = it;
+                                       lowestplayerscore = tempscore;
+                               }
+                       });
+                       M_ARGV(4, entity) = lowestplayer;
+                       M_ARGV(5, entity) = lowestbot;
+                       break;
+               }
+       }
+       return true;
+}
+
+/// \brief Hook that determines the best teams for the player to join.
+MUTATOR_HOOKFUNCTION(surv, FindBestTeams, CBC_ORDER_EXCLUSIVE)
+{
+       if (surv_type == SURVIVAL_TYPE_COOP)
+       {
+               return false;
+       }
+       entity player = M_ARGV(0, entity);
+       if (IS_BOT_CLIENT(player))
+       {
+               return false;
+       }
+       int numattackerhumans = surv_numattackerhumans;
+       int numdefenderhumans = surv_numdefenderhumans;
+       if (player.team == surv_attackerteam)
+       {
+               --numattackerhumans;
+       }
+       else if (player.team == surv_defenderteam)
+       {
+               --numdefenderhumans;
+       }
+       if (numattackerhumans < numdefenderhumans)
+       {
+               M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_attackerteam) - 1);
+               return true;
+       }
+       if (numattackerhumans > numdefenderhumans)
+       {
+               M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_defenderteam) - 1);
+               return true;
+       }
+       M_ARGV(1, float) = SURVIVAL_TEAM_BITS;
+       return true;
+}
+
+/// \brief Hook that is called when player has changed the team.
+MUTATOR_HOOKFUNCTION(surv, Player_ChangedTeam)
+{
+       entity player = M_ARGV(0, entity);
+       int oldteam = M_ARGV(1, float);
+       int newteam = M_ARGV(2, float);
+       string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
+               ", old team = ", ftos(oldteam), " new team = ", ftos(newteam));
+       LOG_TRACE(message);
+       DebugPrintToChatAll(message);
+       if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
+       {
+               Surv_RemovePlayerFromAliveList(player, oldteam);
+       }
+       Surv_RemovePlayerFromTeam(player, oldteam);
+       if (Surv_AddPlayerToTeam(player, newteam) == false)
+       {
+               return;
+       }
+       //Surv_CountAlivePlayers();
+       if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
+       {
+               Surv_AddPlayerToAliveList(player, newteam);
+       }
+}
+
+/// \brief Hook that is called when player is about to be killed when changing
+/// teams.
+MUTATOR_HOOKFUNCTION(surv, Player_ChangeTeamKill)
+{
+       entity player = M_ARGV(0, entity);
+       if (player.team != surv_defenderteam)
+       {
+               return false;
+       }
+       if (player.surv_savedplayerstate == NULL)
+       {
+               return false;
+       }
+       Surv_RestorePlayerState(player, player.surv_savedplayerstate);
+       delete(player.surv_savedplayerstate);
+       player.surv_savedplayerstate = NULL;
+       return true;
+}
+
+/// \brief Hook that is called when player is about to be killed as a result of
+/// the kill command or changing teams.
+MUTATOR_HOOKFUNCTION(surv, ClientKill_Now)
+{
+       entity player = M_ARGV(0, entity);
+       if (player.team == surv_defenderteam)
+       {
+               // Deny suicide.
+               return true;
+       }
+}
+
+/// \brief Hook that is called when player connects to the server.
+MUTATOR_HOOKFUNCTION(surv, ClientConnect)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: ClientConnect, player = ", player.netname);
+       player.surv_savedplayermodel = player.playermodel;
+       if (IS_REAL_CLIENT(player))
+       {
+               STAT(SURV_DEFENDER_TEAM, player) = Team_TeamToNumber(surv_defenderteam);
+               STAT(SURV_DEFENDERS_ALIVE, player) = surv_numdefendersalive;
+               player.redalive_stat = redalive;
+               player.bluealive_stat = bluealive;
+               player.yellowalive_stat = yellowalive;
+               player.pinkalive_stat = pinkalive;
+       }
+       if (player.surv_role == SURVIVAL_ROLE_NONE)
+       {
+               Surv_AddPlayerToTeam(player, player.team);
+       }
+       return true;
+}
+
+/// \brief Hook that is called when player disconnects from the server.
+MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
+{
+    entity player = M_ARGV(0, entity);
+       if (!IS_DEAD(player))
+       {
+               Surv_RemovePlayerFromAliveList(player, player.team);
+       }
+       Surv_RemovePlayerFromTeam(player, player.team);
+       //Surv_CountAlivePlayers();
+}
+
+/// \brief Hook that determines whether player can spawn. It is not called for
+/// players who have joined the team and are dead.
+MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: ForbidSpawn, player = ", player.netname);
+       if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
+       {
+               return false;
+       }
+       return !Surv_CanPlayerSpawn(player);
+}
+
+MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
+       if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
+       {
+               LOG_TRACE("Transmuting to observer");
+               TRANSMUTE(Observer, player);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: MakePlayerObserver, player = ", player.netname);
+       if (player.killindicator_teamchange == -2) // player wants to spectate
+       {
+               LOG_TRACE("killindicator_teamchange == -2");
+               player.surv_state = SURVIVAL_STATE_NOT_PLAYING;
+       }
+       if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
+       {
+               return false;  // allow team reset
+       }
+       return true;  // prevent team reset
+}
+
+MUTATOR_HOOKFUNCTION(surv, reset_map_global)
+{
+       LOG_TRACE("Survival: reset_map_global");
+       surv_allowed_to_spawn = true;
+       if (surv_roundtype == SURVIVAL_ROUND_FIRST)
+       {
+               FOREACH_CLIENT(IS_REAL_CLIENT(it),
+               {
+                       STAT(SURV_ROUND_TIME, it) = 0;
+               });
+       }
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, reset_map_players)
+{
+       LOG_TRACE("Survival: reset_map_players");
+       surv_numattackersalive = 0;
+       surv_numdefendersalive = 0;
+       if (surv_warmup)
+       {
+               surv_warmup = false;
+       }
+       else if (surv_type == SURVIVAL_TYPE_VERSUS)
+       {
+               Surv_SwapTeams();
+       }
+       FOREACH_CLIENT(true,
+       {
+               it.killcount = 0;
+               if ((it.surv_state == SURVIVAL_STATE_NOT_PLAYING) && IS_BOT_CLIENT(it))
+               {
+                       it.team = -1;
+                       it.surv_state = SURVIVAL_STATE_PLAYING;
+               }
+               if (it.surv_state == SURVIVAL_STATE_PLAYING)
+               {
+                       TRANSMUTE(Player, it);
+                       it.surv_state = SURVIVAL_STATE_PLAYING;
+                       PutClientInServer(it);
+               }
+       });
+       bot_relinkplayerlist();
+       return true;
+}
+
+/// \brief Hook that is called when player spawns.
+MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: PlayerSpawn, player = ", player.netname);
+       player.surv_state = SURVIVAL_STATE_PLAYING;
+       Surv_DeterminePlayerModel(player);
+       if (player.surv_savedplayerstate != NULL)
+       {
+               Surv_RestorePlayerState(player, player.surv_savedplayerstate);
+               delete(player.surv_savedplayerstate);
+               player.surv_savedplayerstate = NULL;
+       }
+       else
+       {
+               PlayerTemplateHook_PlayerSpawn(player, Surv_GetPlayerTemplate(player));
+       }
+       switch (player.team)
+       {
+               case surv_attackerteam:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_PLAYER)
+                       {
+                               Send_Notification(NOTIF_ONE, player, MSG_CENTER,
+                                       CENTER_ASSAULT_ATTACKING);
+                       }
+                       break;
+               }
+               case surv_defenderteam:
+               {
+                       Send_Notification(NOTIF_ONE, player, MSG_CENTER,
+                               CENTER_ASSAULT_DEFENDING);
+                       break;
+               }
+       }
+       //Surv_CountAlivePlayers();
+       Surv_AddPlayerToAliveList(player, player.team);
+}
+
+/// \brief UGLY HACK. This is called every frame to keep player model correct.
+MUTATOR_HOOKFUNCTION(surv, FixPlayermodel)
+{
+       entity player = M_ARGV(2, entity);
+       M_ARGV(0, string) = player.surv_playermodel;
+}
+
+/// \brief Hook which is called when the player tries to throw their weapon.
+MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
+{
+       entity player = M_ARGV(0, entity);
+       return PlayerTemplateHook_ForbidThrowCurrentWeapon(
+               Surv_GetPlayerTemplate(player));
+}
+
+/// \brief Hook that is called every frame to determine how player health should
+/// regenerate.
+MUTATOR_HOOKFUNCTION(surv, PlayerRegen)
+{
+       entity player = M_ARGV(0, entity);
+       if (player.team == surv_defenderteam)
+       {
+               return true;
+       }
+       return PlayerTemplateHook_PlayerRegen(player,
+               Surv_GetPlayerTemplate(player));
+}
+
+/// \brief Hook that is called to determine if balance messages will appear.
+MUTATOR_HOOKFUNCTION(surv, HideTeamNagger)
+{
+       return true;
+}
+
+/// \brief Hook that is called when player touches an item.
+MUTATOR_HOOKFUNCTION(surv, ItemTouch)
+{
+       entity item = M_ARGV(0, entity);
+       entity player = M_ARGV(1, entity);
+       switch (player.team)
+       {
+               case surv_attackerteam:
+               {
+                       return PlayerTemplateHook_ItemTouch(player, item,
+                               Surv_GetPlayerTemplate(player));
+               }
+               case surv_defenderteam:
+               {
+                       switch (item.classname)
+                       {
+                               case "item_strength":
+                               {
+                                       W_GiveWeapon(player, WEP_HMG.m_id);
+                                       player.superweapons_finished = max(
+                                               player.superweapons_finished, time) +
+                                               autocvar_g_balance_superweapons_time;
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "item_invincible":
+                               {
+                                       W_GiveWeapon(player, WEP_RPC.m_id);
+                                       player.superweapons_finished = max(
+                                               player.superweapons_finished, time) +
+                                               autocvar_g_balance_superweapons_time;
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_Shield, VOL_BASE, ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return PlayerTemplateHook_ItemTouch(player, item,
+                                               Surv_GetPlayerTemplate(player));
+                               }
+                       }
+                       DebugPrintToChat(player, item.classname);
+                       return MUT_ITEMTOUCH_RETURN;
+               }
+       }
+       return MUT_ITEMTOUCH_CONTINUE;
+}
+
+/// \brief Hook which is called when the damage amount must be determined.
+MUTATOR_HOOKFUNCTION(surv, Damage_Calculate)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float deathtype = M_ARGV(3, float);
+       float damage = M_ARGV(4, float);
+       M_ARGV(4, float) = PlayerTemplateHook_Damage_Calculate(frag_attacker,
+               Surv_GetPlayerTemplate(frag_attacker), frag_target,
+               Surv_GetPlayerTemplate(frag_target), deathtype, damage);
+}
+
+/// \brief Hook which is called when the player was damaged.
+MUTATOR_HOOKFUNCTION(surv, PlayerDamaged)
+{
+       entity target = M_ARGV(1, entity);
+       if (target.team != surv_defenderteam)
+       {
+               return;
+       }
+       Surv_UpdateDefenderHealthStat();
+       entity attacker = M_ARGV(0, entity);
+       if ((attacker.team == surv_attackerteam) && (attacker.surv_role ==
+               SURVIVAL_ROLE_PLAYER))
+       {
+               float health = M_ARGV(2, float);
+               float armor = M_ARGV(3, float);
+               float score = (health + armor) * autocvar_g_surv_attacker_damage_score;
+               GameRules_scoring_add(attacker, SCORE, score);
+       }
+       if (autocvar_g_surv_stealth)
+       {
+               return;
+       }
+       if (GetResourceAmount(target, RESOURCE_HEALTH) < 1)
+       {
+               WaypointSprite_Kill(target.surv_attack_sprite);
+               WaypointSprite_Kill(target.surv_defend_sprite);
+       }
+       else
+       {
+               Surv_UpdateWaypointSpriteHealth(target);
+       }
+}
+
+/// \brief Hook which is called when the player dies.
+MUTATOR_HOOKFUNCTION(surv, PlayerDies, CBC_ORDER_FIRST)
+{
+       //DebugPrintToChatAll("PlayerDies");
+       entity attacker = M_ARGV(1, entity);
+       entity victim = M_ARGV(2, entity);
+       PlayerTemplateHook_PlayerDies(victim, Surv_GetPlayerTemplate(victim));
+       if ((attacker.team == surv_defenderteam) &&
+               (victim.team == surv_attackerteam))
+       {
+               switch (victim.surv_role)
+               {
+                       case SURVIVAL_ROLE_PLAYER:
+                       {
+                               GiveResource(attacker, RESOURCE_HEALTH,
+                                       autocvar_g_surv_defender_attacker_frag_health);
+                               GiveResource(attacker, RESOURCE_ARMOR,
+                                       autocvar_g_surv_defender_attacker_frag_armor);
+                               GiveResource(attacker, RESOURCE_SHELLS,
+                                       autocvar_g_surv_defender_attacker_frag_shells);
+                               GiveResource(attacker, RESOURCE_BULLETS,
+                                       autocvar_g_surv_defender_attacker_frag_bullets);
+                               GiveResource(attacker, RESOURCE_ROCKETS,
+                                       autocvar_g_surv_defender_attacker_frag_rockets);
+                               GiveResource(attacker, RESOURCE_CELLS,
+                                       autocvar_g_surv_defender_attacker_frag_cells);
+                               GiveResource(attacker, RESOURCE_PLASMA,
+                                       autocvar_g_surv_defender_attacker_frag_plasma);
+                               GiveResource(attacker, RESOURCE_FUEL,
+                                       autocvar_g_surv_defender_attacker_frag_fuel);
+                               break;
+                       }
+                       case SURVIVAL_ROLE_CANNON_FODDER:
+                       {
+                               GiveResource(attacker, RESOURCE_HEALTH,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_health);
+                               GiveResource(attacker, RESOURCE_ARMOR,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_armor);
+                               GiveResource(attacker, RESOURCE_SHELLS,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_shells);
+                               GiveResource(attacker, RESOURCE_BULLETS,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_bullets);
+                               GiveResource(attacker, RESOURCE_ROCKETS,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_rockets);
+                               GiveResource(attacker, RESOURCE_CELLS,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_cells);
+                               GiveResource(attacker, RESOURCE_PLASMA,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_plasma);
+                               GiveResource(attacker, RESOURCE_FUEL,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_fuel);
+                               break;
+                       }
+               }
+       }
+       if (!Surv_CanPlayerSpawn(victim))
+       {
+               victim.respawn_flags = RESPAWN_SILENT;
+               if (IS_BOT_CLIENT(victim))
+               {
+                       bot_clear(victim);
+               }
+       }
+       return true;
+}
+
+/// \brief Hook which is called after the player died.
+MUTATOR_HOOKFUNCTION(surv, PlayerDied)
+{
+       //DebugPrintToChatAll("PlayerDied");
+       entity player = M_ARGV(0, entity);
+       Surv_RemovePlayerFromAliveList(player, player.team);
+       //Surv_CountAlivePlayers();
+}
+
+/// \brief Hook which is called when player has scored a frag.
+MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+       if (surv_type == SURVIVAL_TYPE_COOP)
+       {
+               return true;
+       }
+       entity attacker = M_ARGV(0, entity);
+       if ((attacker.team == surv_defenderteam) || (attacker.surv_role ==
+               SURVIVAL_ROLE_CANNON_FODDER))
+       {
+               M_ARGV(2, float) = 0;
+               return true;
+       }
+       entity target = M_ARGV(1, entity);
+       if ((attacker.surv_role == SURVIVAL_ROLE_PLAYER) && (target.team ==
+               surv_defenderteam))
+       {
+               M_ARGV(2, float) = autocvar_g_surv_attacker_frag_score;
+       }
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, SpectateSet)
+{
+       entity client = M_ARGV(0, entity);
+       entity targ = M_ARGV(1, entity);
+
+       if (!autocvar_g_surv_spectate_enemies &&
+               (client.surv_state == SURVIVAL_STATE_PLAYING) &&
+               DIFF_TEAM(targ, client))
+       {
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(surv, SpectateNext)
+{
+       entity client = M_ARGV(0, entity);
+
+       if (!autocvar_g_surv_spectate_enemies &&
+               (client.surv_state == SURVIVAL_STATE_PLAYING))
+       {
+               entity targ = M_ARGV(1, entity);
+               M_ARGV(1, entity) = CA_SpectateNext(client, targ);
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(surv, SpectatePrev)
+{
+       entity client = M_ARGV(0, entity);
+       entity targ = M_ARGV(1, entity);
+       entity first = M_ARGV(2, entity);
+
+       if (!autocvar_g_surv_spectate_enemies &&
+               (client.surv_state == SURVIVAL_STATE_PLAYING))
+       {
+               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;
+}
+
+/// \brief I'm not sure exactly what this function does but it is very
+/// important. Without it bots are completely broken. Is it a hack? Of course.
+MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
+{
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               if (IS_PLAYER(it) || (it.surv_state == SURVIVAL_STATE_PLAYING))
+               {
+                       ++M_ARGV(0, int);
+               }
+               ++M_ARGV(1, int);
+       });
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
+{
+       // Don't announce remaining frags
+       return false;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh b/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh
new file mode 100644 (file)
index 0000000..a381e07
--- /dev/null
@@ -0,0 +1,15 @@
+#pragma once
+
+/// \brief Initializes global data for the gametype.
+/// \return No return.
+void Surv_Initialize();
+
+REGISTER_MUTATOR(surv, false)
+{
+       MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               Surv_Initialize();
+       }
+       return 0;
+}
index 2dd84596e46991e8f3f75daeb63b524273825d01..834b38550a9b83e2cc7fbb5fbccc235553284cb7 100644 (file)
@@ -85,7 +85,7 @@ CLASS(Gametype, Object)
     }
 ENDCLASS(Gametype)
 
-REGISTRY(Gametypes, 24)
+REGISTRY(Gametypes, 25) // Lyberta: added 1
 #define Gametypes_from(i) _Gametypes_from(i, NULL)
 REGISTER_REGISTRY(Gametypes)
 REGISTRY_CHECK(Gametypes)
@@ -480,6 +480,52 @@ CLASS(Invasion, Gametype)
 ENDCLASS(Invasion)
 REGISTER_GAMETYPE(INVASION, NEW(Invasion));
 
+//=============================================================================
+
+#ifdef CSQC
+void HUD_Mod_GG(vector pos, vector mySize);
+#endif
+CLASS(GunGame, Gametype)
+    INIT(GunGame)
+    {
+        this.gametype_init(this, _("GunGame"), "gg", "g_gg", false, "", "timelimit=20", _("Kill players with all weapons"));
+    }
+    METHOD(GunGame, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
+    {
+        return true;
+    }
+#ifdef CSQC
+    ATTRIB(GunGame, m_modicons, void(vector pos, vector mySize), HUD_Mod_GG);
+#endif
+ENDCLASS(GunGame)
+REGISTER_GAMETYPE(GUNGAME, NEW(GunGame));
+#define g_gg IS_GAMETYPE(GUNGAME)
+
+//=============================================================================
+
+// Lyberta: adding survival gametype
+
+#ifdef CSQC
+void HUD_Mod_SURV(vector pos, vector mySize);
+#endif
+CLASS(Survival, Gametype)
+    INIT(Survival)
+    {
+        this.gametype_init(this, _("Survival"), "surv", "g_surv", true, "", "timelimit=20 pointlimit=5 teams=2 leadlimit=0", _("Survive as long as you can"));
+    }
+    METHOD(Survival, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
+       {
+               return true;
+       }
+#ifdef CSQC
+    ATTRIB(Survival, m_modicons, void(vector pos, vector mySize), HUD_Mod_SURV);
+#endif
+ENDCLASS(Survival)
+REGISTER_GAMETYPE(SURVIVAL, NEW(Survival));
+#define g_surv IS_GAMETYPE(SURVIVAL)
+
+//=============================================================================
+
 const int MAPINFO_FEATURE_WEAPONS       = 1; // not defined for instagib-only maps
 const int MAPINFO_FEATURE_VEHICLES      = 2;
 const int MAPINFO_FEATURE_TURRETS       = 4;
index 0552173c1a0874c8c8ca6765ac0a922ff60ff01b..55d4fd858cfe1b2c2b4a2b4a9aff8e65608d6f05 100644 (file)
@@ -1,5 +1,8 @@
 // generated file; do not modify
 #include <common/mutators/mutator/overkill/hmg.qc>
+#include <common/mutators/mutator/overkill/okmachinegun.qc>
+#include <common/mutators/mutator/overkill/okshotgun.qc>
+#include <common/mutators/mutator/overkill/okvortex.qc>
 #include <common/mutators/mutator/overkill/overkill.qc>
 #ifdef CSQC
     #include <common/mutators/mutator/overkill/cl_overkill.qc>
index 13e42431b32ebb3a28c11504a38fb822e69d7640..d25951615d8d28b1963011f4e048590965cc55e1 100644 (file)
@@ -1,5 +1,8 @@
 // generated file; do not modify
 #include <common/mutators/mutator/overkill/hmg.qh>
+#include <common/mutators/mutator/overkill/okmachinegun.qh>
+#include <common/mutators/mutator/overkill/okshotgun.qh>
+#include <common/mutators/mutator/overkill/okvortex.qh>
 #include <common/mutators/mutator/overkill/overkill.qh>
 #ifdef CSQC
     #include <common/mutators/mutator/overkill/cl_overkill.qh>
index eb21953955429543f0edd1cb87ec88dd6ade0a03..4b14194be628f0bc9929e2608463b6426ef37473 100644 (file)
@@ -4,8 +4,5 @@ REGISTER_MUTATOR(ok, false)
 {
        MUTATOR_ONADD {
                cvar_settemp("g_overkill", "1");
-               WEP_SHOTGUN.mdl = "ok_shotgun";
-               WEP_MACHINEGUN.mdl = "ok_mg";
-               WEP_VORTEX.mdl = "ok_sniper";
        }
 }
index 41327956f383b062deb21356e896170cb57c82f9..10287568835d7ed25d7a77e4d7dd4c302a0978d1 100644 (file)
@@ -27,9 +27,9 @@ void W_HeavyMachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity weapone
                return;
        }
 
-       W_DecreaseAmmo(WEP_HMG, actor, WEP_CVAR(hmg, ammo), weaponentity);
+       W_DecreaseAmmo(WEP_HMG, actor, WEP_CVAR_PRI(hmg, ammo), weaponentity);
 
-       W_SetupShot (actor, weaponentity, true, 0, SND_UZI_FIRE, CH_WEAPON_A, WEP_CVAR(hmg, damage));
+       W_SetupShot (actor, weaponentity, true, 0, SND_UZI_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(hmg, damage));
 
        if(!autocvar_g_norecoil)
        {
@@ -37,8 +37,8 @@ void W_HeavyMachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity weapone
                actor.punchangle_y = random () - 0.5;
        }
 
-       float hmg_spread = bound(WEP_CVAR(hmg, spread_min), WEP_CVAR(hmg, spread_min) + (WEP_CVAR(hmg, spread_add) * actor.(weaponentity).misc_bulletcounter), WEP_CVAR(hmg, spread_max));
-       fireBullet(actor, weaponentity, w_shotorg, w_shotdir, hmg_spread, WEP_CVAR(hmg, solidpenetration), WEP_CVAR(hmg, damage), WEP_CVAR(hmg, force), WEP_HMG.m_id, 0);
+       float hmg_spread = bound(WEP_CVAR_PRI(hmg, spread_min), WEP_CVAR_PRI(hmg, spread_min) + (WEP_CVAR_PRI(hmg, spread_add) * actor.(weaponentity).misc_bulletcounter), WEP_CVAR_PRI(hmg, spread_max));
+       fireBullet(actor, weaponentity, w_shotorg, w_shotdir, hmg_spread, WEP_CVAR_PRI(hmg, solidpenetration), WEP_CVAR_PRI(hmg, damage), WEP_CVAR_PRI(hmg, force), WEP_HMG.m_id, 0);
 
        actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
 
@@ -54,8 +54,8 @@ void W_HeavyMachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity weapone
        }
 
        int slot = weaponslot(weaponentity);
-       ATTACK_FINISHED(actor, slot) = time + WEP_CVAR(hmg, refire) * W_WeaponRateFactor(actor);
-       weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(hmg, refire), W_HeavyMachineGun_Attack_Auto);
+       ATTACK_FINISHED(actor, slot) = time + WEP_CVAR_PRI(hmg, refire) * W_WeaponRateFactor(actor);
+       weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(hmg, refire), W_HeavyMachineGun_Attack_Auto);
 }
 
 METHOD(HeavyMachineGun, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
@@ -68,42 +68,114 @@ METHOD(HeavyMachineGun, wr_aim, void(entity thiswep, entity actor, .entity weapo
 
 METHOD(HeavyMachineGun, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
 {
-    if(WEP_CVAR(hmg, reload_ammo) && actor.(weaponentity).clip_load < WEP_CVAR(hmg, ammo)) { // forced reload
+       if ((WEP_CVAR_SEC(hmg, refire_type) == 1) && (fire & 2) && (time >= actor.jump_interval))
+       {
+               // Secondary uses it's own refire timer if refire_type is 1.
+               actor.jump_interval = time + WEP_CVAR_SEC(hmg, refire) * W_WeaponRateFactor(actor);
+               // Ugly hack to reuse the fire mode of the blaster.
+               makevectors(actor.v_angle);
+               Weapon oldwep = actor.(weaponentity).m_weapon; // we can't avoid this hack
+               actor.(weaponentity).m_weapon = WEP_BLASTER;
+               W_Blaster_Attack(
+                       actor,
+                       weaponentity,
+                       WEP_BLASTER.m_id | HITTYPE_SECONDARY,
+                       WEP_CVAR_SEC(hmg, shotangle),
+                       WEP_CVAR_SEC(hmg, damage),
+                       WEP_CVAR_SEC(hmg, edgedamage),
+                       WEP_CVAR_SEC(hmg, radius),
+                       WEP_CVAR_SEC(hmg, force),
+                       WEP_CVAR_SEC(hmg, speed),
+                       WEP_CVAR_SEC(hmg, spread),
+                       WEP_CVAR_SEC(hmg, delay),
+                       WEP_CVAR_SEC(hmg, lifetime)
+               );
+               actor.(weaponentity).m_weapon = oldwep;
+               if ((actor.(weaponentity).wframe == WFRAME_IDLE) ||
+                       (actor.(weaponentity).wframe == WFRAME_FIRE2))
+               {
+                       // Set secondary fire animation.
+                       vector a = '0 0 0';
+                       actor.(weaponentity).wframe = WFRAME_FIRE2;
+                       a = actor.(weaponentity).anim_fire2;
+                       a.z *= g_weaponratefactor;
+                       FOREACH_CLIENT(true, LAMBDA(
+                               if (it == actor || (IS_SPEC(it) && it.enemy == actor))
+                               {
+                                       wframe_send(it, actor.(weaponentity), a, true);
+                               }
+                       ));
+                       animdecide_setaction(actor, ANIMACTION_SHOOT, true);
+               }
+       }
+    if (WEP_CVAR(hmg, reload_ammo) && actor.(weaponentity).clip_load < WEP_CVAR_PRI(hmg, ammo))
+       {
+               // Forced reload.
         thiswep.wr_reload(thiswep, actor, weaponentity);
-    } else
-    {
-        if (fire & 1)
-        if (weapon_prepareattack(thiswep, actor, weaponentity, false, 0))
-        {
-            actor.(weaponentity).misc_bulletcounter = 0;
-            W_HeavyMachineGun_Attack_Auto(thiswep, actor, weaponentity, fire);
-        }
-    }
+               return;
+       }
+       if (fire & 1) // Primary attack
+       {
+               if (!weapon_prepareattack(thiswep, actor, weaponentity, false, 0))
+               {
+                       return;
+               }
+               actor.(weaponentity).misc_bulletcounter = 0;
+               W_HeavyMachineGun_Attack_Auto(thiswep, actor, weaponentity, fire);
+               return;
+       }
+       if ((fire & 2) && (WEP_CVAR_SEC(hmg, refire_type) == 0)) // Secondary attack
+       {
+               if (!weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_SEC(hmg, refire)))
+               {
+                       return;
+               }
+               // ugly instagib hack to reuse the fire mode of the laser
+               makevectors(actor.v_angle);
+               Weapon oldwep = actor.(weaponentity).m_weapon; // we can't avoid this hack
+               actor.(weaponentity).m_weapon = WEP_BLASTER;
+               W_Blaster_Attack(
+                       actor,
+                       weaponentity,
+                       WEP_BLASTER.m_id | HITTYPE_SECONDARY,
+                       WEP_CVAR_SEC(hmg, shotangle),
+                       WEP_CVAR_SEC(hmg, damage),
+                       WEP_CVAR_SEC(hmg, edgedamage),
+                       WEP_CVAR_SEC(hmg, radius),
+                       WEP_CVAR_SEC(hmg, force),
+                       WEP_CVAR_SEC(hmg, speed),
+                       WEP_CVAR_SEC(hmg, spread),
+                       WEP_CVAR_SEC(hmg, delay),
+                       WEP_CVAR_SEC(hmg, lifetime)
+               );
+               actor.(weaponentity).m_weapon = oldwep;
+               weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(hmg, animtime), w_ready);
+       }
 }
 
 METHOD(HeavyMachineGun, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
 {
-    float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(hmg, ammo);
-
-    if(autocvar_g_balance_hmg_reload_ammo)
-        ammo_amount += actor.(weaponentity).(weapon_load[WEP_HMG.m_id]) >= WEP_CVAR(hmg, ammo);
-
-    return ammo_amount;
+       float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(hmg, ammo);
+       if (autocvar_g_balance_hmg_reload_ammo)
+       {
+               ammo_amount += actor.(weaponentity).(weapon_load[WEP_HMG.m_id]) >= WEP_CVAR_PRI(hmg, ammo);
+       }
+       return ammo_amount;
 }
 
 METHOD(HeavyMachineGun, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
 {
-    float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(hmg, ammo);
-
-    if(autocvar_g_balance_hmg_reload_ammo)
-        ammo_amount += actor.(weaponentity).(weapon_load[WEP_HMG.m_id]) >= WEP_CVAR(hmg, ammo);
-
-    return ammo_amount;
+       float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(hmg, ammo);
+       if (autocvar_g_balance_hmg_reload_ammo)
+       {
+               ammo_amount += actor.(weaponentity).(weapon_load[WEP_HMG.m_id]) >= WEP_CVAR_SEC(hmg, ammo);
+       }
+       return ammo_amount;
 }
 
 METHOD(HeavyMachineGun, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
 {
-    W_Reload(actor, weaponentity, WEP_CVAR(hmg, ammo), SND_RELOAD);
+    W_Reload(actor, weaponentity, WEP_CVAR_PRI(hmg, ammo), SND_RELOAD);
 }
 
 METHOD(HeavyMachineGun, wr_suicidemessage, Notification(entity thiswep))
index 07b31ea3cd107c6e9af918b749b17d2ac4c0673f..58ad5001679c57dc79ebefad3076c45f22f17160 100644 (file)
@@ -18,16 +18,29 @@ CLASS(HeavyMachineGun, Weapon)
 
 #define X(BEGIN, P, END, class, prefix) \
        BEGIN(class) \
-               P(class, prefix, ammo, float, NONE) \
-               P(class, prefix, damage, float, NONE) \
-               P(class, prefix, force, float, NONE) \
-               P(class, prefix, refire, float, NONE) \
+               P(class, prefix, ammo, float, PRI) \
+               P(class, prefix, damage, float, PRI) \
+               P(class, prefix, force, float, PRI) \
+               P(class, prefix, refire, float, PRI) \
+               P(class, prefix, solidpenetration, float, PRI) \
+               P(class, prefix, spread_add, float, PRI) \
+               P(class, prefix, spread_max, float, PRI) \
+               P(class, prefix, spread_min, float, PRI) \
+               P(class, prefix, ammo, float, SEC) \
+               P(class, prefix, animtime, float, SEC) \
+               P(class, prefix, damage, float, SEC) \
+               P(class, prefix, delay, float, SEC) \
+               P(class, prefix, edgedamage, float, SEC) \
+               P(class, prefix, force, float, SEC) \
+               P(class, prefix, lifetime, float, SEC) \
+               P(class, prefix, radius, float, SEC) \
+               P(class, prefix, refire, float, SEC) \
+               P(class, prefix, refire_type, float, SEC) \
+               P(class, prefix, shotangle, float, SEC) \
+               P(class, prefix, speed, float, SEC) \
+               P(class, prefix, spread, float, SEC) \
                P(class, prefix, reload_ammo, float, NONE) \
                P(class, prefix, reload_time, float, NONE) \
-               P(class, prefix, solidpenetration, float, NONE) \
-               P(class, prefix, spread_add, float, NONE) \
-               P(class, prefix, spread_max, float, NONE) \
-               P(class, prefix, spread_min, float, NONE) \
                P(class, prefix, switchdelay_drop, float, NONE) \
                P(class, prefix, switchdelay_raise, float, NONE) \
                P(class, prefix, weaponreplace, string, NONE) \
diff --git a/qcsrc/common/mutators/mutator/overkill/okmachinegun.qc b/qcsrc/common/mutators/mutator/overkill/okmachinegun.qc
new file mode 100644 (file)
index 0000000..d2ed859
--- /dev/null
@@ -0,0 +1,206 @@
+#include "okmachinegun.qh"
+
+#ifdef SVQC
+
+spawnfunc(weapon_okmachinegun)
+{
+       if(autocvar_sv_q3acompat_machineshotgunswap)
+       if(this.classname != "droppedweapon")
+       {
+               weapon_defaultspawnfunc(this, WEP_SHOCKWAVE);
+               return;
+       }
+       weapon_defaultspawnfunc(this, WEP_OVERKILL_MACHINEGUN);
+}
+
+void W_OverkillMachineGun_Attack_Auto(Weapon thiswep, entity actor, .entity weaponentity, int fire)
+{
+       float okmachinegun_spread;
+
+       if(!(fire & 1))
+       {
+               w_ready(thiswep, actor, weaponentity, fire);
+               return;
+       }
+
+       if(!thiswep.wr_checkammo1(thiswep, actor, weaponentity))
+       if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
+       {
+               W_SwitchWeapon_Force(actor, w_getbestweapon(actor, weaponentity), weaponentity);
+               w_ready(thiswep, actor, weaponentity, fire);
+               return;
+       }
+
+       W_DecreaseAmmo(WEP_OVERKILL_MACHINEGUN, actor, WEP_CVAR_PRI(okmachinegun, ammo), weaponentity);
+
+       W_SetupShot(actor, weaponentity, true, 0, SND_UZI_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(okmachinegun, damage));
+       if(!autocvar_g_norecoil)
+       {
+               actor.punchangle_x = random() - 0.5;
+               actor.punchangle_y = random() - 0.5;
+       }
+
+       okmachinegun_spread = bound(WEP_CVAR_PRI(okmachinegun, spread_min), WEP_CVAR_PRI(okmachinegun, spread_min) + (WEP_CVAR_PRI(okmachinegun, spread_add) * actor.(weaponentity).misc_bulletcounter), WEP_CVAR_PRI(okmachinegun, spread_max));
+       fireBullet(actor, weaponentity, w_shotorg, w_shotdir, okmachinegun_spread, WEP_CVAR_PRI(okmachinegun, solidpenetration), WEP_CVAR_PRI(okmachinegun, damage), WEP_CVAR_PRI(okmachinegun, force), WEP_OVERKILL_MACHINEGUN.m_id, 0);
+
+       actor.(weaponentity).misc_bulletcounter = actor.(weaponentity).misc_bulletcounter + 1;
+
+       Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
+
+       W_MachineGun_MuzzleFlash(actor, weaponentity);
+       W_AttachToShotorg(actor, weaponentity, actor.(weaponentity).muzzle_flash, '5 0 0');
+
+       if(autocvar_g_casings >= 2) // casing code
+       {
+               makevectors(actor.v_angle); // for some reason, this is lost
+               SpawnCasing(((random() * 50 + 50) * v_right) - (v_forward * (random() * 25 + 25)) - ((random() * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, actor, weaponentity);
+       }
+
+       int slot = weaponslot(weaponentity);
+       ATTACK_FINISHED(actor, slot) = time + WEP_CVAR_PRI(okmachinegun, refire) * W_WeaponRateFactor(actor);
+       weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(okmachinegun, refire), W_OverkillMachineGun_Attack_Auto);
+}
+
+METHOD(OverkillMachineGun, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
+{
+       if(vdist(actor.origin - actor.enemy.origin, <, 3000 - bound(0, skill, 10) * 200))
+               PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
+       else
+               PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
+}
+
+METHOD(OverkillMachineGun, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
+{
+       if ((WEP_CVAR_SEC(okmachinegun, refire_type) == 1) && (fire & 2) && (time >= actor.jump_interval))
+       {
+               // Secondary uses it's own refire timer if refire_type is 1.
+               actor.jump_interval = time + WEP_CVAR_SEC(okmachinegun, refire) * W_WeaponRateFactor(actor);
+               // Ugly hack to reuse the fire mode of the blaster.
+               makevectors(actor.v_angle);
+               Weapon oldwep = actor.(weaponentity).m_weapon; // we can't avoid this hack
+               actor.(weaponentity).m_weapon = WEP_BLASTER;
+               W_Blaster_Attack(
+                       actor,
+                       weaponentity,
+                       WEP_BLASTER.m_id | HITTYPE_SECONDARY,
+                       WEP_CVAR_SEC(okmachinegun, shotangle),
+                       WEP_CVAR_SEC(okmachinegun, damage),
+                       WEP_CVAR_SEC(okmachinegun, edgedamage),
+                       WEP_CVAR_SEC(okmachinegun, radius),
+                       WEP_CVAR_SEC(okmachinegun, force),
+                       WEP_CVAR_SEC(okmachinegun, speed),
+                       WEP_CVAR_SEC(okmachinegun, spread),
+                       WEP_CVAR_SEC(okmachinegun, delay),
+                       WEP_CVAR_SEC(okmachinegun, lifetime)
+               );
+               actor.(weaponentity).m_weapon = oldwep;
+               if ((actor.(weaponentity).wframe == WFRAME_IDLE) ||
+                       (actor.(weaponentity).wframe == WFRAME_FIRE2))
+               {
+                       // Set secondary fire animation.
+                       vector a = '0 0 0';
+                       actor.(weaponentity).wframe = WFRAME_FIRE2;
+                       a = actor.(weaponentity).anim_fire2;
+                       a.z *= g_weaponratefactor;
+                       FOREACH_CLIENT(true, LAMBDA(
+                               if (it == actor || (IS_SPEC(it) && it.enemy == actor))
+                               {
+                                       wframe_send(it, actor.(weaponentity), a, true);
+                               }
+                       ));
+                       animdecide_setaction(actor, ANIMACTION_SHOOT, true);
+               }
+       }
+       if (WEP_CVAR(okmachinegun, reload_ammo) && actor.(weaponentity).clip_load < WEP_CVAR_PRI(okmachinegun, ammo))
+       {
+               // Forced reload
+               thiswep.wr_reload(thiswep, actor, weaponentity);
+               return;
+       }
+       if (fire & 1) // Primary attack
+       {
+               if (!weapon_prepareattack(thiswep, actor, weaponentity, false, 0))
+               {
+                       return;
+               }
+               actor.(weaponentity).misc_bulletcounter = 0;
+               W_OverkillMachineGun_Attack_Auto(thiswep, actor, weaponentity, fire);
+               return;
+       }
+       if ((fire & 2) && (WEP_CVAR_SEC(okmachinegun, refire_type) == 0)) // Secondary attack
+       {
+               if (!weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(okmachinegun, refire)))
+               {
+                       return;
+               }
+               // Ugly hack to reuse the fire mode of the blaster.
+               makevectors(actor.v_angle);
+               Weapon oldwep = actor.(weaponentity).m_weapon; // we can't avoid this hack
+               actor.(weaponentity).m_weapon = WEP_BLASTER;
+               W_Blaster_Attack(
+                       actor,
+                       weaponentity,
+                       WEP_BLASTER.m_id | HITTYPE_SECONDARY,
+                       WEP_CVAR_SEC(okmachinegun, shotangle),
+                       WEP_CVAR_SEC(okmachinegun, damage),
+                       WEP_CVAR_SEC(okmachinegun, edgedamage),
+                       WEP_CVAR_SEC(okmachinegun, radius),
+                       WEP_CVAR_SEC(okmachinegun, force),
+                       WEP_CVAR_SEC(okmachinegun, speed),
+                       WEP_CVAR_SEC(okmachinegun, spread),
+                       WEP_CVAR_SEC(okmachinegun, delay),
+                       WEP_CVAR_SEC(okmachinegun, lifetime)
+               );
+               actor.(weaponentity).m_weapon = oldwep;
+               weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(okmachinegun, animtime), w_ready);
+       }
+}
+
+METHOD(OverkillMachineGun, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
+{
+       float ammo_amount;
+       ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(okmachinegun, ammo);
+       if (WEP_CVAR(okmachinegun, reload_ammo))
+       {
+               ammo_amount += actor.(weaponentity).(weapon_load[WEP_OVERKILL_MACHINEGUN.m_id]) >= WEP_CVAR_PRI(okmachinegun, ammo);
+       }
+       return ammo_amount;
+}
+
+METHOD(OverkillMachineGun, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
+{
+       return true; // Blaster secondary is unlimited.
+}
+
+METHOD(OverkillMachineGun, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
+{
+       W_Reload(actor, weaponentity, WEP_CVAR_PRI(okmachinegun, ammo), SND_RELOAD);
+}
+
+METHOD(OverkillMachineGun, wr_suicidemessage, Notification(entity thiswep))
+{
+       return WEAPON_THINKING_WITH_PORTALS;
+}
+
+METHOD(OverkillMachineGun, wr_killmessage, Notification(entity thiswep))
+{
+       if(w_deathtype & HITTYPE_SECONDARY)
+               return WEAPON_MACHINEGUN_MURDER_SNIPE;
+       else
+               return WEAPON_MACHINEGUN_MURDER_SPRAY;
+}
+
+#endif
+#ifdef CSQC
+
+METHOD(OverkillMachineGun, wr_impacteffect, void(entity thiswep, entity actor))
+{
+       vector org2;
+       org2 = w_org + w_backoff * 2;
+       pointparticles(EFFECT_MACHINEGUN_IMPACT, org2, w_backoff * 1000, 1);
+       if(!w_issilent)
+               sound(actor, CH_SHOTS, SND_RIC_RANDOM(), VOL_BASE, ATTN_NORM);
+}
+
+#endif
+
diff --git a/qcsrc/common/mutators/mutator/overkill/okmachinegun.qh b/qcsrc/common/mutators/mutator/overkill/okmachinegun.qh
new file mode 100644 (file)
index 0000000..24987c5
--- /dev/null
@@ -0,0 +1,55 @@
+#pragma once
+
+CLASS(OverkillMachineGun, Weapon)
+/* ammotype  */ ATTRIB(OverkillMachineGun, ammo_type, int, RESOURCE_BULLETS);
+/* impulse   */ ATTRIB(OverkillMachineGun, impulse, int, 3);
+/* flags        */ ATTRIB(OverkillMachineGun, spawnflags, int, WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS);
+/* rating      */ ATTRIB(OverkillMachineGun, bot_pickupbasevalue, float, 7000);
+/* color        */ ATTRIB(OverkillMachineGun, wpcolor, vector, '1 1 0');
+/* modelname */ ATTRIB(OverkillMachineGun, mdl, string, "ok_mg");
+#ifdef GAMEQC
+/* model        */ ATTRIB(OverkillMachineGun, m_model, Model, MDL_OK_MG_ITEM);
+#endif
+/* crosshair */ ATTRIB(OverkillMachineGun, w_crosshair, string, "gfx/crosshairuzi");
+/* crosshair */ ATTRIB(OverkillMachineGun, w_crosshair_size, float, 0.6);
+/* wepimg      */ ATTRIB(OverkillMachineGun, model2, string, "weaponuzi");
+/* refname   */ ATTRIB(OverkillMachineGun, netname, string, "okmachinegun");
+/* wepname   */ ATTRIB(OverkillMachineGun, m_name, string, _("Overkill MachineGun"));
+
+#define X(BEGIN, P, END, class, prefix) \
+       BEGIN(class) \
+               P(class, prefix, ammo, float, PRI) \
+               P(class, prefix, damage, float, PRI) \
+               P(class, prefix, force, float, PRI) \
+               P(class, prefix, refire, float, PRI) \
+               P(class, prefix, solidpenetration, float, PRI) \
+               P(class, prefix, spread_add, float, PRI) \
+               P(class, prefix, spread_max, float, PRI) \
+               P(class, prefix, spread_min, float, PRI) \
+               P(class, prefix, animtime, float, SEC) \
+               P(class, prefix, damage, float, SEC) \
+               P(class, prefix, delay, float, SEC) \
+               P(class, prefix, edgedamage, float, SEC) \
+               P(class, prefix, force, float, SEC) \
+               P(class, prefix, lifetime, float, SEC) \
+               P(class, prefix, radius, float, SEC) \
+               P(class, prefix, refire, float, SEC) \
+               P(class, prefix, refire_type, float, SEC) \
+               P(class, prefix, shotangle, float, SEC) \
+               P(class, prefix, speed, float, SEC) \
+               P(class, prefix, spread, float, SEC) \
+               P(class, prefix, reload_ammo, float, NONE) \
+               P(class, prefix, reload_time, float, NONE) \
+               P(class, prefix, switchdelay_drop, float, NONE) \
+               P(class, prefix, switchdelay_raise, float, NONE) \
+               P(class, prefix, weaponreplace, string, NONE) \
+               P(class, prefix, weaponstartoverride, float, NONE) \
+               P(class, prefix, weaponstart, float, NONE) \
+               P(class, prefix, weaponthrowable, float, NONE) \
+       END()
+       W_PROPS(X, OverkillMachineGun, okmachinegun)
+#undef X
+
+ENDCLASS(OverkillMachineGun)
+REGISTER_WEAPON(OVERKILL_MACHINEGUN, okmachinegun, NEW(OverkillMachineGun));
+
diff --git a/qcsrc/common/mutators/mutator/overkill/okshotgun.qc b/qcsrc/common/mutators/mutator/overkill/okshotgun.qc
new file mode 100644 (file)
index 0000000..8f606bd
--- /dev/null
@@ -0,0 +1,157 @@
+#include "okshotgun.qh"
+
+#ifdef SVQC
+spawnfunc(weapon_okshotgun) { weapon_defaultspawnfunc(this, WEP_OVERKILL_SHOTGUN); }
+
+METHOD(OverkillShotgun, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
+{
+       if (vdist(actor.origin - actor.enemy.origin, >, WEP_CVAR_PRI(okshotgun, bot_range)))
+       {
+               PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
+       }
+       else
+       {
+               PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
+       }
+}
+
+METHOD(OverkillShotgun, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
+{
+       if ((WEP_CVAR_SEC(okshotgun, refire_type) == 1) && (fire & 2) && (time >= actor.jump_interval))
+       {
+               // Secondary uses it's own refire timer if refire_type is 1.
+               actor.jump_interval = time + WEP_CVAR_SEC(okshotgun, refire) * W_WeaponRateFactor(actor);
+               // Ugly hack to reuse the fire mode of the blaster.
+               makevectors(actor.v_angle);
+               Weapon oldwep = actor.(weaponentity).m_weapon; // we can't avoid this hack
+               actor.(weaponentity).m_weapon = WEP_BLASTER;
+               W_Blaster_Attack(
+                       actor,
+                       weaponentity,
+                       WEP_BLASTER.m_id | HITTYPE_SECONDARY,
+                       WEP_CVAR_SEC(okshotgun, shotangle),
+                       WEP_CVAR_SEC(okshotgun, damage),
+                       WEP_CVAR_SEC(okshotgun, edgedamage),
+                       WEP_CVAR_SEC(okshotgun, radius),
+                       WEP_CVAR_SEC(okshotgun, force),
+                       WEP_CVAR_SEC(okshotgun, speed),
+                       WEP_CVAR_SEC(okshotgun, spread),
+                       WEP_CVAR_SEC(okshotgun, delay),
+                       WEP_CVAR_SEC(okshotgun, lifetime)
+               );
+               actor.(weaponentity).m_weapon = oldwep;
+               if ((actor.(weaponentity).wframe == WFRAME_IDLE) ||
+                       (actor.(weaponentity).wframe == WFRAME_FIRE2))
+               {
+                       // Set secondary fire animation.
+                       vector a = '0 0 0';
+                       actor.(weaponentity).wframe = WFRAME_FIRE2;
+                       a = actor.(weaponentity).anim_fire2;
+                       a.z *= g_weaponratefactor;
+                       FOREACH_CLIENT(true, LAMBDA(
+                               if (it == actor || (IS_SPEC(it) && it.enemy == actor))
+                               {
+                                       wframe_send(it, actor.(weaponentity), a, true);
+                               }
+                       ));
+                       animdecide_setaction(actor, ANIMACTION_SHOOT, true);
+               }
+       }
+       if (WEP_CVAR(okshotgun, reload_ammo) && actor.(weaponentity).clip_load < WEP_CVAR_PRI(okshotgun, ammo))
+       {
+               // Forced reload
+               thiswep.wr_reload(thiswep, actor, weaponentity);
+               return;
+       }
+       if (fire & 1) // Primary attack
+       {
+               if (!weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(okshotgun, animtime)))
+               {
+                       return;
+               }
+               W_Shotgun_Attack(thiswep, actor, weaponentity, true,
+                       WEP_CVAR_PRI(okshotgun, ammo),
+                       WEP_CVAR_PRI(okshotgun, damage),
+                       WEP_CVAR_PRI(okshotgun, bullets),
+                       WEP_CVAR_PRI(okshotgun, spread),
+                       WEP_CVAR_PRI(okshotgun, solidpenetration),
+                       WEP_CVAR_PRI(okshotgun, force));
+               weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(okshotgun, animtime), w_ready);
+               return;
+       }
+       if ((fire & 2) && (WEP_CVAR_SEC(okshotgun, refire_type) == 0)) // Secondary attack
+       {
+               if (!weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_SEC(okshotgun, refire)))
+               {
+                       return;
+               }
+               // Ugly hack to reuse the fire mode of the blaster.
+               makevectors(actor.v_angle);
+               Weapon oldwep = actor.(weaponentity).m_weapon; // we can't avoid this hack
+               actor.(weaponentity).m_weapon = WEP_BLASTER;
+               W_Blaster_Attack(
+                       actor,
+                       weaponentity,
+                       WEP_BLASTER.m_id | HITTYPE_SECONDARY,
+                       WEP_CVAR_SEC(okshotgun, shotangle),
+                       WEP_CVAR_SEC(okshotgun, damage),
+                       WEP_CVAR_SEC(okshotgun, edgedamage),
+                       WEP_CVAR_SEC(okshotgun, radius),
+                       WEP_CVAR_SEC(okshotgun, force),
+                       WEP_CVAR_SEC(okshotgun, speed),
+                       WEP_CVAR_SEC(okshotgun, spread),
+                       WEP_CVAR_SEC(okshotgun, delay),
+                       WEP_CVAR_SEC(okshotgun, lifetime)
+               );
+               actor.(weaponentity).m_weapon = oldwep;
+               weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(okshotgun, animtime), w_ready);
+       }
+}
+
+METHOD(OverkillShotgun, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
+{
+       float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(okshotgun, ammo);
+       ammo_amount += actor.(weaponentity).(weapon_load[WEP_OVERKILL_SHOTGUN.m_id]) >= WEP_CVAR_PRI(okshotgun, ammo);
+       return ammo_amount;
+}
+
+METHOD(OverkillShotgun, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
+{
+       return true; // Blaster secondary is unlimited.
+}
+
+METHOD(OverkillShotgun, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
+{
+       W_Reload(actor, weaponentity, WEP_CVAR_PRI(okshotgun, ammo), SND_RELOAD); // WEAPONTODO
+}
+
+METHOD(OverkillShotgun, wr_suicidemessage, Notification(entity thiswep))
+{
+       return WEAPON_THINKING_WITH_PORTALS;
+}
+
+METHOD(OverkillShotgun, wr_killmessage, Notification(entity thiswep))
+{
+       if(w_deathtype & HITTYPE_SECONDARY)
+               return WEAPON_BLASTER_MURDER;
+       else
+               return WEAPON_SHOTGUN_MURDER;
+}
+
+#endif
+#ifdef CSQC
+.float prevric;
+
+METHOD(OverkillShotgun, wr_impacteffect, void(entity thiswep, entity actor))
+{
+       vector org2 = w_org + w_backoff * 2;
+       pointparticles(EFFECT_SHOTGUN_IMPACT, org2, w_backoff * 1000, 1);
+       if(!w_issilent && time - actor.prevric > 0.25)
+       {
+               if(w_random < 0.05)
+                       sound(actor, CH_SHOTS, SND_RIC_RANDOM(), VOL_BASE, ATTEN_NORM);
+               actor.prevric = time;
+       }
+}
+
+#endif
diff --git a/qcsrc/common/mutators/mutator/overkill/okshotgun.qh b/qcsrc/common/mutators/mutator/overkill/okshotgun.qh
new file mode 100644 (file)
index 0000000..1b6582a
--- /dev/null
@@ -0,0 +1,55 @@
+#pragma once
+
+CLASS(OverkillShotgun, Weapon)
+/* ammotype  */ ATTRIB(OverkillShotgun, ammo_type, int, RESOURCE_SHELLS);
+/* impulse   */ ATTRIB(OverkillShotgun, impulse, int, 2);
+/* flags     */ ATTRIB(OverkillShotgun, spawnflags, int, WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN);
+/* rating    */ ATTRIB(OverkillShotgun, bot_pickupbasevalue, float, 6000);
+/* color     */ ATTRIB(OverkillShotgun, wpcolor, vector, '0.5 0.25 0');
+/* modelname */ ATTRIB(OverkillShotgun, mdl, string, "ok_shotgun");
+#ifdef GAMEQC
+/* model     */ ATTRIB(OverkillShotgun, m_model, Model, MDL_OK_SHOTGUN_ITEM);
+#endif
+/* crosshair */ ATTRIB(OverkillShotgun, w_crosshair, string, "gfx/crosshairshotgun");
+/* crosshair */ ATTRIB(OverkillShotgun, w_crosshair_size, float, 0.65);
+/* wepimg    */ ATTRIB(OverkillShotgun, model2, string, "weaponshotgun");
+/* refname   */ ATTRIB(OverkillShotgun, netname, string, "okshotgun");
+/* wepname   */ ATTRIB(OverkillShotgun, m_name, string, _("Overkill Shotgun"));
+
+#define X(BEGIN, P, END, class, prefix) \
+       BEGIN(class) \
+               P(class, prefix, ammo, float, PRI) \
+               P(class, prefix, animtime, float, PRI) \
+               P(class, prefix, bot_range, float, PRI) \
+               P(class, prefix, bullets, float, PRI) \
+               P(class, prefix, damage, float, PRI) \
+               P(class, prefix, force, float, PRI) \
+               P(class, prefix, refire, float, PRI) \
+               P(class, prefix, solidpenetration, float, PRI) \
+               P(class, prefix, spread, float, PRI) \
+               P(class, prefix, animtime, float, SEC) \
+               P(class, prefix, damage, float, SEC) \
+               P(class, prefix, delay, float, SEC) \
+               P(class, prefix, edgedamage, float, SEC) \
+               P(class, prefix, force, float, SEC) \
+               P(class, prefix, lifetime, float, SEC) \
+               P(class, prefix, radius, float, SEC) \
+               P(class, prefix, refire, float, SEC) \
+               P(class, prefix, refire_type, float, SEC) \
+               P(class, prefix, shotangle, float, SEC) \
+               P(class, prefix, speed, float, SEC) \
+               P(class, prefix, spread, float, SEC) \
+               P(class, prefix, reload_ammo, float, NONE) \
+               P(class, prefix, reload_time, float, NONE) \
+               P(class, prefix, switchdelay_drop, float, NONE) \
+               P(class, prefix, switchdelay_raise, float, NONE) \
+               P(class, prefix, weaponreplace, string,NONE) \
+               P(class, prefix, weaponstartoverride, float, NONE) \
+               P(class, prefix, weaponstart, float, NONE) \
+               P(class, prefix, weaponthrowable, float, NONE) \
+       END()
+       W_PROPS(X, OverkillShotgun, okshotgun)
+#undef X
+
+ENDCLASS(OverkillShotgun)
+REGISTER_WEAPON(OVERKILL_SHOTGUN, okshotgun, NEW(OverkillShotgun));
diff --git a/qcsrc/common/mutators/mutator/overkill/okvortex.qc b/qcsrc/common/mutators/mutator/overkill/okvortex.qc
new file mode 100644 (file)
index 0000000..6b37b0d
--- /dev/null
@@ -0,0 +1,409 @@
+#include "okvortex.qh"
+
+#ifdef SVQC
+
+.float okvortex_lasthit;
+#endif
+
+#if defined(GAMEQC)
+
+METHOD(OverkillVortex, wr_glow, vector(OverkillVortex this, entity actor, entity wepent))
+{
+       if (!WEP_CVAR(okvortex, charge)) return '0 0 0';
+       float charge = wepent.okvortex_charge;
+       float animlimit = WEP_CVAR(okvortex, charge_animlimit);
+       vector g;
+       g.x = autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_red_half * min(1, charge / animlimit);
+       g.y = autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_green_half * min(1, charge / animlimit);
+       g.z = autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_blue_half * min(1, charge / animlimit);
+       if (charge > animlimit)
+       {
+               g.x += autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_red_full * (charge - animlimit) / (1 - animlimit);
+               g.y += autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_green_full * (charge - animlimit) / (1 - animlimit);
+               g.z += autocvar_g_weapon_charge_colormod_hdrmultiplier * autocvar_g_weapon_charge_colormod_blue_full * (charge - animlimit) / (1 - animlimit);
+       }
+       return g;
+}
+#endif
+
+#ifdef SVQC
+spawnfunc(weapon_okvortex) { weapon_defaultspawnfunc(this, WEP_OVERKILL_VORTEX); }
+
+REGISTER_MUTATOR(okvortex_charge, true);
+
+MUTATOR_HOOKFUNCTION(okvortex_charge, GetPressedKeys)
+{
+       entity player = M_ARGV(0, entity);
+
+       // WEAPONTODO
+       if(!WEP_CVAR(okvortex, charge) || !WEP_CVAR(okvortex, charge_velocity_rate))
+               return;
+
+       for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+       {
+               .entity weaponentity = weaponentities[slot];
+
+               if (player.(weaponentity).m_weapon == WEP_OVERKILL_VORTEX && WEP_CVAR(okvortex, charge) && WEP_CVAR(okvortex, charge_velocity_rate) && vdist(vec2(player.velocity), >, WEP_CVAR(okvortex, charge_minspeed)))
+               {
+                       float xyspeed = vlen(vec2(player.velocity));
+                       // add a maximum of charge_velocity_rate when going fast (f = 1), gradually increasing from minspeed (f = 0) to maxspeed
+                               xyspeed = min(xyspeed, WEP_CVAR(okvortex, charge_maxspeed));
+                       float f = (xyspeed - WEP_CVAR(okvortex, charge_minspeed)) / (WEP_CVAR(okvortex, charge_maxspeed) - WEP_CVAR(okvortex, charge_minspeed));
+                       // add the extra charge
+                       player.(weaponentity).okvortex_charge = min(1, player.(weaponentity).okvortex_charge + WEP_CVAR(okvortex, charge_velocity_rate) * f * PHYS_INPUT_TIMELENGTH);
+               }
+       }
+}
+
+void W_OverkillVortex_Attack(Weapon thiswep, entity actor, .entity weaponentity, float issecondary)
+{
+       float mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, myammo, charge;
+
+       mydmg = WEP_CVAR_BOTH(okvortex, !issecondary, damage);
+       myforce = WEP_CVAR_BOTH(okvortex, !issecondary, force);
+       mymindist = WEP_CVAR_BOTH(okvortex, !issecondary, damagefalloff_mindist);
+       mymaxdist = WEP_CVAR_BOTH(okvortex, !issecondary, damagefalloff_maxdist);
+       myhalflife = WEP_CVAR_BOTH(okvortex, !issecondary, damagefalloff_halflife);
+       myforcehalflife = WEP_CVAR_BOTH(okvortex, !issecondary, damagefalloff_forcehalflife);
+       myammo = WEP_CVAR_BOTH(okvortex, !issecondary, ammo);
+
+       float flying;
+       flying = IsFlying(actor); // do this BEFORE to make the trace values from FireRailgunBullet last
+
+       if (WEP_CVAR(okvortex, charge))
+       {
+               charge = WEP_CVAR(okvortex, charge_mindmg) / mydmg + (1 - WEP_CVAR(okvortex, charge_mindmg) / mydmg) * actor.(weaponentity).okvortex_charge;
+               actor.(weaponentity).okvortex_charge *= WEP_CVAR(okvortex, charge_shot_multiplier); // do this AFTER setting mydmg/myforce
+               // O RLY? -- divVerent
+               // YA RLY -- FruitieX
+       }
+       else
+       {
+               charge = 1;
+       }
+       mydmg *= charge;
+       myforce *= charge;
+
+       W_SetupShot(actor, weaponentity, true, 5, SND_NEXFIRE, CH_WEAPON_A, mydmg);
+       if(charge > WEP_CVAR(okvortex, charge_animlimit) && WEP_CVAR(okvortex, charge_animlimit)) // if the OverkillVortex is overcharged, we play an extra sound
+       {
+               sound(actor, CH_WEAPON_B, SND_NEXCHARGE, VOL_BASE * (charge - 0.5 * WEP_CVAR(okvortex, charge_animlimit)) / (1 - 0.5 * WEP_CVAR(okvortex, charge_animlimit)), ATTN_NORM);
+       }
+
+       yoda = 0;
+       damage_goodhits = 0;
+       FireRailgunBullet(actor, weaponentity, w_shotorg, w_shotorg + w_shotdir * max_shot_distance, mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, WEP_OVERKILL_VORTEX.m_id);
+
+       if(yoda && flying)
+               Send_Notification(NOTIF_ONE, actor, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA);
+       if(damage_goodhits && actor.okvortex_lasthit)
+       {
+               Send_Notification(NOTIF_ONE, actor, MSG_ANNCE, ANNCE_ACHIEVEMENT_IMPRESSIVE);
+               damage_goodhits = 0; // only every second time
+       }
+
+       actor.okvortex_lasthit = damage_goodhits;
+
+       //beam and muzzle flash done on client
+       SendCSQCVortexBeamParticle(charge);
+
+       W_DecreaseAmmo(thiswep, actor, myammo, weaponentity);
+}
+
+.float okvortex_chargepool_pauseregen_finished;
+
+METHOD(OverkillVortex, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
+{
+       if(bot_aim(actor, weaponentity, 1000000, 0, 1, false))
+               PHYS_INPUT_BUTTON_ATCK(actor) = true;
+       else
+       {
+               if(WEP_CVAR(okvortex, charge))
+                       PHYS_INPUT_BUTTON_ATCK2(actor) = true;
+       }
+}
+
+METHOD(OverkillVortex, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
+{
+       if (WEP_CVAR(okvortex, charge) && actor.(weaponentity).okvortex_charge < WEP_CVAR(okvortex, charge_limit))
+       {
+               actor.(weaponentity).okvortex_charge = min(1, actor.(weaponentity).okvortex_charge + WEP_CVAR(okvortex, charge_rate) * frametime / W_TICSPERFRAME);
+       }
+
+       if (weaponslot(weaponentity) == 0)
+       {
+               actor.okvortex_charge = actor.(weaponentity).okvortex_charge;
+       }
+
+       if (WEP_CVAR_SEC(okvortex, chargepool))
+               if (actor.(weaponentity).okvortex_chargepool_ammo < 1)
+               {
+                       if (actor.okvortex_chargepool_pauseregen_finished < time)
+                               actor.(weaponentity).okvortex_chargepool_ammo = min(1, actor.(weaponentity).okvortex_chargepool_ammo + WEP_CVAR_SEC(okvortex, chargepool_regen) * frametime / W_TICSPERFRAME);
+                       actor.pauseregen_finished = max(actor.pauseregen_finished, time + WEP_CVAR_SEC(okvortex, chargepool_pause_regen));
+               }
+
+       if(weaponslot(weaponentity) == 0)
+               actor.okvortex_chargepool_ammo = actor.(weaponentity).okvortex_chargepool_ammo;
+
+       if ((WEP_CVAR_SEC(okvortex, refire_type) == 1) && (fire & 2) && (time >= actor.jump_interval))
+       {
+               // Secondary uses it's own refire timer if refire_type is 1.
+               actor.jump_interval = time + WEP_CVAR_SEC(okvortex, refire) * W_WeaponRateFactor(actor);
+               // Ugly hack to reuse the fire mode of the blaster.
+               makevectors(actor.v_angle);
+               Weapon oldwep = actor.(weaponentity).m_weapon; // we can't avoid this hack
+               actor.(weaponentity).m_weapon = WEP_BLASTER;
+               W_Blaster_Attack(
+                       actor,
+                       weaponentity,
+                       WEP_BLASTER.m_id | HITTYPE_SECONDARY,
+                       WEP_CVAR_SEC(okvortex, shotangle),
+                       WEP_CVAR_SEC(okvortex, damage),
+                       WEP_CVAR_SEC(okvortex, edgedamage),
+                       WEP_CVAR_SEC(okvortex, radius),
+                       WEP_CVAR_SEC(okvortex, force),
+                       WEP_CVAR_SEC(okvortex, speed),
+                       WEP_CVAR_SEC(okvortex, spread),
+                       WEP_CVAR_SEC(okvortex, delay),
+                       WEP_CVAR_SEC(okvortex, lifetime)
+               );
+               actor.(weaponentity).m_weapon = oldwep;
+               if ((actor.(weaponentity).wframe == WFRAME_IDLE) ||
+                       (actor.(weaponentity).wframe == WFRAME_FIRE2))
+               {
+                       // Set secondary fire animation.
+                       vector a = '0 0 0';
+                       actor.(weaponentity).wframe = WFRAME_FIRE2;
+                       a = actor.(weaponentity).anim_fire2;
+                       a.z *= g_weaponratefactor;
+                       FOREACH_CLIENT(true, LAMBDA(
+                               if (it == actor || (IS_SPEC(it) && it.enemy == actor))
+                               {
+                                       wframe_send(it, actor.(weaponentity), a, true);
+                               }
+                       ));
+                       animdecide_setaction(actor, ANIMACTION_SHOOT, true);
+               }
+       }
+
+       if (autocvar_g_balance_okvortex_reload_ammo && actor.(weaponentity).clip_load < WEP_CVAR_PRI(okvortex, ammo))
+       {
+               // Rorced reload
+               thiswep.wr_reload(thiswep, actor, weaponentity);
+               return;
+       }
+       if (fire & 1) // Primary attack
+       {
+               if (!weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(okvortex, refire)))
+               {
+                       return;
+               }
+               W_OverkillVortex_Attack(thiswep, actor, weaponentity, 0);
+               weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(okvortex, animtime), w_ready);
+               return;
+       }
+       if ((fire & 2) && (WEP_CVAR(okvortex, secondary) == 2) && (WEP_CVAR_SEC(okvortex, refire_type) == 0))
+       {
+               // Secondary attack
+               if (!weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(okshotgun, refire)))
+               {
+                       return;
+               }
+               // ugly instagib hack to reuse the fire mode of the laser
+               makevectors(actor.v_angle);
+               Weapon oldwep = actor.(weaponentity).m_weapon; // we can't avoid this hack
+               actor.(weaponentity).m_weapon = WEP_BLASTER;
+               W_Blaster_Attack(
+                       actor,
+                       weaponentity,
+                       WEP_BLASTER.m_id | HITTYPE_SECONDARY,
+                       WEP_CVAR_SEC(okvortex, shotangle),
+                       WEP_CVAR_SEC(okvortex, damage),
+                       WEP_CVAR_SEC(okvortex, edgedamage),
+                       WEP_CVAR_SEC(okvortex, radius),
+                       WEP_CVAR_SEC(okvortex, force),
+                       WEP_CVAR_SEC(okvortex, speed),
+                       WEP_CVAR_SEC(okvortex, spread),
+                       WEP_CVAR_SEC(okvortex, delay),
+                       WEP_CVAR_SEC(okvortex, lifetime)
+               );
+               actor.(weaponentity).m_weapon = oldwep;
+               weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(okvortex, animtime), w_ready);
+               return;
+       }
+       //if ((WEP_CVAR(okvortex, charge) && (WEP_CVAR(okvortex, secondary) == 1)) ? (PHYS_INPUT_BUTTON_ZOOM(actor) | PHYS_INPUT_BUTTON_ZOOMSCRIPT(actor)) : (fire & 2))
+       //{
+       //      if(WEP_CVAR(okvortex, charge))
+       //      {
+       //              actor.(weaponentity).okvortex_charge_rottime = time + WEP_CVAR(okvortex, charge_rot_pause);
+       //              float dt = frametime / W_TICSPERFRAME;
+       //
+       //              if(actor.(weaponentity).okvortex_charge < 1)
+       //              {
+       //                      if(WEP_CVAR_SEC(okvortex, chargepool))
+       //                      {
+       //                              if(WEP_CVAR_SEC(okvortex, ammo))
+       //                              {
+       //                                      // always deplete if secondary is held
+       //                                      actor.okvortex_chargepool_ammo = max(0, actor.okvortex_chargepool_ammo - WEP_CVAR_SEC(okvortex, ammo) * dt);
+
+       //                                      dt = min(dt, (1 - actor.(weaponentity).okvortex_charge) / WEP_CVAR(okvortex, charge_rate));
+       //                                      actor.okvortex_chargepool_pauseregen_finished = time + WEP_CVAR_SEC(okvortex, chargepool_pause_regen);
+       //                                      dt = min(dt, actor.okvortex_chargepool_ammo);
+       //                                      dt = max(0, dt);
+
+       //                                      actor.(weaponentity).okvortex_charge += dt * WEP_CVAR(okvortex, charge_rate);
+       //                              }
+       //                      }
+
+       //                      else if(WEP_CVAR_SEC(okvortex, ammo))
+       //                      {
+       //                              if(fire & 2) // only eat ammo when the button is pressed
+       //                              {
+       //                                      dt = min(dt, (1 - actor.(weaponentity).okvortex_charge) / WEP_CVAR(okvortex, charge_rate));
+       //                                      if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
+       //                                      {
+       //                                              // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
+       //                                              if(autocvar_g_balance_vortex_reload_ammo)
+       //                                              {
+       //                                                      dt = min(dt, (actor.(weaponentity).clip_load - WEP_CVAR_PRI(okvortex, ammo)) / WEP_CVAR_SEC(okvortex, ammo));
+       //                                                      dt = max(0, dt);
+       //                                                      if(dt > 0)
+       //                                                      {
+       //                                                              actor.(weaponentity).clip_load = max(WEP_CVAR_SEC(okvortex, ammo), actor.(weaponentity).clip_load - WEP_CVAR_SEC(okvortex, ammo) * dt);
+       //                                                      }
+       //                                                      actor.(weaponentity).(weapon_load[WEP_OVERKILL_VORTEX.m_id]) = actor.(weaponentity).clip_load;
+       //                                              }
+       //                                              else
+       //                                              {
+       //                                                      dt = min(dt, (actor.(thiswep.ammo_field) - WEP_CVAR_PRI(okvortex, ammo)) / WEP_CVAR_SEC(okvortex, ammo));
+       //                                                      dt = max(0, dt);
+       //                                                      if(dt > 0)
+       //                                                      {
+       //                                                              actor.(thiswep.ammo_field) = max(WEP_CVAR_SEC(okvortex, ammo), actor.(thiswep.ammo_field) - WEP_CVAR_SEC(okvortex, ammo) * dt);
+       //                                                      }
+       //                                              }
+       //                                      }
+       //                                      actor.(weaponentity).okvortex_charge += dt * WEP_CVAR(okvortex, charge_rate);
+       //                              }
+       //                      }
+
+       //                      else
+       //                      {
+       //                              dt = min(dt, (1 - actor.(weaponentity).okvortex_charge) / WEP_CVAR(okvortex, charge_rate));
+       //                              actor.(weaponentity).okvortex_charge += dt * WEP_CVAR(okvortex, charge_rate);
+       //                      }
+       //              }
+       //      }
+       //      else if(WEP_CVAR(okvortex, secondary))
+       //      {
+       //              if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_SEC(okvortex, refire)))
+       //              {
+       //                      W_OverkillVortex_Attack(thiswep, actor, weaponentity, 1);
+       //                      weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_SEC(okvortex, animtime), w_ready);
+       //              }
+       //      }
+       //}
+}
+
+METHOD(OverkillVortex, wr_setup, void(entity thiswep, entity actor, .entity weaponentity))
+{
+       actor.okvortex_lasthit = 0;
+}
+
+METHOD(OverkillVortex, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
+{
+       float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(okvortex, ammo);
+       ammo_amount += (autocvar_g_balance_okvortex_reload_ammo && actor.(weaponentity).(weapon_load[WEP_OVERKILL_VORTEX.m_id]) >= WEP_CVAR_PRI(okvortex, ammo));
+       return ammo_amount;
+}
+
+METHOD(OverkillVortex, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
+{
+       if (WEP_CVAR(okvortex, secondary))
+       {
+               // don't allow charging if we don't have enough ammo
+               float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(okvortex, ammo);
+               ammo_amount += actor.(weaponentity).(weapon_load[WEP_OVERKILL_VORTEX.m_id]) >= WEP_CVAR_SEC(okvortex, ammo);
+               return ammo_amount;
+       }
+       else
+       {
+               return false; // zoom is not a fire mode
+       }
+}
+
+METHOD(OverkillVortex, wr_resetplayer, void(entity thiswep, entity actor))
+{
+       if (WEP_CVAR(okvortex, charge)) {
+               if (WEP_CVAR_SEC(okvortex, chargepool)) {
+                       actor.okvortex_chargepool_ammo = 1;
+               }
+               actor.okvortex_charge = WEP_CVAR(okvortex, charge_start);
+               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+               {
+                       .entity weaponentity = weaponentities[slot];
+                       actor.(weaponentity).okvortex_charge = WEP_CVAR(okvortex, charge_start);
+
+                       if (WEP_CVAR_SEC(okvortex, chargepool))
+                               actor.(weaponentity).okvortex_chargepool_ammo = 1;
+               }
+       }
+       actor.okvortex_lasthit = 0;
+}
+
+METHOD(OverkillVortex, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
+{
+       W_Reload(actor, weaponentity, WEP_CVAR_PRI(okvortex, ammo), SND_RELOAD);
+}
+
+METHOD(OverkillVortex, wr_suicidemessage, Notification(entity thiswep))
+{
+       return WEAPON_THINKING_WITH_PORTALS;
+}
+
+METHOD(OverkillVortex, wr_killmessage, Notification(entity thiswep))
+{
+       return WEAPON_VORTEX_MURDER;
+}
+
+METHOD(OverkillVortex, wr_zoom, bool(entity thiswep, entity actor))
+{
+       return PHYS_INPUT_BUTTON_ATCK2(actor) && !WEP_CVAR(okvortex, secondary);
+}
+
+#endif
+#ifdef CSQC
+
+METHOD(OverkillVortex, wr_impacteffect, void(entity thiswep, entity actor))
+{
+       entity this = actor;
+       vector org2 = w_org + w_backoff * 6;
+       pointparticles(EFFECT_VORTEX_IMPACT, org2, '0 0 0', 1);
+       if(!w_issilent)
+               sound(this, CH_SHOTS, SND_NEXIMPACT, VOL_BASE, ATTN_NORM);
+}
+
+METHOD(OverkillVortex, wr_init, void(entity thiswep))
+{
+       if(autocvar_cl_reticle && autocvar_cl_reticle_weapon)
+       {
+               precache_pic("gfx/reticle_nex");
+       }
+}
+
+METHOD(OverkillVortex, wr_zoom, bool(entity thiswep, entity actor))
+{
+       if(button_zoom || zoomscript_caught || (!WEP_CVAR(okvortex, secondary) && button_attack2))
+       {
+               return true;
+       }
+       else
+       {
+               // no weapon specific image for this weapon
+               return false;
+       }
+}
+
+#endif
diff --git a/qcsrc/common/mutators/mutator/overkill/okvortex.qh b/qcsrc/common/mutators/mutator/overkill/okvortex.qh
new file mode 100644 (file)
index 0000000..ebc2f34
--- /dev/null
@@ -0,0 +1,74 @@
+#pragma once
+
+CLASS(OverkillVortex, Weapon)
+/* ammotype  */ ATTRIB(OverkillVortex, ammo_type, int, RESOURCE_CELLS);
+/* impulse   */ ATTRIB(OverkillVortex, impulse, int, 7);
+/* flags     */ ATTRIB(OverkillVortex, spawnflags, int, WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN);
+/* rating    */ ATTRIB(OverkillVortex, bot_pickupbasevalue, float, 8000);
+/* color     */ ATTRIB(OverkillVortex, wpcolor, vector, '0.5 1 1');
+/* modelname */ ATTRIB(OverkillVortex, mdl, string, "ok_sniper");
+#ifdef GAMEQC
+/* model     */ ATTRIB(OverkillVortex, m_model, Model, MDL_OK_SNIPER_ITEM);
+#endif
+/* crosshair */ ATTRIB(OverkillVortex, w_crosshair, string, "gfx/crosshairnex");
+/* crosshair */ ATTRIB(OverkillVortex, w_crosshair_size, float, 0.65);
+/* reticle   */ ATTRIB(OverkillVortex, w_reticle, string, "gfx/reticle_nex");
+/* wepimg    */ ATTRIB(OverkillVortex, model2, string, "weaponnex");
+/* refname   */ ATTRIB(OverkillVortex, netname, string, "okvortex");
+/* wepname   */ ATTRIB(OverkillVortex, m_name, string, _("Overkill Vortex"));
+
+#define X(BEGIN, P, END, class, prefix) \
+       BEGIN(class) \
+               P(class, prefix, ammo, float, PRI) \
+               P(class, prefix, animtime, float, PRI) \
+               P(class, prefix, chargepool, float, SEC) \
+               P(class, prefix, chargepool_pause_regen, float, SEC) \
+               P(class, prefix, chargepool_regen, float, SEC) \
+               P(class, prefix, charge, float, NONE) \
+               P(class, prefix, charge_animlimit, float, NONE) \
+               P(class, prefix, charge_limit, float, NONE) \
+               P(class, prefix, charge_maxspeed, float, NONE) \
+               P(class, prefix, charge_mindmg, float, NONE) \
+               P(class, prefix, charge_minspeed, float, NONE) \
+               P(class, prefix, charge_rate, float, NONE) \
+               P(class, prefix, charge_rot_pause, float, NONE) \
+               P(class, prefix, charge_rot_rate, float, NONE) \
+               P(class, prefix, charge_shot_multiplier, float, NONE) \
+               P(class, prefix, charge_start, float, NONE) \
+               P(class, prefix, charge_velocity_rate, float, NONE) \
+               P(class, prefix, damagefalloff_forcehalflife, float, BOTH) \
+               P(class, prefix, damagefalloff_halflife, float, BOTH) \
+               P(class, prefix, damagefalloff_maxdist, float, BOTH) \
+               P(class, prefix, damagefalloff_mindist, float, BOTH) \
+               P(class, prefix, damage, float, PRI) \
+               P(class, prefix, force, float, PRI) \
+               P(class, prefix, refire, float, PRI) \
+               P(class, prefix, secondary, float, NONE) \
+               P(class, prefix, reload_ammo, float, NONE) \
+        P(class, prefix, reload_time, float, NONE) \
+        P(class, prefix, switchdelay_raise, float, NONE) \
+        P(class, prefix, switchdelay_drop, float, NONE) \
+        P(class, prefix, weaponreplace, string, NONE) \
+        P(class, prefix, weaponstart, float, NONE) \
+        P(class, prefix, weaponstartoverride, float, NONE) \
+        P(class, prefix, weaponthrowable, float, NONE) \
+               P(class, prefix, ammo, float, SEC) \
+               P(class, prefix, animtime, float, SEC) \
+               P(class, prefix, damage, float, SEC) \
+               P(class, prefix, delay, float, SEC) \
+               P(class, prefix, edgedamage, float, SEC) \
+               P(class, prefix, force, float, SEC) \
+               P(class, prefix, lifetime, float, SEC) \
+               P(class, prefix, radius, float, SEC) \
+               P(class, prefix, refire, float, SEC) \
+               P(class, prefix, refire_type, float, SEC) \
+               P(class, prefix, shotangle, float, SEC) \
+               P(class, prefix, speed, float, SEC) \
+               P(class, prefix, spread, float, SEC) \
+       END()
+    W_PROPS(X, OverkillVortex, okvortex)
+#undef X
+
+ENDCLASS(OverkillVortex)
+REGISTER_WEAPON(OVERKILL_VORTEX, okvortex, NEW(OverkillVortex));
+
index f901d11593d2951ccf0e542002be2d4230886f85..48dc36b6588b0879d5356443bef11b64f52fa3c4 100644 (file)
@@ -8,7 +8,7 @@ void W_RocketPropelledChainsaw_Explode(entity this, entity directhitentity)
        this.event_damage = func_null;
        this.takedamage = DAMAGE_NO;
 
-       RadiusDamage (this, this.realowner, WEP_CVAR(rpc, damage), WEP_CVAR(rpc, edgedamage), WEP_CVAR(rpc, radius), NULL, NULL, WEP_CVAR(rpc, force), this.projectiledeathtype, directhitentity);
+       RadiusDamage (this, this.realowner, WEP_CVAR_PRI(rpc, damage), WEP_CVAR_PRI(rpc, edgedamage), WEP_CVAR_PRI(rpc, radius), NULL, NULL, WEP_CVAR_PRI(rpc, force), this.projectiledeathtype, directhitentity);
 
        delete(this);
 }
@@ -55,9 +55,9 @@ void W_RocketPropelledChainsaw_Think(entity this)
 
        tracebox(this.origin, this.mins, this.maxs, this.origin + this.pos1 * (2 * this.wait), MOVE_NORMAL, this);
        if(IS_PLAYER(trace_ent))
-               Damage (trace_ent, this, this.realowner, WEP_CVAR(rpc, damage2), this.projectiledeathtype, this.origin, normalize(this.origin - trace_ent.origin) * WEP_CVAR(rpc, force));
+               Damage (trace_ent, this, this.realowner, WEP_CVAR_PRI(rpc, damage2), this.projectiledeathtype, this.origin, normalize(this.origin - other.origin) * WEP_CVAR_PRI(rpc, force));
 
-       this.velocity = this.pos1 * (this.cnt + (WEP_CVAR(rpc, speedaccel) * sys_frametime));
+       this.velocity = this.pos1 * (this.cnt + (WEP_CVAR_PRI(rpc, speedaccel) * sys_frametime));
 
        UpdateCSQCProjectile(this);
        this.nextthink = time;
@@ -68,18 +68,18 @@ void W_RocketPropelledChainsaw_Attack (Weapon thiswep, entity actor, .entity wea
        entity missile = spawn(); //WarpZone_RefSys_SpawnSameRefSys(actor);
        entity flash = spawn ();
 
-       W_DecreaseAmmo(thiswep, actor, WEP_CVAR(rpc, ammo), weaponentity);
-       W_SetupShot_ProjectileSize (actor, weaponentity, '-3 -3 -3', '3 3 3', false, 5, SND_ROCKET_FIRE, CH_WEAPON_A, WEP_CVAR(rpc, damage));
+       W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(rpc, ammo), weaponentity);
+       W_SetupShot_ProjectileSize (actor, weaponentity, '-3 -3 -3', '3 3 3', false, 5, SND_ROCKET_FIRE, CH_WEAPON_A, WEP_CVAR_PRI(rpc, damage));
        Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
        PROJECTILE_MAKETRIGGER(missile);
 
        missile.owner = missile.realowner = actor;
        missile.bot_dodge = true;
-       missile.bot_dodgerating = WEP_CVAR(rpc, damage) * 2;
+       missile.bot_dodgerating = WEP_CVAR_PRI(rpc, damage) * 2;
 
        missile.takedamage = DAMAGE_YES;
-       missile.damageforcescale = WEP_CVAR(rpc, damageforcescale);
-       missile.health = WEP_CVAR(rpc, health);
+       missile.damageforcescale = WEP_CVAR_PRI(rpc, damageforcescale);
+       missile.health = WEP_CVAR_PRI(rpc, health);
        missile.event_damage = W_RocketPropelledChainsaw_Damage;
        missile.damagedbycontents = true;
        IL_PUSH(g_damagedbycontents, missile);
@@ -89,12 +89,12 @@ void W_RocketPropelledChainsaw_Attack (Weapon thiswep, entity actor, .entity wea
        setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
 
        setorigin(missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point
-       W_SetupProjVelocity_Basic(missile, WEP_CVAR(rpc, speed), 0);
+       W_SetupProjVelocity_Basic(missile, WEP_CVAR_PRI(rpc, speed), 0);
 
        settouch(missile, W_RocketPropelledChainsaw_Touch);
 
        setthink(missile, W_RocketPropelledChainsaw_Think);
-       missile.cnt = time + WEP_CVAR(rpc, lifetime);
+       missile.cnt = time + WEP_CVAR_PRI(rpc, lifetime);
        missile.nextthink = time;
        missile.flags = FL_PROJECTILE;
        IL_PUSH(g_projectiles, missile);
@@ -113,46 +113,113 @@ void W_RocketPropelledChainsaw_Attack (Weapon thiswep, entity actor, .entity wea
 
 METHOD(RocketPropelledChainsaw, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
 {
-    PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR(rpc, speed), 0, WEP_CVAR(rpc, lifetime), false);
+    PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(rpc, speed), 0, WEP_CVAR_PRI(rpc, lifetime), false);
 }
 
 METHOD(RocketPropelledChainsaw, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
 {
-    if(WEP_CVAR(rpc, reload_ammo) && actor.(weaponentity).clip_load < WEP_CVAR(rpc, ammo)) {
-        thiswep.wr_reload(thiswep, actor, weaponentity);
-    } else
-    {
-        if (fire & 1)
-        {
-            if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR(rpc, refire)))
-            {
-                W_RocketPropelledChainsaw_Attack(thiswep, actor, weaponentity);
-                weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(rpc, animtime), w_ready);
-            }
-        }
-
-        if (fire & 2)
-        {
-            // to-do
-        }
-    }
+       if ((WEP_CVAR_SEC(rpc, refire_type) == 1) && (fire & 2) && (time >= actor.jump_interval))
+       {
+               // Secondary uses it's own refire timer if refire_type is 1.
+               actor.jump_interval = time + WEP_CVAR_SEC(rpc, refire) * W_WeaponRateFactor(actor);
+               // Ugly hack to reuse the fire mode of the blaster.
+               makevectors(actor.v_angle);
+               Weapon oldwep = actor.(weaponentity).m_weapon; // we can't avoid this hack
+               actor.(weaponentity).m_weapon = WEP_BLASTER;
+               W_Blaster_Attack(
+                       actor,
+                       weaponentity,
+                       WEP_BLASTER.m_id | HITTYPE_SECONDARY,
+                       WEP_CVAR_SEC(rpc, shotangle),
+                       WEP_CVAR_SEC(rpc, damage),
+                       WEP_CVAR_SEC(rpc, edgedamage),
+                       WEP_CVAR_SEC(rpc, radius),
+                       WEP_CVAR_SEC(rpc, force),
+                       WEP_CVAR_SEC(rpc, speed),
+                       WEP_CVAR_SEC(rpc, spread),
+                       WEP_CVAR_SEC(rpc, delay),
+                       WEP_CVAR_SEC(rpc, lifetime)
+               );
+               actor.(weaponentity).m_weapon = oldwep;
+               if ((actor.(weaponentity).wframe == WFRAME_IDLE) ||
+                       (actor.(weaponentity).wframe == WFRAME_FIRE2))
+               {
+                       // Set secondary fire animation.
+                       vector a = '0 0 0';
+                       actor.(weaponentity).wframe = WFRAME_FIRE2;
+                       a = actor.(weaponentity).anim_fire2;
+                       a.z *= g_weaponratefactor;
+                       FOREACH_CLIENT(true, LAMBDA(
+                               if (it == actor || (IS_SPEC(it) && it.enemy == actor))
+                               {
+                                       wframe_send(it, actor.(weaponentity), a, true);
+                               }
+                       ));
+                       animdecide_setaction(actor, ANIMACTION_SHOOT, true);
+               }
+       }
+       if (WEP_CVAR(rpc, reload_ammo) && actor.(weaponentity).clip_load < WEP_CVAR_PRI(rpc, ammo))
+       {
+               // Forced reload
+               thiswep.wr_reload(thiswep, actor, weaponentity);
+               return;
+       }
+       if (fire & 1) // Primary attack
+       {
+               if (!weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(rpc, refire)))
+               {
+                       return;
+               }
+               W_RocketPropelledChainsaw_Attack(thiswep, actor, weaponentity);
+               weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(rpc, animtime), w_ready);
+               return;
+       }
+       if ((fire & 2) && (WEP_CVAR_SEC(rpc, refire_type) == 0)) // Secondary attack
+       {
+               if (!weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_SEC(rpc, refire)))
+               {
+                       return;
+               }
+               // ugly instagib hack to reuse the fire mode of the laser
+               makevectors(actor.v_angle);
+               Weapon oldwep = actor.(weaponentity).m_weapon; // we can't avoid this hack
+               actor.(weaponentity).m_weapon = WEP_BLASTER;
+               W_Blaster_Attack(
+                       actor,
+                       weaponentity,
+                       WEP_BLASTER.m_id | HITTYPE_SECONDARY,
+                       WEP_CVAR_SEC(rpc, shotangle),
+                       WEP_CVAR_SEC(rpc, damage),
+                       WEP_CVAR_SEC(rpc, edgedamage),
+                       WEP_CVAR_SEC(rpc, radius),
+                       WEP_CVAR_SEC(rpc, force),
+                       WEP_CVAR_SEC(rpc, speed),
+                       WEP_CVAR_SEC(rpc, spread),
+                       WEP_CVAR_SEC(rpc, delay),
+                       WEP_CVAR_SEC(rpc, lifetime)
+               );
+               actor.(weaponentity).m_weapon = oldwep;
+               weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(rpc, animtime), w_ready);
+       }
 }
 
 METHOD(RocketPropelledChainsaw, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
 {
-    float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR(rpc, ammo);
-    ammo_amount += actor.(weaponentity).(weapon_load[WEP_RPC.m_id]) >= WEP_CVAR(rpc, ammo);
-    return ammo_amount;
+       float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(rpc, ammo);
+       ammo_amount += actor.(weaponentity).(weapon_load[WEP_RPC.m_id]) >= WEP_CVAR_PRI(rpc, ammo);
+       return ammo_amount;
 }
 
 METHOD(RocketPropelledChainsaw, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
 {
-    return false;
+       float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(rpc, ammo);
+       ammo_amount += actor.(weaponentity).(weapon_load[WEP_RPC.m_id]) >= WEP_CVAR_SEC(rpc, ammo);
+       return ammo_amount;
 }
 
 METHOD(RocketPropelledChainsaw, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
 {
-    W_Reload(actor, weaponentity, WEP_CVAR(rpc, ammo), SND_RELOAD);
+    W_Reload(actor, weaponentity, WEP_CVAR_PRI(rpc, ammo), SND_RELOAD);
 }
 
 METHOD(RocketPropelledChainsaw, wr_suicidemessage, Notification(entity thiswep))
index 560f7f4fa4313cdb68a2b69f1dc0306a696b96e0..c1c032b18a1ea39579cfd12ef014f85dae76c3ef 100644 (file)
@@ -18,21 +18,34 @@ CLASS(RocketPropelledChainsaw, Weapon)
 
 #define X(BEGIN, P, END, class, prefix) \
        BEGIN(class) \
-               P(class, prefix, ammo, float, NONE) \
-               P(class, prefix, animtime, float, NONE) \
-               P(class, prefix, damage2, float, NONE) \
-               P(class, prefix, damageforcescale, float, NONE) \
-               P(class, prefix, damage, float, NONE) \
-               P(class, prefix, edgedamage, float, NONE) \
-               P(class, prefix, force, float, NONE) \
-               P(class, prefix, health, float, NONE) \
-               P(class, prefix, lifetime, float, NONE) \
-               P(class, prefix, radius, float, NONE) \
-               P(class, prefix, refire, float, NONE) \
+               P(class, prefix, ammo, float, PRI) \
+               P(class, prefix, animtime, float, PRI) \
+               P(class, prefix, damage, float, PRI) \
+               P(class, prefix, damage2, float, PRI) \
+               P(class, prefix, damageforcescale, float, PRI) \
+               P(class, prefix, edgedamage, float, PRI) \
+               P(class, prefix, force, float, PRI) \
+               P(class, prefix, health, float, PRI) \
+               P(class, prefix, lifetime, float, PRI) \
+               P(class, prefix, radius, float, PRI) \
+               P(class, prefix, refire, float, PRI) \
+               P(class, prefix, speedaccel, float, PRI) \
+               P(class, prefix, speed, float, PRI) \
+               P(class, prefix, ammo, float, SEC) \
+               P(class, prefix, animtime, float, SEC) \
+               P(class, prefix, damage, float, SEC) \
+               P(class, prefix, delay, float, SEC) \
+               P(class, prefix, edgedamage, float, SEC) \
+               P(class, prefix, force, float, SEC) \
+               P(class, prefix, lifetime, float, SEC) \
+               P(class, prefix, radius, float, SEC) \
+               P(class, prefix, refire, float, SEC) \
+               P(class, prefix, refire_type, float, SEC) \
+               P(class, prefix, shotangle, float, SEC) \
+               P(class, prefix, speed, float, SEC) \
+               P(class, prefix, spread, float, SEC) \
                P(class, prefix, reload_ammo, float, NONE) \
         P(class, prefix, reload_time, float, NONE) \
-               P(class, prefix, speedaccel, float, NONE) \
-               P(class, prefix, speed, float, NONE) \
         P(class, prefix, switchdelay_drop, float, NONE) \
         P(class, prefix, switchdelay_raise, float, NONE) \
         P(class, prefix, weaponreplace, string, NONE) \
index b47e587511643e3076e172835052c16787d3079d..4b7c7f22f1636261693aed7302d185026abc5a83 100644 (file)
@@ -112,53 +112,6 @@ MUTATOR_HOOKFUNCTION(ok, ForbidThrowCurrentWeapon)
        return true;
 }
 
-MUTATOR_HOOKFUNCTION(ok, PlayerPreThink)
-{
-       if(game_stopped)
-               return;
-
-       entity player = M_ARGV(0, entity);
-
-       if(IS_DEAD(player) || !IS_PLAYER(player) || STAT(FROZEN, player))
-               return;
-
-       if(PHYS_INPUT_BUTTON_ATCK2(player) && time >= player.jump_interval)
-       if( !forbidWeaponUse(player)
-               || (round_handler_IsActive() && !round_handler_IsRoundStarted()) )
-       {
-               player.jump_interval = time + WEP_CVAR_PRI(blaster, refire) * W_WeaponRateFactor(player);
-               makevectors(player.v_angle);
-
-               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
-               {
-                       .entity weaponentity = weaponentities[slot];
-
-                       if(player.(weaponentity).m_weapon == WEP_Null && slot != 0)
-                               continue;
-
-                       Weapon oldwep = player.(weaponentity).m_weapon;
-                       player.(weaponentity).m_weapon = WEP_BLASTER;
-                       W_Blaster_Attack(
-                               player,
-                               weaponentity,
-                               WEP_BLASTER.m_id | HITTYPE_SECONDARY,
-                               WEP_CVAR_SEC(vaporizer, shotangle),
-                               WEP_CVAR_SEC(vaporizer, damage),
-                               WEP_CVAR_SEC(vaporizer, edgedamage),
-                               WEP_CVAR_SEC(vaporizer, radius),
-                               WEP_CVAR_SEC(vaporizer, force),
-                               WEP_CVAR_SEC(vaporizer, speed),
-                               WEP_CVAR_SEC(vaporizer, spread),
-                               WEP_CVAR_SEC(vaporizer, delay),
-                               WEP_CVAR_SEC(vaporizer, lifetime)
-                       );
-                       player.(weaponentity).m_weapon = oldwep;
-               }
-       }
-
-       PHYS_INPUT_BUTTON_ATCK2(player) = false;
-}
-
 MUTATOR_HOOKFUNCTION(ok, PlayerWeaponSelect)
 {
        entity player = M_ARGV(0, entity);
@@ -274,7 +227,7 @@ MUTATOR_HOOKFUNCTION(ok, FilterItem)
 
 MUTATOR_HOOKFUNCTION(ok, SetStartItems, CBC_ORDER_LAST)
 {
-       WepSet ok_start_items = (WEPSET(MACHINEGUN) | WEPSET(VORTEX) | WEPSET(SHOTGUN));
+       WepSet ok_start_items = (WEPSET(OVERKILL_MACHINEGUN) | WEPSET(OVERKILL_VORTEX) | WEPSET(OVERKILL_SHOTGUN));
 
        if(WEP_RPC.weaponstart > 0) { ok_start_items |= WEPSET(RPC); }
        if(WEP_HMG.weaponstart > 0) { ok_start_items |= WEPSET(HMG); }
@@ -312,7 +265,7 @@ void ok_Initialize()
        WEP_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
        WEP_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
 
-       WEP_SHOTGUN.mdl = "ok_shotgun";
-       WEP_MACHINEGUN.mdl = "ok_mg";
-       WEP_VORTEX.mdl = "ok_sniper";
+       //WEP_SHOTGUN.mdl = "ok_shotgun";
+       //WEP_MACHINEGUN.mdl = "ok_mg";
+       //WEP_VORTEX.mdl = "ok_sniper";
 }
index 73f22b836ce6485c9a5e9164e96bf5238a14e95e..46ac1c3384056ceff911b37b6ab7995614c6ae25 100644 (file)
@@ -54,6 +54,9 @@ REGISTER_WAYPOINT(OnsCPAttack, _("Control point"), '1 0.5 0', 2);
 REGISTER_WAYPOINT(OnsGen, _("Generator"), '1 0.5 0', 1);
 REGISTER_WAYPOINT(OnsGenShielded, _("Generator"), '1 0.5 0', 1);
 
+REGISTER_WAYPOINT(SurvivalKill, _("Kill"), '0.8 0.8 0', 1);
+REGISTER_WAYPOINT(SurvivalDefend, _("Defend"), '0 1 0', 1);
+
 REGISTER_WAYPOINT(Weapon, _("Weapon"), '0 0 0', 1);
 
 REGISTER_WAYPOINT(Monster, _("Monster"), '1 0 0', 1);
index cba6023bda8b10aadfcf99df8bdc8a47ffe22561..d1a677d2b443be59a2132e3d3ed90a95a9a7dbe8 100644 (file)
 
     MSG_INFO_NOTIF(SUPERWEAPON_PICKUP,                      N_CONSOLE,  1, 0, "s1", "s1",       "superweapons",         _("^BG%s^K1 picked up a Superweapon"), "")
 
+    MSG_INFO_NOTIF(SURVIVAL_1ST_ROUND_ATTACKER,             N_CONSOLE,    0, 0, "", "",         "",                     _("^BGFirst round. Eliminate the enemy team ^F1as fast as you can^BG."), "")
+    MSG_INFO_NOTIF(SURVIVAL_1ST_ROUND_DEFENDER,             N_CONSOLE,    0, 0, "", "",         "",                     _("^BGFirst round. Defend yourself ^F1as long as you can^BG."), "")
+    MSG_INFO_NOTIF(SURVIVAL_2ND_ROUND_ATTACKER,             N_CONSOLE,    0, 1, "f1time", "",   "",                     _("^BGSecond round. Eliminate the enemy team in less than ^F1%s^BG."), "")
+    MSG_INFO_NOTIF(SURVIVAL_2ND_ROUND_DEFENDER,             N_CONSOLE,    0, 1, "f1time", "",   "",                     _("^BGSecond round. Defend yourself for ^F1%s^BG."), "")
+    MSG_INFO_NOTIF(SURVIVAL_COOP_DEFENDER,                  N_CONSOLE,    0, 0, "", "",         "",                     _("^BGDefend yourself ^F1as long as you can^BG."), "")
+    MSG_INFO_NOTIF(SURVIVAL_DEFENDERS_ELIMINATED,           N_CONSOLE,    0, 0, "", "",         "",                     _("^BGDefenders were ^F1eliminated^BG."), "")
+    MSG_INFO_NOTIF(SURVIVAL_DEFENDERS_ELIMINATED_IN,        N_CONSOLE,    0, 1, "f1time", "",   "",                     _("^BGDefenders were eliminated in ^F1%s^BG."), "")
+    MSG_INFO_NOTIF(SURVIVAL_DEFENDERS_SURVIVED,             N_CONSOLE,    0, 0, "", "",         "",                     _("^BGDefenders have ^F1survived^BG."), "")
+
     MSG_INFO_NOTIF(TEAMCHANGE_LARGERTEAM,                   N_CONSOLE,  0, 0, "", "",           "",                     _("^BGYou cannot change to a larger team"), "")
     MSG_INFO_NOTIF(TEAMCHANGE_NOTALLOWED,                   N_CONSOLE,  0, 0, "", "",           "",                     _("^BGYou are not allowed to change teams"), "")
 
     MSG_CENTER_NOTIF(SUPERWEAPON_LOST,                  N_ENABLE,    0, 0, "",               CPID_POWERUP,           "0 0",  _("^F2Superweapons have been lost"), "")
     MSG_CENTER_NOTIF(SUPERWEAPON_PICKUP,                N_ENABLE,    0, 0, "",               CPID_POWERUP,           "0 0",  _("^F2You now have a superweapon"), "")
 
+    MSG_CENTER_NOTIF(SURVIVAL_1ST_ROUND_ATTACKER,       N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGFirst round. Eliminate the enemy team ^F1as fast as you can^BG."), "")
+    MSG_CENTER_NOTIF(SURVIVAL_1ST_ROUND_DEFENDER,       N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGFirst round. Defend yourself ^F1as long as you can^BG."), "")
+    MSG_CENTER_NOTIF(SURVIVAL_2ND_ROUND_ATTACKER,       N_ENABLE,    0, 1, "f1time",         CPID_Null,              "0 0",  _("^BGSecond round. Eliminate the enemy team in less than ^F1%s^BG."), "")
+    MSG_CENTER_NOTIF(SURVIVAL_2ND_ROUND_DEFENDER,       N_ENABLE,    0, 1, "f1time",         CPID_Null,              "0 0",  _("^BGSecond round. Defend yourself for ^F1%s^BG."), "")
+    MSG_CENTER_NOTIF(SURVIVAL_COOP_DEFENDER,            N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGDefend yourself ^F1as long as you can^BG."), "")
+    MSG_CENTER_NOTIF(SURVIVAL_DEFENDERS_ELIMINATED,     N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGDefenders were ^F1eliminated^BG."), "")
+    MSG_CENTER_NOTIF(SURVIVAL_DEFENDERS_ELIMINATED_IN,  N_ENABLE,    0, 1, "f1time",         CPID_Null,              "0 0",  _("^BGDefenders were eliminated in ^F1%s^BG."), "")
+    MSG_CENTER_NOTIF(SURVIVAL_DEFENDERS_SURVIVED,       N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGDefenders have ^F1survived^BG."), "")
+
     MULTITEAM_CENTER(TEAMCHANGE, 4,                     N_ENABLE,    0, 1, "",               CPID_TEAMCHANGE,        "1 f1", _("^K1Changing to ^TC^TT^K1 in ^COUNT"), "", NAME)
     MSG_CENTER_NOTIF(TEAMCHANGE_AUTO,                   N_ENABLE,    0, 1, "",               CPID_TEAMCHANGE,        "1 f1", _("^K1Changing team in ^COUNT"), "")
     MSG_CENTER_NOTIF(TEAMCHANGE_SPECTATE,               N_ENABLE,    0, 1, "",               CPID_TEAMCHANGE,        "1 f1", _("^K1Spectating in ^COUNT"), "")
index 94e408d7f614be2d769fc229625a390bfd357ba0..1469eb09be900dd532212e0c7a87f43d6aa95c86 100644 (file)
@@ -86,9 +86,11 @@ REGISTER_STAT(WEAPON_CLIPLOAD, int)
 REGISTER_STAT(WEAPON_CLIPSIZE, int)
 
 REGISTER_STAT(VORTEX_CHARGE, float)
+REGISTER_STAT(OVERKILL_VORTEX_CHARGE, float)
 REGISTER_STAT(LAST_PICKUP, float)
 REGISTER_STAT(HUD, int)
 REGISTER_STAT(VORTEX_CHARGEPOOL, float)
+REGISTER_STAT(OVERKILL_VORTEX_CHARGEPOOL, float)
 REGISTER_STAT(HIT_TIME, float)
 REGISTER_STAT(DAMAGE_DEALT_TOTAL, int)
 REGISTER_STAT(TYPEHIT_TIME, float)
@@ -281,6 +283,15 @@ REGISTER_STAT(DOM_PPS_BLUE, float)
 REGISTER_STAT(DOM_PPS_YELLOW, float)
 REGISTER_STAT(DOM_PPS_PINK, float)
 
+// gungame
+REGISTER_STAT(GUNGAME_LEADING_WEAPON, int)
+
+// Lyberta: survival
+REGISTER_STAT(SURV_ROUND_TIME, float)
+REGISTER_STAT(SURV_DEFENDER_TEAM, int)
+REGISTER_STAT(SURV_DEFENDERS_ALIVE, int)
+REGISTER_STAT(SURV_DEFENDER_HEALTH, float)
+
 REGISTER_STAT(TELEPORT_MAXSPEED, float, autocvar_g_teleport_maxspeed)
 REGISTER_STAT(TELEPORT_TELEFRAG_AVOID, int, autocvar_g_telefrags_avoid)
 
index e163df4bc61d1958c0ff203bbd8937005abaddef..1663cd21f1b8c8f7ba97a910d374852c7f6c4e9d 100644 (file)
@@ -3,15 +3,15 @@
 #ifdef SVQC
 spawnfunc(weapon_shotgun) { weapon_defaultspawnfunc(this, WEP_SHOTGUN); }
 
-void W_Shotgun_Attack(Weapon thiswep, entity actor, .entity weaponentity, float isprimary)
+void W_Shotgun_Attack(Weapon thiswep, entity actor, .entity weaponentity, float isprimary, float ammocount, float damage, float bullets, float spread, float solidpenetration, float force)
 {
-       W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(shotgun, ammo), weaponentity);
+       W_DecreaseAmmo(thiswep, actor, ammocount, weaponentity);
 
-       W_SetupShot(actor, weaponentity, true, 5, SND_SHOTGUN_FIRE, ((isprimary) ? CH_WEAPON_A : CH_WEAPON_SINGLE), WEP_CVAR_PRI(shotgun, damage) * WEP_CVAR_PRI(shotgun, bullets));
-       for(int sc = 0;sc < WEP_CVAR_PRI(shotgun, bullets);sc = sc + 1)
-               fireBullet(actor, weaponentity, w_shotorg, w_shotdir, WEP_CVAR_PRI(shotgun, spread), WEP_CVAR_PRI(shotgun, solidpenetration), WEP_CVAR_PRI(shotgun, damage), WEP_CVAR_PRI(shotgun, force), WEP_SHOTGUN.m_id, 0);
+       W_SetupShot(actor, weaponentity, true, 5, SND_SHOTGUN_FIRE, ((isprimary) ? CH_WEAPON_A : CH_WEAPON_SINGLE), damage * bullets);
+       for(int sc = 0;sc < bullets;sc = sc + 1)
+               fireBullet(actor, weaponentity, w_shotorg, w_shotdir, spread, solidpenetration, damage, force, WEP_SHOTGUN.m_id, 0);
 
-       Send_Effect(EFFECT_SHOTGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, WEP_CVAR_PRI(shotgun, ammo));
+       Send_Effect(EFFECT_SHOTGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, ammocount);
 
        // casing code
        if(autocvar_g_casings >= 1)
@@ -152,7 +152,13 @@ void W_Shotgun_Attack3_Frame2(Weapon thiswep, entity actor, .entity weaponentity
        }
 
        sound(actor, CH_WEAPON_SINGLE, SND_Null, VOL_BASE, ATTN_NORM); // kill previous sound
-       W_Shotgun_Attack(WEP_SHOTGUN, actor, weaponentity, true); // actually is secondary, but we trick the last shot into playing full reload sound
+       W_Shotgun_Attack(WEP_SHOTGUN, actor, weaponentity, true,
+               WEP_CVAR_PRI(shotgun, ammo),
+               WEP_CVAR_PRI(shotgun, damage),
+               WEP_CVAR_PRI(shotgun, bullets),
+               WEP_CVAR_PRI(shotgun, spread),
+               WEP_CVAR_PRI(shotgun, solidpenetration),
+               WEP_CVAR_PRI(shotgun, force)); // actually is secondary, but we trick the last shot into playing full reload sound
        weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), w_ready);
 }
 void W_Shotgun_Attack3_Frame1(Weapon thiswep, entity actor, .entity weaponentity, int fire)
@@ -165,7 +171,13 @@ void W_Shotgun_Attack3_Frame1(Weapon thiswep, entity actor, .entity weaponentity
                return;
        }
 
-       W_Shotgun_Attack(WEP_SHOTGUN, actor, weaponentity, false);
+       W_Shotgun_Attack(WEP_SHOTGUN, actor, weaponentity, false,
+               WEP_CVAR_PRI(shotgun, ammo),
+               WEP_CVAR_PRI(shotgun, damage),
+               WEP_CVAR_PRI(shotgun, bullets),
+               WEP_CVAR_PRI(shotgun, spread),
+               WEP_CVAR_PRI(shotgun, solidpenetration),
+               WEP_CVAR_PRI(shotgun, force));
        weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame2);
 }
 
@@ -178,6 +190,7 @@ METHOD(Shotgun, wr_aim, void(entity thiswep, entity actor, .entity weaponentity)
     else
         PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false);
 }
+
 METHOD(Shotgun, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
 {
     if(WEP_CVAR(shotgun, reload_ammo) && actor.(weaponentity).clip_load < WEP_CVAR_PRI(shotgun, ammo)) // forced reload
@@ -195,7 +208,13 @@ METHOD(Shotgun, wr_think, void(entity thiswep, entity actor, .entity weaponentit
             {
                 if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(shotgun, animtime)))
                 {
-                    W_Shotgun_Attack(thiswep, actor, weaponentity, true);
+                    W_Shotgun_Attack(thiswep, actor, weaponentity, true,
+                                               WEP_CVAR_PRI(shotgun, ammo),
+                                               WEP_CVAR_PRI(shotgun, damage),
+                                               WEP_CVAR_PRI(shotgun, bullets),
+                                               WEP_CVAR_PRI(shotgun, spread),
+                                               WEP_CVAR_PRI(shotgun, solidpenetration),
+                                               WEP_CVAR_PRI(shotgun, force));
                     actor.(weaponentity).shotgun_primarytime = time + WEP_CVAR_PRI(shotgun, refire) * W_WeaponRateFactor(actor);
                     weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(shotgun, animtime), w_ready);
                 }
@@ -207,7 +226,13 @@ METHOD(Shotgun, wr_think, void(entity thiswep, entity actor, .entity weaponentit
             {
                 if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_SEC(shotgun, alt_animtime)))
                 {
-                    W_Shotgun_Attack(thiswep, actor, weaponentity, false);
+                    W_Shotgun_Attack(thiswep, actor, weaponentity, false,
+                                               WEP_CVAR_PRI(shotgun, ammo),
+                                               WEP_CVAR_PRI(shotgun, damage),
+                                               WEP_CVAR_PRI(shotgun, bullets),
+                                               WEP_CVAR_PRI(shotgun, spread),
+                                               WEP_CVAR_PRI(shotgun, solidpenetration),
+                                               WEP_CVAR_PRI(shotgun, force));
                     actor.(weaponentity).shotgun_primarytime = time + WEP_CVAR_SEC(shotgun, alt_refire) * W_WeaponRateFactor(actor);
                     weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame1);
                 }
index 8d74a7b3272ffc2da3ef3650fff42a223357cf16..43ca6e77ef03f8c77659d0d02bf20103ea15cab5 100644 (file)
@@ -26,6 +26,10 @@ MACRO_END
        { WriteByte(chan, this.vortex_charge * 16); }, \
        { (viewmodels[this.m_wepent_slot]).vortex_charge = ReadByte() / 16; }) \
     \
+    PROP(false, okvortex_charge, WEPENT_SET_NORMAL, \
+       { WriteByte(chan, this.okvortex_charge * 16); }, \
+       { (viewmodels[this.m_wepent_slot]).okvortex_charge = ReadByte() / 16; }) \
+    \
     PROP(false, m_gunalign, WEPENT_SET_NORMAL, \
        { WriteByte(chan, this.m_gunalign); }, \
        { (viewmodels[this.m_wepent_slot]).m_gunalign = ReadByte(); }) \
index 46180d7c0b4e2c82fe02ee5fb6f6e1e8b8234c0d..25f3993cd2548d19dbea4299d6c8544394d8c40c 100644 (file)
@@ -4,6 +4,7 @@ REGISTER_NET_LINKED(ENT_CLIENT_WEPENT)
 REGISTER_NET_TEMP(CLIENT_WEPENT)
 
 .float vortex_charge;
+.float okvortex_charge;
 .int tuba_instrument;
 
 #ifdef SVQC
index 481914200a99f625338e4be2466d158a1e5bcd34..63faabf35d4f94a7bccaaafb70e4ec83e6352a0f 100644 (file)
@@ -75,6 +75,7 @@ void GameType_ConfigureSliders_for_CurrentGametype(entity me)
                case MAPINFO_TYPE_CTS:             GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
                case MAPINFO_TYPE_INVASION:        GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
                case MAPINFO_TYPE_TEAM_DEATHMATCH: GameType_ConfigureSliders(me, _("Point limit:"),     5,  100,  5, "g_tdm_point_limit",         "g_tdm_teams_override",         _("The amount of points needed before the match will end")); break;
+               case MAPINFO_TYPE_SURVIVAL:        GameType_ConfigureSliders(me, _("Point limit:"),     1,   20,  1, "g_surv_point_limit",        string_null,                    _("The amount of points needed before the match will end")); break;
                default:                           GameType_ConfigureSliders(me, _("Frag limit:"),      5,  100,  5, "fraglimit_override",        string_null,                    _("The amount of frags needed before the match will end")); break;
        }
 }
index 7b381b544588ae4d9049d972ad0d74d44c7c9a23..05b54e3ad72d5f773fcb923a0781daa37c11ec4d 100644 (file)
@@ -693,6 +693,8 @@ float updateCompression()
        GAMETYPE(MAPINFO_TYPE_NEXBALL) \
        GAMETYPE(MAPINFO_TYPE_ONSLAUGHT) \
        GAMETYPE(MAPINFO_TYPE_ASSAULT) \
+       GAMETYPE(MAPINFO_TYPE_GUNGAME) \
+       GAMETYPE(MAPINFO_TYPE_SURVIVAL) \
        /* GAMETYPE(MAPINFO_TYPE_INVASION) */ \
        /**/
 
index de7e01aa7a862d3f328d0c02a3fdd542e5238ad9..008999b165d14689c83ba353f1f1ad892e97c192 100644 (file)
@@ -19,6 +19,7 @@
 #include <server/miscfunctions.qc>
 #include <server/player.qc>
 #include <server/playerdemo.qc>
+#include <server/playertemplates.qc>
 #include <server/portals.qc>
 #include <server/race.qc>
 #include <server/resources.qc>
index 67f6aae4db511fd7cb1b91cb1cdb7412c7f017ea..70952b435d6ba08d1f0a318708df835ee4365a8a 100644 (file)
@@ -19,6 +19,7 @@
 #include <server/miscfunctions.qh>
 #include <server/player.qh>
 #include <server/playerdemo.qh>
+#include <server/playertemplates.qh>
 #include <server/portals.qh>
 #include <server/race.qh>
 #include <server/resources.qh>
index 7754a16514c245abca724aef83e0b026c2eaa315..3e83ec7bec575a54393daf1bd2a0b86680d2e3eb 100644 (file)
@@ -61,6 +61,8 @@
 
 #include "../lib/warpzone/server.qh"
 
+#include <common/mutators/mutator/overkill/okvortex.qh>
+
 STATIC_METHOD(Client, Add, void(Client this, int _team))
 {
     ClientConnect(this);
@@ -1758,6 +1760,8 @@ void SpectateCopy(entity this, entity spectatee)
        this.weapons = spectatee.weapons;
        this.vortex_charge = spectatee.vortex_charge;
        this.vortex_chargepool_ammo = spectatee.vortex_chargepool_ammo;
+       this.okvortex_charge = spectatee.okvortex_charge;
+       this.okvortex_chargepool_ammo = spectatee.okvortex_chargepool_ammo;
        this.hagar_load = spectatee.hagar_load;
        this.arc_heat_percent = spectatee.arc_heat_percent;
        this.minelayer_mines = spectatee.minelayer_mines;
index 4c3b1d221ea4ae73be7f0ff7cc7b7ed5d7b9000f..4adf563fc0dca646fb2e86726977871780da042c 100644 (file)
@@ -326,6 +326,9 @@ float client_cefc_accumulatortime;
 .float vortex_charge = _STAT(VORTEX_CHARGE);
 .float vortex_charge_rottime;
 .float vortex_chargepool_ammo = _STAT(VORTEX_CHARGEPOOL);
+.float okvortex_charge = _STAT(OVERKILL_VORTEX_CHARGE);
+.float okvortex_charge_rottime;
+.float okvortex_chargepool_ammo = _STAT(OVERKILL_VORTEX_CHARGEPOOL);
 .float hagar_load = _STAT(HAGAR_LOAD);
 
 .int grab; // 0 = can't grab, 1 = owner can grab, 2 = owner and team mates can grab, 3 = anyone can grab
index ce25dfa522772b08e3f1dfb41dcc2a536a2f4902..753499d23fb76cb7ee228c78532d75579d93994d 100644 (file)
@@ -271,6 +271,7 @@ void cvar_changes_init()
                BADCVAR("g_domination_default_teams");
                BADCVAR("g_freezetag");
                BADCVAR("g_freezetag_teams");
+               BADCVAR("g_gg");
                BADCVAR("g_invasion_teams");
                BADCVAR("g_invasion_type");
                BADCVAR("g_jailbreak");
@@ -287,6 +288,7 @@ void cvar_changes_init()
                BADCVAR("g_race_qualifying_timelimit_override");
                BADCVAR("g_runematch");
                BADCVAR("g_snafu");
+               BADCVAR("g_surv"); // Lyberta: adding gamemode cvar
                BADCVAR("g_tdm");
                BADCVAR("g_tdm_teams");
                BADCVAR("g_vip");
diff --git a/qcsrc/server/playertemplates.qc b/qcsrc/server/playertemplates.qc
new file mode 100644 (file)
index 0000000..a9fed4e
--- /dev/null
@@ -0,0 +1,1131 @@
+/// \file
+/// \brief Source file that contains implementation of the player templates
+/// system.
+/// \author Lyberta
+/// \copyright GNU GPLv3 or any later version.
+
+#include <common/items/item.qh>
+#include "autocvars.qh"
+#include "resources.qh"
+#include "mutators/events.qh"
+
+const string playertemplate_cvar_prefix = "g_player_template_";
+
+string PlayerTemplate_GetFullCvarName(string template, string variable)
+{
+       return strcat(playertemplate_cvar_prefix, template, "_", variable);
+}
+
+string PlayerTemplate_GetDefaultCvarName(string variable)
+{
+       switch (variable)
+       {
+               case "start_health":
+               {
+                       return "g_balance_health_start";
+               }
+               case "start_armor":
+               {
+                       return "g_balance_armor_start";
+               }
+               case "start_ammo_shells":
+               {
+                       return "g_start_ammo_shells";
+               }
+               case "start_ammo_bullets":
+               {
+                       return "g_start_ammo_nails";
+               }
+               case "start_ammo_rockets":
+               {
+                       return "g_start_ammo_rockets";
+               }
+               case "start_ammo_cells":
+               {
+                       return "g_start_ammo_cells";
+               }
+               case "start_ammo_plasma":
+               {
+                       return "g_start_ammo_plasma";
+               }
+               case "start_ammo_fuel":
+               {
+                       return "g_start_ammo_fuel";
+               }
+               case "random_start_weapons_count":
+               {
+                       return "g_random_start_weapons_count";
+               }
+               case "random_start_weapons":
+               {
+                       return "g_random_start_weapons";
+               }
+               case "random_start_shells":
+               {
+                       return "g_random_start_shells";
+               }
+               case "random_start_bullets":
+               {
+                       return "g_random_start_bullets";
+               }
+               case "random_start_rockets":
+               {
+                       return "g_random_start_rockets";
+               }
+               case "random_start_cells":
+               {
+                       return "g_random_start_cells";
+               }
+               case "random_start_plasma":
+               {
+                       return "g_random_start_plasma";
+               }
+               case "drop_weapons":
+               {
+                       return "g_weapon_throwable";
+               }
+               case "health_regen_factor":
+               {
+                       return "g_balance_health_regen";
+               }
+               case "health_regen_linear":
+               {
+                       return "g_balance_health_regenlinear";
+               }
+               case "health_rot_factor":
+               {
+                       return "g_balance_health_rot";
+               }
+               case "health_rot_linear":
+               {
+                       return "g_balance_health_rotlinear";
+               }
+               case "health_regen_stable":
+               {
+                       return "g_balance_health_regenstable";
+               }
+               case "health_rot_stable":
+               {
+                       return "g_balance_health_rotstable";
+               }
+               default:
+               {
+                       // TODO: Report error.
+                       return "";
+               }
+       }
+}
+
+float PlayerTemplate_GetDefaultFloatValue(string variable)
+{
+       switch (variable)
+       {
+               case "start_health":
+               case "start_armor":
+               case "start_ammo_shells":
+               case "start_ammo_bullets":
+               case "start_ammo_rockets":
+               case "start_ammo_cells":
+               case "start_ammo_plasma":
+               case "start_ammo_fuel":
+               case "random_start_weapons_count":
+               case "random_start_shells":
+               case "random_start_bullets":
+               case "random_start_rockets":
+               case "random_start_cells":
+               case "random_start_plasma":
+               case "drop_weapons":
+               case "health_regen_factor":
+               case "health_regen_linear":
+               case "health_rot_factor":
+               case "health_rot_linear":
+               case "health_regen_stable":
+               case "health_rot_stable":
+               {
+                       return cvar(PlayerTemplate_GetDefaultCvarName(variable));
+               }
+               case "unlimited_ammo":
+               {
+                       return !autocvar_g_use_ammunition;
+               }
+               case "default_start_weapons":
+               {
+                       return 1;
+               }
+               case "attack_scale":
+               case "defense_scale":
+               {
+                       return 1;
+               }
+               case "blaster_self_damage":
+               {
+                       return 1;
+               }
+               default:
+               {
+                       // TODO: Report error.
+                       return 0;
+               }
+       }
+}
+
+string PlayerTemplate_GetDefaultStringValue(string variable)
+{
+       switch (variable)
+       {
+               case "random_start_weapons":
+               {
+                       cvar_string(PlayerTemplate_GetDefaultCvarName(variable));
+               }
+               case "start_weapons":
+               {
+                       return "";
+               }
+               default:
+               {
+                       // TODO: Report error.
+                       return "";
+               }
+       }
+}
+
+float PlayerTemplate_GetFloatValue(string template, string variable)
+{
+       if (template == "default")
+       {
+               return PlayerTemplate_GetDefaultFloatValue(variable);
+       }
+       string fullname = PlayerTemplate_GetFullCvarName(template, variable);
+       if (!(cvar_type(fullname) & CVAR_TYPEFLAG_EXISTS))
+       {
+               return PlayerTemplate_GetDefaultFloatValue(variable);
+       }
+       if (cvar_string(fullname) == "default")
+       {
+               return PlayerTemplate_GetDefaultFloatValue(variable);
+       }
+       return cvar(fullname);
+}
+
+string PlayerTemplate_GetStringValue(string template, string variable)
+{
+       if (template == "default")
+       {
+               return PlayerTemplate_GetDefaultStringValue(variable);
+       }
+       string fullname = PlayerTemplate_GetFullCvarName(template, variable);
+       if (!(cvar_type(fullname) & CVAR_TYPEFLAG_EXISTS))
+       {
+               return PlayerTemplate_GetDefaultStringValue(variable);
+       }
+       if (cvar_string(fullname) == "default")
+       {
+               return PlayerTemplate_GetDefaultStringValue(variable);
+       }
+       return cvar_string(fullname);
+}
+
+float PlayerTemplate_GivePlayerItem(entity player, string template,
+       string variable)
+{
+       string value = PlayerTemplate_GetStringValue(template, variable);
+       if (value == "default")
+       {
+               return MUT_ITEMTOUCH_CONTINUE;
+       }
+       int numfields = tokenize_console(PlayerTemplate_GetStringValue(template,
+               variable));
+       if (numfields == 0)
+       {
+               return MUT_ITEMTOUCH_CONTINUE;
+       }
+       for (int i = 1; i < numfields; ++i)
+       {
+               switch (i)
+               {
+                       case 1:
+                       {
+                               GiveResource(player, RESOURCE_HEALTH, stof(argv(i)));
+                               break;
+                       }
+                       case 2:
+                       {
+                               GiveResource(player, RESOURCE_ARMOR, stof(argv(i)));
+                               break;
+                       }
+                       case 3:
+                       {
+                               GiveResource(player, RESOURCE_SHELLS, stof(argv(i)));
+                               break;
+                       }
+                       case 4:
+                       {
+                               GiveResource(player, RESOURCE_BULLETS, stof(argv(i)));
+                               break;
+                       }
+                       case 5:
+                       {
+                               GiveResource(player, RESOURCE_ROCKETS, stof(argv(i)));
+                               break;
+                       }
+                       case 6:
+                       {
+                               GiveResource(player, RESOURCE_CELLS, stof(argv(i)));
+                               break;
+                       }
+                       case 7:
+                       {
+                               GiveResource(player, RESOURCE_PLASMA, stof(argv(i)));
+                               break;
+                       }
+                       case 8:
+                       {
+                               GiveResource(player, RESOURCE_FUEL, stof(argv(i)));
+                               break;
+                       }
+               }
+       }
+       switch (argv(0))
+       {
+               case "add":
+               {
+                       return MUT_ITEMTOUCH_CONTINUE;
+               }
+               case "override":
+               {
+                       return MUT_ITEMTOUCH_RETURN;
+               }
+               default:
+               {
+                       return MUT_ITEMTOUCH_CONTINUE;
+               }
+       }
+}
+
+// =========================== Hook handlers =================================
+
+void PlayerTemplateHook_PlayerSpawn(entity player, string template)
+{
+       if (template == "default")
+       {
+               return;
+       }
+       // Give health, armor and ammo.
+       SetResourceAmount(player, RESOURCE_HEALTH,
+               PlayerTemplate_GetFloatValue(template, "start_health"));
+       SetResourceAmount(player, RESOURCE_ARMOR, 
+               PlayerTemplate_GetFloatValue(template, "start_armor"));
+       if (PlayerTemplate_GetFloatValue(template, "unlimited_ammo"))
+       {
+               player.items |= IT_UNLIMITED_AMMO;
+       }
+       else
+       {
+               SetResourceAmount(player, RESOURCE_SHELLS,
+                       PlayerTemplate_GetFloatValue(template, "start_ammo_shells"));
+               SetResourceAmount(player, RESOURCE_BULLETS,
+                       PlayerTemplate_GetFloatValue(template, "start_ammo_bullets"));
+               SetResourceAmount(player, RESOURCE_ROCKETS,
+                       PlayerTemplate_GetFloatValue(template, "start_ammo_rockets"));
+               SetResourceAmount(player, RESOURCE_CELLS,
+                       PlayerTemplate_GetFloatValue(template, "start_ammo_cells"));
+               SetResourceAmount(player, RESOURCE_PLASMA,
+                       PlayerTemplate_GetFloatValue(template, "start_ammo_plasma"));
+               SetResourceAmount(player, RESOURCE_FUEL,
+                       PlayerTemplate_GetFloatValue(template, "start_ammo_fuel"));
+       }
+       if (autocvar_g_instagib == 1)
+       {
+               return;
+       }
+       // Give weapons.
+       if (PlayerTemplate_GetFloatValue(template, "default_start_weapons"))
+       {
+               FOREACH(Weapons, it != WEP_Null,
+               {
+                       if (it.weaponstart)
+                       {
+                               player.weapons |= it.m_wepset;
+                       }
+               });
+       }
+       int numweapons = tokenize_console(PlayerTemplate_GetStringValue(template,
+               "start_weapons"));
+       for (int i = 0; i < numweapons; ++i)
+       {
+               string weapon = argv(i);
+               FOREACH(Weapons, it != WEP_Null,
+               {
+                       if (it.netname == weapon)
+                       {
+                               player.weapons |= it.m_wepset;
+                               break;
+                       }
+               });
+       }
+       if (!warmup_stage)
+       {
+               entity ammo_entity = spawn();
+               SetResourceAmount(ammo_entity, RESOURCE_SHELLS,
+                       PlayerTemplate_GetFloatValue(template, "random_start_shells"));
+               SetResourceAmount(ammo_entity, RESOURCE_BULLETS,
+                       PlayerTemplate_GetFloatValue(template, "random_start_bullets"));
+               SetResourceAmount(ammo_entity, RESOURCE_ROCKETS,
+                       PlayerTemplate_GetFloatValue(template, "random_start_rockets"));
+               SetResourceAmount(ammo_entity, RESOURCE_CELLS,
+                       PlayerTemplate_GetFloatValue(template, "random_start_cells"));
+               SetResourceAmount(ammo_entity, RESOURCE_PLASMA,
+                       PlayerTemplate_GetFloatValue(template, "random_start_plasma"));
+               GiveRandomWeapons(player, PlayerTemplate_GetFloatValue(template,
+                       "random_start_weapons_count"),
+                       PlayerTemplate_GetStringValue(template, "random_start_weapons"),
+                       ammo_entity);
+               remove(ammo_entity);
+               return;
+       }
+       // Give random weapons.
+       numweapons = tokenize_console(PlayerTemplate_GetStringValue(template,
+               "random_start_weapons"));
+       // Give all weapons during warmup stage.
+       for (int i = 0; i < numweapons; ++i)
+       {
+               string weapon = argv(i);
+               FOREACH(Weapons, it != WEP_Null,
+               {
+                       if (it.netname == weapon)
+                       {
+                               player.weapons |= it.m_wepset;
+                               break;
+                       }
+               });
+       }
+}
+
+bool PlayerTemplateHook_ForbidThrowCurrentWeapon(string template)
+{
+       return !PlayerTemplate_GetFloatValue(template, "drop_weapons");
+}
+
+float PlayerTemplateHook_PlayerRegen(entity player, string template)
+{
+       if (template == "default")
+       {
+               return false;
+       }
+       M_ARGV(5, float) = PlayerTemplate_GetFloatValue(template,
+               "health_regen_factor");
+       M_ARGV(6, float) = PlayerTemplate_GetFloatValue(template,
+               "health_regen_linear");
+       M_ARGV(7, float) = PlayerTemplate_GetFloatValue(template,
+               "health_rot_factor");
+       M_ARGV(8, float) = PlayerTemplate_GetFloatValue(template,
+               "health_rot_linear");
+       M_ARGV(9, float) = PlayerTemplate_GetFloatValue(template,
+               "health_regen_stable");
+       M_ARGV(10, float) = PlayerTemplate_GetFloatValue(template,
+               "health_rot_stable");
+       return false;
+}
+
+float PlayerTemplateHook_ItemTouch(entity player, entity item, string template)
+{
+       if (template == "default")
+       {
+               return MUT_ITEMTOUCH_CONTINUE;
+       }
+       switch (item.classname)
+       {
+               case "item_health_small":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_health_small");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_HealthSmall, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "item_health_medium":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_health_medium");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_HealthMedium, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "item_health_big":
+               case "item_health_large":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_health_big");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_HealthBig, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "item_health_mega":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_health_mega");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_HealthMega, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "item_armor_small":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_armor_small");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_ArmorSmall, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "item_armor_medium":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_armor_medium");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_ArmorMedium, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "item_armor_big":
+               case "item_armor_large":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_armor_big");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_ArmorBig, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "item_armor_mega":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_armor_mega");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_ArmorMega, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "item_shells":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_shells");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_ITEMPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "item_bullets":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_bullets");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_ITEMPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "item_rockets":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_rockets");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_ITEMPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "item_cells":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_cells");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_ITEMPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "weapon_machinegun":
+               case "weapon_uzi":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_machinegun");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_WEAPONPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "weapon_grenadelauncher":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_mortar");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_WEAPONPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "weapon_electro":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_electro");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_WEAPONPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "weapon_crylink":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_crylink");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_WEAPONPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "weapon_nex":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_vortex");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_WEAPONPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "weapon_hagar":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_hagar");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_WEAPONPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "weapon_rocketlauncher":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_devastator");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_WEAPONPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "droppedweapon":
+               {
+                       float result;
+                       switch (item.weapon)
+                       {
+                               case WEP_SHOTGUN.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_dropped_shotgun");
+                                       break;
+                               }
+                               case WEP_MACHINEGUN.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_dropped_machinegun");
+                                       break;
+                               }
+                               case WEP_MORTAR.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_dropped_mortar");
+                                       break;
+                               }
+                               case WEP_ELECTRO.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_dropped_electro");
+                                       break;
+                               }
+                               case WEP_CRYLINK.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_dropped_crylink");
+                                       break;
+                               }
+                               case WEP_VORTEX.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_dropped_vortex");
+                                       break;
+                               }
+                               case WEP_HAGAR.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_dropped_hagar");
+                                       break;
+                               }
+                               case WEP_DEVASTATOR.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_dropped_devastator");
+                                       break;
+                               }
+                               case WEP_MINE_LAYER.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_dropped_minelayer");
+                                       break;
+                               }
+                               case WEP_HLAC.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_dropped_hlac");
+                                       break;
+                               }
+                               case WEP_RIFLE.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_dropped_rifle");
+                                       break;
+                               }
+                               case WEP_SEEKER.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_dropped_seeker");
+                                       break;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       delete(item);
+                                       sound(player, CH_TRIGGER, SND_WEAPONPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "replacedweapon":
+               {
+                       float result;
+                       switch (item.weapon)
+                       {
+                               case WEP_MINE_LAYER.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_minelayer");
+                                       break;
+                               }
+                               case WEP_HLAC.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_hlac");
+                                       break;
+                               }
+                               case WEP_RIFLE.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_rifle");
+                                       break;
+                               }
+                               case WEP_SEEKER.m_id:
+                               {
+                                       result = PlayerTemplate_GivePlayerItem(player, template,
+                                               "pickup_weapon_seeker");
+                                       break;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_WEAPONPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "item_strength":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_strength");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               case "item_invincible":
+               {
+                       float result = PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_shield");
+                       switch (result)
+                       {
+                               case MUT_ITEMTOUCH_CONTINUE:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case MUT_ITEMTOUCH_RETURN:
+                               {
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_Shield, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+               default:
+               {
+                       switch (item.netname)
+                       {
+                               case "Vaporizer Ammo":
+                               case "Extra life":
+                               case "Invisibility":
+                               case "Speed":
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               default:
+                               {
+                                       PrintToChatAll(strcat("Unrecognized item, classname: ",
+                                               item.classname, " netname: ", item.netname));
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+       }
+       return MUT_ITEMTOUCH_CONTINUE;
+}
+
+float PlayerTemplateHook_Damage_Calculate(entity attacker,
+       string attackertemplate, entity victim, string victimtemplate,
+       float deathtype, float damage)
+{
+       if (autocvar_g_instagib == 1)
+       {
+               return damage;
+       }
+       if ((attacker == victim) && (DEATH_ISWEAPON(deathtype, WEP_BLASTER)) &&
+               (PlayerTemplate_GetFloatValue(victimtemplate, "blaster_self_damage") ==
+               0))
+       {
+               return 0;
+       }
+       damage *= PlayerTemplate_GetFloatValue(attackertemplate, "attack_scale");
+       damage /= PlayerTemplate_GetFloatValue(victimtemplate, "defense_scale");
+       return damage;
+}
+
+void PlayerTemplateHook_PlayerDies(entity player, string template)
+{
+       if (template == "default")
+       {
+               return;
+       }
+       if (PlayerTemplate_GetFloatValue(template, "drop_weapons"))
+       {
+               return;
+       }
+       player.weapons = WEPSET(Null);
+}
diff --git a/qcsrc/server/playertemplates.qh b/qcsrc/server/playertemplates.qh
new file mode 100644 (file)
index 0000000..d9ecb7a
--- /dev/null
@@ -0,0 +1,95 @@
+/// \file
+/// \brief Header file that describes the player templates system.
+/// \author Lyberta
+/// \copyright GNU GPLv3 or any later version.
+
+#pragma once
+
+/// \brief Returns the full name of the player template cvar.
+/// \param[in] template Name of the template.
+/// \param[in] variable Name of the variable.
+/// \return Full name of the variable.
+string PlayerTemplate_GetFullCvarName(string template, string variable);
+
+/// \brief Returns the name of the cvar that is used for default value.
+/// \param[in] Name of the player template variable.
+/// \return Name of the cvar that is used for default value.
+string PlayerTemplate_GetDefaultCvarName(string variable);
+
+/// \brief Returns the default floating point value of the player template
+/// variable.
+/// \param[in] variable Name of the variable.
+/// \return Default floating point value of the player template variable.
+float PlayerTemplate_GetDefaultFloatValue(string variable);
+
+/// \brief Returns the default string value of the player template variable.
+/// \param[in] variable Name of the variable.
+/// \return Default string value of the player template variable.
+string PlayerTemplate_GetDefaultStringValue(string variable);
+
+/// \brief Gets the floating point value of the variable from the given
+/// template.
+/// \param[in] template Name of the template.
+/// \param[in] variable Name of the variable.
+/// \return Value of the variable.
+float PlayerTemplate_GetFloatValue(string template, string variable);
+
+/// \brief Gets the string value of the variable from the given template.
+/// \param[in] template Name of the template.
+/// \param[in] variable Name of the variable.
+/// \return Value of the variable.
+string PlayerTemplate_GetStringValue(string template, string variable);
+
+/// \brief Gives player items according to the given template's variable.
+/// \param[in] player Player to give items to.
+/// \param[in] template Name of the template.
+/// \param[in] variable Name of the variable.
+/// \return Enum value to pass to mutator hook.
+float PlayerTemplate_GivePlayerItem(entity player, string template,
+       string variable);
+
+// =========================== Hook handlers =================================
+
+/// \brief Setups the player during spawn according to the given template.
+/// \param[in,out] player Player to setup.
+/// \param[in] template Name of the template.
+/// \return No return.
+void PlayerTemplateHook_PlayerSpawn(entity player, string template);
+
+/// \brief Forbids weapon dropping according to the given template.
+/// \param[in] template Name of the template.
+/// \return Value to pass to mutator hook.
+bool PlayerTemplateHook_ForbidThrowCurrentWeapon(string template);
+
+/// \brief Regenerates player health according to the given template.
+/// \param[in] player Player to regenerate.
+/// \param[in] template Name of the template.
+/// \return Value to pass to mutator hook.
+float PlayerTemplateHook_PlayerRegen(entity player, string template);
+
+/// \brief Gives player items according to the given template.
+/// \param[in,out] player Player to give items to.
+/// \param[in] item Item which player has picked up.
+/// \param[in] template Name of the template.
+/// \return Enum value to pass to mutator hook.
+float PlayerTemplateHook_ItemTouch(entity player, entity item, string template);
+
+/// \brief Changes the damage done using templates' attack and defense scales.
+/// \param[in] attacke Attacker entity.
+/// \param[in] attackertemplate Template of the attacker.
+/// \param[in] victim Victim entity.
+/// \param[in] victimtemplate Template of the victim.
+/// \param[in] deathtype Type of the damage.
+/// \param[in] damage Damage to adjust.
+/// \return Adjusted damage.
+float PlayerTemplateHook_Damage_Calculate(entity attacker,
+       string attackertemplate, entity victim, string victimtemplate,
+       float deathtype, float damage);
+
+/// \brief Strips the player of their weapons if the player is not allowed to
+/// drop them.
+/// \param[in,out] player Player to work with.
+/// \param[in] template Name of the template.
+/// \return No return.
+/// \note You must hook with CBC_ORDER_FIRST in order for this to be effective.
+void PlayerTemplateHook_PlayerDies(entity player, string template);
index c1d3b8c237a0a92221ec5f2c985dfe3997342434..158e827e687f1930a0caef796384ae8acf1892df 100644 (file)
Binary files a/sound/misc/kill.ogg and b/sound/misc/kill.ogg differ
diff --git a/survival.cfg b/survival.cfg
new file mode 100644 (file)
index 0000000..b98f2cc
--- /dev/null
@@ -0,0 +1,127 @@
+// Cvars for survival gamemode.
+
+alias cl_hook_gamestart_surv
+alias sv_hook_gamestart_surv
+alias sv_vote_gametype_hook_surv
+
+set g_surv_respawn_delay_small 0
+set g_surv_respawn_delay_small_count 0
+set g_surv_respawn_delay_large 0
+set g_surv_respawn_delay_large_count 0
+set g_surv_respawn_delay_max 0
+set g_surv_respawn_waves 0
+set g_surv_weapon_stay 0
+
+set g_surv 0 "Survival: survive as long as you can"
+set g_surv_type "versus" "Type of the survival gametype"
+set g_surv_warmup 10 "How long the players will have time to run around the map before the round starts"
+set g_surv_round_timelimit 300 "Round time limit in seconds"
+seta g_surv_point_limit -1 "Survival point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_surv_point_leadlimit -1 "Survival point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+set g_surv_team_size 4 "How much players are allowed in teams (excluding cannon fodder)"
+set g_surv_stealth 0 "If set, defenders will not be shown on the radar"
+set g_surv_spectate_enemies 0 "Whether to allow spectating enemy players while dead"
+
+set g_surv_attacker_template "surv_attacker" "Player template of attackers"
+set g_surv_defender_template "surv_defender" "Player template of defenders"
+set g_surv_cannon_fodder_template "surv_cannon_fodder" "Player template of cannon fodder"
+
+set g_surv_attacker_force_overkill_models 0 "Whether to force overkill player models for attackers"
+set g_surv_defender_force_overkill_models 1 "Whether to force overkill player models for defenders"
+set g_surv_cannon_fodder_force_overkill_models 0 "Whether to force overkill player models for cannon fodder"
+
+set g_player_template_surv_attacker_unlimited_ammo 1 "Whether to give attackers unlimited ammo"
+set g_player_template_surv_attacker_random_start_weapons_count 2 "Number of weapons that can be randomly given to attackers during spawn"
+set g_player_template_surv_attacker_random_start_weapons "machinegun mortar electro crylink vortex hagar devastator" "Weapons that can be randomly given to attackers during spawn"
+
+set g_player_template_surv_defender_start_health 200 "How much health do defenders get during spawn"
+set g_player_template_surv_defender_start_armor 200 "How much armor do defenders get during spawn"
+set g_player_template_surv_defender_start_ammo_shells 15 "How many shells do defenders get during spawn"
+set g_player_template_surv_defender_start_ammo_bullets 15 "How many bullets do defenders get during spawn"
+set g_player_template_surv_defender_start_ammo_rockets 0 "How many rockets do defenders get during spawn"
+set g_player_template_surv_defender_start_ammo_cells 30 "How many cells do defenders get during spawn"
+set g_player_template_surv_defender_default_start_weapons 0 "Whether to give defenders default start weapons"
+set g_player_template_surv_defender_start_weapons "okshotgun okmachinegun okvortex" "Which weapons do defenders get during spawn"
+
+set g_player_template_surv_cannon_fodder_unlimited_ammo 1 "Whether to give cannon fodder unlimited ammo"
+set g_player_template_surv_cannon_fodder_random_start_weapons_count 2 "Number of weapons that can be randomly given to cannon fodder during spawn"
+set g_player_template_surv_cannon_fodder_random_start_weapons "machinegun mortar electro crylink vortex hagar devastator" "Weapons that can be randomly given to cannon fodder during spawn"
+
+set g_player_template_surv_defender_drop_weapons 0 "Whether defenders can drop weapons by throwing them or by dying"
+
+set g_player_template_surv_attacker_pickup_item_shells "add 25 25" "What items do attackers get when they pickup shells"
+set g_player_template_surv_attacker_pickup_item_bullets "add 25 25" "What items do attackers get when they pickup bullets"
+set g_player_template_surv_attacker_pickup_item_rockets "add 25 25" "What items do attackers get when they pickup rockets"
+set g_player_template_surv_attacker_pickup_item_cells "add 25 25" "What items do attackers get when they pickup cells"
+set g_player_template_surv_attacker_pickup_weapon_dropped_shotgun "add 25 25" "What items do attackers get when they pickup a dropped shotgun"
+set g_player_template_surv_attacker_pickup_weapon_dropped_machinegun "add 25 25" "What items do attackers get when they pickup a dropped machinegun"
+set g_player_template_surv_attacker_pickup_weapon_dropped_mortar "add 25 25" "What items do attackers get when they pickup a dropped mortar"
+set g_player_template_surv_attacker_pickup_weapon_dropped_electro "add 25 25" "What items do attackers get when they pickup a dropped electro"
+set g_player_template_surv_attacker_pickup_weapon_dropped_crylink "add 25 25" "What items do attackers get when they pickup a dropped crylink"
+set g_player_template_surv_attacker_pickup_weapon_dropped_vortex "add 25 25" "What items do attackers get when they pickup a dropped vortex"
+set g_player_template_surv_attacker_pickup_weapon_dropped_hagar "add 25 25" "What items do attackers get when they pickup a dropped hagar"
+set g_player_template_surv_attacker_pickup_weapon_dropped_devastator "add 25 25" "What items do attackers get when they pickup a dropped devastator"
+set g_player_template_surv_attacker_pickup_weapon_dropped_minelayer "add 25 25" "What items do attackers get when they pickup a dropped mine layer"
+set g_player_template_surv_attacker_pickup_weapon_dropped_hlac "add 25 25" "What items do attackers get when they pickup a dropped HLAC"
+set g_player_template_surv_attacker_pickup_weapon_dropped_rifle "add 25 25" "What items do attackers get when they pickup a dropped rifle"
+set g_player_template_surv_attacker_pickup_weapon_dropped_seeker "add 25 25" "What items do attackers get when they pickup a dropped TAG seeker"
+
+set g_player_template_surv_defender_pickup_health_small "override 0 0 5 5 10 10" "What items do defenders get when they pickup small health"
+set g_player_template_surv_defender_pickup_health_medium "override 0 0 10 10 20 20" "What items do defenders get when they pickup medium health"
+set g_player_template_surv_defender_pickup_health_big "override 0 0 20 15 40 40" "What items do defenders get when they pickup big health"
+set g_player_template_surv_defender_pickup_health_mega "override 0 0 30 30 50 50" "What items do defenders get when they pickup mega health"
+set g_player_template_surv_defender_pickup_armor_small "override 0 0 5 5 10 10" "What items do defenders get when they pickup small armor"
+set g_player_template_surv_defender_pickup_armor_medium "override 0 0 10 10 20 20" "What items do defenders get when they pickup medium armor"
+set g_player_template_surv_defender_pickup_armor_big "override 0 0 20 15 40 40" "What items do defenders get when they pickup big armor"
+set g_player_template_surv_defender_pickup_armor_mega "override 0 0 30 30 50 50" "What items do defenders get when they pickup mega armor"
+set g_player_template_surv_defender_pickup_weapon_machinegun "override 0 0 0 80" "What items do defenders get when they pickup a machinegun"
+set g_player_template_surv_defender_pickup_weapon_mortar "override 0 0 0 0 40" "What items do defenders get when they pickup a mortar"
+set g_player_template_surv_defender_pickup_weapon_electro "override 0 0 0 0 0 30" "What items do defenders get when they pickup an electro"
+set g_player_template_surv_defender_pickup_weapon_crylink "override 0 0 0 0 0 30" "What items do defenders get when they pickup a crylink"
+set g_player_template_surv_defender_pickup_weapon_vortex "override 0 0 0 0 0 30" "What items do defenders get when they pickup a vortex"
+set g_player_template_surv_defender_pickup_weapon_hagar "override 0 0 0 0 40" "What items do defenders get when they pickup a hagar"
+set g_player_template_surv_defender_pickup_weapon_devastator "override 0 0 0 0 40" "What items do defenders get when they pickup a devastator"
+set g_player_template_surv_defender_pickup_weapon_minelayer "override 0 0 0 0 40" "What items do defenders get when they pickup a mine layer"
+set g_player_template_surv_defender_pickup_weapon_hlac "override 0 0 0 0 0 30" "What items do defenders get when they pickup a HLAC"
+set g_player_template_surv_defender_pickup_weapon_rifle "override 0 0 0 80" "What items do defenders get when they pickup a rifle"
+set g_player_template_surv_defender_pickup_weapon_seeker "override 0 0 0 0 40" "What items do defenders get when they pickup a TAG seeker"
+set g_player_template_surv_defender_pickup_weapon_dropped_shotgun "override 0 0 15" "What items do defenders get when they pickup a dropped shotgun"
+set g_player_template_surv_defender_pickup_weapon_dropped_machinegun "override 0 0 0 80" "What items do defenders get when they pickup a dropped machinegun"
+set g_player_template_surv_defender_pickup_weapon_dropped_mortar "override 0 0 0 0 40" "What items do defenders get when they pickup a dropped mortar"
+set g_player_template_surv_defender_pickup_weapon_dropped_electro "override 0 0 0 0 0 30" "What items do defenders get when they pickup a dropped electro"
+set g_player_template_surv_defender_pickup_weapon_dropped_crylink "override 0 0 0 0 0 30" "What items do defenders get when they pickup a dropped crylink"
+set g_player_template_surv_defender_pickup_weapon_dropped_vortex "override 0 0 0 0 0 30" "What items do defenders get when they pickup a dropped vortex"
+set g_player_template_surv_defender_pickup_weapon_dropped_hagar "override 0 0 0 0 40" "What items do defenders get when they pickup a dropped hagar"
+set g_player_template_surv_defender_pickup_weapon_dropped_devastator "override 0 0 0 0 40" "What items do defenders get when they pickup a dropped devastator"
+set g_player_template_surv_defender_pickup_weapon_dropped_minelayer "override 0 0 0 0 40" "What items do defenders get when they pickup a dropped mine layer"
+set g_player_template_surv_defender_pickup_weapon_dropped_hlac "override 0 0 0 0 0 30" "What items do defenders get when they pickup a dropped HLAC"
+set g_player_template_surv_defender_pickup_weapon_dropped_rifle "override 0 0 0 80" "What items do defenders get when they pickup a dropped rifle"
+set g_player_template_surv_defender_pickup_weapon_dropped_seeker "override 0 0 0 0 40" "What items do defenders get when they pickup a dropped TAG seeker"
+
+set g_surv_attacker_damage_score 0.025 "How much score attackers gain per 1 point of damage"
+
+set g_player_template_surv_attacker_blaster_self_damage 0 "Whether attackers get damaged with their own blaster"
+
+set g_player_template_surv_defender_defense_scale 2 "How much defenders get damaged. Higher values mean less damage"
+set g_player_template_surv_defender_blaster_self_damage 0 "Whether defenders get damaged with their own blaster"
+
+set g_player_template_surv_cannon_fodder_blaster_self_damage 0 "Whether cannon fodder gets damaged with their own blaster"
+
+set g_surv_attacker_frag_score 10 "How much score attackers get for fragging defenders"
+
+set g_surv_defender_attacker_frag_health 0 "How much health do defenders get when they frag an attacker"
+set g_surv_defender_attacker_frag_armor 0 "How much armor do defenders get when they frag an attacker"
+set g_surv_defender_attacker_frag_shells 0 "How many shells do defenders get when they frag an attacker"
+set g_surv_defender_attacker_frag_bullets 0 "How many bullets do defenders get when they frag an attacker"
+set g_surv_defender_attacker_frag_rockets 0 "How many rockets do defenders get when they frag an attacker"
+set g_surv_defender_attacker_frag_cells 0 "How many cells do defenders get when they frag an attacker"
+set g_surv_defender_attacker_frag_plasma 0 "How much plasma do defenders get when they frag an attacker"
+set g_surv_defender_attacker_frag_fuel 0 "How much fuel do defenders get when they frag an attacker"
+set g_surv_defender_cannon_fodder_frag_health 0 "How much health do defenders get when they frag cannon fodder"
+set g_surv_defender_cannon_fodder_frag_armor 0 "How much armor do defenders get when they frag cannon fodder"
+set g_surv_defender_cannon_fodder_frag_shells 0 "How many shells do defenders get when they frag cannon fodder"
+set g_surv_defender_cannon_fodder_frag_bullets 0 "How many bullets do defenders get when they frag cannon fodder"
+set g_surv_defender_cannon_fodder_frag_rockets 0 "How many rockets do defenders get when they frag cannon fodder"
+set g_surv_defender_cannon_fodder_frag_cells 0 "How many cells do defenders get when they frag cannon fodder"
+set g_surv_defender_cannon_fodder_frag_plasma 0 "How much plasma do defenders get when they frag cannon fodder"
+set g_surv_defender_cannon_fodder_frag_fuel 0 "How much fuel do defenders get when they frag cannon fodder"