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 float autocvar_g_br_revive_health = 0.25;
54 float autocvar_g_br_bleeding_health = 0.5;
55 float autocvar_g_br_bleeding_armor = 50;
56 float autocvar_g_br_drop_damage = 0.5;
57 float autocvar_g_br_drop_speed_max = 2.5;
58 float autocvar_g_br_drop_speed_min = 1.25;
59 float autocvar_g_br_drop_speed_vertical_min = 0.1;
60 bool autocvar_g_br_squad_colors = true;
61 float autocvar_g_br_drop_accel_dive = 50;
62 float autocvar_g_br_drop_accel_turn = 600;
63 bool autocvar_g_br_startweapons = false;
64 float autocvar_g_br_squad_waypoint_distance = 1500;
66 MUTATOR_HOOKFUNCTION(br, reset_map_global)
68 dropship_path_length = 0; // this should kill the dropship
69 dropship_path_direction = '0 0 0';
73 ring = dropship = NULL;
76 MUTATOR_HOOKFUNCTION(br, reset_map_players)
78 FOREACH_CLIENT(true, {
79 GameRules_scoring_add(it, BR_RANK, -GameRules_scoring_add(it, BR_RANK, 0));
80 GameRules_scoring_add(it, BR_SQUAD, -GameRules_scoring_add(it, BR_SQUAD, 0));
81 GameRules_scoring_add(it, BR_REVIVALS, -GameRules_scoring_add(it, BR_REVIVALS, 0));
83 STAT(DROP, it) = DROP_LANDED;
84 STAT(BLEEDING, it) = false;
88 it.br_wepset_old = start_weapons;
89 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
91 it.br_weapon_prev[slot] = WEP_Null;
92 it.br_lastweapon_prev[slot] = 0;
100 MUTATOR_HOOKFUNCTION(br, CheckRules_World)
102 if(!br_started && !warmup_stage && (time > game_starttime) && br_CheckPlayers())
105 M_ARGV(0, float) = br_WinningCondition();
109 MUTATOR_HOOKFUNCTION(br, GiveFragsForKill, CBC_ORDER_FIRST)
111 M_ARGV(2, float) = 0; // no frags counted in Battle Royale
115 MUTATOR_HOOKFUNCTION(br, ForbidPlayerScore_Clear)
117 // don't clear player score
121 MUTATOR_HOOKFUNCTION(br, GetPlayerStatus)
123 entity player = M_ARGV(0, entity);
124 return IN_SQUAD(player);
127 MUTATOR_HOOKFUNCTION(br, ClientConnect)
129 entity player = M_ARGV(0, entity);
131 STAT(SQUADCOLORS, player) = squads_colored;
134 ring_timelimit(ring);
136 br_SquadUpdateInfo();
139 MUTATOR_HOOKFUNCTION(br, ClientDisconnect)
141 entity player = M_ARGV(0, entity);
143 br_RemovePlayer(player);
144 br_SquadUpdateInfo();
147 MUTATOR_HOOKFUNCTION(br, PutClientInServer)
149 entity player = M_ARGV(0, entity);
153 if(!warmup_stage && (time > game_starttime) && br_CheckPlayers())
154 STAT(DROP, player) = DROP_TRANSPORT; // inhibits the spawn effect when the match is about to start
158 if (IN_SQUAD(player))
159 Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_JOIN_DEAD);
161 Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_JOIN_LATE);
163 TRANSMUTE(Observer, player);
166 MUTATOR_HOOKFUNCTION(br, MakePlayerObserver)
168 entity player = M_ARGV(0, entity);
169 bool is_forced = M_ARGV(1, bool);
171 if(is_forced && IN_SQUAD(player))
173 br_SquadMember_Remove(player);
174 br_SquadUpdateInfo();
179 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
186 MUTATOR_HOOKFUNCTION(br, SpectateCopy)
188 entity spectatee = M_ARGV(0, entity);
189 entity client = M_ARGV(1, entity);
191 STAT(DROP, client) = STAT(DROP, spectatee);
192 STAT(BLEEDING, client) = STAT(BLEEDING, spectatee);
195 MUTATOR_HOOKFUNCTION(br, SpectateSet)
197 entity client = M_ARGV(0, entity);
198 entity target = M_ARGV(1, entity);
200 return (IN_SQUAD(client) && !client.br_squad.br_squad_dead && DIFF_SQUAD(client, target));
203 MUTATOR_HOOKFUNCTION(br, SpectateNext)
205 entity client = M_ARGV(0, entity);
207 if(!IS_REAL_CLIENT(client) || !IN_SQUAD(client) || client.br_squad.br_squad_dead)
212 if(client.enemy && client.enemy.br_squad_next)
213 new_target = client.enemy.br_squad_next;
215 new_target = client.br_squad.br_squad_first;
217 while((new_target == client) || IS_DEAD(new_target) || !IS_PLAYER(new_target))
219 new_target = new_target.br_squad_next;
221 new_target = client.br_squad.br_squad_first;
223 M_ARGV(1, entity) = new_target;
228 MUTATOR_HOOKFUNCTION(br, SpectatePrev)
230 entity client = M_ARGV(0, entity);
232 if(!IS_REAL_CLIENT(client) || !IN_SQUAD(client) || client.br_squad.br_squad_dead)
233 return MUT_SPECPREV_CONTINUE;
237 if(client.enemy && client.enemy.br_squad_prev)
238 new_target = client.enemy.br_squad_prev;
240 new_target = client.br_squad.br_squad_last;
242 while((new_target == client) || IS_DEAD(new_target) || !IS_PLAYER(new_target))
244 new_target = new_target.br_squad_prev;
246 new_target = client.br_squad.br_squad_last;
248 M_ARGV(1, entity) = new_target;
250 return MUT_SPECPREV_FOUND;
253 MUTATOR_HOOKFUNCTION(br, ForbidSpawn)
258 MUTATOR_HOOKFUNCTION(br, WantWeapon)
260 if(autocvar_g_br_startweapons)
263 M_ARGV(1, float) = 0;
267 MUTATOR_HOOKFUNCTION(br, SetStartItems)
269 start_items &= ~(IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS);
271 start_health = warmup_start_health = cvar("g_br_start_health");
272 start_armorvalue = warmup_start_armorvalue = cvar("g_br_start_armor");
273 start_ammo_shells = warmup_start_ammo_shells = cvar("g_br_start_ammo_shells");
274 start_ammo_nails = warmup_start_ammo_nails = cvar("g_br_start_ammo_nails");
275 start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_br_start_ammo_rockets");
276 start_ammo_cells = warmup_start_ammo_cells = cvar("g_br_start_ammo_cells");
277 start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_br_start_ammo_plasma");
278 start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_br_start_ammo_fuel");
281 // adjusted freezetag reviving code
282 #ifdef IN_REVIVING_RANGE
283 #undef IN_REVIVING_RANGE
286 #define IN_REVIVING_RANGE(player, it, revive_extra_size) \
287 (it != player && IS_PLAYER(it) && !IS_DEAD(it) && SAME_SQUAD(it, player) \
288 && boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
290 MUTATOR_HOOKFUNCTION(br, PlayerPreThink, CBC_ORDER_FIRST)
292 entity player = M_ARGV(0, entity);
294 if (game_stopped || !frametime || !IS_PLAYER(player))
299 const float ring_damage_interval = 0.75;
300 vector current_origin;
302 current_origin = player.origin + player.view_ofs;
304 current_origin = player.vehicle.origin;
305 if(vlen(current_origin - ring.origin) > ring_calculate_current_radius(ring))
307 if(!player.br_ring_warned)
309 player.br_ring_warned = true;
310 player.br_ring_damage_time = time + ring_damage_interval;
311 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_BR_RING_WARN);
315 if (player.br_ring_damage_time < time)
317 if(player.vehicle) // if the player is controlling a vehicle
319 if(autocvar_g_br_ring_exitvehicle)
320 vehicles_exit(player.vehicle, VHEF_RELEASE); // begone!
322 vehicles_damage(player.vehicle, ring, ring, 10 * ring.strength * ring_damage_interval, DEATH_RING.m_id, DMG_NOWEP, player.vehicle.origin, '0 0 0');
324 Damage(player, ring, ring, ring.strength * ring_damage_interval, DEATH_RING.m_id, DMG_NOWEP, player.origin, '0 0 0');
325 player.br_ring_damage_time = time + ring_damage_interval;
330 player.br_ring_warned = false;
334 if((STAT(DROP, player) == DROP_FALLING) && (player.br_drop_detached != 2) && IN_SQUAD(player) && (player != player.br_squad.br_squad_drop_leader)){
335 // atck2 has to be released then pressed to detach
336 if(!(STAT(PRESSED_KEYS, player) & KEY_ATCK2)){
337 if(player.br_drop_detached == 0){
338 player.br_drop_detached = 1;
342 if(player.br_drop_detached == 1){
343 player.br_drop_detached = 2;
344 Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP);
349 if(STAT(DROP, player) == DROP_TRANSPORT){
350 if(time > (player.br_squad.br_drop_time + br_drop_time_secs))
352 if(!player.br_drop_instructions)
354 player.br_drop_instructions = true;
355 Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_DROPSHIP);
358 // jump has to be released then pressed to launch
359 if(!(STAT(PRESSED_KEYS, player) & KEY_JUMP)){
360 if(player.br_drop_launch == 0){
361 player.br_drop_launch = 1;
365 if(player.br_drop_launch == 1){
366 player.br_drop_launch = 2;
371 if(!(IS_REAL_CLIENT(player) && (player.br_drop_launch == 2)) && (dropship_path_length > player.br_squad.br_force_drop_distance)){
372 player.velocity = dropship_path_direction * dropship_speed;
375 if(!(IN_SQUAD(player) && player.br_squad.br_squad_drop_leader))
377 player.effects &= ~EF_NODRAW;
378 player.takedamage = DAMAGE_AIM;
379 player.solid = SOLID_SLIDEBOX;
380 if(!autocvar__notarget)
381 player.flags &= ~FL_NOTARGET;
382 Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP);
383 STAT(DROP, player) = DROP_FALLING;
384 player.br_drop_detached = 0;
385 float mindropspeed = PHYS_MAXAIRSPEED(player) * max(autocvar_g_br_drop_speed_min, 0); // no maxspeed_mod available here
386 float maxdropspeed_ratio = drop_speed_vertical_max; // moving straight down is glitchy
387 float mindropspeed_ratio = bound(0, autocvar_g_br_drop_speed_vertical_min, drop_speed_vertical_max);
388 float pitch_view = max(player.v_angle.x, 0);
390 // pitch_view angle needs to be between 0 and 90 degrees
392 pitch_view = 180 - pitch_view;
394 player.velocity.x = cos(player.angles.y * DEG2RAD);
395 player.velocity.y = sin(player.angles.y * DEG2RAD);
396 player.velocity.z = -tan(bound(asin(mindropspeed_ratio), pitch_view * DEG2RAD, asin(maxdropspeed_ratio)));
398 player.velocity = normalize(player.velocity) * mindropspeed;
400 player.angles.x = br_CalculatePlayerDropAngle(player) - 90;
401 player.angles.y = vectoangles(vec2(player.velocity)).y + 180;
402 player.angles.z = 180;
404 if(IN_SQUAD(player) && ((IS_REAL_CLIENT(player) && (player.br_drop_launch == 2)) || br_SquadIsBotsOnly(player.br_squad)))
406 player.br_squad.br_squad_drop_leader = player;
408 bool other_side = false;
409 int drop_position = 1;
414 FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && (it != player) && SAME_SQUAD(it, player) && (STAT(DROP, it) == DROP_TRANSPORT), {
415 it.effects &= ~EF_NODRAW;
416 it.takedamage = DAMAGE_AIM;
417 it.solid = SOLID_SLIDEBOX;
418 if(!autocvar__notarget)
419 it.flags &= ~FL_NOTARGET;
420 Kill_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CPID_BR_DROP);
421 STAT(DROP, it) = DROP_FALLING;
422 it.br_drop_detached = 0;
423 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_BR_DROP_DETACH);
425 it.br_drop_position = drop_position;
427 drop_position += copysign(1, drop_position);
429 other_side = !other_side;
431 br_PositionDropMember(it, player, it.br_drop_position, -1);
433 it.velocity = player.velocity;
434 it.angles = player.angles;
441 // adjusted freezetag reviving code
442 entity revivers_last = NULL;
443 entity revivers_first = NULL;
445 bool player_is_reviving = false;
446 bool player_is_being_revived = false;
447 vector revive_extra_size = '1 1 1' * max(autocvar_g_br_revive_extra_size, 0);
448 FOREACH_CLIENT(IS_PLAYER(it), {
449 // check if player is reviving anyone
450 if (STAT(BLEEDING, it))
452 if (STAT(BLEEDING, player))
454 if (!IN_REVIVING_RANGE(player, it, revive_extra_size))
456 player_is_reviving = true;
460 if (!STAT(BLEEDING, player))
461 continue; // both player and it are NOT bleeding
462 if (!IN_REVIVING_RANGE(player, it, revive_extra_size))
465 // found a squadmate that is reviving player
467 revivers_last.chain = it;
471 player_is_being_revived = true;
474 revivers_last.chain = NULL;
476 if (!player_is_being_revived) // no squadmate nearby
478 float clearspeed = max(autocvar_g_br_revive_clearspeed, 0);
479 if (STAT(BLEEDING, player))
480 STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * clearspeed, 1);
481 else if (!player_is_reviving)
482 STAT(REVIVE_PROGRESS, player) = 0; // reviving nobody
484 else // OK, there is at least one squadmate reviving us
486 float spd = max(autocvar_g_br_revive_speed, 0);
487 STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * spd, 1);
489 if(STAT(REVIVE_PROGRESS, player) >= 1)
493 // EVERY squad mate nearby gets a point (even if multiple!)
494 for(entity it = revivers_first; it; it = it.chain)
496 GameRules_scoring_add(it, BR_REVIVALS, +1);
499 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_BR_REVIVED, revivers_first.netname);
500 Send_Notification(NOTIF_ONE, revivers_first, MSG_CENTER, CENTER_BR_REVIVE, player.netname);
501 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_BR_REVIVED, player.netname, revivers_first.netname);
502 if(autocvar_sv_eventlog)
504 string revivers = "";
505 for(entity it = revivers_first; it; it = it.chain)
506 revivers = strcat(revivers, ftos(it.playerid), ",");
507 revivers = substring(revivers, 0, strlen(revivers) - 1);
508 GameLogEcho(strcat(":br:revival:", ftos(player.playerid), ":", revivers));
512 for(entity it = revivers_first; it; it = it.chain)
513 STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
516 if (STAT(BLEEDING, player))
518 entity player_wp = player.waypointsprite_attached;
519 if (player_is_being_revived)
521 WaypointSprite_UpdateSprites(player_wp, WP_BRReviving, WP_Null, WP_Null);
522 WaypointSprite_UpdateTeamRadar(player_wp, RADARICON_WAYPOINT, WP_BR_REVIVING_COLOR);
526 WaypointSprite_UpdateSprites(player_wp, WP_BRBleeding, WP_Null, WP_Null);
527 WaypointSprite_UpdateTeamRadar(player_wp, RADARICON_WAYPOINT, WP_BR_BLEEDING_COLOR);
530 WaypointSprite_UpdateMaxHealth(player_wp, 1);
531 WaypointSprite_UpdateHealth(player_wp, STAT(REVIVE_PROGRESS, player));
537 #undef IN_REVIVING_RANGE
539 MUTATOR_HOOKFUNCTION(br, PM_Physics)
541 entity player = M_ARGV(0, entity);
542 float maxspeed_mod = M_ARGV(1, float);
543 float dt = M_ARGV(2, float); // tick rate
545 if(STAT(DROP, player) == DROP_TRANSPORT)
548 // set the drop stat to landed on the next frame if it was set on landing
549 if(STAT(DROP, player) == DROP_LANDING)
550 STAT(DROP, player) = DROP_LANDED;
552 // TODO: improve dropping physics
553 if(STAT(DROP, player) == DROP_FALLING){
554 float maxairspeed = PHYS_MAXAIRSPEED(player) * max(maxspeed_mod, 1);
555 float mindropspeed = maxairspeed * max(autocvar_g_br_drop_speed_min, 0);
556 float dropspeed = vlen(vec2(player.velocity) + eZ * min(player.velocity.z, 0));
557 if(player.velocity.z > 0)
558 dropspeed -= vlen(player.velocity) - dropspeed;
559 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)
561 ITEMS_STAT(player) |= IT_USING_JETPACK;
562 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));
563 bool player_is_drop_leader = has_drop_leader && (player == player.br_squad.br_squad_drop_leader);
564 if(player_is_drop_leader || !has_drop_leader)
566 float maxdropspeed = maxairspeed * max(autocvar_g_br_drop_speed_max, 0);
567 float maxdropspeed_ratio = drop_speed_vertical_max; // moving straight down is glitchy
568 float mindropspeed_ratio = bound(0, autocvar_g_br_drop_speed_vertical_min, drop_speed_vertical_max);
569 float accel_dive = max(autocvar_g_br_drop_accel_dive, 0);
570 float accel_turn = max(autocvar_g_br_drop_accel_turn, 0);
571 float dropspeed_xy = vlen(vec2(player.velocity));
572 float pitch_current = br_CalculatePlayerDropAngle(player);
573 float pitch_view = max(player.v_angle.x, 0);
575 // pitch_view angle needs to be between 0 and 90 degrees
577 pitch_view = 180 - pitch_view;
579 float pitch_diff = pitch_current - pitch_view;
580 float pitch_ratio_wish = 0;
582 // calculate how much the player wants to change pitch (ratio is at least 0.1)
583 // ratio is between -1 (looking straight down) and +1 (looking straight ahead or up)
584 if((pitch_diff < 0) && (pitch_current < 90))
585 pitch_ratio_wish = bound(-1, sin(pitch_diff / (90 - pitch_current) * M_PI_2), -0.1);
586 else if((pitch_diff > 0) && (pitch_current > 0))
587 pitch_ratio_wish = bound(0.1, sin(pitch_diff / pitch_current * M_PI_2), 1);
589 makevectors(player.v_angle);
590 // horizontal wishvel as usual
591 vector wishvel = v_forward * CS(player).movement.x + v_right * CS(player).movement.y;
593 // except make turning backwards easier by limiting the maximum turning angle to 90 degrees
594 vector wish_angles = vectoangles(vec2(wishvel));
595 vector vel_angles = vectoangles(vec2(player.velocity));
597 float diff_angle = wish_angles.y - vel_angles.y;
600 if(diff_angle < -180)
603 wish_angles.y = (vel_angles.y + bound(-90, diff_angle, 90) + 360) % 360;
604 makevectors(wish_angles);
606 wishvel = normalize(v_forward) * min(1, vlen(wishvel) / maxairspeed);
607 // vertical wishvel using forward movement and the previously calculated ratio
608 wishvel.z = pitch_ratio_wish * bound(0, CS(player).movement.x / maxairspeed, 1);
609 // apply turn acceleration to wishvel
610 wishvel *= accel_turn;
611 player.velocity += wishvel * dt;
612 player.velocity = normalize(eZ * player.velocity.z + normalize(vec2(player.velocity)) * dropspeed_xy);
614 // if there is no horizontal movement point the horizontal vector towards the view direction
615 if(vlen(vec2(player.velocity)) == 0)
616 player.velocity += (eX * cos(player.angles.y * DEG2RAD) + eY * sin(player.angles.y * DEG2RAD)) * sqrt(1 - pow(maxdropspeed_ratio, 2));
618 // modify mindropspeed_ratio and maxdropspeed_ratio so that the player does not rotate beyond the view angle
619 float pitch_ratio_view = sin(pitch_view * DEG2RAD);
620 if(pitch_ratio_wish > 0)
621 mindropspeed_ratio = bound(mindropspeed_ratio, pitch_ratio_view, maxdropspeed_ratio);
622 else if(pitch_ratio_wish < 0)
623 maxdropspeed_ratio = bound(mindropspeed_ratio, pitch_ratio_view, maxdropspeed_ratio);
625 // constrain to vertical min/maxdropspeed
626 if(player.velocity.z > -mindropspeed_ratio)
627 player.velocity.z = -mindropspeed_ratio;
628 if(player.velocity.z < -maxdropspeed_ratio)
629 player.velocity.z = -maxdropspeed_ratio;
631 // adjust horizontal speed so that vertical speed + horizontal speed = maxdropspeed
632 float dropangle = br_CalculatePlayerDropAngle(player);
633 const float accelangle = 20;
634 dropspeed = bound(mindropspeed, dropspeed + accel_dive * (dropangle - accelangle) / accelangle * dt, maxdropspeed);
635 player.velocity = normalize(player.velocity) * dropspeed;
637 player.angles.x = dropangle - 90;
638 player.angles.y = vectoangles(vec2(player.velocity)).y + 180;
639 player.angles.z = 180;
641 if(player_is_drop_leader)
643 FOREACH_CLIENT(IS_PLAYER(it) && (it != player) && SAME_SQUAD(it, player) && (it.br_drop_detached != 2) && (STAT(DROP, it) == DROP_FALLING), {
644 if(!br_PositionDropMember(it, player, it.br_drop_position, drop_distance_disconnect))
646 it.br_drop_detached = 2;
647 Kill_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CPID_BR_DROP);
651 it.velocity = player.velocity;
652 it.angles = player.angles;
655 else if((player.br_drop_detached != 2) && IN_SQUAD(player))
657 player.br_drop_detached = 2;
658 Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP);
663 if(!br_PositionDropMember(player, player.br_squad.br_squad_drop_leader, player.br_drop_position, drop_distance_disconnect))
665 player.br_drop_detached = 2;
666 Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP);
669 player.velocity = player.br_squad.br_squad_drop_leader.velocity;
670 player.angles = player.br_squad.br_squad_drop_leader.angles; // no fixangles, only moves the player model not the player view
677 if((player.br_drop_detached != 2) && IN_SQUAD(player) && (player != player.br_squad.br_squad_drop_leader))
679 player.br_drop_detached = 2;
680 Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP);
683 STAT(DROP, player) = DROP_LANDING;
684 set_movetype(player, MOVETYPE_WALK);
685 ITEMS_STAT(player) &= ~IT_USING_JETPACK;
686 player.flags |= FL_PICKUPITEMS;
687 player.dphitcontentsmask |= DPCONTENTS_BODY;
689 STAT(WEAPONS, player) = player.br_wepset_old;
691 .entity weaponentity = weaponentities[0];
692 W_SwitchWeapon_Force(player, w_getbestweapon(player, weaponentity), weaponentity);
696 // injured players can't swim
697 if(STAT(BLEEDING, player)){
698 if(player.waterlevel >= WATERLEVEL_SWIMMING)
700 CS(player).movement.z = -60; // drift towards bottom
701 player.v_angle.x = 0;
702 player.com_in_jump = false;
707 MUTATOR_HOOKFUNCTION(br, Damage_Calculate)
709 entity frag_target = M_ARGV(2, entity);
710 float frag_deathtype = M_ARGV(3, float);
712 if(STAT(DROP, frag_target) != DROP_LANDED)
714 // weapon impact has no push force while dropping
715 M_ARGV(6, vector) = '0 0 0';
717 if(STAT(DROP, frag_target) == DROP_TRANSPORT)
718 M_ARGV(4, float) = M_ARGV(5, float) = 0; // can't take damage while on the dropship
719 else if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER)); // do not adjust vaporizer damage
722 switch(frag_deathtype)
724 case DEATH_FALL.m_id:
725 case DEATH_SHOOTING_STAR.m_id:
726 // do not take fall damage when landing from dropship
727 M_ARGV(4, float) = M_ARGV(5, float) = 0;
730 // only take half of the usual damage
731 M_ARGV(4, float) *= max(autocvar_g_br_drop_damage, 0);
732 M_ARGV(5, float) *= max(autocvar_g_br_drop_damage, 0);
738 MUTATOR_HOOKFUNCTION(br, PlayerDies, CBC_ORDER_FIRST)
740 entity frag_attacker = M_ARGV(1, entity);
741 entity frag_target = M_ARGV(2, entity);
742 float frag_deathtype = M_ARGV(3, float); // float for some reason, breaks if changed to int
744 if(!IS_PLAYER(frag_target))
747 if(STAT(DROP, frag_target) == DROP_TRANSPORT)
749 frag_target.effects &= ~EF_NODRAW;
750 frag_target.takedamage = DAMAGE_AIM;
751 frag_target.solid = SOLID_SLIDEBOX;
752 if(!autocvar__notarget)
753 frag_target.flags &= ~FL_NOTARGET;
754 Kill_Notification(NOTIF_ONE_ONLY, frag_target, MSG_CENTER, CPID_BR_DROP);
757 if(STAT(DROP, frag_target) == DROP_FALLING)
759 if((frag_target.br_drop_detached != 2) && IN_SQUAD(frag_target) && (frag_target != frag_target.br_squad.br_squad_drop_leader))
761 frag_target.br_drop_detached = 2;
762 Kill_Notification(NOTIF_ONE_ONLY, frag_target, MSG_CENTER, CPID_BR_DROP);
766 if(STAT(DROP, frag_target) != DROP_LANDED)
768 set_movetype(frag_target, MOVETYPE_WALK);
769 frag_target.dphitcontentsmask |= DPCONTENTS_BODY;
770 STAT(WEAPONS, frag_target) = frag_target.br_wepset_old;
771 STAT(DROP, frag_target) = DROP_LANDED;
774 if(STAT(BLEEDING, frag_target) || BR_KILLS_INSTANTLY(frag_target, frag_deathtype))
776 if(STAT(BLEEDING, frag_target))
778 Kill_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CPID_BR_DOWN);
779 STAT(BLEEDING, frag_target) = false;
781 // restore weapons on death to make weapon drop work
782 STAT(WEAPONS, frag_target) = frag_target.br_wepset_old;
783 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
785 .entity weaponentity = weaponentities[slot];
786 frag_target.(weaponentity).m_weapon = frag_target.br_weapon_prev[slot];
789 WaypointSprite_Kill(frag_target.br_allywaypoint);
791 frag_target.respawn_flags = RESPAWN_SILENT | RESPAWN_FORCE;
792 frag_target.respawn_time = time + 2;
796 frag_target.flags &= ~FL_PICKUPITEMS;
797 RemoveGrapplingHooks(frag_target);
798 StatusEffects_removeall(frag_target, STATUSEFFECT_REMOVE_NORMAL);
800 SetResource(frag_target, RES_HEALTH, start_health * max(autocvar_g_br_bleeding_health, 0));
801 SetResource(frag_target, RES_ARMOR, max(autocvar_g_br_bleeding_armor, 0));
802 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_BR_DOWN_WAIT);
803 STAT(BLEEDING, frag_target) = true;
805 FOREACH_CLIENT(IS_PLAYER(it),
807 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
809 .entity weaponentity = weaponentities[slot];
810 if(it.(weaponentity).hook.aiment == frag_target)
811 RemoveHook(it.(weaponentity).hook);
815 frag_target.br_wepset_old = STAT(WEAPONS, frag_target);
816 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
818 .entity weaponentity = weaponentities[slot];
819 frag_target.br_weapon_prev[slot] = frag_target.(weaponentity).m_switchweapon;
820 frag_target.br_lastweapon_prev[slot] = frag_target.(weaponentity).cnt;
822 STAT(WEAPONS, frag_target) = '0 0 0';
824 WaypointSprite_Spawn(WP_BRBleeding, 0, 0, frag_target, '0 0 64', NULL, 0, frag_target, waypointsprite_attached, true, RADARICON_WAYPOINT);
826 if(frag_attacker == frag_target || !frag_attacker || ITEM_DAMAGE_NEEDKILL(frag_deathtype))
828 if(IS_PLAYER(frag_target))
829 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_BR_DOWN_SELF);
830 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_BR_DOWN_SELF, frag_target.netname);
834 if(IS_PLAYER(frag_target))
835 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_BR_DOWN, frag_attacker.netname);
836 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_BR_DOWN, frag_target.netname, frag_attacker.netname);
839 br_SquadUpdateInfo();
844 MUTATOR_HOOKFUNCTION(br, PlayerDied)
846 entity player = M_ARGV(0, entity);
849 br_LastPlayerForSquad_Notify(player.br_squad);
850 br_SquadUpdateInfo();
854 MUTATOR_HOOKFUNCTION(br, ClientObituary)
856 entity frag_inflictor = M_ARGV(0, entity);
857 entity frag_attacker = M_ARGV(1, entity);
858 entity frag_target = M_ARGV(2, entity);
859 float frag_deathtype = M_ARGV(3, float); // float for some reason, breaks if changed to int
860 //entity frag_weaponentity = M_ARGV(4, entity);
862 if(!STAT(BLEEDING, frag_target) && !BR_KILLS_INSTANTLY(frag_target, frag_deathtype))
864 frag_target.br_bleeding_inflictor = frag_inflictor;
865 frag_target.br_bleeding_attacker = frag_attacker;
866 frag_target.br_bleeding_deathtype = frag_deathtype;
867 //frag_target.br_bleeding_weaponentity = frag_weaponentity; // TODO: get entity field
871 if(STAT(BLEEDING, frag_target) && frag_target.br_bleeding_attacker)
873 entity new_inflictor = frag_target.br_bleeding_inflictor;
874 entity new_attacker = frag_target.br_bleeding_attacker;
875 int new_deathtype = frag_target.br_bleeding_deathtype;
876 .entity new_weaponentity = frag_target.br_bleeding_weaponentity;
877 frag_target.br_bleeding_attacker = frag_target.br_bleeding_inflictor = NULL;
879 Obituary(new_attacker, new_inflictor, frag_target, new_deathtype, new_weaponentity);
886 MUTATOR_HOOKFUNCTION(br, SetResource)
888 entity player = M_ARGV(7, entity);
889 if(!IS_PLAYER(player))
892 entity res_type = M_ARGV(8, entity);
893 float amount = M_ARGV(9, float);
895 if(STAT(BLEEDING, player) && (res_type == RES_HEALTH || res_type == RES_ARMOR))
897 if(amount > GetResource(player, res_type)) // prevent the player from getting health or armor in any way
902 MUTATOR_HOOKFUNCTION(br, GetResourceLimit)
904 entity player = M_ARGV(7, entity);
906 if(!IS_PLAYER(player) || !STAT(BLEEDING, player))
909 entity res_type = M_ARGV(8, entity);
914 M_ARGV(9, float) *= max(autocvar_g_br_bleeding_health, 0);
917 M_ARGV(9, float) = max(autocvar_g_br_bleeding_armor, 0);
921 MUTATOR_HOOKFUNCTION(br, PlayerRegen, CBC_ORDER_FIRST)
923 entity player = M_ARGV(0, entity);
925 if(STAT(BLEEDING, player)){
926 M_ARGV(7, float) = max(autocvar_g_br_bleed, 0);
927 M_ARGV(8, float) = max(autocvar_g_br_bleedlinear, 0);
928 M_ARGV(2, float) = M_ARGV(10, float) = 0;
931 M_ARGV(2, float) = M_ARGV(3, float) = 0; // no regeneration or rot in battle royale
935 MUTATOR_HOOKFUNCTION(br, PlayerCanCrouch)
937 entity player = M_ARGV(0, entity);
938 if(STAT(BLEEDING, player))
939 M_ARGV(1, bool) = true;
940 else if(STAT(DROP, player) != DROP_LANDED)
941 M_ARGV(1, bool) = false;
944 MUTATOR_HOOKFUNCTION(br, PlayerJump)
946 entity player = M_ARGV(0, entity);
947 return STAT(BLEEDING, player) || (STAT(DROP, player) != DROP_LANDED);
950 MUTATOR_HOOKFUNCTION(br, BotShouldAttack)
952 entity bot = M_ARGV(0, entity);
953 entity target = M_ARGV(1, entity);
955 return SAME_SQUAD(bot, target) || (STAT(DROP, bot) != DROP_LANDED) || (STAT(DROP, target) == DROP_TRANSPORT);
958 MUTATOR_HOOKFUNCTION(br, TurretValidateTarget)
960 entity turret = M_ARGV(0, entity);
961 entity target = M_ARGV(1, entity);
963 if(!br_started || SAME_SQUAD(turret, target) || (STAT(DROP, target) == DROP_TRANSPORT))
965 M_ARGV(3, float) = -1;
972 MUTATOR_HOOKFUNCTION(br, AccuracyTargetValid)
974 entity attacker = M_ARGV(0, entity);
975 entity target = M_ARGV(1, entity);
977 if(SAME_SQUAD(attacker, target) || (STAT(DROP, target) == DROP_TRANSPORT))
978 return MUT_ACCADD_INDIFFERENT;
979 return MUT_ACCADD_VALID;
982 MUTATOR_HOOKFUNCTION(br, CustomizeWaypoint)
984 entity wp = M_ARGV(0, entity);
985 entity player = M_ARGV(1, entity);
990 if((wp == wp.owner.br_allywaypoint) && (vdist(wp.owner.origin - player.origin, <, autocvar_g_br_squad_waypoint_distance) || STAT(BLEEDING, wp.owner)))
993 if(!IS_PLAYER(player) || (IN_SQUAD(wp.owner) && DIFF_SQUAD(wp.owner, player)))
997 MUTATOR_HOOKFUNCTION(br, ClientKill)
999 entity player = M_ARGV(0, entity);
1003 // no forfeiting once the game started
1004 Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_FORFEIT);
1011 MUTATOR_HOOKFUNCTION(br, ClientCommand_Spectate)
1013 entity player = M_ARGV(0, entity);
1017 // no forfeiting once the game started
1018 Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_FORFEIT);
1019 return MUT_SPECCMD_RETURN;
1021 return MUT_SPECCMD_CONTINUE;
1024 MUTATOR_HOOKFUNCTION(br, PlayerSpawn)
1026 if(!br_started && !warmup_stage && (time > game_starttime) && br_CheckPlayers())
1030 MUTATOR_HOOKFUNCTION(br, SV_StartFrame)
1035 if(autocvar_g_br_supply_interval > 0 && time - br_event_supply_time >= autocvar_g_br_supply_interval)
1037 br_event_supply_time = time;
1041 if(autocvar_g_br_vehicle_interval > 0 && time - br_event_vehicle_time >= autocvar_g_br_vehicle_interval)
1043 br_event_vehicle_time = time;
1049 float current_radius = ring_calculate_current_radius(ring);
1051 IL_EACH(g_items, it.bot_pickup, {
1052 if(vdist(it.origin - ring.origin, >, current_radius))
1053 it.bot_pickup = false;
1058 void(entity this) havocbot_role_br_reviving;
1059 void(entity this) havocbot_role_br_generic;
1061 bool squad_needs_revive(entity this)
1063 for(entity member = this.br_squad.br_squad_first; member; member = member.br_squad_next)
1065 if(IS_DEAD(member) || !IS_PLAYER(member))
1068 if(STAT(BLEEDING, member))
1075 bool br_bot_ignore_in_ring(entity this)
1080 if(vlen(this.origin - ring.origin) > ring_calculate_current_radius(ring))
1086 void havocbot_goalrating_br_findplayers(entity this, float ratingscale)
1091 for(entity member = this.br_squad.br_squad_first; member; member = member.br_squad_next)
1093 if(IS_DEAD(member) || !IS_PLAYER(member) || (member == this))
1096 // either wants to be revived by another player or wants to revive another player
1097 if(STAT(BLEEDING, member) != STAT(BLEEDING, this))
1098 navigation_routerating(this, member, ratingscale, 100000);
1102 void havocbot_role_br_reviving(entity this)
1107 if(!squad_needs_revive(this))
1109 LOG_TRACE("changing role to generic");
1110 this.havocbot_role = havocbot_role_br_generic;
1111 navigation_goalrating_timeout_force(this);
1115 navigation_goalrating_start(this);
1116 havocbot_goalrating_br_findplayers(this, 20000);
1117 navigation_goalrating_end(this);
1120 void havocbot_role_br_generic(entity this)
1125 if(squad_needs_revive(this))
1127 LOG_TRACE("changing role to reviving");
1128 this.havocbot_role = havocbot_role_br_reviving;
1132 havocbot_role_generic(this);
1135 MUTATOR_HOOKFUNCTION(br, HavocBot_ChooseRole)
1137 entity bot = M_ARGV(0, entity);
1140 bot.havocbot_role = havocbot_role_br_generic;
1145 float br_CalculatePlayerDropAngle(entity this)
1147 if(this.velocity.z < 0)
1149 float dropspeed_xy = vlen(vec2(this.velocity));
1150 float dropspeed_z = fabs(this.velocity.z);
1151 return 90 - atan(dropspeed_xy / dropspeed_z) * RAD2DEG;
1157 bool br_PositionDropMember(entity this, entity leader, int position, float disconnect_range) {
1158 float pl_mins = min(leader.mins.x, leader.mins.y);
1159 float pl_maxs = max(leader.maxs.x, leader.maxs.y);
1161 vector member_offset;
1162 member_offset.x = cos((leader.angles.y + 90) * DEG2RAD);
1163 member_offset.y = sin((leader.angles.y + 90) * DEG2RAD);
1164 member_offset.z = 0;
1165 member_offset *= pl_maxs - pl_mins + 32; // I hope individual players never get different mins/maxs
1166 member_offset *= position;
1168 vector member_destination = leader.origin + member_offset;
1170 tracebox(this.origin, this.mins, this.maxs, member_destination, MOVE_NORMAL, this);
1172 if((trace_fraction < 1) && (disconnect_range >= 0))
1174 if(vlen(member_destination - trace_endpos) > disconnect_range)
1178 setorigin(this, trace_endpos);
1182 void br_LastPlayerForSquad_Notify(entity squad)
1184 entity player = br_SquadFindLastAlive(squad, false);
1186 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ALONE);
1189 void br_RemovePlayer(entity player)
1191 br_SquadMember_Remove(player);
1193 FOREACH_CLIENT((it.br_bleeding_attacker == player) || (it.br_bleeding_inflictor == player), {
1194 it.br_bleeding_attacker = it.br_bleeding_inflictor = NULL;
1198 void br_Revive(entity player)
1200 if(STAT(BLEEDING, player))
1202 Kill_Notification(NOTIF_ONE, player, MSG_CENTER, CPID_BR_DOWN);
1203 STAT(BLEEDING, player) = false;
1205 player.flags |= FL_PICKUPITEMS;
1206 SetResource(player, RES_HEALTH, start_health * max(autocvar_g_br_revive_health, 0));
1207 SetResource(player, RES_ARMOR, 0);
1209 STAT(WEAPONS, player) = player.br_wepset_old;
1210 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1212 .entity weaponentity = weaponentities[slot];
1213 W_SwitchWeapon_Force(player, player.br_weapon_prev[slot], weaponentity);
1214 player.(weaponentity).cnt = player.br_lastweapon_prev[slot];
1217 player.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
1219 STAT(REVIVE_PROGRESS, player) = 0;
1220 player.revival_time = time;
1222 WaypointSprite_Kill(player.waypointsprite_attached);
1224 br_SquadUpdateInfo();
1227 int br_WinningCondition()
1229 int total_squads = br_SquadUpdateInfo();
1231 if ((total_squads > 1) || !br_started)
1232 return WINNING_NEVER;
1234 entity winner_squad = NULL;
1235 IL_EACH(squads, !it.br_squad_dead, { winner_squad = it; break; });
1237 for(entity member = winner_squad.br_squad_first; member; member = member.br_squad_next)
1239 GameRules_scoring_add(member, BR_RANK, 1);
1240 member.winning = true;
1243 delete(round_handler);
1244 round_handler = NULL;
1249 bool br_isEliminated(entity e)
1251 return (IN_SQUAD(e) && (IS_DEAD(e) || !IS_PLAYER(e)));
1254 bool br_CheckPlayers()
1257 FOREACH_CLIENT(IS_PLAYER(it), ++total_players);
1259 static int prev_players = 0;
1260 if (total_players >= autocvar_g_br_minplayers || total_players == 0)
1262 if(prev_players > 0)
1263 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
1265 return (total_players >= autocvar_g_br_minplayers);
1268 if(prev_players != total_players)
1270 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, autocvar_g_br_minplayers - total_players);
1271 prev_players = total_players;
1278 // battle royale does not need those, besides, the timelimit won't be visible anymore after the game started
1279 cvar_set("timelimit", "0");
1280 cvar_set("fraglimit", "0");
1281 cvar_set("leadlimit", "0");
1283 reset_map(true, false);
1285 ring = ring_initialize();
1286 dropship = dropship_initialize();
1288 if(!ring || !dropship)
1291 LOG_WARN("Failed to determine ring starting point");
1293 LOG_WARN("Failed to determine dropship route");
1295 LOG_WARN("Prerequisites not met. Cannot start battle royale, aborting...");
1314 int num_players = 0;
1316 FOREACH_CLIENT(IS_PLAYER(it), {
1317 STAT(DROP, it) = DROP_TRANSPORT;
1318 PutPlayerInServer(it);
1320 it.br_wepset_old = STAT(WEAPONS, it);
1321 STAT(WEAPONS, it) = '0 0 0';
1322 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1324 it.br_weapon_prev[slot] = WEP_Null;
1325 it.br_lastweapon_prev[slot] = 0;
1331 max_squad_size = max(autocvar_g_br_squad_size, 1);
1332 if(num_players <= max_squad_size)
1333 max_squad_size = ceil(num_players / 2);
1335 for(int num_squads = 0; (num_squads * max_squad_size) < num_players; ++num_squads)
1337 entity new_squad = new_pure(squad);
1338 new_squad.br_squad_drop_leader = NULL;
1339 new_squad.br_squad_id = num_squads + 1;
1341 IL_PUSH(squads, new_squad);
1344 FOREACH_CLIENT_RANDOM(IS_PLAYER(it), {
1345 entity current_squad = br_SquadGetRandomAvail();
1346 br_SquadMember_Add(current_squad, it);
1347 GameRules_scoring_add(it, BR_SQUAD, current_squad.br_squad_id);
1349 setorigin(it, dropship.origin + eZ * (dropship.mins.z - it.maxs.z - 64));
1350 it.angles = vectoangles(dropship_path_direction) + '45 0 0';
1352 it.velocity = '0 0 0';
1353 set_movetype(it, MOVETYPE_FLY);
1354 it.flags &= ~FL_PICKUPITEMS;
1355 it.flags |= FL_NOTARGET;
1356 it.dphitcontentsmask &= ~DPCONTENTS_BODY;
1357 it.effects |= EF_NODRAW;
1358 it.takedamage = DAMAGE_NO;
1359 it.solid = SOLID_NOT;
1360 it.br_drop_instructions = false;
1361 it.br_drop_launch = 0;
1362 UNSET_ONGROUND(it); // otherwise this isn't unset if the player drops in the same frame
1364 WaypointSprite_Spawn(WP_BRAlly, 0, 0, it, '0 0 64', NULL, 0, it, br_allywaypoint, true, RADARICON_WAYPOINT);
1367 squads_colored = autocvar_g_br_squad_colors;
1369 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1372 STAT(SQUADCOLORS, it) = squads_colored;
1375 IL_EACH(squads, true,
1380 squad_color = 16 * floor(random() * 15) + floor(random() * 15); // color 15 is special, don't select it as a squad color
1382 for(entity member = it.br_squad_first; member; member = member.br_squad_next)
1384 member.clientcolors = 1024 + squad_color;
1388 it.br_drop_time = time;
1390 float min_distance = max(autocvar_g_br_drop_distance_force, 0);
1391 if(!br_SquadIsBotsOnly(it))
1392 it.br_force_drop_distance = min_distance;
1394 it.br_force_drop_distance = min_distance + random() * max(dropship_path_length - (min_distance + dropship_speed * br_drop_time_secs), 0);
1397 br_event_supply_time = br_event_vehicle_time = time;
1398 round_handler.cnt = 0; // emulate round handler round start
1401 void br_dummy_Think(entity this){}
1403 void br_Initialize()
1406 squads_colored = autocvar_g_br_squad_colors;
1408 // emulate the round handler, useful because this will cause a lot of code to correctly treat the stage before the match starts
1409 round_handler = new_pure(round_handler);
1410 round_handler.count = 0;
1411 round_handler.cnt = 1;
1412 round_handler.wait = false;
1413 setthink(round_handler, br_dummy_Think);
1415 EliminatedPlayers_Init(br_isEliminated);