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"
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
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
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
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
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
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
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
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
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
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.
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
// =========
//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
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"
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"
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"
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"
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"
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"
// 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" \
" +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)
{
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")
{
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
// 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>
// 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>
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+#include "br.qh"
+
+#ifdef GAMEQC
+REGISTER_NET_LINKED(ENT_CLIENT_RING)
+#endif
--- /dev/null
+#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
--- /dev/null
+#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);
+}
--- /dev/null
+#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);
--- /dev/null
+#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);
+}
--- /dev/null
+#pragma once
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+// 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);
+}
--- /dev/null
+#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;
--- /dev/null
+#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;
+}
--- /dev/null
+#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();
--- /dev/null
+#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);
+}
--- /dev/null
+#pragma once
+
+entity ring_initialize();
+void ring_timelimit(entity this);
--- /dev/null
+#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;
+}
--- /dev/null
+#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);
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;
}
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);
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"), "")
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)
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!")), "")
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..."), "")
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)
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)
REGISTER_SP(ONS_TAKES);
REGISTER_SP(ONS_CAPS);
+
+REGISTER_SP(BR_RANK);
+REGISTER_SP(BR_SQUAD);
+REGISTER_SP(BR_REVIVALS);
#endif
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)
#endif
CSQCPlayer_SetMinsMaxs(e);
- if (!IS_DEAD(e))
+ if (!IS_DEAD(e) && (STAT(DROP) != DROP_FALLING))
e.angles.y = input_angles.y;
}
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
// 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
// ======
// 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);
// 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;
}
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;
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)
{
BADCVAR("g_tdm");
BADCVAR("g_tdm_on_dm_maps");
BADCVAR("g_tdm_teams");
+ BADCVAR("g_br");
BADCVAR("g_vip");
BADCVAR("leadlimit");
BADCVAR("nextmap");