5 #include <server/elimination.qh>
6 #include <common/resources/sv_resources.qh>
7 #include <common/mutators/base.qh>
9 #define BR_KILLS_INSTANTLY(pl, dt) \
10 (!IN_SQUAD((pl)) || (br_SquadFindLastAlive((pl).br_squad, true) == (pl)) || ((dt) == DEATH_HURTTRIGGER.m_id) \
11 || ((dt) == DEATH_KILL.m_id) || ((dt) == DEATH_TEAMCHANGE.m_id) || ((dt) == DEATH_AUTOTEAMCHANGE.m_id) \
12 || DEATH_ISWEAPON(dt, WEP_VAPORIZER))
14 float br_CalculatePlayerDropAngle(entity this);
15 bool br_PositionDropMember(entity this, entity leader, int position, float disconnect_range);
16 void br_LastPlayerForSquad_Notify(entity squad);
17 void br_RemovePlayer(entity player);
18 void br_Revive(entity player);
20 bool br_CheckPlayers();
21 int br_WinningCondition();
25 bool squads_colored = false;
27 const float br_drop_time_secs = 1;
28 const float drop_speed_vertical_max = 0.9;
29 const float drop_distance_disconnect = 32;
30 const float drop_speed_crash = 0.9;
31 float br_event_supply_time;
32 float br_event_vehicle_time;
33 bool br_started = false;
36 .float br_force_drop_distance;
38 .int br_drop_detached;
39 .float br_drop_position;
40 .bool br_drop_instructions;
41 .float br_ring_damage_time;
43 .entity br_bleeding_inflictor;
44 .entity br_bleeding_attacker;
45 .int br_bleeding_deathtype;
46 ..entity br_bleeding_weaponentity;
48 // weapon set restoring for revive/drop
49 .WepSet br_wepset_old;
50 .Weapon br_weapon_prev[MAX_WEAPONSLOTS];
51 .float br_lastweapon_prev[MAX_WEAPONSLOTS];
53 // alpha restoring for drop
56 float autocvar_g_br_revive_health = 0.25;
57 float autocvar_g_br_bleeding_health = 0.5;
58 float autocvar_g_br_bleeding_armor = 50;
59 float autocvar_g_br_drop_damage = 0.5;
60 float autocvar_g_br_drop_speed_max = 2.5;
61 float autocvar_g_br_drop_speed_min = 1.25;
62 float autocvar_g_br_drop_speed_vertical_min = 0.1;
63 bool autocvar_g_br_squad_colors = true;
64 float autocvar_g_br_drop_accel_dive = 50;
65 float autocvar_g_br_drop_accel_turn = 600;
66 bool autocvar_g_br_startweapons = false;
67 float autocvar_g_br_squad_waypoint_distance = 1500;
69 MUTATOR_HOOKFUNCTION(br, reset_map_global)
71 dropship_path_length = 0; // this should kill the dropship
72 dropship_path_direction = '0 0 0';
76 ring = dropship = NULL;
79 MUTATOR_HOOKFUNCTION(br, reset_map_players)
81 FOREACH_CLIENT(true, {
82 GameRules_scoring_add(it, BR_RANK, -GameRules_scoring_add(it, BR_RANK, 0));
83 GameRules_scoring_add(it, BR_SQUAD, -GameRules_scoring_add(it, BR_SQUAD, 0));
84 GameRules_scoring_add(it, BR_REVIVALS, -GameRules_scoring_add(it, BR_REVIVALS, 0));
86 STAT(DROP, it) = DROP_LANDED;
87 STAT(BLEEDING, it) = false;
91 it.br_wepset_old = start_weapons;
92 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
94 it.br_weapon_prev[slot] = WEP_Null;
95 it.br_lastweapon_prev[slot] = 0;
103 MUTATOR_HOOKFUNCTION(br, CheckRules_World)
105 if(!br_started && !warmup_stage && (time > game_starttime) && br_CheckPlayers())
108 M_ARGV(0, float) = br_WinningCondition();
112 MUTATOR_HOOKFUNCTION(br, GiveFragsForKill, CBC_ORDER_FIRST)
114 M_ARGV(2, float) = 0; // no frags counted in Battle Royale
118 MUTATOR_HOOKFUNCTION(br, ForbidPlayerScore_Clear)
120 // don't clear player score
124 MUTATOR_HOOKFUNCTION(br, GetPlayerStatus)
126 entity player = M_ARGV(0, entity);
127 return IN_SQUAD(player);
130 MUTATOR_HOOKFUNCTION(br, ClientConnect)
132 entity player = M_ARGV(0, entity);
134 STAT(SQUADCOLORS, player) = squads_colored;
137 ring_timelimit(ring);
139 br_SquadUpdateInfo();
142 MUTATOR_HOOKFUNCTION(br, ClientDisconnect)
144 entity player = M_ARGV(0, entity);
146 br_RemovePlayer(player);
147 br_SquadUpdateInfo();
150 MUTATOR_HOOKFUNCTION(br, PutClientInServer)
152 entity player = M_ARGV(0, entity);
156 if(!warmup_stage && (time > game_starttime) && br_CheckPlayers())
157 STAT(DROP, player) = DROP_TRANSPORT; // inhibits the spawn effect when the match is about to start
161 if (IN_SQUAD(player))
162 Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_JOIN_DEAD);
164 Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_JOIN_LATE);
166 TRANSMUTE(Observer, player);
169 MUTATOR_HOOKFUNCTION(br, MakePlayerObserver)
171 entity player = M_ARGV(0, entity);
172 bool is_forced = M_ARGV(1, bool);
174 if(is_forced && IN_SQUAD(player))
176 br_SquadMember_Remove(player);
177 br_SquadUpdateInfo();
182 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
189 MUTATOR_HOOKFUNCTION(br, SpectateCopy)
191 entity spectatee = M_ARGV(0, entity);
192 entity client = M_ARGV(1, entity);
194 STAT(DROP, client) = STAT(DROP, spectatee);
195 STAT(BLEEDING, client) = STAT(BLEEDING, spectatee);
198 MUTATOR_HOOKFUNCTION(br, SpectateSet)
200 entity client = M_ARGV(0, entity);
201 entity target = M_ARGV(1, entity);
203 return (IN_SQUAD(client) && !client.br_squad.br_squad_dead && DIFF_SQUAD(client, target));
206 MUTATOR_HOOKFUNCTION(br, SpectateNext)
208 entity client = M_ARGV(0, entity);
210 if(!IS_REAL_CLIENT(client) || !IN_SQUAD(client) || client.br_squad.br_squad_dead)
215 if(client.enemy && client.enemy.br_squad_next)
216 new_target = client.enemy.br_squad_next;
218 new_target = client.br_squad.br_squad_first;
220 while((new_target == client) || IS_DEAD(new_target) || !IS_PLAYER(new_target))
222 new_target = new_target.br_squad_next;
224 new_target = client.br_squad.br_squad_first;
226 M_ARGV(1, entity) = new_target;
231 MUTATOR_HOOKFUNCTION(br, SpectatePrev)
233 entity client = M_ARGV(0, entity);
235 if(!IS_REAL_CLIENT(client) || !IN_SQUAD(client) || client.br_squad.br_squad_dead)
236 return MUT_SPECPREV_CONTINUE;
240 if(client.enemy && client.enemy.br_squad_prev)
241 new_target = client.enemy.br_squad_prev;
243 new_target = client.br_squad.br_squad_last;
245 while((new_target == client) || IS_DEAD(new_target) || !IS_PLAYER(new_target))
247 new_target = new_target.br_squad_prev;
249 new_target = client.br_squad.br_squad_last;
251 M_ARGV(1, entity) = new_target;
253 return MUT_SPECPREV_FOUND;
256 MUTATOR_HOOKFUNCTION(br, ForbidSpawn)
261 MUTATOR_HOOKFUNCTION(br, WantWeapon)
263 if(autocvar_g_br_startweapons)
266 M_ARGV(1, float) = 0;
270 MUTATOR_HOOKFUNCTION(br, SetStartItems)
272 start_items &= ~(IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS);
274 start_health = warmup_start_health = cvar("g_br_start_health");
275 start_armorvalue = warmup_start_armorvalue = cvar("g_br_start_armor");
276 start_ammo_shells = warmup_start_ammo_shells = cvar("g_br_start_ammo_shells");
277 start_ammo_nails = warmup_start_ammo_nails = cvar("g_br_start_ammo_nails");
278 start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_br_start_ammo_rockets");
279 start_ammo_cells = warmup_start_ammo_cells = cvar("g_br_start_ammo_cells");
280 start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_br_start_ammo_plasma");
281 start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_br_start_ammo_fuel");
284 // adjusted freezetag reviving code
285 #ifdef IN_REVIVING_RANGE
286 #undef IN_REVIVING_RANGE
289 #define IN_REVIVING_RANGE(player, it, revive_extra_size) \
290 (it != player && IS_PLAYER(it) && !IS_DEAD(it) && SAME_SQUAD(it, player) \
291 && boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
293 MUTATOR_HOOKFUNCTION(br, PlayerPreThink, CBC_ORDER_FIRST)
295 entity player = M_ARGV(0, entity);
297 if (game_stopped || !frametime || !IS_PLAYER(player))
302 const float ring_damage_interval = 0.75;
303 vector current_origin;
305 current_origin = player.origin + player.view_ofs;
307 current_origin = player.vehicle.origin;
308 if(vlen(current_origin - ring.origin) > ring_calculate_current_radius(ring))
310 if(!player.br_ring_warned)
312 player.br_ring_warned = true;
313 player.br_ring_damage_time = time + ring_damage_interval;
314 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_BR_RING_WARN);
318 if (player.br_ring_damage_time < time)
320 if(player.vehicle) // if the player is controlling a vehicle
322 if(autocvar_g_br_ring_exitvehicle)
323 vehicles_exit(player.vehicle, VHEF_RELEASE); // begone!
325 vehicles_damage(player.vehicle, ring, ring, 10 * ring.strength * ring_damage_interval, DEATH_RING.m_id, DMG_NOWEP, player.vehicle.origin, '0 0 0');
327 Damage(player, ring, ring, ring.strength * ring_damage_interval, DEATH_RING.m_id, DMG_NOWEP, player.origin, '0 0 0');
328 player.br_ring_damage_time = time + ring_damage_interval;
333 player.br_ring_warned = false;
337 if((STAT(DROP, player) == DROP_FALLING) && (player.br_drop_detached != 2) && IN_SQUAD(player) && (player != player.br_squad.br_squad_drop_leader)){
338 // atck2 has to be released then pressed to detach
339 if(!(STAT(PRESSED_KEYS, player) & KEY_ATCK2)){
340 if(player.br_drop_detached == 0){
341 player.br_drop_detached = 1;
345 if(player.br_drop_detached == 1){
346 player.br_drop_detached = 2;
347 Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP);
352 if(STAT(DROP, player) == DROP_TRANSPORT){
353 if(time > (player.br_squad.br_drop_time + br_drop_time_secs))
355 if(!player.br_drop_instructions)
357 player.br_drop_instructions = true;
358 Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_DROPSHIP);
361 // jump has to be released then pressed to launch
362 if(!(STAT(PRESSED_KEYS, player) & KEY_JUMP)){
363 if(player.br_drop_launch == 0){
364 player.br_drop_launch = 1;
368 if(player.br_drop_launch == 1){
369 player.br_drop_launch = 2;
374 if(!(IS_REAL_CLIENT(player) && (player.br_drop_launch == 2)) && (dropship_path_length > player.br_squad.br_force_drop_distance)){
375 player.velocity = dropship_path_direction * dropship_speed;
378 if(!(IN_SQUAD(player) && player.br_squad.br_squad_drop_leader))
380 player.alpha = player.br_alpha_old;
381 player.takedamage = DAMAGE_AIM;
382 player.solid = SOLID_SLIDEBOX;
383 if(!autocvar__notarget)
384 player.flags &= ~FL_NOTARGET;
385 Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP);
386 STAT(DROP, player) = DROP_FALLING;
387 player.br_drop_detached = 0;
388 float mindropspeed = PHYS_MAXAIRSPEED(player) * max(autocvar_g_br_drop_speed_min, 0); // no maxspeed_mod available here
389 float maxdropspeed_ratio = drop_speed_vertical_max; // moving straight down is glitchy
390 float mindropspeed_ratio = bound(0, autocvar_g_br_drop_speed_vertical_min, drop_speed_vertical_max);
391 float pitch_view = max(player.v_angle.x, 0);
393 // pitch_view angle needs to be between 0 and 90 degrees
395 pitch_view = 180 - pitch_view;
397 player.velocity.x = cos(player.angles.y * DEG2RAD);
398 player.velocity.y = sin(player.angles.y * DEG2RAD);
399 player.velocity.z = -tan(bound(asin(mindropspeed_ratio), pitch_view * DEG2RAD, asin(maxdropspeed_ratio)));
401 player.velocity = normalize(player.velocity) * mindropspeed;
403 player.angles.x = br_CalculatePlayerDropAngle(player) - 90;
404 player.angles.y = vectoangles(vec2(player.velocity)).y + 180;
405 player.angles.z = 180;
407 if(IN_SQUAD(player) && ((IS_REAL_CLIENT(player) && (player.br_drop_launch == 2)) || br_SquadIsBotsOnly(player.br_squad)))
409 player.br_squad.br_squad_drop_leader = player;
411 bool other_side = false;
412 int drop_position = 1;
417 FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && (it != player) && SAME_SQUAD(it, player) && (STAT(DROP, it) == DROP_TRANSPORT), {
418 it.alpha = it.br_alpha_old;
419 it.takedamage = DAMAGE_AIM;
420 it.solid = SOLID_SLIDEBOX;
421 if(!autocvar__notarget)
422 it.flags &= ~FL_NOTARGET;
423 Kill_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CPID_BR_DROP);
424 STAT(DROP, it) = DROP_FALLING;
425 it.br_drop_detached = 0;
426 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_BR_DROP_DETACH);
428 it.br_drop_position = drop_position;
430 drop_position += copysign(1, drop_position);
432 other_side = !other_side;
434 br_PositionDropMember(it, player, it.br_drop_position, -1);
436 it.velocity = player.velocity;
437 it.angles = player.angles;
444 // adjusted freezetag reviving code
445 entity revivers_last = NULL;
446 entity revivers_first = NULL;
448 bool player_is_reviving = false;
449 bool player_is_being_revived = false;
450 vector revive_extra_size = '1 1 1' * max(autocvar_g_br_revive_extra_size, 0);
451 FOREACH_CLIENT(IS_PLAYER(it), {
452 // check if player is reviving anyone
453 if (STAT(BLEEDING, it))
455 if (STAT(BLEEDING, player))
457 if (!IN_REVIVING_RANGE(player, it, revive_extra_size))
459 player_is_reviving = true;
463 if (!STAT(BLEEDING, player))
464 continue; // both player and it are NOT bleeding
465 if (!IN_REVIVING_RANGE(player, it, revive_extra_size))
468 // found a squadmate that is reviving player
470 revivers_last.chain = it;
474 player_is_being_revived = true;
477 revivers_last.chain = NULL;
479 if (!player_is_being_revived) // no squadmate nearby
481 float clearspeed = max(autocvar_g_br_revive_clearspeed, 0);
482 if (STAT(BLEEDING, player))
483 STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * clearspeed, 1);
484 else if (!player_is_reviving)
485 STAT(REVIVE_PROGRESS, player) = 0; // reviving nobody
487 else // OK, there is at least one squadmate reviving us
489 float spd = max(autocvar_g_br_revive_speed, 0);
490 STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * spd, 1);
492 if(STAT(REVIVE_PROGRESS, player) >= 1)
496 // EVERY squad mate nearby gets a point (even if multiple!)
497 for(entity it = revivers_first; it; it = it.chain)
499 GameRules_scoring_add(it, BR_REVIVALS, +1);
502 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_BR_REVIVED, revivers_first.netname);
503 Send_Notification(NOTIF_ONE, revivers_first, MSG_CENTER, CENTER_BR_REVIVE, player.netname);
504 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_BR_REVIVED, player.netname, revivers_first.netname);
505 if(autocvar_sv_eventlog)
507 string revivers = "";
508 for(entity it = revivers_first; it; it = it.chain)
509 revivers = strcat(revivers, ftos(it.playerid), ",");
510 revivers = substring(revivers, 0, strlen(revivers) - 1);
511 GameLogEcho(strcat(":br:revival:", ftos(player.playerid), ":", revivers));
515 for(entity it = revivers_first; it; it = it.chain)
516 STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
519 if (STAT(BLEEDING, player))
521 entity player_wp = player.waypointsprite_attached;
522 if (player_is_being_revived)
524 WaypointSprite_UpdateSprites(player_wp, WP_BRReviving, WP_Null, WP_Null);
525 WaypointSprite_UpdateTeamRadar(player_wp, RADARICON_WAYPOINT, WP_BR_REVIVING_COLOR);
529 WaypointSprite_UpdateSprites(player_wp, WP_BRBleeding, WP_Null, WP_Null);
530 WaypointSprite_UpdateTeamRadar(player_wp, RADARICON_WAYPOINT, WP_BR_BLEEDING_COLOR);
533 WaypointSprite_UpdateMaxHealth(player_wp, 1);
534 WaypointSprite_UpdateHealth(player_wp, STAT(REVIVE_PROGRESS, player));
540 #undef IN_REVIVING_RANGE
542 MUTATOR_HOOKFUNCTION(br, PM_Physics)
544 entity player = M_ARGV(0, entity);
545 float maxspeed_mod = M_ARGV(1, float);
546 float dt = M_ARGV(2, float); // tick rate
548 if(STAT(DROP, player) == DROP_TRANSPORT)
551 // set the drop stat to landed on the next frame if it was set on landing
552 if(STAT(DROP, player) == DROP_LANDING)
553 STAT(DROP, player) = DROP_LANDED;
555 // TODO: improve dropping physics
556 if(STAT(DROP, player) == DROP_FALLING){
557 float maxairspeed = PHYS_MAXAIRSPEED(player) * max(maxspeed_mod, 1);
558 float mindropspeed = maxairspeed * max(autocvar_g_br_drop_speed_min, 0);
559 float dropspeed = vlen(vec2(player.velocity) + eZ * min(player.velocity.z, 0));
560 if(player.velocity.z > 0)
561 dropspeed -= vlen(player.velocity) - dropspeed;
562 if(!IS_ONGROUND(player) && (player.waterlevel < WATERLEVEL_SWIMMING) && (dropspeed >= (mindropspeed * drop_speed_crash)) && ((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)
564 ITEMS_STAT(player) |= IT_USING_JETPACK;
565 bool has_drop_leader = IN_SQUAD(player) && (player.br_drop_detached != 2) && (player.br_squad.br_squad_drop_leader && (STAT(DROP, player.br_squad.br_squad_drop_leader) == DROP_FALLING));
566 bool player_is_drop_leader = has_drop_leader && (player == player.br_squad.br_squad_drop_leader);
567 if(player_is_drop_leader || !has_drop_leader)
569 float maxdropspeed = maxairspeed * max(autocvar_g_br_drop_speed_max, 0);
570 float maxdropspeed_ratio = drop_speed_vertical_max; // moving straight down is glitchy
571 float mindropspeed_ratio = bound(0, autocvar_g_br_drop_speed_vertical_min, drop_speed_vertical_max);
572 float accel_dive = max(autocvar_g_br_drop_accel_dive, 0);
573 float accel_turn = max(autocvar_g_br_drop_accel_turn, 0);
574 float dropspeed_xy = vlen(vec2(player.velocity));
575 float pitch_current = br_CalculatePlayerDropAngle(player);
576 float pitch_view = max(player.v_angle.x, 0);
578 // pitch_view angle needs to be between 0 and 90 degrees
580 pitch_view = 180 - pitch_view;
582 float pitch_diff = pitch_current - pitch_view;
583 float pitch_ratio_wish = 0;
585 // calculate how much the player wants to change pitch (ratio is at least 0.1)
586 // ratio is between -1 (looking straight down) and +1 (looking straight ahead or up)
587 if((pitch_diff < 0) && (pitch_current < 90))
588 pitch_ratio_wish = bound(-1, sin(pitch_diff / (90 - pitch_current) * M_PI_2), -0.1);
589 else if((pitch_diff > 0) && (pitch_current > 0))
590 pitch_ratio_wish = bound(0.1, sin(pitch_diff / pitch_current * M_PI_2), 1);
592 makevectors(player.v_angle);
593 // horizontal wishvel as usual
594 vector wishvel = v_forward * CS(player).movement.x + v_right * CS(player).movement.y;
596 // except make turning backwards easier by limiting the maximum turning angle to 90 degrees
597 vector wish_angles = vectoangles(vec2(wishvel));
598 vector vel_angles = vectoangles(vec2(player.velocity));
600 float diff_angle = wish_angles.y - vel_angles.y;
603 if(diff_angle < -180)
606 wish_angles.y = (vel_angles.y + bound(-90, diff_angle, 90) + 360) % 360;
607 makevectors(wish_angles);
609 wishvel = normalize(v_forward) * min(1, vlen(wishvel) / maxairspeed);
610 // vertical wishvel using forward movement and the previously calculated ratio
611 wishvel.z = pitch_ratio_wish * bound(0, CS(player).movement.x / maxairspeed, 1);
612 // apply turn acceleration to wishvel
613 wishvel *= accel_turn;
614 player.velocity += wishvel * dt;
615 player.velocity = normalize(eZ * player.velocity.z + normalize(vec2(player.velocity)) * dropspeed_xy);
617 // if there is no horizontal movement point the horizontal vector towards the view direction
618 if(vlen(vec2(player.velocity)) == 0)
619 player.velocity += (eX * cos(player.angles.y * DEG2RAD) + eY * sin(player.angles.y * DEG2RAD)) * sqrt(1 - pow(maxdropspeed_ratio, 2));
621 // modify mindropspeed_ratio and maxdropspeed_ratio so that the player does not rotate beyond the view angle
622 float pitch_ratio_view = sin(pitch_view * DEG2RAD);
623 if(pitch_ratio_wish > 0)
624 mindropspeed_ratio = bound(mindropspeed_ratio, pitch_ratio_view, maxdropspeed_ratio);
625 else if(pitch_ratio_wish < 0)
626 maxdropspeed_ratio = bound(mindropspeed_ratio, pitch_ratio_view, maxdropspeed_ratio);
628 // constrain to vertical min/maxdropspeed
629 if(player.velocity.z > -mindropspeed_ratio)
630 player.velocity.z = -mindropspeed_ratio;
631 if(player.velocity.z < -maxdropspeed_ratio)
632 player.velocity.z = -maxdropspeed_ratio;
634 // adjust horizontal speed so that vertical speed + horizontal speed = maxdropspeed
635 float dropangle = br_CalculatePlayerDropAngle(player);
636 const float accelangle = 20;
637 dropspeed = bound(mindropspeed, dropspeed + accel_dive * (dropangle - accelangle) / accelangle * dt, maxdropspeed);
638 player.velocity = normalize(player.velocity) * dropspeed;
640 player.angles.x = dropangle - 90;
641 player.angles.y = vectoangles(vec2(player.velocity)).y + 180;
642 player.angles.z = 180;
644 if(player_is_drop_leader)
646 FOREACH_CLIENT(IS_PLAYER(it) && (it != player) && SAME_SQUAD(it, player) && (it.br_drop_detached != 2) && (STAT(DROP, it) == DROP_FALLING), {
647 if(!br_PositionDropMember(it, player, it.br_drop_position, drop_distance_disconnect))
649 it.br_drop_detached = 2;
650 Kill_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CPID_BR_DROP);
654 it.velocity = player.velocity;
655 it.angles = player.angles;
658 else if((player.br_drop_detached != 2) && IN_SQUAD(player))
660 player.br_drop_detached = 2;
661 Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP);
666 if(!br_PositionDropMember(player, player.br_squad.br_squad_drop_leader, player.br_drop_position, drop_distance_disconnect))
668 player.br_drop_detached = 2;
669 Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP);
672 player.velocity = player.br_squad.br_squad_drop_leader.velocity;
673 player.angles = player.br_squad.br_squad_drop_leader.angles; // no fixangles, only moves the player model not the player view
680 if((player.br_drop_detached != 2) && IN_SQUAD(player) && (player != player.br_squad.br_squad_drop_leader))
682 player.br_drop_detached = 2;
683 Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP);
686 STAT(DROP, player) = DROP_LANDING;
687 set_movetype(player, MOVETYPE_WALK);
688 ITEMS_STAT(player) &= ~IT_USING_JETPACK;
689 player.flags |= FL_PICKUPITEMS;
690 player.dphitcontentsmask |= DPCONTENTS_BODY;
692 STAT(WEAPONS, player) = player.br_wepset_old;
694 .entity weaponentity = weaponentities[0];
695 W_SwitchWeapon_Force(player, w_getbestweapon(player, weaponentity), weaponentity);
699 // injured players can't swim
700 if(STAT(BLEEDING, player)){
701 if(player.waterlevel >= WATERLEVEL_SWIMMING)
703 CS(player).movement.z = -60; // drift towards bottom
704 player.v_angle.x = 0;
705 player.com_in_jump = false;
710 MUTATOR_HOOKFUNCTION(br, Damage_Calculate)
712 entity frag_target = M_ARGV(2, entity);
713 float frag_deathtype = M_ARGV(3, float);
715 if(STAT(DROP, frag_target) != DROP_LANDED)
717 // weapon impact has no push force while dropping
718 M_ARGV(6, vector) = '0 0 0';
720 if(STAT(DROP, frag_target) == DROP_TRANSPORT)
721 M_ARGV(4, float) = M_ARGV(5, float) = 0; // can't take damage while on the dropship
722 else if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER)); // do not adjust vaporizer damage
725 switch(frag_deathtype)
727 case DEATH_FALL.m_id:
728 case DEATH_SHOOTING_STAR.m_id:
729 // do not take fall damage when landing from dropship
730 M_ARGV(4, float) = M_ARGV(5, float) = 0;
733 // only take half of the usual damage
734 M_ARGV(4, float) *= max(autocvar_g_br_drop_damage, 0);
735 M_ARGV(5, float) *= max(autocvar_g_br_drop_damage, 0);
741 MUTATOR_HOOKFUNCTION(br, PlayerDies, CBC_ORDER_FIRST)
743 entity frag_attacker = M_ARGV(1, entity);
744 entity frag_target = M_ARGV(2, entity);
745 float frag_deathtype = M_ARGV(3, float); // float for some reason, breaks if changed to int
747 if(!IS_PLAYER(frag_target))
750 if(STAT(DROP, frag_target) == DROP_TRANSPORT)
752 frag_target.alpha = frag_target.br_alpha_old;
753 frag_target.takedamage = DAMAGE_AIM;
754 frag_target.solid = SOLID_SLIDEBOX;
755 if(!autocvar__notarget)
756 frag_target.flags &= ~FL_NOTARGET;
757 Kill_Notification(NOTIF_ONE_ONLY, frag_target, MSG_CENTER, CPID_BR_DROP);
760 if(STAT(DROP, frag_target) == DROP_FALLING)
762 if((frag_target.br_drop_detached != 2) && IN_SQUAD(frag_target) && (frag_target != frag_target.br_squad.br_squad_drop_leader))
764 frag_target.br_drop_detached = 2;
765 Kill_Notification(NOTIF_ONE_ONLY, frag_target, MSG_CENTER, CPID_BR_DROP);
769 if(STAT(DROP, frag_target) != DROP_LANDED)
771 set_movetype(frag_target, MOVETYPE_WALK);
772 frag_target.dphitcontentsmask |= DPCONTENTS_BODY;
773 STAT(WEAPONS, frag_target) = frag_target.br_wepset_old;
774 STAT(DROP, frag_target) = DROP_LANDED;
777 if(STAT(BLEEDING, frag_target) || BR_KILLS_INSTANTLY(frag_target, frag_deathtype))
779 if(STAT(BLEEDING, frag_target))
781 Kill_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CPID_BR_DOWN);
782 STAT(BLEEDING, frag_target) = false;
784 // restore weapons on death to make weapon drop work
785 STAT(WEAPONS, frag_target) = frag_target.br_wepset_old;
786 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
788 .entity weaponentity = weaponentities[slot];
789 frag_target.(weaponentity).m_weapon = frag_target.br_weapon_prev[slot];
792 WaypointSprite_Kill(frag_target.br_allywaypoint);
794 frag_target.respawn_flags = RESPAWN_SILENT | RESPAWN_FORCE;
795 frag_target.respawn_time = time + 2;
799 frag_target.flags &= ~FL_PICKUPITEMS;
800 RemoveGrapplingHooks(frag_target);
801 StatusEffects_removeall(frag_target, STATUSEFFECT_REMOVE_NORMAL);
803 SetResource(frag_target, RES_HEALTH, start_health * max(autocvar_g_br_bleeding_health, 0));
804 SetResource(frag_target, RES_ARMOR, max(autocvar_g_br_bleeding_armor, 0));
805 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_BR_DOWN_WAIT);
806 STAT(BLEEDING, frag_target) = true;
808 FOREACH_CLIENT(IS_PLAYER(it),
810 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
812 .entity weaponentity = weaponentities[slot];
813 if(it.(weaponentity).hook.aiment == frag_target)
814 RemoveHook(it.(weaponentity).hook);
818 frag_target.br_wepset_old = STAT(WEAPONS, frag_target);
819 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
821 .entity weaponentity = weaponentities[slot];
822 frag_target.br_weapon_prev[slot] = frag_target.(weaponentity).m_switchweapon;
823 frag_target.br_lastweapon_prev[slot] = frag_target.(weaponentity).cnt;
825 STAT(WEAPONS, frag_target) = '0 0 0';
827 WaypointSprite_Spawn(WP_BRBleeding, 0, 0, frag_target, '0 0 64', NULL, 0, frag_target, waypointsprite_attached, true, RADARICON_WAYPOINT);
829 if(frag_attacker == frag_target || !frag_attacker || ITEM_DAMAGE_NEEDKILL(frag_deathtype))
831 if(IS_PLAYER(frag_target))
832 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_BR_DOWN_SELF);
833 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_BR_DOWN_SELF, frag_target.netname);
837 if(IS_PLAYER(frag_target))
838 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_BR_DOWN, frag_attacker.netname);
839 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_BR_DOWN, frag_target.netname, frag_attacker.netname);
842 br_SquadUpdateInfo();
847 MUTATOR_HOOKFUNCTION(br, PlayerDied)
849 entity player = M_ARGV(0, entity);
852 br_LastPlayerForSquad_Notify(player.br_squad);
853 br_SquadUpdateInfo();
857 MUTATOR_HOOKFUNCTION(br, ClientObituary)
859 entity frag_inflictor = M_ARGV(0, entity);
860 entity frag_attacker = M_ARGV(1, entity);
861 entity frag_target = M_ARGV(2, entity);
862 float frag_deathtype = M_ARGV(3, float); // float for some reason, breaks if changed to int
863 //entity frag_weaponentity = M_ARGV(4, entity);
865 if(!STAT(BLEEDING, frag_target) && !BR_KILLS_INSTANTLY(frag_target, frag_deathtype))
867 frag_target.br_bleeding_inflictor = frag_inflictor;
868 frag_target.br_bleeding_attacker = frag_attacker;
869 frag_target.br_bleeding_deathtype = frag_deathtype;
870 //frag_target.br_bleeding_weaponentity = frag_weaponentity; // TODO: get entity field
874 if(STAT(BLEEDING, frag_target) && frag_target.br_bleeding_attacker)
876 entity new_inflictor = frag_target.br_bleeding_inflictor;
877 entity new_attacker = frag_target.br_bleeding_attacker;
878 int new_deathtype = frag_target.br_bleeding_deathtype;
879 .entity new_weaponentity = frag_target.br_bleeding_weaponentity;
880 frag_target.br_bleeding_attacker = frag_target.br_bleeding_inflictor = NULL;
882 Obituary(new_attacker, new_inflictor, frag_target, new_deathtype, new_weaponentity);
889 MUTATOR_HOOKFUNCTION(br, SetResource)
891 entity player = M_ARGV(7, entity);
892 if(!IS_PLAYER(player))
895 entity res_type = M_ARGV(8, entity);
896 float amount = M_ARGV(9, float);
898 if(STAT(BLEEDING, player) && (res_type == RES_HEALTH || res_type == RES_ARMOR))
900 if(amount > GetResource(player, res_type)) // prevent the player from getting health or armor in any way
905 MUTATOR_HOOKFUNCTION(br, GetResourceLimit)
907 entity player = M_ARGV(7, entity);
909 if(!IS_PLAYER(player) || !STAT(BLEEDING, player))
912 entity res_type = M_ARGV(8, entity);
917 M_ARGV(9, float) *= max(autocvar_g_br_bleeding_health, 0);
920 M_ARGV(9, float) = max(autocvar_g_br_bleeding_armor, 0);
924 MUTATOR_HOOKFUNCTION(br, PlayerRegen, CBC_ORDER_FIRST)
926 entity player = M_ARGV(0, entity);
928 if(STAT(BLEEDING, player)){
929 M_ARGV(7, float) = max(autocvar_g_br_bleed, 0);
930 M_ARGV(8, float) = max(autocvar_g_br_bleedlinear, 0);
931 M_ARGV(2, float) = M_ARGV(10, float) = 0;
934 M_ARGV(2, float) = M_ARGV(3, float) = 0; // no regeneration or rot in battle royale
938 MUTATOR_HOOKFUNCTION(br, PlayerCanCrouch)
940 entity player = M_ARGV(0, entity);
941 if(STAT(BLEEDING, player))
942 M_ARGV(1, bool) = true;
943 else if(STAT(DROP, player) != DROP_LANDED)
944 M_ARGV(1, bool) = false;
947 MUTATOR_HOOKFUNCTION(br, PlayerJump)
949 entity player = M_ARGV(0, entity);
950 return STAT(BLEEDING, player) || (STAT(DROP, player) != DROP_LANDED);
953 MUTATOR_HOOKFUNCTION(br, BotShouldAttack)
955 entity bot = M_ARGV(0, entity);
956 entity target = M_ARGV(1, entity);
958 return SAME_SQUAD(bot, target) || (STAT(DROP, bot) != DROP_LANDED) || (STAT(DROP, target) == DROP_TRANSPORT);
961 MUTATOR_HOOKFUNCTION(br, TurretValidateTarget)
963 entity turret = M_ARGV(0, entity);
964 entity target = M_ARGV(1, entity);
966 if(!br_started || SAME_SQUAD(turret, target) || (STAT(DROP, target) == DROP_TRANSPORT))
968 M_ARGV(3, float) = -1;
975 MUTATOR_HOOKFUNCTION(br, AccuracyTargetValid)
977 entity attacker = M_ARGV(0, entity);
978 entity target = M_ARGV(1, entity);
980 if(SAME_SQUAD(attacker, target) || (STAT(DROP, target) == DROP_TRANSPORT))
981 return MUT_ACCADD_INDIFFERENT;
982 return MUT_ACCADD_VALID;
985 MUTATOR_HOOKFUNCTION(br, CustomizeWaypoint)
987 entity wp = M_ARGV(0, entity);
988 entity player = M_ARGV(1, entity);
993 if((wp == wp.owner.br_allywaypoint) && (vdist(wp.owner.origin - player.origin, <, autocvar_g_br_squad_waypoint_distance) || STAT(BLEEDING, wp.owner)))
996 if(!IS_PLAYER(player) || (IN_SQUAD(wp.owner) && DIFF_SQUAD(wp.owner, player)))
1000 MUTATOR_HOOKFUNCTION(br, ClientKill)
1002 entity player = M_ARGV(0, entity);
1006 // no forfeiting once the game started
1007 Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_FORFEIT);
1014 MUTATOR_HOOKFUNCTION(br, ClientCommand_Spectate)
1016 entity player = M_ARGV(0, entity);
1020 // no forfeiting once the game started
1021 Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_FORFEIT);
1022 return MUT_SPECCMD_RETURN;
1024 return MUT_SPECCMD_CONTINUE;
1027 MUTATOR_HOOKFUNCTION(br, PlayerSpawn)
1029 if(!br_started && !warmup_stage && (time > game_starttime) && br_CheckPlayers())
1033 MUTATOR_HOOKFUNCTION(br, SV_StartFrame)
1038 if(autocvar_g_br_supply_interval > 0 && time - br_event_supply_time >= autocvar_g_br_supply_interval)
1040 br_event_supply_time = time;
1044 if(autocvar_g_br_vehicle_interval > 0 && time - br_event_vehicle_time >= autocvar_g_br_vehicle_interval)
1046 br_event_vehicle_time = time;
1052 float current_radius = ring_calculate_current_radius(ring);
1054 IL_EACH(g_items, it.bot_pickup, {
1055 if(vdist(it.origin - ring.origin, >, current_radius))
1056 it.bot_pickup = false;
1061 void(entity this) havocbot_role_br_reviving;
1062 void(entity this) havocbot_role_br_generic;
1064 bool squad_needs_revive(entity this)
1066 for(entity member = this.br_squad.br_squad_first; member; member = member.br_squad_next)
1068 if(IS_DEAD(member) || !IS_PLAYER(member))
1071 if(STAT(BLEEDING, member))
1078 bool br_bot_ignore_in_ring(entity this)
1083 if(vlen(this.origin - ring.origin) > ring_calculate_current_radius(ring))
1089 void havocbot_goalrating_br_findplayers(entity this, float ratingscale)
1094 for(entity member = this.br_squad.br_squad_first; member; member = member.br_squad_next)
1096 if(IS_DEAD(member) || !IS_PLAYER(member) || (member == this))
1099 // either wants to be revived by another player or wants to revive another player
1100 if(STAT(BLEEDING, member) != STAT(BLEEDING, this))
1101 navigation_routerating(this, member, ratingscale, 100000);
1105 void havocbot_role_br_reviving(entity this)
1110 if(!squad_needs_revive(this))
1112 LOG_TRACE("changing role to generic");
1113 this.havocbot_role = havocbot_role_br_generic;
1114 navigation_goalrating_timeout_force(this);
1118 navigation_goalrating_start(this);
1119 havocbot_goalrating_br_findplayers(this, 20000);
1120 navigation_goalrating_end(this);
1123 void havocbot_role_br_generic(entity this)
1128 if(squad_needs_revive(this))
1130 LOG_TRACE("changing role to reviving");
1131 this.havocbot_role = havocbot_role_br_reviving;
1135 havocbot_role_generic(this);
1138 MUTATOR_HOOKFUNCTION(br, HavocBot_ChooseRole)
1140 entity bot = M_ARGV(0, entity);
1143 bot.havocbot_role = havocbot_role_br_generic;
1148 float br_CalculatePlayerDropAngle(entity this)
1150 if(this.velocity.z < 0)
1152 float dropspeed_xy = vlen(vec2(this.velocity));
1153 float dropspeed_z = fabs(this.velocity.z);
1154 return 90 - atan(dropspeed_xy / dropspeed_z) * RAD2DEG;
1160 bool br_PositionDropMember(entity this, entity leader, int position, float disconnect_range) {
1161 float pl_mins = min(leader.mins.x, leader.mins.y);
1162 float pl_maxs = max(leader.maxs.x, leader.maxs.y);
1164 vector member_offset;
1165 member_offset.x = cos((leader.angles.y + 90) * DEG2RAD);
1166 member_offset.y = sin((leader.angles.y + 90) * DEG2RAD);
1167 member_offset.z = 0;
1168 member_offset *= pl_maxs - pl_mins + 32; // I hope individual players never get different mins/maxs
1169 member_offset *= position;
1171 vector member_destination = leader.origin + member_offset;
1173 tracebox(this.origin, this.mins, this.maxs, member_destination, MOVE_NORMAL, this);
1175 if((trace_fraction < 1) && (disconnect_range >= 0))
1177 if(vlen(member_destination - trace_endpos) > disconnect_range)
1181 setorigin(this, trace_endpos);
1185 void br_LastPlayerForSquad_Notify(entity squad)
1187 entity player = br_SquadFindLastAlive(squad, false);
1189 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ALONE);
1192 void br_RemovePlayer(entity player)
1194 br_SquadMember_Remove(player);
1196 FOREACH_CLIENT((it.br_bleeding_attacker == player) || (it.br_bleeding_inflictor == player), {
1197 it.br_bleeding_attacker = it.br_bleeding_inflictor = NULL;
1201 void br_Revive(entity player)
1203 if(STAT(BLEEDING, player))
1205 Kill_Notification(NOTIF_ONE, player, MSG_CENTER, CPID_BR_DOWN);
1206 STAT(BLEEDING, player) = false;
1208 player.flags |= FL_PICKUPITEMS;
1209 SetResource(player, RES_HEALTH, start_health * max(autocvar_g_br_revive_health, 0));
1210 SetResource(player, RES_ARMOR, 0);
1212 STAT(WEAPONS, player) = player.br_wepset_old;
1213 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1215 .entity weaponentity = weaponentities[slot];
1216 W_SwitchWeapon_Force(player, player.br_weapon_prev[slot], weaponentity);
1217 player.(weaponentity).cnt = player.br_lastweapon_prev[slot];
1220 player.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
1222 STAT(REVIVE_PROGRESS, player) = 0;
1223 player.revival_time = time;
1225 WaypointSprite_Kill(player.waypointsprite_attached);
1227 br_SquadUpdateInfo();
1230 int br_WinningCondition()
1232 int total_squads = br_SquadUpdateInfo();
1234 if ((total_squads > 1) || !br_started)
1235 return WINNING_NEVER;
1237 entity winner_squad = NULL;
1238 IL_EACH(squads, !it.br_squad_dead, { winner_squad = it; break; });
1240 for(entity member = winner_squad.br_squad_first; member; member = member.br_squad_next)
1242 GameRules_scoring_add(member, BR_RANK, 1);
1243 member.winning = true;
1246 delete(round_handler);
1247 round_handler = NULL;
1252 bool br_isEliminated(entity e)
1254 return (IN_SQUAD(e) && (IS_DEAD(e) || !IS_PLAYER(e)));
1257 bool br_CheckPlayers()
1260 FOREACH_CLIENT(IS_PLAYER(it), ++total_players);
1262 static int prev_players = 0;
1263 if (total_players >= autocvar_g_br_minplayers || total_players == 0)
1265 if(prev_players > 0)
1266 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
1268 return (total_players >= autocvar_g_br_minplayers);
1271 if(prev_players != total_players)
1273 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, autocvar_g_br_minplayers - total_players);
1274 prev_players = total_players;
1281 // battle royale does not need those, besides, the timelimit won't be visible anymore after the game started
1282 cvar_set("timelimit", "0");
1283 cvar_set("fraglimit", "0");
1284 cvar_set("leadlimit", "0");
1286 reset_map(true, false);
1288 ring = ring_initialize();
1289 dropship = dropship_initialize();
1291 if(!ring || !dropship)
1294 LOG_WARN("Failed to determine ring starting point");
1296 LOG_WARN("Failed to determine dropship route");
1298 LOG_WARN("Prerequisites not met. Cannot start battle royale, aborting...");
1317 int num_players = 0;
1319 FOREACH_CLIENT(IS_PLAYER(it), {
1320 STAT(DROP, it) = DROP_TRANSPORT;
1321 PutPlayerInServer(it);
1323 it.br_wepset_old = STAT(WEAPONS, it);
1324 STAT(WEAPONS, it) = '0 0 0';
1325 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1327 it.br_weapon_prev[slot] = WEP_Null;
1328 it.br_lastweapon_prev[slot] = 0;
1334 if(autocvar_g_br_squad_size >= 1)
1335 max_squad_size = min(autocvar_g_br_squad_size, ceil(num_players / 2));
1336 else if(autocvar_g_br_squad_size <= -1)
1337 max_squad_size = min(-autocvar_g_br_squad_size, floor(num_players / 2));
1339 max_squad_size = floor(num_players / 2);
1341 for(int num_squads = 0; (num_squads * max_squad_size) < num_players; ++num_squads)
1343 entity new_squad = new_pure(squad);
1344 new_squad.br_squad_drop_leader = NULL;
1345 new_squad.br_squad_id = num_squads + 1;
1347 IL_PUSH(squads, new_squad);
1350 FOREACH_CLIENT_RANDOM(IS_PLAYER(it), {
1351 entity current_squad = br_SquadGetRandomAvail();
1352 br_SquadMember_Add(current_squad, it);
1353 GameRules_scoring_add(it, BR_SQUAD, current_squad.br_squad_id);
1355 setorigin(it, dropship.origin + eZ * (dropship.mins.z - it.maxs.z - 64));
1356 it.angles = vectoangles(dropship_path_direction) + '45 0 0';
1358 it.velocity = '0 0 0';
1359 set_movetype(it, MOVETYPE_FLY);
1360 it.flags &= ~FL_PICKUPITEMS;
1361 it.flags |= FL_NOTARGET;
1362 it.dphitcontentsmask &= ~DPCONTENTS_BODY;
1363 it.br_alpha_old = it.alpha;
1365 it.takedamage = DAMAGE_NO;
1366 it.solid = SOLID_NOT;
1367 it.br_drop_instructions = false;
1368 it.br_drop_launch = 0;
1369 UNSET_ONGROUND(it); // otherwise this isn't unset if the player drops in the same frame
1371 WaypointSprite_Spawn(WP_BRAlly, 0, 0, it, '0 0 64', NULL, 0, it, br_allywaypoint, true, RADARICON_WAYPOINT);
1374 squads_colored = autocvar_g_br_squad_colors;
1376 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1379 STAT(SQUADCOLORS, it) = squads_colored;
1382 int squad_colors_taken = 0;
1383 const int squad_colors_num = 6; // there are 6 colors which look distinct enough
1384 const int squad_colors_taken_mask = 2 ** squad_colors_num - 1;
1385 IL_EACH(squads, true,
1389 float squad_color = 0;
1393 squad_color = floor(random() * squad_colors_num);
1394 int squad_color_bit = 1 << squad_color;
1396 if(!(squad_color_bit & squad_colors_taken))
1398 squad_colors_taken |= squad_color_bit;
1400 // only select easily distinguishable colors
1425 if(squad_colors_taken == squad_colors_taken_mask)
1426 squad_colors_taken = 0;
1428 squad_color = 16 * squad_color + squad_color;
1430 for(entity member = it.br_squad_first; member; member = member.br_squad_next)
1432 member.clientcolors = 1024 + squad_color;
1436 it.br_drop_time = time;
1438 float min_distance = max(autocvar_g_br_drop_distance_force, 0);
1439 if(!br_SquadIsBotsOnly(it))
1440 it.br_force_drop_distance = min_distance;
1442 it.br_force_drop_distance = min_distance + random() * max(dropship_path_length - (min_distance + dropship_speed * br_drop_time_secs), 0);
1445 br_event_supply_time = br_event_vehicle_time = time;
1448 void br_Initialize()
1451 squads_colored = autocvar_g_br_squad_colors;
1453 EliminatedPlayers_Init(br_isEliminated);