]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
implement battle royale gamemode in xonotic
authorJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Mon, 17 Jan 2022 02:35:04 +0000 (03:35 +0100)
committerJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Mon, 17 Jan 2022 02:35:04 +0000 (03:35 +0100)
50 files changed:
_hud_descriptions.cfg
balance-mario.cfg
balance-nexuiz25.cfg
balance-overkill.cfg
balance-samual.cfg
balance-xdf.cfg
balance-xonotic.cfg
balance-xpm.cfg
gamemodes-client.cfg
gamemodes-server.cfg
hud_luma.cfg
hud_luminos.cfg
hud_luminos_minimal.cfg
hud_luminos_minimal_xhair.cfg
hud_luminos_old.cfg
hud_nexuiz.cfg
qcsrc/client/hud/panel/scoreboard.qc
qcsrc/common/deathtypes/all.inc
qcsrc/common/ent_cs.qc
qcsrc/common/gamemodes/gamemode/_mod.inc
qcsrc/common/gamemodes/gamemode/_mod.qh
qcsrc/common/gamemodes/gamemode/br/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/br.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/br.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/cl_br.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/cl_br.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/cl_ring.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/cl_ring.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/ring.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/ring.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/sv_br.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/sv_br.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/sv_dropship.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/sv_dropship.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/sv_ring.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/sv_ring.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/sv_squad.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/br/sv_squad.qh [new file with mode: 0644]
qcsrc/common/mapinfo.qc
qcsrc/common/mutators/mutator/waypoints/all.inc
qcsrc/common/notifications/all.inc
qcsrc/common/notifications/all.qh
qcsrc/common/scores.qh
qcsrc/common/stats.qh
qcsrc/lib/csqcmodel/cl_player.qc
qcsrc/menu/xonotic/util.qc
qcsrc/server/command/cmd.qc
qcsrc/server/damage.qc
qcsrc/server/world.qc

index 02d2d5a0f46adb114c507ccbc327b5dc57ed39c3..eaf4a7fe4e865e58c13e5a16b62929767005184e 100644 (file)
@@ -198,6 +198,7 @@ seta hud_panel_modicons_bg_padding "" "if set to something else than \"\" = over
 seta hud_panel_modicons_ca_layout "" "2 possible layouts: 0) number of alive players; 1) icons and number of alive players"
 seta hud_panel_modicons_dom_layout "" "3 possible layouts: 0) only icons; 1) icons and percentage of average pps (points per second); 2) icons and average pps"
 seta hud_panel_modicons_freezetag_layout "" "2 possible layouts: 0) number of alive players; 1) icons and number of alive players"
+seta hud_panel_modicons_br_layout "" "2 possible layouts: 0) number of alive squads/players; 1) icons and number of alive squads/players"
 
 seta hud_panel_pressedkeys_pos "" "position of this base of the panel"
 seta hud_panel_pressedkeys_size "" "size of this panel"
index 42e3a66967db0f6d958a220c9a9e6425c1683f1d..325db8e58f2027a5c1f3276dfc6fd22a0c2220cc 100644 (file)
@@ -49,6 +49,14 @@ set g_lms_start_ammo_rockets 160
 set g_lms_start_ammo_cells 180
 set g_lms_start_ammo_plasma 180
 set g_lms_start_ammo_fuel 0
+set g_br_start_health 100
+set g_br_start_armor 0
+set g_br_start_ammo_shells 0
+set g_br_start_ammo_nails 0
+set g_br_start_ammo_rockets 0
+set g_br_start_ammo_cells 0
+set g_br_start_ammo_plasma 0
+set g_br_start_ammo_fuel 0
 set g_balance_nix_roundtime 25
 set g_balance_nix_incrtime 1.6
 set g_balance_nix_ammo_shells 60
index 6cdc29dcc4deec4152bd1e8f985cf87630e90de9..d34e8e30996a331096030925a81b1596350ba9bb 100644 (file)
@@ -49,6 +49,14 @@ set g_lms_start_ammo_rockets 50
 set g_lms_start_ammo_cells 50
 set g_lms_start_ammo_plasma 50
 set g_lms_start_ammo_fuel 0
+set g_br_start_health 150
+set g_br_start_armor 0
+set g_br_start_ammo_shells 0
+set g_br_start_ammo_nails 0
+set g_br_start_ammo_rockets 0
+set g_br_start_ammo_cells 0
+set g_br_start_ammo_plasma 0
+set g_br_start_ammo_fuel 0
 set g_balance_nix_roundtime 25
 set g_balance_nix_incrtime 1.6
 set g_balance_nix_ammo_shells 15
index e33ee827f66699289c388db8bd38351d4e8c8ccf..752a8982500d103bacf486394fb119fe5bc4f9f1 100644 (file)
@@ -49,6 +49,14 @@ set g_lms_start_ammo_rockets 160
 set g_lms_start_ammo_cells 180
 set g_lms_start_ammo_plasma 180
 set g_lms_start_ammo_fuel 0
+set g_br_start_health 100
+set g_br_start_armor 0
+set g_br_start_ammo_shells 0
+set g_br_start_ammo_nails 0
+set g_br_start_ammo_rockets 0
+set g_br_start_ammo_cells 0
+set g_br_start_ammo_plasma 0
+set g_br_start_ammo_fuel 0
 set g_balance_nix_roundtime 25
 set g_balance_nix_incrtime 1.6
 set g_balance_nix_ammo_shells 60
index 7850aaba8b97eac07e8c77dce3ee632d85433a09..44b110586d674d28dc7b1631c8537bea4a3f2ae3 100644 (file)
@@ -49,6 +49,14 @@ set g_lms_start_ammo_rockets 160
 set g_lms_start_ammo_cells 180
 set g_lms_start_ammo_plasma 180
 set g_lms_start_ammo_fuel 0
+set g_br_start_health 100
+set g_br_start_armor 0
+set g_br_start_ammo_shells 0
+set g_br_start_ammo_nails 0
+set g_br_start_ammo_rockets 0
+set g_br_start_ammo_cells 0
+set g_br_start_ammo_plasma 0
+set g_br_start_ammo_fuel 0
 set g_balance_nix_roundtime 25
 set g_balance_nix_incrtime 1.6
 set g_balance_nix_ammo_shells 60
index 94e98a6461ccc1f85ded776b3bd7685f9c6f71ad..3bc9cc7aaa878ebafd3995a8a5b1631644ecdf67 100644 (file)
@@ -49,6 +49,14 @@ set g_lms_start_ammo_rockets 160
 set g_lms_start_ammo_cells 180
 set g_lms_start_ammo_plasma 180
 set g_lms_start_ammo_fuel 0
+set g_br_start_health 100
+set g_br_start_armor 0
+set g_br_start_ammo_shells 0
+set g_br_start_ammo_nails 0
+set g_br_start_ammo_rockets 0
+set g_br_start_ammo_cells 0
+set g_br_start_ammo_plasma 0
+set g_br_start_ammo_fuel 0
 set g_balance_nix_roundtime 25
 set g_balance_nix_incrtime 1.6
 set g_balance_nix_ammo_shells 60
index 784e447f418137208402e10d7b9cd2a99c974621..ba3bf0b5a743cff2fb6a80cca17762bdee29d615 100644 (file)
@@ -49,6 +49,14 @@ set g_lms_start_ammo_rockets 160
 set g_lms_start_ammo_cells 180
 set g_lms_start_ammo_plasma 180
 set g_lms_start_ammo_fuel 0
+set g_br_start_health 100
+set g_br_start_armor 0
+set g_br_start_ammo_shells 0
+set g_br_start_ammo_nails 0
+set g_br_start_ammo_rockets 0
+set g_br_start_ammo_cells 0
+set g_br_start_ammo_plasma 0
+set g_br_start_ammo_fuel 0
 set g_balance_nix_roundtime 25
 set g_balance_nix_incrtime 1.6
 set g_balance_nix_ammo_shells 60
index 189be9c718544d7eb19e52dc8d67329c6df49e39..ba72b43db857bb98d7c08753763dcfda3d84d0c2 100644 (file)
@@ -49,6 +49,14 @@ set g_lms_start_ammo_rockets 160
 set g_lms_start_ammo_cells 180
 set g_lms_start_ammo_plasma 180
 set g_lms_start_ammo_fuel 0
+set g_br_start_health 100
+set g_br_start_armor 0
+set g_br_start_ammo_shells 0
+set g_br_start_ammo_nails 0
+set g_br_start_ammo_rockets 0
+set g_br_start_ammo_cells 0
+set g_br_start_ammo_plasma 0
+set g_br_start_ammo_fuel 0
 set g_balance_nix_roundtime 25
 set g_balance_nix_incrtime 1.6
 set g_balance_nix_ammo_shells 60
index c43b9d1d3f2e6fe73fddbb33e8489670350edc4d..cf3e35b69b0e80138f6a69872ed1a46b7dd82776 100644 (file)
@@ -32,6 +32,7 @@ alias cl_hook_gamestart_ka
 alias cl_hook_gamestart_ft
 alias cl_hook_gamestart_inv
 alias cl_hook_gamestart_duel
+alias cl_hook_gamestart_br
 alias cl_hook_gameend "rpn /cl_matchcount dup load 1 + =" // increase match count every time a game ends
 alias cl_hook_shutdown
 alias cl_hook_activeweapon
index 17b90c624fde8f8af9a50534ad5e69fae6e27ead..259209f09a4a0fa6d9d0728dcb096dfc138e9eec 100644 (file)
@@ -29,6 +29,7 @@ alias sv_hook_gamestart_ka
 alias sv_hook_gamestart_ft
 alias sv_hook_gamestart_inv
 alias sv_hook_gamestart_duel
+alias sv_hook_gamestart_br
 // there is currently no hook for when the match is restarted
 // see sv_hook_readyrestart for previous uses of this hook
 //alias sv_hook_gamerestart
@@ -58,6 +59,7 @@ alias sv_vote_gametype_hook_ons
 alias sv_vote_gametype_hook_rc
 alias sv_vote_gametype_hook_tdm
 alias sv_vote_gametype_hook_duel
+alias sv_vote_gametype_hook_br
 
 // Example preset to allow 1v1ctf to be used for the gametype voting screen.
 // Aliases can have max 31 chars so the gametype can have max 9 chars.
@@ -208,6 +210,13 @@ set g_duel_respawn_delay_large_count 0
 set g_duel_respawn_delay_max 0
 set g_duel_respawn_waves 0
 set g_duel_weapon_stay 0
+set g_br_respawn_delay_small 0
+set g_br_respawn_delay_small_count 0
+set g_br_respawn_delay_large 0
+set g_br_respawn_delay_large_count 0
+set g_br_respawn_delay_max 0
+set g_br_respawn_waves 0
+set g_br_weapon_stay 0
 
 
 // =========
@@ -559,3 +568,37 @@ set g_duel 0 "Duel: frag the opponent more in a one versus one arena battle"
 //set g_duel_warmup 180 "Have a short warmup period before beginning the actual duel"
 set g_duel_with_powerups 0 "Enable powerups to spawn in the duel gamemode"
 set g_duel_not_dm_maps 0 "when this is set, DM maps will NOT be listed in duel"
+
+// ======
+//  battle royale
+// ======
+set g_br 0 "Battle Royale: survive on a shrinking battlefield"
+set g_br_minplayers 2
+set g_br_squad_size 3
+set g_br_squad_colors 1
+set g_br_startweapons 0
+set g_br_bleed 0.02
+set g_br_bleedlinear 1
+set g_br_bleeding_health 0.5
+set g_br_revive_speed 0.4
+set g_br_revive_clearspeed 1.6
+set g_br_revive_extra_size 100
+set g_br_revive_health 0.25
+set g_br_dropship_color "0.5 0 0.5"
+set g_br_dropship_scale 3
+set g_br_dropship_speed 200
+set g_br_drop_speed_max 2
+set g_br_drop_speed_horizontal_max 0.9
+set g_br_drop_speed_horizontal_min 0.5
+set g_br_drop_speed_vertical 1.5
+set g_br_drop_acceleration 1200
+set g_br_drop_distance_force 500
+set g_br_ring_alpha 0.5
+set g_br_ring_color "1 0 0"
+set g_br_ring_radius -1
+set g_br_ring_duration 150
+set g_br_ring_timing "0.6 0.8 0.9"
+set g_br_ring_strength "2.5 5 10 20 50"
+set g_br_ring_wait 30
+set g_br_ring_center_factor 0.25
+set g_br_ring_fadedistance 2000
index 7bcbef2b937fa8afb4af63737e06203c809eab90..e346d41da7de69377b0e416582774e5baa72f718 100644 (file)
@@ -199,6 +199,7 @@ seta hud_panel_modicons_bg_padding "0"
 seta hud_panel_modicons_ca_layout "1"
 seta hud_panel_modicons_dom_layout "1"
 seta hud_panel_modicons_freezetag_layout "1"
+seta hud_panel_modicons_br_layout "1"
 
 seta hud_panel_pressedkeys_pos "0.445000 0.710000"
 seta hud_panel_pressedkeys_size "0.110000 0.090000"
index 715d57efdcff1b8da2a2a9acfb537277446b097c..769cbae0596988830417a386784fe212e1af0bf6 100644 (file)
@@ -199,6 +199,7 @@ seta hud_panel_modicons_bg_padding "0"
 seta hud_panel_modicons_ca_layout "1"
 seta hud_panel_modicons_dom_layout "1"
 seta hud_panel_modicons_freezetag_layout "1"
+seta hud_panel_modicons_br_layout "1"
 
 seta hud_panel_pressedkeys_pos "0.450000 0.720000"
 seta hud_panel_pressedkeys_size "0.110000 0.090000"
index 25f871bf12b7165008527e3d25b4d25300a9a238..84e53e538c51f09a848f5b31072b2cd95ef54b62 100644 (file)
@@ -199,6 +199,7 @@ seta hud_panel_modicons_bg_padding ""
 seta hud_panel_modicons_ca_layout "1"
 seta hud_panel_modicons_dom_layout "1"
 seta hud_panel_modicons_freezetag_layout "1"
+seta hud_panel_modicons_br_layout "1"
 
 seta hud_panel_pressedkeys_pos "0.450000 0.650000"
 seta hud_panel_pressedkeys_size "0.100000 0.110000"
index e3e857b0a0f5a9fdf7a9f7ee47e9440709793071..6f4d92eb2e11178eae09932cc4a71fa4c23f92a9 100644 (file)
@@ -199,6 +199,7 @@ seta hud_panel_modicons_bg_padding ""
 seta hud_panel_modicons_ca_layout "1"
 seta hud_panel_modicons_dom_layout "1"
 seta hud_panel_modicons_freezetag_layout "1"
+seta hud_panel_modicons_br_layout "1"
 
 seta hud_panel_pressedkeys_pos "0.450000 0.690000"
 seta hud_panel_pressedkeys_size "0.100000 0.110000"
index fdf1efff39a046b5aea8571c6a01b8672736c1d1..62df374279c0a4312072bca12c6cd8b95c4dd742 100644 (file)
@@ -199,6 +199,7 @@ seta hud_panel_modicons_bg_padding ""
 seta hud_panel_modicons_ca_layout "1"
 seta hud_panel_modicons_dom_layout "1"
 seta hud_panel_modicons_freezetag_layout "1"
+seta hud_panel_modicons_br_layout "1"
 
 seta hud_panel_pressedkeys_pos "0.410000 0.710000"
 seta hud_panel_pressedkeys_size "0.180000 0.130000"
index 74132c08a90604924cc5d2546398467ab50f1197..85541b69d522b8fbdd83afb891e116f57f3f89a0 100644 (file)
@@ -199,6 +199,7 @@ seta hud_panel_modicons_bg_padding ""
 seta hud_panel_modicons_ca_layout "1"
 seta hud_panel_modicons_dom_layout "1"
 seta hud_panel_modicons_freezetag_layout "1"
+seta hud_panel_modicons_br_layout "1"
 
 seta hud_panel_pressedkeys_pos "0.440000 0.760000"
 seta hud_panel_pressedkeys_size "0.120000 0.094368"
index b3d24e59db7d8d9bd102ff42ce3ca66874380873..1e125f6f29eb21c12d7b818ded60f94d26813b66 100644 (file)
@@ -392,12 +392,12 @@ void Cmd_Scoreboard_Help()
 // otherwise the previous exclusive rule warns anyway
 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
 #define SCOREBOARD_DEFAULT_COLUMNS \
-"ping pl fps name |" \
+"ping pl fps +br/squad name |" \
 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
-" -teams,lms/deaths +ft,tdm/deaths" \
+" -teams,lms,br/deaths +ft,tdm/deaths" \
 " +tdm/sum" \
-" -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
-" -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
+" -teams,lms,rc,cts,inv,ka,br/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
+" -cts,dm,tdm,ka,ft,br/frags" /* tdm already has this in "score" */ \
 " +tdm,ft,dom,ons,as/teamkills"\
 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
@@ -407,7 +407,9 @@ void Cmd_Scoreboard_Help()
 " +as/objectives +nb/faults +nb/goals" \
 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
 " +dom/ticks +dom/takes" \
-" -lms,rc,cts,inv,nb/score"
+" -lms,rc,cts,inv,nb,br/score" \
+" +br/revivals" \
+" +br/rank"
 
 void Cmd_Scoreboard_SetFields(int argc)
 {
index c11e5fedf0091dad5db74ea5772f3eed14f47c5e..d9728d3a268762f00f63d607297aa40428aa280a 100644 (file)
@@ -10,6 +10,7 @@ REGISTER_DEATHTYPE(GENERIC,                 DEATH_SELF_GENERIC,             NULL
 REGISTER_DEATHTYPE(HURTTRIGGER,             DEATH_SELF_VOID,                DEATH_MURDER_VOID,              "")
 REGISTER_DEATHTYPE(KILL,                    DEATH_SELF_SUICIDE,             NULL,                           "")
 REGISTER_DEATHTYPE(LAVA,                    DEATH_SELF_LAVA,                DEATH_MURDER_LAVA,              "")
+REGISTER_DEATHTYPE(RING,                    DEATH_SELF_RING,                NULL,                           "")
 REGISTER_DEATHTYPE(MIRRORDAMAGE,            DEATH_SELF_BETRAYAL,            NULL,                           "")
 REGISTER_DEATHTYPE(MONSTER_MAGE,            DEATH_SELF_MON_MAGE,            DEATH_MURDER_MONSTER,           "monster")
 REGISTER_DEATHTYPE(MONSTER_SHAMBLER_CLAW,   DEATH_SELF_MON_SHAMBLER_CLAW,   DEATH_MURDER_MONSTER,           "monster")
index a3691386be379296ae2b494025666e320a13de18..b976d651ed8b7cf5465a2360765bfcec02ed381e 100644 (file)
@@ -188,6 +188,7 @@ ENTCS_PROP(SOLID, true, sv_solid, solid, ENTCS_SET_NORMAL,
                        {
                                if (radar_showenemies) break;
                                if (SAME_TEAM(to, player)) break;
+                               if (SAME_SQUAD(to, player)) break;
                                if (!(IS_PLAYER(to) || to.caplayer)) break;
                        }
                        sf &= ENTCS_PUBLICMASK; // no private updates
index a33ec87a01a34a5a8406f57aa3c3d52829cf3994..bbc986ea4c6fee2d28cb0a954d1ce2d01cce5db2 100644 (file)
@@ -1,6 +1,7 @@
 // generated file; do not modify
 
 #include <common/gamemodes/gamemode/assault/_mod.inc>
+#include <common/gamemodes/gamemode/br/_mod.inc>
 #include <common/gamemodes/gamemode/clanarena/_mod.inc>
 #include <common/gamemodes/gamemode/ctf/_mod.inc>
 #include <common/gamemodes/gamemode/cts/_mod.inc>
index ffd71d59d3f1092453b6d83f8048003693dfa531..8a73e4945719c0e92668b3623d9ce6e0cf919b40 100644 (file)
@@ -1,6 +1,7 @@
 // generated file; do not modify
 
 #include <common/gamemodes/gamemode/assault/_mod.qh>
+#include <common/gamemodes/gamemode/br/_mod.qh>
 #include <common/gamemodes/gamemode/clanarena/_mod.qh>
 #include <common/gamemodes/gamemode/ctf/_mod.qh>
 #include <common/gamemodes/gamemode/cts/_mod.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/br/_mod.inc b/qcsrc/common/gamemodes/gamemode/br/_mod.inc
new file mode 100644 (file)
index 0000000..42767d9
--- /dev/null
@@ -0,0 +1,21 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/br/br.qc>
+#ifdef CSQC
+    #include <common/gamemodes/gamemode/br/cl_br.qc>
+#endif
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/br/sv_br.qc>
+#endif
+#include <common/gamemodes/gamemode/br/ring.qc>
+#ifdef CSQC
+    #include <common/gamemodes/gamemode/br/cl_ring.qc>
+#endif
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/br/sv_ring.qc>
+#endif
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/br/sv_dropship.qc>
+#endif
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/br/sv_squad.qc>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/br/_mod.qh b/qcsrc/common/gamemodes/gamemode/br/_mod.qh
new file mode 100644 (file)
index 0000000..9462da4
--- /dev/null
@@ -0,0 +1,21 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/br/br.qh>
+#ifdef CSQC
+    #include <common/gamemodes/gamemode/br/cl_br.qh>
+#endif
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/br/sv_br.qh>
+#endif
+#include <common/gamemodes/gamemode/br/ring.qh>
+#ifdef CSQC
+    #include <common/gamemodes/gamemode/br/cl_ring.qh>
+#endif
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/br/sv_ring.qh>
+#endif
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/br/sv_dropship.qh>
+#endif
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/br/sv_squad.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/br/br.qc b/qcsrc/common/gamemodes/gamemode/br/br.qc
new file mode 100644 (file)
index 0000000..1a0663f
--- /dev/null
@@ -0,0 +1,5 @@
+#include "br.qh"
+
+#ifdef GAMEQC
+REGISTER_NET_LINKED(ENT_CLIENT_RING)
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/br/br.qh b/qcsrc/common/gamemodes/gamemode/br/br.qh
new file mode 100644 (file)
index 0000000..c6ad354
--- /dev/null
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <common/mapinfo.qh>
+
+#ifdef CSQC
+void HUD_Mod_BR(vector pos, vector mySize);
+void HUD_Mod_BR_Export(int fh);
+#endif
+#ifdef SVQC
+IntrusiveList squads;
+#endif
+CLASS(BattleRoyale, Gametype)
+#ifdef SVQC
+    STATIC_INIT(BattleRoyale)
+    {
+        squads = IL_NEW();
+    }
+#endif
+    INIT(BattleRoyale)
+    {
+        this.gametype_init(this, _("Battle Royale"),"br","g_br",GAMETYPE_FLAG_HIDELIMITS,"","",_("Survive and kill until there are no enemies left"));
+    }
+    METHOD(BattleRoyale, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
+    {
+        if(diameter > 4096)
+            return true;
+        return false;
+    }
+#ifdef CSQC
+    ATTRIB(BattleRoyale, m_modicons, void(vector pos, vector mySize), HUD_Mod_BR);
+    ATTRIB(BattleRoyale, m_modicons_export, void(int fh), HUD_Mod_BR_Export);
+#endif
+ENDCLASS(BattleRoyale)
+REGISTER_GAMETYPE(BR, NEW(BattleRoyale));
+#define g_br IS_GAMETYPE(BR)
+
+#ifdef GAMEQC
+const int DROP_LANDED    = 0;
+const int DROP_FALLING   = 1;
+const int DROP_TRANSPORT = 2;
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/br/cl_br.qc b/qcsrc/common/gamemodes/gamemode/br/cl_br.qc
new file mode 100644 (file)
index 0000000..3dddb96
--- /dev/null
@@ -0,0 +1,124 @@
+#include "cl_br.qh"
+
+#include <common/mutators/base.qh>
+
+REGISTER_MUTATOR(cl_br, true);
+
+MUTATOR_HOOKFUNCTION(cl_br, WantEventchase)
+{
+    return (STAT(DROP) == DROP_FALLING);
+}
+
+MUTATOR_HOOKFUNCTION(cl_br, DrawCrosshair)
+{
+    return (STAT(BLEEDING) || (STAT(DROP) != DROP_LANDED));
+}
+
+MUTATOR_HOOKFUNCTION(cl_br, PlayerCanCrouch)
+{
+    if(STAT(BLEEDING))
+        M_ARGV(1, bool) = true;
+    else if(STAT(DROP) != DROP_LANDED)
+        M_ARGV(1, bool) = false;
+}
+
+MUTATOR_HOOKFUNCTION(cl_br, PlayerJump)
+{
+    return STAT(BLEEDING) || (STAT(DROP) != DROP_LANDED);
+}
+
+MUTATOR_HOOKFUNCTION(cl_br, PM_Physics)
+{
+    if(STAT(DROP) == DROP_FALLING)
+        ITEMS_STAT(csqcplayer) |= IT_USING_JETPACK;
+
+    return (STAT(DROP) != DROP_LANDED);
+}
+
+// adjusted clanarena hud
+#include <client/draw.qh>
+
+void HUD_Mod_BR_Export(int fh)
+{
+    HUD_Write_Cvar("hud_panel_modicons_br_layout");
+}
+
+void DrawBRItem(vector myPos, vector mySize, float aspect_ratio, int layout, int i)
+{
+    TC(int, layout); TC(int, i);
+    int stat = -1;
+    vector color = '0 0 0';
+    switch(i)
+    {
+        case 0: stat = STAT(SQUADSALIVE); color = '1 1 0'; break;
+        default:
+        case 1: stat = STAT(PLAYERSALIVE); color = '1 0 0'; break;
+    }
+
+    float aspect_size;
+    if(mySize.x/mySize.y > aspect_ratio)
+    {
+        aspect_size = aspect_ratio * mySize.y;
+        myPos.x = myPos.x + (mySize.x - aspect_size) / 2;
+        mySize.x = aspect_size;
+    }
+    else
+    {
+        aspect_size = 1/aspect_ratio * mySize.x;
+        myPos.y = myPos.y + (mySize.y - aspect_size) / 2;
+        mySize.y = aspect_size;
+    }
+
+    if(layout)
+    {
+        if(i == 0)
+        {
+            drawpic_aspect_skin(myPos + eX / 12 * mySize.x - eY / 12 * mySize.y, "player_blue", vec2(mySize.x / 3, mySize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+            drawpic_aspect_skin(myPos + eY / 12 * mySize.y, "player_yellow", vec2(mySize.x / 3, mySize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+            drawpic_aspect_skin(myPos + eX /  6 * mySize.x + eY / 12 * mySize.y, "player_pink", vec2(mySize.x / 3, mySize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+        }
+        else
+        {
+            drawpic_aspect_skin(myPos, "player_red", vec2(mySize.x / 2, mySize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+        }
+        drawstring_aspect(myPos + eX * mySize.x / 2, ftos(stat), vec2(mySize.x / 2, mySize.y), color, panel_fg_alpha, DRAWFLAG_NORMAL);
+    }
+    else
+        drawstring_aspect(myPos, ftos(stat), mySize, color, panel_fg_alpha, DRAWFLAG_NORMAL);
+}
+
+void HUD_Mod_BR_Draw(vector myPos, vector mySize, int layout)
+{
+    int rows, columns;
+    float aspect_ratio;
+    aspect_ratio = (layout) ? 2 : 1;
+    rows = HUD_GetRowCount(2, mySize, aspect_ratio);
+    columns = ceil(2/rows);
+
+    int i;
+    float row = 0, column = 0;
+    vector pos = '0 0 0', itemSize;
+    itemSize = vec2(mySize.x / columns, mySize.y / rows);
+    for(i=0; i<2; ++i)
+    {
+        pos.x = myPos.x + column * itemSize.x;
+        pos.y = myPos.y + row * itemSize.y;
+
+        DrawBRItem(pos, itemSize, aspect_ratio, layout, i);
+
+        ++row;
+        if(row >= rows)
+        {
+            row = 0;
+            ++column;
+        }
+    }
+}
+
+// Battle Royale HUD modicons
+void HUD_Mod_BR(vector myPos, vector mySize)
+{
+    mod_active = 1; // required in each mod function that always shows something
+
+    HUD_Mod_BR_Draw(myPos, mySize, autocvar_hud_panel_modicons_br_layout);
+}
diff --git a/qcsrc/common/gamemodes/gamemode/br/cl_br.qh b/qcsrc/common/gamemodes/gamemode/br/cl_br.qh
new file mode 100644 (file)
index 0000000..faec240
--- /dev/null
@@ -0,0 +1,7 @@
+#pragma once
+
+int autocvar_hud_panel_modicons_br_layout = 1;
+
+void HUD_Mod_BR(vector myPos, vector mySize);
+void HUD_Mod_BR_Draw(vector myPos, vector mySize, int layout);
+void HUD_Mod_BR_Export(int fh);
diff --git a/qcsrc/common/gamemodes/gamemode/br/cl_ring.qc b/qcsrc/common/gamemodes/gamemode/br/cl_ring.qc
new file mode 100644 (file)
index 0000000..609cb23
--- /dev/null
@@ -0,0 +1,134 @@
+#include "cl_ring.qh"
+
+// TODO: support dark colors
+
+bool ring_is_closing(entity this);
+
+float autocvar_g_br_ring_fadedistance = 2000;
+
+#define RING_MODEL_PATH "models/sphere/sphere.md3"
+#define RING_MODEL_RADIUS 64 // radius of model at 1.0 scale
+void ring_draw(entity this)
+{
+    float current_radius = ring_calculate_current_radius(this);
+
+    if(ring_is_closing(this)) // only move when we actually should, increases visual fidelity in case the server lags and sends the wait signal late
+    {
+        setorigin(this, this.origin + this.velocity * (time - this.move_time)); // 3D is drawn before 2D, the origin will also be used in ring_draw2d afterwards
+        this.move_time = time;
+    }
+
+    if(current_radius <= 0)
+    {
+        setmodel(this, MDL_Null);
+        this.scale = this.alpha = 0;
+        this.velocity = '0 0 0';
+        this.draw = func_null;
+        return;
+    }
+
+    this.scale = current_radius / RING_MODEL_RADIUS;
+    this.alpha = max((1 - min(max(current_radius - vlen((csqcplayer.origin + csqcplayer.view_ofs) - this.origin), 0) / max(autocvar_g_br_ring_fadedistance, 1), 1)) * this.br_ring_alpha, 0.01);
+    if(vlen((csqcplayer.origin + csqcplayer.view_ofs) - this.origin) > current_radius)
+        this.alpha = max(this.alpha / 4, 0.01); // let's weaken the ring visuals a bit, everything is already ring colored
+}
+
+void ring_draw2d(entity this)
+{
+    float current_radius = ring_calculate_current_radius(this);
+
+    if(vlen((csqcplayer.origin + csqcplayer.view_ofs) - this.origin) > current_radius)
+    {
+        R_BeginPolygon("", DRAWFLAG_ADDITIVE, true);
+        R_PolygonVertex('0 0 0', '0 0 0', this.colormod, min(this.br_ring_alpha, 0.5));
+        R_PolygonVertex(autocvar_vid_conwidth * '1 0 0', '1 0 0', this.colormod, min(this.br_ring_alpha, 0.5));
+        R_PolygonVertex(autocvar_vid_conwidth * '1 0 0' + autocvar_vid_conheight * '0 1 0', '1 1 0', this.colormod, min(this.br_ring_alpha, 0.5));
+        R_PolygonVertex(autocvar_vid_conheight * '0 1 0', '0 1 0', this.colormod, min(this.br_ring_alpha, 0.5));
+        R_EndPolygon();
+    }
+}
+
+void ring_construct(entity this, bool isnew)
+{
+    this.netname = BR_RING_NAME;
+
+    setorigin(this, this.origin);
+    setsize(this, '0 0 0', '0 0 0');
+    set_movetype(this, MOVETYPE_NOCLIP);
+    _setmodel(this, RING_MODEL_PATH);
+
+    this.classname   = "ring";
+    this.solid       = SOLID_NOT;
+    this.draw        = ring_draw;
+    this.drawmask    = MASK_NORMAL;
+    this.draw2d      = ring_draw2d;
+
+    if(isnew)
+    {
+        IL_PUSH(g_drawables, this);
+        IL_PUSH(g_drawables_2d, this);
+    }
+}
+
+NET_HANDLE(ENT_CLIENT_RING, bool isnew)
+{
+    float sf;
+    sf = ReadByte();
+
+    if(sf & BR_RING_SETUP)
+    {
+        this.origin = ReadVector();
+        setorigin(this, this.origin);
+
+        this.br_ring_start = ReadCoord();
+        this.br_ring_duration = max(ReadCoord(), 1);
+
+        this.radius = max(ReadCoord(), 0);
+
+        this.colormod = normalize(ReadVector());
+
+        this.br_ring_alpha = bound(0.01, ReadCoord(), 1);
+
+        this.br_ring_stage_count = max(ReadByte(), 1);
+        this.br_ring_stage_waittime = max(ReadCoord(), 0);
+        for(int i = 0; i < (this.br_ring_stage_count + 1); ++i)
+            this.br_ring_stage_timing[i] = ReadCoord();
+
+        ring_construct(this, isnew);
+    }
+
+    if(sf & BR_RING_MOVE)
+    {
+        this.origin = ReadVector();
+        setorigin(this, this.origin);
+
+        this.velocity = ReadVector();
+
+        this.move_time = time;
+    }
+
+    return true;
+}
+
+bool ring_is_closing(entity this)
+{
+    float time_elapsed = time - this.br_ring_start;
+
+    for(int stage = 0; stage < this.br_ring_stage_count; ++stage)
+    {
+        float stage_duration = this.br_ring_duration * this.br_ring_stage_timing[stage];
+        stage_duration += this.br_ring_stage_waittime * stage;
+
+        if(time_elapsed > stage_duration)
+        {
+            if(time_elapsed < (stage_duration + this.br_ring_stage_waittime))
+            {
+                return false;
+            }
+        }
+        else
+            return true;
+    }
+
+    return (time_elapsed <= this.br_ring_duration);
+}
diff --git a/qcsrc/common/gamemodes/gamemode/br/cl_ring.qh b/qcsrc/common/gamemodes/gamemode/br/cl_ring.qh
new file mode 100644 (file)
index 0000000..6f70f09
--- /dev/null
@@ -0,0 +1 @@
+#pragma once
diff --git a/qcsrc/common/gamemodes/gamemode/br/ring.qc b/qcsrc/common/gamemodes/gamemode/br/ring.qc
new file mode 100644 (file)
index 0000000..08b2690
--- /dev/null
@@ -0,0 +1,33 @@
+#include "ring.qh"
+
+#ifdef GAMEQC
+float ring_calculate_current_radius(entity this)
+{
+    float time_elapsed = time - this.br_ring_start;
+    float time_elapsed_minus_wait = time_elapsed;
+
+    for(int stage = 0; stage < this.br_ring_stage_count; ++stage)
+    {
+        float stage_duration = this.br_ring_duration * this.br_ring_stage_timing[stage];
+        stage_duration += this.br_ring_stage_waittime * stage;
+
+        if(time_elapsed > stage_duration)
+        {
+            if(time_elapsed >= (stage_duration + this.br_ring_stage_waittime))
+            {
+                time_elapsed_minus_wait -= this.br_ring_stage_waittime;
+            }
+            else
+            {
+                time_elapsed_minus_wait -= time_elapsed - stage_duration;
+                break;
+            }
+        }
+        else
+            break;
+    }
+
+    float radius_ratio = 1 - time_elapsed_minus_wait / this.br_ring_duration;
+    return this.radius * radius_ratio;
+}
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/br/ring.qh b/qcsrc/common/gamemodes/gamemode/br/ring.qh
new file mode 100644 (file)
index 0000000..c15777b
--- /dev/null
@@ -0,0 +1,18 @@
+#pragma once
+
+#ifdef GAMEQC
+const string BR_RING_NAME = "^3the ring";
+
+const int BR_RING_SETUP  = 2;
+const int BR_RING_MOVE   = 4;
+
+#define BR_RING_STAGE_MAX 128
+.float br_ring_start;
+.float br_ring_duration;
+.float br_ring_alpha;
+.int br_ring_stage_count;
+.float br_ring_stage_waittime;
+.float br_ring_stage_timing[BR_RING_STAGE_MAX];
+
+float ring_calculate_current_radius(entity this);
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/br/sv_br.qc b/qcsrc/common/gamemodes/gamemode/br/sv_br.qc
new file mode 100644 (file)
index 0000000..87a527f
--- /dev/null
@@ -0,0 +1,986 @@
+// battle royale
+// author: Juhu
+
+#include "sv_br.qh"
+#include <server/elimination.qh>
+#include <server/resources.qh>
+#include <common/mutators/base.qh>
+
+#define BR_KILLS_INSTANTLY(pl, dt) (!IN_SQUAD((pl)) || (br_SquadFindLastAlive((pl).br_squad, true) == (pl)) || ((dt) == DEATH_HURTTRIGGER.m_id) || ((dt) == DEATH_KILL.m_id) || ((dt) == DEATH_TEAMCHANGE.m_id) || ((dt) == DEATH_AUTOTEAMCHANGE.m_id))
+
+void br_SetPlayerDropAngle(entity this);
+void br_LastPlayerForSquad_Notify(entity squad);
+void br_RemovePlayer(entity player);
+void br_Revive(entity player);
+void br_PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force);
+int br_WinningCondition();
+
+entity ring;
+entity dropship;
+
+.bool br_ring_warned;
+.float br_force_drop_distance;
+
+.entity br_bleeding_inflictor;
+.entity br_bleeding_attacker;
+.int br_bleeding_deathtype;
+..entity br_bleeding_weaponentity;
+
+// weapon set restoring for revive/drop
+.WepSet br_wepset_old;
+.Weapon br_weapon_prev[MAX_WEAPONSLOTS];
+.float br_lastweapon_prev[MAX_WEAPONSLOTS];
+
+float autocvar_g_br_revive_health = 0.25;
+float autocvar_g_br_bleeding_health = 0.5;
+float autocvar_g_br_drop_speed_max = 2;
+float autocvar_g_br_drop_speed_horizontal_max = 0.9;
+float autocvar_g_br_drop_speed_horizontal_min = 0.5;
+float autocvar_g_br_drop_speed_vertical = 1.5;
+bool autocvar_g_br_squad_colors = true;
+float autocvar_g_br_drop_acceleration = 1200;
+bool autocvar_g_br_startweapons = false;
+
+.vector br_drop_velocity;
+.vector br_drop_angles;
+
+MUTATOR_HOOKFUNCTION(br, reset_map_global)
+{
+    dropship_path_length = 0; // this should kill the dropship
+    dropship_path_direction = '0 0 0';
+
+    delete(ring);
+    ring = dropship = NULL;
+}
+
+MUTATOR_HOOKFUNCTION(br, reset_map_players)
+{
+    FOREACH_CLIENT(true, {
+        GameRules_scoring_add(it, BR_RANK, -GameRules_scoring_add(it, BR_RANK, 0));
+        GameRules_scoring_add(it, BR_SQUAD, -GameRules_scoring_add(it, BR_SQUAD, 0));
+        GameRules_scoring_add(it, BR_REVIVALS, -GameRules_scoring_add(it, BR_REVIVALS, 0));
+
+        br_RemovePlayer(it);
+
+        it.br_wepset_old = '0 0 0';
+        for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+        {
+            it.br_weapon_prev[slot] = WEP_Null;
+            it.br_lastweapon_prev[slot] = 0;
+        }
+    });
+
+    br_SquadUpdateInfo();
+}
+
+MUTATOR_HOOKFUNCTION(br, CheckRules_World)
+{
+    M_ARGV(0, float) = br_WinningCondition();
+    return true;
+}
+
+MUTATOR_HOOKFUNCTION(br, ForbidPlayerScore_Clear)
+{
+    // don't clear player score
+    return true;
+}
+
+MUTATOR_HOOKFUNCTION(br, GetPlayerStatus)
+{
+    entity player = M_ARGV(0, entity);
+    return IN_SQUAD(player);
+}
+
+MUTATOR_HOOKFUNCTION(br, ClientConnect)
+{
+    if(ring)
+        ring_timelimit(ring);
+
+    br_SquadUpdateInfo();
+}
+
+MUTATOR_HOOKFUNCTION(br, ClientDisconnect)
+{
+    entity player = M_ARGV(0, entity);
+
+    br_RemovePlayer(player);
+    br_SquadUpdateInfo();
+}
+
+MUTATOR_HOOKFUNCTION(br, PutClientInServer)
+{
+    entity player = M_ARGV(0, entity);
+
+    if (!(round_handler_IsActive() && round_handler_IsRoundStarted()))
+        return false;
+
+    if (IN_SQUAD(player))
+    {
+        Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_JOIN_DEAD);
+        if(IS_REAL_CLIENT(player) && !player.br_squad.br_squad_dead)
+        {
+            SpectateNext(player);
+            TRANSMUTE(Spectator, player);
+        }
+        else
+        {
+            TRANSMUTE(Observer, player);
+        }
+    }
+    else
+    {
+        Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_JOIN_LATE);
+        TRANSMUTE(Observer, player);
+    }
+}
+
+MUTATOR_HOOKFUNCTION(br, MakePlayerObserver)
+{
+    entity player = M_ARGV(0, entity);
+
+    return IN_SQUAD(player);
+}
+
+MUTATOR_HOOKFUNCTION(br, SpectateSet)
+{
+    entity client = M_ARGV(0, entity);
+    entity target = M_ARGV(1, entity);
+
+    return (IN_SQUAD(client) && !client.br_squad.br_squad_dead && DIFF_SQUAD(client, target));
+}
+
+MUTATOR_HOOKFUNCTION(br, SpectateNext)
+{
+    entity client = M_ARGV(0, entity);
+
+    if(!IS_REAL_CLIENT(client) || !IN_SQUAD(client) || client.br_squad.br_squad_dead)
+        return false;
+
+    entity new_target;
+
+    if(client.enemy && client.enemy.br_squad_next)
+        new_target = client.enemy.br_squad_next;
+    else
+        new_target = client.br_squad.br_squad_first;
+
+    while((new_target == client) || IS_DEAD(new_target) || IS_SPEC(new_target) || IS_OBSERVER(new_target))
+    {
+        new_target = new_target.br_squad_next;
+        if(!new_target)
+            new_target = client.br_squad.br_squad_first;
+    }
+    M_ARGV(1, entity) = new_target;
+
+    return true;
+}
+
+MUTATOR_HOOKFUNCTION(br, SpectatePrev)
+{
+    entity client = M_ARGV(0, entity);
+
+    if(!IS_REAL_CLIENT(client) || !IN_SQUAD(client) || client.br_squad.br_squad_dead)
+        return MUT_SPECPREV_CONTINUE;
+
+    entity new_target;
+
+    if(client.enemy && client.enemy.br_squad_prev)
+        new_target = client.enemy.br_squad_prev;
+    else
+        new_target = client.br_squad.br_squad_last;
+
+    while((new_target == client) || IS_DEAD(new_target) || IS_SPEC(new_target) || IS_OBSERVER(new_target))
+    {
+        new_target = new_target.br_squad_prev;
+        if(!new_target)
+            new_target = client.br_squad.br_squad_last;
+    }
+    M_ARGV(1, entity) = new_target;
+
+    return MUT_SPECPREV_FOUND;
+}
+
+MUTATOR_HOOKFUNCTION(br, PlayerSpawn)
+{
+    entity player = M_ARGV(0, entity);
+
+    player.event_damage = br_PlayerDamage;
+}
+
+MUTATOR_HOOKFUNCTION(br, ForbidSpawn)
+{
+    return (round_handler_IsActive() && round_handler_IsRoundStarted());
+}
+
+MUTATOR_HOOKFUNCTION(br, SetStartItems)
+{
+    start_items &= ~(IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS);
+
+    start_health       = warmup_start_health       = cvar("g_br_start_health");
+    start_armorvalue   = warmup_start_armorvalue   = cvar("g_br_start_armor");
+    start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_br_start_ammo_shells");
+    start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_br_start_ammo_nails");
+    start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_br_start_ammo_rockets");
+    start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_br_start_ammo_cells");
+    start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_br_start_ammo_plasma");
+    start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_br_start_ammo_fuel");
+}
+
+// adjusted freezetag reviving code
+#ifdef IN_REVIVING_RANGE
+    #undef IN_REVIVING_RANGE
+#endif
+
+#define IN_REVIVING_RANGE(player, it, revive_extra_size) \
+    (it != player && !IS_DEAD(it) && !IS_SPEC(it) && !IS_OBSERVER(it) && SAME_SQUAD(it, player) \
+    && boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
+
+MUTATOR_HOOKFUNCTION(br, PlayerPreThink, CBC_ORDER_FIRST)
+{
+    entity player = M_ARGV(0, entity);
+
+    if (game_stopped || !frametime || !IS_PLAYER(player))
+        return true;
+
+    if(ring)
+    {
+        if(vlen((player.origin + player.view_ofs) - ring.origin) > ring_calculate_current_radius(ring))
+        {
+            if(!player.br_ring_warned)
+            {
+                player.br_ring_warned = true;
+                Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_BR_RING_WARN);
+            }
+            player.event_damage(player, ring, ring, ring.strength * frametime, DEATH_RING.m_id, DMG_NOWEP, player.origin, '0 0 0'); // ring damage
+        }
+        else
+        {
+            player.br_ring_warned = false;
+        }
+    }
+
+    if(STAT(DROP, player) == DROP_TRANSPORT){
+        if(!(STAT(PRESSED_KEYS, player) & KEY_JUMP) && (dropship_path_length > player.br_squad.br_force_drop_distance)){
+            player.velocity = dropship_path_direction * max(autocvar_g_br_dropship_speed, 0);
+        }
+        else{
+            if(!(IN_SQUAD(player) && player.br_squad.br_squad_drop_leader))
+            {
+                player.effects &= ~EF_NODRAW;
+                set_movetype(player, MOVETYPE_WALK);
+                Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROPSHIP);
+                STAT(DROP, player) = DROP_FALLING;
+                float maxdropspeed = PHYS_MAXAIRSPEED(player) * max(autocvar_g_br_drop_speed_max, 0); // no maxspeed_mod available here
+                float maxdropspeed_xy = maxdropspeed * max(autocvar_g_br_drop_speed_horizontal_max, 0);
+                player.velocity.x = cos(player.angles.y * DEG2RAD);
+                player.velocity.y = sin(player.angles.y * DEG2RAD);
+                player.velocity.z = 0;
+
+                player.velocity = player.velocity * maxdropspeed_xy * 0.75;
+                player.velocity.z = (maxdropspeed - vlen(player.velocity)) * -max(autocvar_g_br_drop_speed_vertical, 0);
+
+                br_SetPlayerDropAngle(player);
+
+                // the .br_drop values aren't modified by physics and will be used by other squad members
+                player.br_drop_velocity = player.velocity;
+                player.br_drop_angles = player.angles;
+
+                if(IN_SQUAD(player))
+                {
+                    player.br_squad.br_squad_drop_leader = player;
+
+                    vector drop_offset;
+                    drop_offset.x = cos((player.angles.y + 90) * DEG2RAD);
+                    drop_offset.y = sin((player.angles.y + 90) * DEG2RAD);
+                    drop_offset.z = 0;
+                    drop_offset = drop_offset * vlen(vec2(player.maxs - player.mins)) + drop_offset * 32; // I hope individual players never get different mins/maxs
+
+                    FOREACH_CLIENT(IS_PLAYER(it) && (it != player) && SAME_SQUAD(it, player) && (STAT(DROP, it) == DROP_TRANSPORT), {
+                        it.effects &= ~EF_NODRAW;
+                        set_movetype(it, MOVETYPE_WALK);
+                        Kill_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CPID_BR_DROPSHIP);
+                        STAT(DROP, it) = DROP_FALLING;
+
+                        setorigin(it, player.origin + drop_offset); // FIXME: this can teleport players into brushes/void
+                        drop_offset *= 2;
+
+                        it.velocity = it.br_drop_velocity = player.velocity;
+                        it.angles = it.br_drop_angles = player.angles;
+                    });
+                }
+            }
+        }
+    }
+
+    // adjusted freezetag reviving code
+    entity revivers_last = NULL;
+    entity revivers_first = NULL;
+
+    bool player_is_reviving = false;
+    bool player_is_being_revived = false;
+    vector revive_extra_size = '1 1 1' * max(autocvar_g_br_revive_extra_size, 0);
+    FOREACH_CLIENT(IS_PLAYER(it), {
+        // check if player is reviving anyone
+        if (STAT(BLEEDING, it))
+        {
+            if (STAT(BLEEDING, player))
+                continue;
+            if (!IN_REVIVING_RANGE(player, it, revive_extra_size))
+                continue;
+            player_is_reviving = true;
+            break;
+        }
+
+        if (!STAT(BLEEDING, player))
+            continue; // both player and it are NOT bleeding
+        if (!IN_REVIVING_RANGE(player, it, revive_extra_size))
+            continue;
+
+        // found a squadmate that is reviving player
+        if (revivers_last)
+            revivers_last.chain = it;
+        revivers_last = it;
+        if (!revivers_first)
+            revivers_first = it;
+        player_is_being_revived = true;
+    });
+    if (revivers_last)
+        revivers_last.chain = NULL;
+
+    if (!player_is_being_revived) // no squadmate nearby
+    {
+        float clearspeed = max(autocvar_g_br_revive_clearspeed, 0);
+        if (STAT(BLEEDING, player))
+            STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * clearspeed, 1);
+        else if (!player_is_reviving)
+            STAT(REVIVE_PROGRESS, player) = 0; // reviving nobody
+    }
+    else // OK, there is at least one squadmate reviving us
+    {
+        float spd = max(autocvar_g_br_revive_speed, 0);
+        STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * spd, 1);
+
+        if(STAT(REVIVE_PROGRESS, player) >= 1)
+        {
+            br_Revive(player);
+
+            // EVERY squad mate nearby gets a point (even if multiple!)
+            for(entity it = revivers_first; it; it = it.chain)
+            {
+                GameRules_scoring_add(it, BR_REVIVALS, +1);
+            }
+
+            Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_BR_REVIVED, revivers_first.netname);
+            Send_Notification(NOTIF_ONE, revivers_first, MSG_CENTER, CENTER_BR_REVIVE, player.netname);
+            Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_BR_REVIVED, player.netname, revivers_first.netname);
+            if(autocvar_sv_eventlog)
+            {
+                string revivers = "";
+                for(entity it = revivers_first; it; it = it.chain)
+                    revivers = strcat(revivers, ftos(it.playerid), ",");
+                revivers = substring(revivers, 0, strlen(revivers) - 1);
+                GameLogEcho(strcat(":br:revival:", ftos(player.playerid), ":", revivers));
+            }
+        }
+
+        for(entity it = revivers_first; it; it = it.chain)
+            STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
+    }
+
+    if (STAT(BLEEDING, player))
+    {
+        entity player_wp = player.waypointsprite_attached;
+        if (player_is_being_revived)
+        {
+            WaypointSprite_UpdateSprites(player_wp, WP_BRReviving, WP_Null, WP_Null);
+            WaypointSprite_UpdateTeamRadar(player_wp, RADARICON_WAYPOINT, WP_BR_REVIVING_COLOR);
+        }
+        else
+        {
+            WaypointSprite_UpdateSprites(player_wp, WP_BRBleeding, WP_Null, WP_Null);
+            WaypointSprite_UpdateTeamRadar(player_wp, RADARICON_WAYPOINT, WP_BR_BLEEDING_COLOR);
+        }
+
+        WaypointSprite_UpdateMaxHealth(player_wp, 1);
+        WaypointSprite_UpdateHealth(player_wp, STAT(REVIVE_PROGRESS, player));
+    }
+
+    return true;
+}
+
+#undef IN_REVIVING_RANGE
+
+MUTATOR_HOOKFUNCTION(br, PM_Physics)
+{
+    entity player = M_ARGV(0, entity);
+    float maxspeed_mod = M_ARGV(1, float);
+    float dt = M_ARGV(2, float); // tick rate
+
+    if(STAT(DROP, player) == DROP_TRANSPORT)
+        return true;
+
+    // TODO: improve dropping physics
+    if(STAT(DROP, player) == DROP_FALLING){
+        if(!IS_ONGROUND(player) && ((tracebox(player.origin, player.mins, player.maxs, player.origin - '0 0 1', MOVE_NOMONSTERS, player), trace_fraction) >= 1)) // IS_ONGROUND doesn't work if jump is held (jump is theoretically blocked until landed)
+        {
+            ITEMS_STAT(player) |= IT_USING_JETPACK;
+            bool has_drop_leader = IN_SQUAD(player) && (player.br_squad.br_squad_drop_leader && (STAT(DROP, player.br_squad.br_squad_drop_leader) == DROP_FALLING));
+            bool player_is_drop_leader = has_drop_leader && (player == player.br_squad.br_squad_drop_leader);
+            if(player_is_drop_leader || !has_drop_leader)
+            {
+                float maxdropspeed = PHYS_MAXAIRSPEED(player) * max(maxspeed_mod, 1) * max(autocvar_g_br_drop_speed_max, 0);
+                float maxdropspeed_xy = maxdropspeed * max(autocvar_g_br_drop_speed_horizontal_max, 0);
+                float mindropspeed_xy = maxdropspeed * max(autocvar_g_br_drop_speed_horizontal_min, 0);
+
+                makevectors(player.v_angle);
+                vector wishvel = v_forward * CS(player).movement.x + v_right * CS(player).movement.y;
+                wishvel = normalize(wishvel) * min(1, vlen(wishvel) / maxdropspeed);
+                wishvel.x *= max(autocvar_g_br_drop_acceleration, 0);
+                wishvel.y *= max(autocvar_g_br_drop_acceleration, 0);
+                player.velocity = player.velocity + wishvel * dt;
+                player.velocity.z = 0;
+
+                if(vlen(player.velocity) == 0)
+                {
+                    player.velocity.x = cos(player.angles.y * DEG2RAD);
+                    player.velocity.y = sin(player.angles.y * DEG2RAD);
+                    player.velocity = player.velocity * mindropspeed_xy;
+                }
+                if(vlen(player.velocity) < mindropspeed_xy)
+                    player.velocity = normalize(player.velocity) * mindropspeed_xy;
+                if(vlen(player.velocity) > maxdropspeed_xy)
+                    player.velocity = normalize(player.velocity) * maxdropspeed_xy;
+
+                player.velocity.z = (maxdropspeed - vlen(player.velocity)) * -max(autocvar_g_br_drop_speed_vertical, 0);
+
+                br_SetPlayerDropAngle(player);
+
+                // the .br_drop values aren't modified by physics and will be used by other squad members
+                player.br_drop_velocity = player.velocity;
+                player.br_drop_angles = player.angles;
+
+                if(player_is_drop_leader)
+                {
+                    FOREACH_CLIENT(IS_PLAYER(it) && (it != player) && SAME_SQUAD(it, player) && (STAT(DROP, it) == DROP_FALLING), {
+                        it.velocity = it.br_drop_velocity = player.br_drop_velocity;
+                        it.angles = it.br_drop_angles = player.br_drop_angles;
+                    });
+                }
+            }
+            else
+            {
+                player.velocity = player.br_drop_velocity;
+                player.angles = player.br_drop_angles; // no fixangles, only moves the player model not the player view
+            }
+
+            return true;
+        }
+        else
+        {
+            STAT(DROP, player) = DROP_LANDED;
+            ITEMS_STAT(player) &= ~IT_USING_JETPACK;
+            player.flags |= FL_PICKUPITEMS;
+            player.dphitcontentsmask |= DPCONTENTS_BODY;
+
+            if(autocvar_g_br_startweapons)
+            {
+                SetResource(player, RES_SHELLS, start_ammo_shells);
+                SetResource(player, RES_BULLETS, start_ammo_nails);
+                SetResource(player, RES_ROCKETS, start_ammo_rockets);
+                SetResource(player, RES_CELLS, start_ammo_cells);
+                SetResource(player, RES_PLASMA, start_ammo_plasma);
+                SetResource(player, RES_FUEL, start_ammo_fuel);
+                STAT(WEAPONS, player) = player.br_wepset_old;
+
+                .entity weaponentity = weaponentities[0];
+                W_SwitchWeapon_Force(player, w_getbestweapon(player, weaponentity), weaponentity);
+            }
+        }
+    }
+}
+
+MUTATOR_HOOKFUNCTION(br, Damage_Calculate)
+{
+    entity target = M_ARGV(2, entity);
+
+    if(STAT(DROP, target) == DROP_FALLING)
+    {
+        // only take half of the usual damage
+        M_ARGV(4, float) /= 2;
+        M_ARGV(5, float) /= 2;
+        // weapon impact has no push force while dropping
+        M_ARGV(6, vector) = '0 0 0';
+    }
+}
+
+MUTATOR_HOOKFUNCTION(br, PlayerDies)
+{
+    entity frag_attacker = M_ARGV(1, entity);
+    entity frag_target = M_ARGV(2, entity);
+    float frag_deathtype = M_ARGV(3, float); // float for some reason, breaks if changed to int
+
+    if(!IS_PLAYER(frag_target))
+        return false;
+
+    if(STAT(DROP, frag_target) == DROP_TRANSPORT)
+    {
+        frag_target.effects &= ~EF_NODRAW;
+        set_movetype(frag_target, MOVETYPE_WALK);
+        Kill_Notification(NOTIF_ONE_ONLY, frag_target, MSG_CENTER, CPID_BR_DROPSHIP);
+    }
+
+    if(STAT(DROP, frag_target) != DROP_LANDED)
+    {
+        frag_target.dphitcontentsmask |= DPCONTENTS_BODY;
+        STAT(DROP, frag_target) = DROP_LANDED;
+    }
+
+    if(STAT(BLEEDING, frag_target) || BR_KILLS_INSTANTLY(frag_target, frag_deathtype))
+    {
+        if(STAT(BLEEDING, frag_target))
+        {
+            Kill_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CPID_BR_DOWN);
+            STAT(BLEEDING, frag_target) = false;
+        }
+        frag_target.respawn_flags = RESPAWN_SILENT | RESPAWN_FORCE;
+        frag_target.respawn_time = time + 2;
+        return true;
+    }
+
+    frag_target.flags &= ~FL_PICKUPITEMS;
+    RemoveGrapplingHooks(frag_target);
+    StatusEffects_removeall(frag_target, STATUSEFFECT_REMOVE_NORMAL);
+
+    SetResource(frag_target, RES_HEALTH, start_health * max(autocvar_g_br_bleeding_health, 0));
+    SetResource(frag_target, RES_ARMOR, 0);
+    Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_BR_DOWN_WAIT);
+    STAT(BLEEDING, frag_target) = true;
+
+    FOREACH_CLIENT(IS_PLAYER(it),
+    {
+        for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+        {
+            .entity weaponentity = weaponentities[slot];
+            if(it.(weaponentity).hook.aiment == frag_target)
+                RemoveHook(it.(weaponentity).hook);
+        }
+    });
+
+    frag_target.br_wepset_old = STAT(WEAPONS, frag_target);
+    for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+    {
+        .entity weaponentity = weaponentities[slot];
+        frag_target.br_weapon_prev[slot] = frag_target.(weaponentity).m_switchweapon;
+        frag_target.br_lastweapon_prev[slot] = frag_target.(weaponentity).cnt;
+    }
+    STAT(WEAPONS, frag_target) = '0 0 0';
+
+    WaypointSprite_Spawn(WP_BRBleeding, 0, 0, frag_target, '0 0 64', NULL, 0, frag_target, waypointsprite_attached, true, RADARICON_WAYPOINT).br_squad = frag_target.br_squad;
+
+    if(frag_attacker == frag_target || !frag_attacker || ITEM_DAMAGE_NEEDKILL(frag_deathtype))
+    {
+        if(IS_PLAYER(frag_target))
+            Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_BR_DOWN_SELF);
+        Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_BR_DOWN_SELF, frag_target.netname);
+    }
+    else
+    {
+        if(IS_PLAYER(frag_target))
+            Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_BR_DOWN, frag_attacker.netname);
+        Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_BR_DOWN, frag_target.netname, frag_attacker.netname);
+    }
+
+    br_SquadUpdateInfo();
+
+    return true;
+}
+
+MUTATOR_HOOKFUNCTION(br, PlayerDied)
+{
+    entity player = M_ARGV(0, entity);
+    if(round_handler_IsActive() && round_handler_IsRoundStarted())
+        br_LastPlayerForSquad_Notify(player.br_squad);
+    br_SquadUpdateInfo();
+}
+
+MUTATOR_HOOKFUNCTION(br, ClientObituary)
+{
+    entity frag_target = M_ARGV(2, entity);
+    float frag_deathtype = M_ARGV(3, float); // float for some reason, breaks if changed to int
+
+    if(BR_KILLS_INSTANTLY(frag_target, frag_deathtype))
+        return false;
+    else
+        return !STAT(BLEEDING, frag_target);
+}
+
+MUTATOR_HOOKFUNCTION(br, SetResource)
+{
+    entity player = M_ARGV(0, entity);
+    if(!IS_PLAYER(player))
+        return false;
+
+    int res_type = M_ARGV(1, int);
+    float amount = M_ARGV(2, float);
+
+    if(STAT(BLEEDING, player) && (res_type == RES_HEALTH || res_type == RES_ARMOR))
+    {
+        if(amount > GetResource(player, res_type)) // prevent the player from getting health or armor in any way
+            return true;
+    }
+}
+
+MUTATOR_HOOKFUNCTION(br, GetResourceLimit)
+{
+    entity player = M_ARGV(0, entity);
+
+    if(!IS_PLAYER(player) || !STAT(BLEEDING, player))
+        return false;
+
+    int res_type = M_ARGV(1, int);
+
+    switch(res_type)
+    {
+        case RES_HEALTH:
+            M_ARGV(2, float) *= max(autocvar_g_br_bleeding_health, 0);
+            break;
+        case RES_ARMOR:
+            M_ARGV(2, float) = 0;
+    }
+}
+
+MUTATOR_HOOKFUNCTION(br, PlayerRegen)
+{
+    entity player = M_ARGV(0, entity);
+
+    if(STAT(BLEEDING, player)){
+        M_ARGV(7, float) = max(autocvar_g_br_bleed, 0);
+        M_ARGV(8, float) = max(autocvar_g_br_bleedlinear, 0);
+        M_ARGV(2, float) = M_ARGV(10, float) = 0;
+        return false;
+    }
+    return true;
+}
+
+MUTATOR_HOOKFUNCTION(br, PlayerCanCrouch)
+{
+    entity player = M_ARGV(0, entity);
+    if(STAT(BLEEDING, player))
+        M_ARGV(1, bool) = true;
+    else if(STAT(DROP, player) != DROP_LANDED)
+        M_ARGV(1, bool) = false;
+}
+
+MUTATOR_HOOKFUNCTION(br, PlayerJump)
+{
+    entity player = M_ARGV(0, entity);
+    return STAT(BLEEDING, player) || (STAT(DROP, player) != DROP_LANDED);
+}
+
+MUTATOR_HOOKFUNCTION(br, BotShouldAttack)
+{
+    entity bot = M_ARGV(0, entity);
+    entity target = M_ARGV(1, entity);
+
+    return SAME_SQUAD(bot, target) || (STAT(DROP, target) == DROP_TRANSPORT);
+}
+
+MUTATOR_HOOKFUNCTION(br, AccuracyTargetValid)
+{
+    entity attacker = M_ARGV(0, entity);
+    entity target = M_ARGV(1, entity);
+
+    if(SAME_SQUAD(attacker, target) || (STAT(DROP, target) == DROP_TRANSPORT))
+        return MUT_ACCADD_INDIFFERENT;
+    return MUT_ACCADD_VALID;
+}
+
+MUTATOR_HOOKFUNCTION(br, CustomizeWaypoint)
+{
+    entity wp = M_ARGV(0, entity);
+    entity player = M_ARGV(1, entity);
+
+    if(!IS_PLAYER(player) || DIFF_SQUAD(wp, player))
+        return true;
+}
+
+MUTATOR_HOOKFUNCTION(br, ClientKill)
+{
+    entity player = M_ARGV(0, entity);
+
+    if(round_handler_IsActive() && round_handler_IsRoundStarted())
+    {
+        // no forfeiting once the game started
+        Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_FORFEIT);
+        return true;
+    }
+
+    return false;
+}
+
+MUTATOR_HOOKFUNCTION(br, ClientCommand_Spectate)
+{
+    entity player = M_ARGV(0, entity);
+
+    if(round_handler_IsActive() && round_handler_IsRoundStarted())
+    {
+        // no forfeiting once the game started
+        Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_FORFEIT);
+        return MUT_SPECCMD_RETURN;
+    }
+    return MUT_SPECCMD_CONTINUE;
+}
+
+void br_SetPlayerDropAngle(entity this)
+{
+    this.angles.y = vectoangles(vec2(this.velocity)).y;
+
+    this.angles.x = -90;
+    if(this.velocity.z < 0)
+    {
+        float dropspeed_xy = vlen(vec2(this.velocity));
+        float dropspeed_z = fabs(this.velocity.z);
+        //this.angles.x -= dropspeed_z / (dropspeed_z + dropspeed_xy) * 90; // anything beyond 90 degrees flips the model on view change for some reason
+        this.angles.x += dropspeed_z / (dropspeed_z + dropspeed_xy) * 90; // but this looks nice too, I guess
+    }
+}
+
+void br_LastPlayerForSquad_Notify(entity squad)
+{
+    entity player = br_SquadFindLastAlive(squad, false);
+    if(player)
+        Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ALONE);
+}
+
+void br_RemovePlayer(entity player)
+{
+    br_SquadMember_Remove(player);
+
+    FOREACH_CLIENT((it.br_bleeding_attacker == player) || (it.br_bleeding_inflictor == player), {
+        it.br_bleeding_attacker = it.br_bleeding_inflictor = NULL;
+    });
+}
+
+void br_Revive(entity player)
+{
+    if(STAT(BLEEDING, player))
+    {
+        Kill_Notification(NOTIF_ONE, player, MSG_CENTER, CPID_BR_DOWN);
+        STAT(BLEEDING, player) = false;
+    }
+    player.flags |= FL_PICKUPITEMS;
+    SetResource(player, RES_HEALTH, start_health * max(autocvar_g_br_revive_health, 0));
+
+    STAT(WEAPONS, player) = player.br_wepset_old;
+    for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+    {
+        .entity weaponentity = weaponentities[slot];
+        W_SwitchWeapon_Force(player, player.br_weapon_prev[slot], weaponentity);
+        player.(weaponentity).cnt = player.br_lastweapon_prev[slot];
+    }
+
+    player.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
+
+    STAT(REVIVE_PROGRESS, player) = 0;
+    player.revival_time = time;
+
+    WaypointSprite_Kill(player.waypointsprite_attached);
+
+    br_SquadUpdateInfo();
+}
+
+void br_PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
+{
+    if(STAT(DROP, this) == DROP_TRANSPORT)
+        return; // can't take damage while on the dropship
+
+    // do not take fall damage when landing from dropship
+    if(STAT(DROP, this) == DROP_FALLING)
+    {
+        switch(deathtype)
+        {
+            case DEATH_FALL.m_id:
+            case DEATH_SHOOTING_STAR.m_id:
+                return;
+        }
+    }
+
+    if(STAT(BLEEDING, this) && this.br_bleeding_attacker)
+    {
+        inflictor = this.br_bleeding_inflictor;
+        attacker = this.br_bleeding_attacker;
+        deathtype = this.br_bleeding_deathtype;
+        weaponentity = this.br_bleeding_weaponentity;
+    }
+
+    PlayerDamage(this, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
+
+    if(STAT(BLEEDING, this))
+    {
+        this.br_bleeding_inflictor = inflictor;
+        this.br_bleeding_attacker = attacker;
+        this.br_bleeding_deathtype = deathtype;
+        this.br_bleeding_weaponentity = weaponentity;
+
+        this.fixangle = false;
+    }
+}
+
+int br_WinningCondition()
+{
+    int total_squads = br_SquadUpdateInfo();
+
+    if ((total_squads > 1) || !(round_handler_IsActive() && round_handler_IsRoundStarted()))
+        return WINNING_NEVER;
+
+    entity winner_squad = NULL;
+    IL_EACH(squads, !it.br_squad_dead, winner_squad = it);
+
+    for(entity member = winner_squad.br_squad_first; member; member = member.br_squad_next)
+    {
+        GameRules_scoring_add(member, BR_RANK, 1);
+    }
+
+    return WINNING_YES;
+}
+
+bool br_isEliminated(entity e)
+{
+    return (IN_SQUAD(e) && (IS_DEAD(e) || IS_SPEC(e) || IS_OBSERVER(e)));
+}
+
+bool br_CheckWinner()
+{
+    return false;
+}
+
+bool br_CheckPlayers()
+{
+    total_players = 0;
+    FOREACH_CLIENT(IS_PLAYER(it), ++total_players);
+
+    static int prev_players = 0;
+    if (total_players >= autocvar_g_br_minplayers || total_players == 0)
+    {
+        if(prev_players > 0)
+            Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
+        prev_players = 0;
+        return (total_players >= autocvar_g_br_minplayers);
+    }
+
+    if(prev_players != total_players)
+    {
+        Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, autocvar_g_br_minplayers - total_players);
+        prev_players = total_players;
+    }
+
+    return false;
+}
+
+void br_RoundStart(){
+    ring = ring_initialize();
+
+    dropship = dropship_initialize();
+
+    if(!dropship)
+    {
+        delete(ring);
+        ring = NULL;
+
+        FOREACH_CLIENT(IS_PLAYER(it), {
+            TRANSMUTE(Observer, it);
+            PutClientInServer(it);
+        });
+        LOG_SEVERE("Failed to determine dropship route, aborting...");
+    }
+
+    int num_players = 0;
+
+    FOREACH_CLIENT(IS_PLAYER(it), {
+        GameRules_scoring_add(it, BR_RANK, -GameRules_scoring_add(it, BR_RANK, 0));
+        GameRules_scoring_add(it, BR_SQUAD, -GameRules_scoring_add(it, BR_SQUAD, 0));
+        GameRules_scoring_add(it, BR_REVIVALS, -GameRules_scoring_add(it, BR_REVIVALS, 0));
+
+        RemoveGrapplingHooks(it);
+        StatusEffects_removeall(it, STATUSEFFECT_REMOVE_CLEAR);
+
+        // isn't there another way to initialize these?
+        SetResource(it, RES_HEALTH, start_health);
+        SetResource(it, RES_ARMOR, start_armorvalue);
+        SetResource(it, RES_SHELLS, 0);
+        SetResource(it, RES_BULLETS, 0);
+        SetResource(it, RES_ROCKETS, 0);
+        SetResource(it, RES_CELLS, 0);
+        SetResource(it, RES_PLASMA, 0);
+        SetResource(it, RES_FUEL, 0);
+        STAT(WEAPONS, it) = '0 0 0';
+        it.br_wepset_old = start_weapons;
+        for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+        {
+            it.br_weapon_prev[slot] = WEP_Null;
+            it.br_lastweapon_prev[slot] = 0;
+        }
+
+        ++num_players;
+    });
+
+    max_squad_size = max(autocvar_g_br_squad_size, 1);
+    if(num_players <= max_squad_size)
+        max_squad_size = ceil(num_players / 2);
+
+    for(int num_squads = 0; (num_squads * max_squad_size) < num_players; ++num_squads)
+    {
+        entity new_squad = spawn();
+        new_squad.br_squad_drop_leader = NULL;
+        new_squad.br_squad_id = num_squads + 1;
+
+        IL_PUSH(squads, new_squad);
+    }
+
+    FOREACH_CLIENT(IS_PLAYER(it), {
+        entity current_squad = br_SquadGetRandomAvail();
+        br_SquadMember_Add(current_squad, it);
+        GameRules_scoring_add(it, BR_SQUAD, current_squad.br_squad_id);
+
+        setorigin(it, dropship.origin + eZ * (dropship.mins.z - it.maxs.z - 64)); // FIXME: this can teleport players into brushes/void
+        it.angles = vectoangles(dropship_path_direction) + '45 0 0';
+        it.fixangle = true;
+        it.velocity = '0 0 0';
+        set_movetype(it, MOVETYPE_FLY_WORLDONLY);
+        it.flags &= ~FL_PICKUPITEMS;
+        it.dphitcontentsmask &= ~DPCONTENTS_BODY;
+        it.effects |= EF_NODRAW;
+        Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_BR_DROPSHIP);
+        STAT(DROP, it) = DROP_TRANSPORT;
+        UNSET_ONGROUND(it); // otherwise this isn't unset if the player drops in the same frame
+    });
+
+    squads_colored = autocvar_g_br_squad_colors;
+    IL_EACH(squads, true,
+    {
+        if(squads_colored)
+        {
+            float squad_color;
+            squad_color = floor(random() * 256);
+
+            for(entity member = it.br_squad_first; member; member = member.br_squad_next)
+            {
+                setcolor(member, squad_color);
+            }
+        }
+
+        float min_distance = max(autocvar_g_br_drop_distance_force, 0);
+        if(!br_SquadIsBotsOnly(it))
+            it.br_force_drop_distance = min_distance;
+        else
+            it.br_force_drop_distance = min_distance + random() * max(dropship_path_length - min_distance, 0);
+    });
+}
+
+void br_Initialize()
+{
+    // TODO: remove round handler
+    round_handler_Spawn(br_CheckPlayers, br_CheckWinner, br_RoundStart);
+    round_handler_Init(5, 0, 0); // no warmup or timelimit in battle royale
+
+    EliminatedPlayers_Init(br_isEliminated);
+}
diff --git a/qcsrc/common/gamemodes/gamemode/br/sv_br.qh b/qcsrc/common/gamemodes/gamemode/br/sv_br.qh
new file mode 100644 (file)
index 0000000..010bf27
--- /dev/null
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+
+bool squads_colored = false;
+
+void br_Initialize();
+
+REGISTER_MUTATOR(br, false)
+{
+    MUTATOR_STATIC();
+    MUTATOR_ONADD
+    {
+        GameRules_teams(false);
+        GameRules_limit_score(0);
+        GameRules_limit_lead(0);
+        GameRules_score_enabled(false);
+        GameRules_scoring(0, 0, 0, {
+            field(SP_BR_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY);
+            field(SP_BR_SQUAD, "squad", 0);
+            field(SP_BR_REVIVALS, "revivals", 0);
+        });
+
+        br_Initialize();
+    }
+    return 0;
+}
+
+float autocvar_g_br_revive_extra_size = 100;
+float autocvar_g_br_revive_speed = 0.4;
+float autocvar_g_br_revive_clearspeed = 1.6;
+float autocvar_g_br_bleed = 0.02;
+float autocvar_g_br_bleedlinear = 1;
+int autocvar_g_br_squad_size = 3;
+int autocvar_g_br_minplayers = 2;
diff --git a/qcsrc/common/gamemodes/gamemode/br/sv_dropship.qc b/qcsrc/common/gamemodes/gamemode/br/sv_dropship.qc
new file mode 100644 (file)
index 0000000..60960e5
--- /dev/null
@@ -0,0 +1,162 @@
+#include "sv_dropship.qh"
+
+float autocvar_g_br_dropship_scale = 3;
+vector autocvar_g_br_dropship_color = '0.5 0 0.5';
+
+entity dropship_spawn(Vehicle info, float entity_scale, vector color);
+void dropship_think(entity this);
+vector dropship_getMultipliers();
+vector dropship_seekPoint(entity this, vector orig, int axis, int direction, float multiplier);
+
+entity dropship_initialize()
+{
+    entity this = dropship_spawn(VEH_RACER, autocvar_g_br_dropship_scale, autocvar_g_br_dropship_color);
+
+    bool moveSucceeded = MoveToRandomLocationWithinBounds(this, world.mins, world.maxs, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 2500, 8192, 1024);
+    if(!moveSucceeded)
+    {
+        delete(this);
+        return NULL;
+    }
+
+    vector mult;
+
+    vector startorigin;
+    startorigin = dropship_seekPoint(this, this.origin, 2, 2, 1);
+    startorigin = dropship_seekPoint(this, startorigin, 0, 0, 1);
+    startorigin = dropship_seekPoint(this, startorigin, 1, 0, 1);
+    mult = dropship_getMultipliers();
+    startorigin = dropship_seekPoint(this, startorigin, 0, 1, mult.x);
+    startorigin = dropship_seekPoint(this, startorigin, 1, 1, mult.y);
+
+    vector endorigin;
+    mult = dropship_getMultipliers();
+    endorigin = dropship_seekPoint(this, startorigin, 0, 1, 1 - mult.x);
+    endorigin = dropship_seekPoint(this, endorigin,   1, 1, 1 - mult.y);
+
+    endorigin = startorigin + normalize(endorigin - startorigin) * vlen(world.maxs - world.mins);
+
+    tracebox(startorigin, this.mins, this.maxs, endorigin, MOVE_NORMAL, this);
+    dropship_path_length = trace_fraction * vlen(endorigin - startorigin);
+    endorigin = trace_endpos;
+    dropship_path_direction = normalize(endorigin - startorigin);
+
+    setorigin(this, startorigin);
+    this.angles = vectoangles(dropship_path_direction);
+    this.velocity = '0 0 0';
+
+    return this;
+}
+
+entity dropship_spawn(Vehicle info, float entity_scale, vector color)
+{
+    entity this = new(vehicle);
+    this.active = ACTIVE_ACTIVE;
+
+    _setmodel(this, info.model);
+
+    this.vehicle_flags |= VHF_ISVEHICLE;
+
+    this.takedamage             = DAMAGE_NO;
+    this.bot_attack             = false;
+    this.iscreature             = true;
+    this.teleportable           = false;
+    this.damagedbycontents      = false;
+    this.vehicleid              = info.vehicleid;
+    this.vehicledef             = info;
+    this.dphitcontentsmask      = DPCONTENTS_SOLID;
+    this.flags                  = FL_NOTARGET;
+    this.nextthink              = time;
+    setthink(this, dropship_think);
+
+    this.scale = entity_scale;
+    setsize(this, info.m_mins * entity_scale, info.m_maxs * entity_scale);
+    set_movetype(this, MOVETYPE_FLY_WORLDONLY);
+
+    this.colormod = color;
+    this.alpha = 1;
+
+    CSQCMODEL_AUTOINIT(this);
+
+    return this;
+}
+
+void dropship_think(entity this)
+{
+    this.nextthink = time;
+
+    if(dropship_path_length > 0){
+        this.alpha = bound(0.01, dropship_path_length / autocvar_g_br_drop_distance_force, 1);
+        this.velocity = dropship_path_direction * autocvar_g_br_dropship_speed;
+        dropship_path_length -= autocvar_g_br_dropship_speed * frametime;
+    }
+    else{
+        delete(this);
+    }
+
+    CSQCMODEL_AUTOUPDATE(this);
+}
+
+vector dropship_getMultipliers()
+{
+    vector mult;
+    mult.x = (1 - cos(random() * 90 * DEG2RAD)) * 0.5;
+    mult.y = min((1 - cos(random() * 90 * DEG2RAD)) * 0.5, 0.5 - mult.x);
+    mult.z = 0;
+    bool multswap = (random() >= 0.5);
+    if(multswap){
+        float tmp;
+        tmp = mult.x;
+        mult.x = mult.y;
+        mult.y = tmp;
+    }
+
+    return mult;
+}
+
+vector dropship_seekPoint(entity this, vector orig, int axis, int direction, float multiplier)
+{
+    vector vec_axis;
+    switch(axis)
+    {
+        default: case 0:
+            vec_axis = eX;
+            break;
+        case 1:
+            vec_axis = eY;
+            break;
+        case 2:
+            vec_axis = eZ;
+    }
+
+    float first_fraction;
+    float second_fraction = 0;
+    vector first_end;
+    vector second_end = '0 0 0';
+
+    first_end = orig;
+    first_end = first_end - first_end * vec_axis * vec_axis + world.maxs * vec_axis * vec_axis;
+    first_fraction = (tracebox(orig, this.mins, this.maxs, first_end, MOVE_NORMAL, this), trace_fraction);
+
+    if(direction != 2)
+    {
+        second_end = orig;
+        second_end = second_end - second_end * vec_axis * vec_axis + world.mins * vec_axis * vec_axis;
+        second_fraction = (tracebox(orig, this.mins, this.maxs, second_end, MOVE_NORMAL, this), trace_fraction);
+    }
+
+    float dist_to_edge;
+    if(((direction == 0) && (first_fraction < second_fraction)) ||
+       ((direction == 1) && (first_fraction > second_fraction)) ||
+        (direction == 2))
+    {
+        dist_to_edge = (first_end * vec_axis - orig * vec_axis) * first_fraction * multiplier;
+    }
+    else
+    {
+        dist_to_edge = (second_end * vec_axis - orig * vec_axis) * second_fraction * multiplier;
+    }
+    orig = orig + dist_to_edge * vec_axis;
+
+    return orig;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/br/sv_dropship.qh b/qcsrc/common/gamemodes/gamemode/br/sv_dropship.qh
new file mode 100644 (file)
index 0000000..4aab1ab
--- /dev/null
@@ -0,0 +1,9 @@
+#pragma once
+
+float dropship_path_length = 0;
+vector dropship_path_direction = '0 0 0';
+
+float autocvar_g_br_drop_distance_force = 500;
+float autocvar_g_br_dropship_speed = 200;
+
+entity dropship_initialize();
diff --git a/qcsrc/common/gamemodes/gamemode/br/sv_ring.qc b/qcsrc/common/gamemodes/gamemode/br/sv_ring.qc
new file mode 100644 (file)
index 0000000..69c6c73
--- /dev/null
@@ -0,0 +1,219 @@
+#include "sv_ring.qh"
+
+.float br_ring_stage;
+.float br_ring_stage_strength[BR_RING_STAGE_MAX];
+.float br_ring_timelimit;
+
+void ring_link(entity this);
+bool ring_send(entity this, entity to, float sf);
+void ring_think(entity this);
+void ring_newStage(entity this);
+bool ring_parseTiming(entity this);
+void ring_parseStrength(entity this, bool has_invalid);
+void ring_alignPosition(entity this);
+
+float autocvar_g_br_ring_duration = 150;
+vector autocvar_g_br_ring_color = '1 0 0';
+float autocvar_g_br_ring_alpha = 0.5;
+float autocvar_g_br_ring_radius = -1; // useful for per map settings
+string autocvar_g_br_ring_timing = "0.6 0.8 0.9";
+string autocvar_g_br_ring_strength = "2.5 5 10 20 50";
+float autocvar_g_br_ring_wait = 30;
+float autocvar_g_br_ring_center_factor = 0.25;
+
+entity ring_initialize()
+{
+    entity this = spawn();
+    this.netname = BR_RING_NAME;
+
+    setsize(this, '0 0 0', '0 0 0');
+    set_movetype(this, MOVETYPE_NOCLIP);
+
+    this.classname = "ring";
+    this.br_ring_start = time;
+    this.br_ring_duration = max(autocvar_g_br_ring_duration, 1);
+    this.radius = (autocvar_g_br_ring_radius <= 0) ? vlen(vec2(world.maxs - world.mins)) / 2 : autocvar_g_br_ring_radius;
+    this.br_ring_stage = -1;
+    this.colormod = autocvar_g_br_ring_color; // TODO: color changing ring
+    this.alpha = this.br_ring_alpha = bound(0.01, autocvar_g_br_ring_alpha, 1);
+    this.br_ring_stage_waittime = max(autocvar_g_br_ring_wait, 0);
+
+    bool has_invalid_timings = ring_parseTiming(this);
+    ring_parseStrength(this, has_invalid_timings);
+
+    this.strength = this.br_ring_stage_strength[0];
+    MoveToRandomLocationWithinBounds(this, world.mins, world.maxs, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 2500, 0, 0);
+    ring_alignPosition(this);
+    ring_link(this);
+
+    return this;
+}
+
+void ring_link(entity this)
+{
+    Net_LinkEntity(this, false, 0, ring_send);
+    this.nextthink = this.br_ring_start + this.br_ring_stage_waittime;
+    this.br_ring_timelimit = this.nextthink - game_starttime;
+    ring_timelimit(this);
+    setthink(this, ring_think);
+}
+
+bool ring_send(entity this, entity to, float sf)
+{
+    WriteHeader(MSG_ENTITY, ENT_CLIENT_RING);
+    WriteByte(MSG_ENTITY, sf);
+    if(sf & BR_RING_SETUP)
+    {
+        WriteVector(MSG_ENTITY, this.origin);
+        WriteCoord(MSG_ENTITY, this.br_ring_start);
+        WriteCoord(MSG_ENTITY, this.br_ring_duration);
+        WriteCoord(MSG_ENTITY, this.radius);
+        WriteVector(MSG_ENTITY, this.colormod);
+        WriteCoord(MSG_ENTITY, this.br_ring_alpha);
+
+        WriteByte(MSG_ENTITY, this.br_ring_stage_count);
+        WriteCoord(MSG_ENTITY, this.br_ring_stage_waittime);
+        for(int i = 0; i < (this.br_ring_stage_count + 1); ++i)
+            WriteCoord(MSG_ENTITY, this.br_ring_stage_timing[i]);
+    }
+
+    if(sf & BR_RING_MOVE)
+    {
+        WriteVector(MSG_ENTITY, this.origin);
+        WriteVector(MSG_ENTITY, this.velocity);
+    }
+
+    return true;
+}
+
+void ring_think(entity this)
+{
+    float time_elapsed = time - this.br_ring_start;
+
+    if(time_elapsed >= (this.br_ring_duration + this.br_ring_stage_waittime * this.br_ring_stage_count))
+    {
+        this.velocity = '0 0 0';
+        this.SendFlags |= BR_RING_MOVE; // not really necessary but for completeness sake
+        this.nextthink = 0; // ring reached its final state, no further thinking required
+    }
+    else
+    {
+        for(int stage = this.br_ring_stage_count - 1; stage >= 0; --stage)
+        {
+            float stage_duration_current = this.br_ring_duration * this.br_ring_stage_timing[stage];
+            stage_duration_current += this.br_ring_stage_waittime * stage;
+
+            float stage_duration_next = this.br_ring_duration * this.br_ring_stage_timing[stage + 1];
+            stage_duration_next += this.br_ring_stage_waittime * (stage + 1);
+
+            if(time_elapsed >= (stage_duration_current + this.br_ring_stage_waittime))
+            {
+                if(stage != this.br_ring_stage)
+                {
+                    this.br_ring_stage = stage;
+                    ring_newStage(this);
+                    this.SendFlags |= BR_RING_MOVE;
+
+                    Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_BR_RING_CLOSE, stage + 1);
+                }
+                this.nextthink = this.br_ring_start + stage_duration_next;
+                break;
+            }
+            else if(time_elapsed >= stage_duration_current)
+            {
+                if(vlen(this.velocity) > 0)
+                {
+                    this.velocity = '0 0 0';
+                    this.SendFlags |= BR_RING_MOVE;
+                }
+                this.nextthink = this.br_ring_start + stage_duration_current + this.br_ring_stage_waittime;
+                this.br_ring_timelimit = this.nextthink - game_starttime;
+                ring_timelimit(this);
+                break;
+            }
+        }
+    }
+}
+
+void ring_newStage(entity this)
+{
+    this.strength = this.br_ring_stage_strength[this.br_ring_stage + 1];
+    if(this.br_ring_stage > 0) // first stage should center the ring a bit, no moving required
+        this.velocity = (this.radius / this.br_ring_duration) * normalize(eX * (random() * 2 - 1) + eY * (random() * 2 - 1));
+}
+
+bool ring_parseTiming(entity this)
+{
+    int num_timings = tokenize(autocvar_g_br_ring_timing);
+    if(num_timings > (BR_RING_STAGE_MAX - 2))
+    {
+        LOG_INFO("too many stages defined by g_br_ring_timing");
+        num_timings = BR_RING_STAGE_MAX - 2;
+    }
+    int invalid_timings = 0;
+    for(int i = 0; i < num_timings; ++i)
+    {
+        float current_timing = stof(argv(i));
+        if((current_timing > this.br_ring_stage_timing[i - invalid_timings]) && (current_timing < 1))
+            this.br_ring_stage_timing[i + 1 - invalid_timings] = current_timing;
+        else
+        {
+            ++invalid_timings;
+            LOG_INFO("invalid timing value \"", argv(i), "\" at position ", itos(i + 1), " in g_br_ring_timing was discarded");
+        }
+    }
+    this.br_ring_stage_count = num_timings + 1 - invalid_timings;
+    this.br_ring_stage_timing[0] = 0;
+    this.br_ring_stage_timing[this.br_ring_stage_count] = 1;
+
+    return (invalid_timings > 0);
+}
+
+void ring_parseStrength(entity this, bool has_invalid)
+{
+    int num_strength = tokenize(autocvar_g_br_ring_strength);
+    if(!has_invalid) // don't warn about this if we already got errors in the timing list
+    {
+        if(num_strength < (this.br_ring_stage_count + 1))
+        {
+            LOG_INFO("not enough strength values in g_br_ring_strength for the defined stages in g_br_ring_timing");
+        }
+        if(num_strength > (this.br_ring_stage_count + 1))
+        {
+            LOG_INFO("too many strength values in g_br_ring_strength for the defined stages in g_br_ring_timing");
+        }
+    }
+    for(int i = 0; i < (this.br_ring_stage_count + 1); ++i)
+    {
+        float current_strength;
+        float prev_strength = ((i > 0) ? this.br_ring_stage_strength[i - 1] : 1);
+        if(i < num_strength)
+            current_strength = stof(argv(i));
+        else
+            current_strength = prev_strength;
+
+        if(current_strength > 0)
+            this.br_ring_stage_strength[i] = current_strength;
+        else
+        {
+            this.br_ring_stage_strength[i] = prev_strength;
+            LOG_INFO("invalid strength value \"", argv(i), "\" at position ", itos(i + 1), " in g_br_ring_strength replaced with preceeding value \"", ftos(prev_strength), "\"");
+        }
+    }
+}
+
+void ring_timelimit(entity this)
+{
+    WriteByte(MSG_ALL, 3); // svc_updatestat
+    WriteByte(MSG_ALL, 236); // STAT_TIMELIMIT
+    WriteCoord(MSG_ALL, this.br_ring_timelimit / 60);
+}
+
+void ring_alignPosition(entity this)
+{
+    float f = bound(0, autocvar_g_br_ring_center_factor, 1);
+    vector ringorigin = world.mins + (world.maxs - world.mins) * ((0.5 - f/2) + random() * f);
+    ringorigin.z = this.origin.z;
+
+    setorigin(this, ringorigin);
+}
diff --git a/qcsrc/common/gamemodes/gamemode/br/sv_ring.qh b/qcsrc/common/gamemodes/gamemode/br/sv_ring.qh
new file mode 100644 (file)
index 0000000..464b1e8
--- /dev/null
@@ -0,0 +1,4 @@
+#pragma once
+
+entity ring_initialize();
+void ring_timelimit(entity this);
diff --git a/qcsrc/common/gamemodes/gamemode/br/sv_squad.qc b/qcsrc/common/gamemodes/gamemode/br/sv_squad.qc
new file mode 100644 (file)
index 0000000..3e18f1a
--- /dev/null
@@ -0,0 +1,160 @@
+#include "sv_squad.qh"
+
+.int br_squad_members;
+.int br_squad_members_alive;
+
+int br_SquadUpdateInfo()
+{
+    int alive_players = 0;
+    int alive_squads = 0;
+
+    IL_EACH(squads, !it.br_squad_dead,
+    {
+        it.br_squad_members_alive = 0;
+
+        for(entity member = it.br_squad_first; member; member = member.br_squad_next)
+        {
+            if (IS_DEAD(member) || IS_SPEC(member) || IS_OBSERVER(member))
+                continue;
+            ++alive_players;
+
+            if (STAT(BLEEDING, member))
+                continue;
+            ++it.br_squad_members_alive;
+        }
+
+        if(it.br_squad_members_alive > 0)
+            ++alive_squads;
+    });
+
+    // all members are either dead or bleeding, kill the squad
+    IL_EACH(squads, !it.br_squad_dead && (it.br_squad_members_alive == 0),
+    {
+        it.br_squad_dead = true;
+
+        for(entity member = it.br_squad_first; member; member = member.br_squad_next)
+        {
+            Send_Notification(NOTIF_ONE, member, MSG_CENTER, CENTER_BR_DEAD_SQUAD);
+            GameRules_scoring_add(member, BR_RANK, alive_squads + 1);
+
+            if (IS_DEAD(member) || IS_SPEC(member) || IS_OBSERVER(member))
+                continue;
+
+            // kill player
+            SetResourceExplicit(member, RES_HEALTH, 0);
+            if(member.event_damage)
+                member.event_damage(member, member, member, 1, DEATH_ROT.m_id, DMG_NOWEP, member.origin, '0 0 0');
+        }
+    });
+
+    FOREACH_CLIENT(IS_REAL_CLIENT(it),
+    {
+        STAT(SQUADSALIVE, it) = alive_squads;
+        STAT(PLAYERSALIVE, it) = alive_players;
+    });
+
+    eliminatedPlayers.SendFlags |= 1;
+
+    return alive_squads;
+}
+
+void br_SquadMember_Add(entity squad, entity player)
+{
+    player.br_squad = squad;
+    ++squad.br_squad_members;
+
+    entity member_prev = squad.br_squad_last;
+    squad.br_squad_last = player;
+    player.br_squad_next = NULL;
+    player.br_squad_prev = member_prev;
+    if(member_prev)
+        member_prev.br_squad_next = player;
+
+    if(squad.br_squad_first == NULL)
+        squad.br_squad_first = player;
+}
+
+void br_SquadMember_Remove(entity player)
+{
+    entity squad = player.br_squad;
+
+    if(!squad)
+        return;
+
+    --squad.br_squad_members;
+
+    entity member_next = player.br_squad_next;
+    entity member_prev = player.br_squad_prev;
+    if(member_next)
+        member_next.br_squad_prev = member_prev;
+    if(member_prev)
+        member_prev.br_squad_next = member_next;
+
+    if(squad.br_squad_first == player)
+        squad.br_squad_first = member_next;
+    if(squad.br_squad_last == player)
+        squad.br_squad_last = member_prev;
+
+    if(squad.br_squad_drop_leader == player)
+        squad.br_squad_drop_leader = NULL; // TODO: delegate another drop leader
+
+    if(!squad.br_squad_first) // empty squad
+        delete(squad);
+
+    player.br_squad = NULL;
+    player.br_squad_next = NULL;
+    player.br_squad_prev = NULL;
+}
+
+bool br_SquadIsBotsOnly(entity squad)
+{
+    for(entity member = squad.br_squad_first; member; member = member.br_squad_next)
+    {
+        if(IS_REAL_CLIENT(member))
+            return false;
+    }
+
+    return true;
+}
+
+entity br_SquadGetRandomAvail()
+{
+    int num_avail = 0;
+    IL_EACH(squads, it.br_squad_members < max_squad_size, ++num_avail);
+
+    if(num_avail == 0)
+        return NULL;
+
+    int target_id = floor(random() * num_avail);
+
+    int current_id = 0;
+    IL_EACH(squads, it.br_squad_members < max_squad_size,
+    {
+        if(current_id == target_id)
+            return it;
+
+        ++current_id;
+    });
+
+    return NULL;
+}
+
+entity br_SquadFindLastAlive(entity squad, bool healthy_only)
+{
+    if(!squad)
+        return NULL;
+
+    entity last_alive = NULL;
+    for(entity member = squad.br_squad_first; member; member = member.br_squad_next)
+    {
+        if (!IS_DEAD(member) && !IS_SPEC(member) && !IS_OBSERVER(member) && (!STAT(BLEEDING, member) || !healthy_only))
+        {
+            if(last_alive)
+                return NULL; // more than one squad member is alive
+
+            last_alive = member;
+        }
+    }
+
+    return last_alive;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/br/sv_squad.qh b/qcsrc/common/gamemodes/gamemode/br/sv_squad.qh
new file mode 100644 (file)
index 0000000..4f8bd40
--- /dev/null
@@ -0,0 +1,30 @@
+#pragma once
+
+// player squad entity
+.entity br_squad;
+
+#define IN_SQUAD(a) ((a).br_squad != NULL)
+#define SAME_SQUAD(a,b) (IN_SQUAD((a)) && ((a).br_squad == (b).br_squad))
+#define DIFF_SQUAD(a,b) (!IN_SQUAD((a)) || ((a).br_squad != (b).br_squad))
+
+int max_squad_size;
+
+// squad drop leader
+.entity br_squad_drop_leader;
+
+// squad stats
+.int br_squad_id;
+.bool br_squad_dead;
+
+// squad member list
+.entity br_squad_first;
+.entity br_squad_last;
+.entity br_squad_next;
+.entity br_squad_prev;
+
+int br_SquadUpdateInfo();
+void br_SquadMember_Add(entity squad, entity player);
+void br_SquadMember_Remove(entity player);
+bool br_SquadIsBotsOnly(entity squad);
+entity br_SquadGetRandomAvail();
+entity br_SquadFindLastAlive(entity squad, bool healthy_only);
index c0b67ff4d535e97e9752132fafd7134c28c9ae6d..035fd0d981c73ac0ee9d3ec8b154f21c0cc95c7a 100644 (file)
@@ -1142,7 +1142,7 @@ int MapInfo_CurrentFeatures()
        int req = 0;
     // TODO: find a better way to check if weapons are required on the map
        if(!(cvar("g_instagib") || cvar("g_overkill") || cvar("g_nix") || cvar("g_weaponarena") || !cvar("g_pickup_items") || !cvar("g_melee_only") 
-               || cvar("g_race") || cvar("g_cts") || cvar("g_nexball") || cvar("g_ca") || cvar("g_freezetag") || cvar("g_lms")))
+               || cvar("g_race") || cvar("g_cts") || cvar("g_nexball") || cvar("g_ca") || cvar("g_freezetag") || cvar("g_lms") || cvar("g_br")))
                req |= MAPINFO_FEATURE_WEAPONS;
        return req;
 }
index daa5af4d51dff6d099a53f1cc13d299c94e899df..656273f6a7820af0c29f86a0c2016a88f3baa75a 100644 (file)
@@ -57,6 +57,11 @@ 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);
 
+const vector WP_BR_BLEEDING_COLOR = '0.8 0.1 0.1';
+const vector WP_BR_REVIVING_COLOR = '0.2 0.8 0.2';
+REGISTER_WAYPOINT(BRBleeding, _("Bleeding!"), "", WP_BR_BLEEDING_COLOR, 1);
+REGISTER_WAYPOINT(BRReviving, _("Reviving"), "", WP_BR_REVIVING_COLOR, 1);
+
 REGISTER_WAYPOINT(Weapon, _("Weapon"), "", '0 0 0', 1);
 
 REGISTER_WAYPOINT(Monster, _("Monster"), "", '1 0 0', 1);
index f1b2af8e600164799b48c0922cd20db778d36fcb..eaa4be977541ad8d63b5d9c2b2a85ff52d58f980 100644 (file)
@@ -299,6 +299,7 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
     MSG_INFO_NOTIF(DEATH_SELF_FIRE,                         N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 became a bit too crispy%s%s"), _("^BG%s^K1 felt a little hot%s%s"))
     MSG_INFO_NOTIF(DEATH_SELF_GENERIC,                      N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_selfkill",      _("^BG%s^K1 died%s%s"), "")
     MSG_INFO_NOTIF(DEATH_SELF_LAVA,                         N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_lava",          _("^BG%s^K1 turned into hot slag%s%s"), _("^BG%s^K1 found a hot place%s%s"))
+    MSG_INFO_NOTIF(DEATH_SELF_RING,                         N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 couldn't escape from ^K2the ring^K1%s%s"), "")
     MSG_INFO_NOTIF(DEATH_SELF_MON_MAGE,                     N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was exploded by a Mage%s%s"), "")
     MSG_INFO_NOTIF(DEATH_SELF_MON_SHAMBLER_CLAW,            N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1's innards became outwards by a Shambler%s%s"), "")
     MSG_INFO_NOTIF(DEATH_SELF_MON_SHAMBLER_SMASH,           N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was smashed by a Shambler%s%s"), "")
@@ -391,6 +392,10 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
     MSG_INFO_NOTIF(LMS_FORFEIT,                             N_CHATCON,  1, 0, "s1", "",         "",     _("^BG%s^F3 forfeited"), "")
     MSG_INFO_NOTIF(LMS_NOLIVES,                             N_CONSOLE,  1, 0, "s1", "",         "",     _("^BG%s^F3 has no more lives left"), "")
 
+    MSG_INFO_NOTIF(BR_REVIVED,                              N_CONSOLE,  2, 0, "s1 s2", "",      "",     _("^BG%s^K3 was revived by ^BG%s"), "")
+    MSG_INFO_NOTIF(BR_DOWN,                                 N_CONSOLE,  2, 0, "s1 s2", "",      "",     _("^BG%s^K1 was downed by ^BG%s"), "")
+    MSG_INFO_NOTIF(BR_DOWN_SELF,                            N_CONSOLE,  1, 0, "s1", "",         "",     _("^BG%s^K1 downed themself"), "")
+
     MSG_INFO_NOTIF(MONSTERS_DISABLED,                       N_CONSOLE,  0, 0, "", "",           "",     _("^BGMonsters are currently disabled"), "")
 
     MULTITEAM_INFO(NEXBALL_RETURN_HELD,                     N_CONSOLE,  0, 0, "", "",           "",     _("^BGThe ^TC^TT^BG team held the ball for too long"), "", NAME)
@@ -622,6 +627,7 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
     MSG_CENTER_NOTIF(DEATH_SELF_FIRE,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You got a little bit too crispy!")), BOLD(_("^K1You felt a little too hot!")))
     MSG_CENTER_NOTIF(DEATH_SELF_GENERIC,                N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You fragged yourself!")), BOLD(_("^K1You need to be more careful!")))
     MSG_CENTER_NOTIF(DEATH_SELF_LAVA,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You couldn't stand the heat!")), "")
+    MSG_CENTER_NOTIF(DEATH_SELF_RING,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You couldn't escape from ^K2the ring^K1!")), "")
     MSG_CENTER_NOTIF(DEATH_SELF_MONSTER,                N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You were killed by a monster!")), BOLD(_("^K1You need to watch out for monsters!")))
     MSG_CENTER_NOTIF(DEATH_SELF_NADE,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You forgot to put the pin back in!")), BOLD(_("^K1Tastes like chicken!")))
     MSG_CENTER_NOTIF(DEATH_SELF_NADE_NAPALM,            N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1Hanging around a napalm explosion is bad!")), "")
@@ -704,6 +710,19 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
 
     MSG_CENTER_NOTIF(LMS_NOLIVES,                       N_ENABLE,    0, 0, "",               CPID_LMS,               "0 0",  _("^BGYou have no lives left, you must wait until the next match"), "")
 
+    MSG_CENTER_NOTIF(BR_JOIN_LATE,                      N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGMatch already started, you must wait until the next match"), "")
+    MSG_CENTER_NOTIF(BR_JOIN_DEAD,                      N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGYou are dead, you must wait until the next match"), "")
+    MSG_CENTER_NOTIF(BR_FORFEIT,                        N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGForfeiting not possible"), "")
+    MSG_CENTER_NOTIF(BR_DOWN_WAIT,                      N_ENABLE,    0, 0, "",               CPID_BR_DOWN,          "-1 0",  _("^F1Waiting for revive..."), "")
+    MSG_CENTER_NOTIF(BR_DEAD_SQUAD,                     N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1Your squad has been eliminated"), "")
+    MSG_CENTER_NOTIF(BR_REVIVE,                         N_ENABLE,    1, 0, "s1",             CPID_Null,              "0 0",  _("^K3You revived ^BG%s"), "")
+    MSG_CENTER_NOTIF(BR_REVIVED,                        N_ENABLE,    1, 0, "s1",             CPID_Null,              "0 0",  _("^K3You were revived by ^BG%s"), "")
+    MSG_CENTER_NOTIF(BR_DOWN,                           N_ENABLE,    1, 0, "s1",             CPID_Null,              "0 0",  _("^K1You were downed by ^BG%s"), "")
+    MSG_CENTER_NOTIF(BR_DOWN_SELF,                      N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You downed yourself"), "")
+    MSG_CENTER_NOTIF(BR_DROPSHIP,                       N_ENABLE,    0, 0, "",               CPID_BR_DROPSHIP,      "-1 0",  _("^BG^F1Jump^BG to drop"), "")
+    MSG_CENTER_NOTIF(BR_RING_WARN,                      N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^F4You are outside of ^F2the ring^F4, get back in fast!"), "")
+    MSG_CENTER_NOTIF(BR_RING_CLOSE,                     N_ENABLE,    0, 1, "f1",             CPID_Null,              "0 0",  _("^F2Ring^F4 is closing! (strength: ^F1%s^F4)"), "")
+
     MSG_CENTER_NOTIF(MISSING_TEAMS,                     N_ENABLE,    0, 1, "missing_teams",  CPID_MISSING_TEAMS,     "-1 0", _("^BGWaiting for players to join...\nNeed active players for: %s"), "")
     MSG_CENTER_NOTIF(MISSING_PLAYERS,                   N_ENABLE,    0, 1, "f1",             CPID_MISSING_PLAYERS,   "-1 0", _("^BGWaiting for %s player(s) to join..."), "")
 
@@ -833,6 +852,7 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
     MSG_MULTI_NOTIF(DEATH_SELF_FIRE,                    N_ENABLE,  NULL,           INFO_DEATH_SELF_FIRE,                   CENTER_DEATH_SELF_FIRE)
     MSG_MULTI_NOTIF(DEATH_SELF_GENERIC,                 N_ENABLE,  NULL,           INFO_DEATH_SELF_GENERIC,                CENTER_DEATH_SELF_GENERIC)
     MSG_MULTI_NOTIF(DEATH_SELF_LAVA,                    N_ENABLE,  NULL,           INFO_DEATH_SELF_LAVA,                   CENTER_DEATH_SELF_LAVA)
+    MSG_MULTI_NOTIF(DEATH_SELF_RING,                    N_ENABLE,  NULL,           INFO_DEATH_SELF_RING,                   CENTER_DEATH_SELF_RING)
     MSG_MULTI_NOTIF(DEATH_SELF_MON_MAGE,                N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_MAGE,               CENTER_DEATH_SELF_MONSTER)
     MSG_MULTI_NOTIF(DEATH_SELF_MON_SHAMBLER_CLAW,       N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_SHAMBLER_CLAW,      CENTER_DEATH_SELF_MONSTER)
     MSG_MULTI_NOTIF(DEATH_SELF_MON_SHAMBLER_SMASH,      N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_SHAMBLER_SMASH,     CENTER_DEATH_SELF_MONSTER)
index babadf8c873272fb7a7e847e31cf2cf41a438fc1..812608a7b5ab8c7fac7631b8b558b177b133bb13 100644 (file)
@@ -59,6 +59,8 @@ ENUMCLASS(CPID)
        CASE(CPID, KEYHUNT)
        CASE(CPID, KEYHUNT_OTHER)
        CASE(CPID, LMS)
+       CASE(CPID, BR_DOWN)
+       CASE(CPID, BR_DROPSHIP)
        CASE(CPID, MISSING_TEAMS)
        CASE(CPID, MISSING_PLAYERS)
        CASE(CPID, INSTAGIB_FINDAMMO)
index cf50a7fd12ee0ebadbe177ecdea804d84e518a40..999ea62be7499d16593da77fdb48b842bd45f07c 100644 (file)
@@ -88,6 +88,10 @@ REGISTER_SP(NEXBALL_FAULTS);
 
 REGISTER_SP(ONS_TAKES);
 REGISTER_SP(ONS_CAPS);
+
+REGISTER_SP(BR_RANK);
+REGISTER_SP(BR_SQUAD);
+REGISTER_SP(BR_REVIVALS);
 #endif
 
 
index 463d98c850157f9c595dd4e57759ce75fb57c60c..0659f5b67e8c561ce3a9e41b23501f8303e4708e 100644 (file)
@@ -126,6 +126,10 @@ REGISTER_STAT(HEALING_ORB_ALPHA, float)
 REGISTER_STAT(PLASMA, int)
 REGISTER_STAT(FROZEN, int)
 REGISTER_STAT(REVIVE_PROGRESS, float)
+REGISTER_STAT(BLEEDING, bool)
+REGISTER_STAT(DROP, int)
+REGISTER_STAT(SQUADSALIVE, int)
+REGISTER_STAT(PLAYERSALIVE, int)
 REGISTER_STAT(ROUNDLOST, int)
 REGISTER_STAT(CAPTURE_PROGRESS, float)
 REGISTER_STAT(ENTRAP_ORB, float)
index 5b35bfa4ef2e781817a1b71a9ab11cbc4dfb8ead..d1793a0f87e21273a1fd0f1329a4b1ba7f8c5dbb 100644 (file)
@@ -613,7 +613,7 @@ void CSQCPlayer_SetCamera()
 #endif
                        CSQCPlayer_SetMinsMaxs(e);
 
-                       if (!IS_DEAD(e))
+                       if (!IS_DEAD(e) && (STAT(DROP) != DROP_FALLING))
                                e.angles.y = input_angles.y;
                }
 
index e77049d200153e9c2f15fb0ca097edd1c1649fe3..227aa8ea85612e3a4c08116bee725feb0c4faf87 100644 (file)
@@ -683,6 +683,7 @@ float updateCompression()
        GAMETYPE(MAPINFO_TYPE_ASSAULT) \
        /* GAMETYPE(MAPINFO_TYPE_DUEL) */ \
        /* GAMETYPE(MAPINFO_TYPE_INVASION) */ \
+       /* GAMETYPE(MAPINFO_TYPE_BR) */ \
        /**/
 
 // hidden gametypes come last so indexing always works correctly
index a8a8747e1822558f0b3c572b58a5d105b7d4c773..cefc4d5ac6bc5c51c9609bad0787d999abd92924 100644 (file)
@@ -906,7 +906,7 @@ void SV_ParseClientCommand(entity this, string command)
                                // since gamecode doesn't have any calls earlier than this, do the connecting message here
                                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CONNECTING, this.netname);
                        }
-                       if(teamplay)
+                       if(teamplay || (squads_colored && IN_SQUAD(this)))
                                return;
                        break;
                case "c2s": Net_ClientCommand(this, command); return; // handled by net.qh
index d480c229b9b244024d6efae2e81eccb30657657e..0ccf32bceea3731df58c5d7faca26f8b48ea158c 100644 (file)
@@ -305,9 +305,9 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .en
        // ======
        // MURDER
        // ======
-       else if(IS_PLAYER(attacker))
+       else if(IS_PLAYER(attacker) || IN_SQUAD(attacker))
        {
-               if(SAME_TEAM(attacker, targ))
+               if(SAME_TEAM(attacker, targ) || SAME_SQUAD(attacker, targ))
                {
                        LogDeath("tk", deathtype, attacker, targ);
                        GiveFrags(attacker, targ, -1, deathtype, weaponentity);
@@ -594,7 +594,7 @@ void Damage(entity targ, entity inflictor, entity attacker, float damage, int de
        // special rule: gravity bombs and sound-based attacks do not affect team mates (other than for disconnecting the hook)
        if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || (deathtype & HITTYPE_SOUND))
        {
-               if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
+               if(IS_PLAYER(targ) && (SAME_TEAM(targ, attacker) || SAME_SQUAD(targ, attacker)))
                {
                        return;
                }
@@ -633,7 +633,7 @@ void Damage(entity targ, entity inflictor, entity attacker, float damage, int de
                                damage = 0;
                                force = '0 0 0';
                        }
-                       else if(!STAT(FROZEN, targ) && SAME_TEAM(attacker, targ))
+                       else if(!STAT(FROZEN, targ) && (SAME_TEAM(attacker, targ) || SAME_SQUAD(attacker, targ)))
                        {
                                if(autocvar_teamplay_mode == 1)
                                        damage = 0;
@@ -781,7 +781,7 @@ void Damage(entity targ, entity inflictor, entity attacker, float damage, int de
 
                        if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
                        {
-                               if (DIFF_TEAM(victim, attacker))
+                               if (DIFF_TEAM(victim, attacker) && DIFF_SQUAD(victim, attacker))
                                {
                                        if(damage > 0)
                                        {
index 728c9d757e6ffa77ad2f1edfc0637492dc2c8c0f..4fb2b5795248fae106f5aab7d8b0be3b2ce83b9f 100644 (file)
@@ -301,6 +301,7 @@ void cvar_changes_init()
                BADCVAR("g_tdm");
                BADCVAR("g_tdm_on_dm_maps");
                BADCVAR("g_tdm_teams");
+               BADCVAR("g_br");
                BADCVAR("g_vip");
                BADCVAR("leadlimit");
                BADCVAR("nextmap");