Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
sv_freezetag.qc
Go to the documentation of this file.
1#include "sv_freezetag.qh"
2
5
9//int autocvar_g_freezetag_teams;
13
21
23{
24 total_players = 0;
25 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
26 {
28 }
30 {
32 if (GetResource(it, RES_HEALTH) < 1 || STAT(FROZEN, it))
33 {
34 continue;
35 }
36 entity team_ = Entity_GetTeam(it);
37 int num_alive = Team_GetNumberOfAlivePlayers(team_);
38 ++num_alive;
39 Team_SetNumberOfAlivePlayers(team_, num_alive);
40 });
42 {
47 });
48
49 eliminatedPlayers.SendFlags = 0xFFFFFF;
50}
51
53{
54 static float prev_missing_teams_mask;
57 return true;
58 if(total_players == 0)
59 return false;
60 for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
61 {
64 {
66 }
67 }
68 if(prev_missing_teams_mask != missing_teams_mask)
69 prev_missing_teams_mask = missing_teams_mask;
70 return false;
71}
72
74{
77 {
78 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
79 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
81 it.freezetag_frozen_timeout = 0;
82 it.freezetag_revive_time = 0;
84 });
85 game_stopped = true;
87 return true;
88 }
89
90 int winner_team = Team_GetWinnerAliveTeam();
91 if (!winner_team)
92 {
94 return false;
95 }
96
97 // delay round ending a bit
100 && round_handler_GetEndTime() - time > 0) // don't delay past timelimit
101 {
103 {
105 return 0;
106 }
108 {
109 return 0;
110 }
111 }
112
113 if(winner_team > 0)
114 {
115 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
116 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
117 TeamScore_AddToTeam(winner_team, ST_FT_ROUNDS, +1);
118 }
119 else if(winner_team == -1)
120 {
121 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
122 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
123 }
124
126 it.freezetag_frozen_timeout = 0;
127 it.freezetag_revive_time = 0;
129 });
130
131 game_stopped = true;
133 return true;
134}
135
137{
138 entity last_pl = NULL;
139 FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
140 if (!STAT(FROZEN, it) && GetResource(it, RES_HEALTH) >= 1)
141 {
142 if (!last_pl)
143 last_pl = it;
144 else
145 return NULL;
146 }
147 });
148 return last_pl;
149}
150
152{
155 {
157 if(pl)
158 Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
159 }
160}
161
163{
164 if(attacker == targ)
165 {
166 // you froze your own dumb self
167 // counted as "suicide" already
168 GameRules_scoring_add(targ, SCORE, -1);
169 }
170 else if(IS_PLAYER(attacker))
171 {
172 // got frozen by an enemy
173 // counted as "kill" and "death" already
174 if(SAME_TEAM(attacker, targ))
175 GameRules_scoring_add(attacker, SCORE, -1);
176 else
177 GameRules_scoring_add(attacker, SCORE, +1);
178 GameRules_scoring_add(targ, SCORE, -1);
179 }
180 // else nothing - got frozen by the gametype rules themselves
181}
182
184{
185 if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
186 {
187 delete(this);
188 return;
189 }
190 vector ice_org = this.owner.origin - '0 0 16';
191 if (this.origin != ice_org)
192 setorigin(this, ice_org);
193 this.nextthink = time;
194}
195
196// to be called when the player is frozen by freezetag (on death, spectator join etc), gives the score
197void freezetag_Freeze(entity targ, entity attacker)
198{
199 if(!IS_PLAYER(targ) || STAT(FROZEN, targ))
200 return;
201
202 targ.freezetag_frozen_time = time;
204 targ.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
205
206 STAT(FROZEN, targ) = true;
207 STAT(REVIVE_PROGRESS, targ) = 0;
208 SetResource(targ, RES_HEALTH, 1);
209 targ.revive_speed = 0;
210 if(targ.bot_attack)
212 targ.bot_attack = false;
213 targ.freeze_time = time;
214
215 entity ice = new(ice);
216 ice.owner = targ;
217 ice.scale = targ.scale;
218 // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player
220 ice.nextthink = time;
221 ice.frame = floor(random() * 21); // ice model has 20 different looking frames
222 setmodel(ice, MDL_ICE);
223 ice.alpha = 1;
224 ice.colormod = Team_ColorRGB(targ.team);
225 ice.glowmod = ice.colormod;
226 targ.iceblock = ice;
227 targ.revival_time = 0;
228
230
232
234 {
235 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
236 {
237 .entity weaponentity = weaponentities[slot];
238 if(it.(weaponentity).hook.aiment == targ)
239 RemoveHook(it.(weaponentity).hook);
240 }
241 });
242
243 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
244
246
247 freezetag_Add_Score(targ, attacker);
248}
249
250void freezetag_Unfreeze(entity targ, bool reset_health)
251{
252 if(!STAT(FROZEN, targ))
253 return;
254
255 if (reset_health)
256 SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
257
258 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
259
260 STAT(FROZEN, targ) = false;
261 STAT(REVIVE_PROGRESS, targ) = 0;
262 targ.revival_time = time;
263 if(!targ.bot_attack)
264 IL_PUSH(g_bot_targets, targ);
265 targ.bot_attack = true;
266
267 WaypointSprite_Kill(targ.waypointsprite_attached);
268
270 {
271 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
272 {
273 .entity weaponentity = weaponentities[slot];
274 if(it.(weaponentity).hook.aiment == targ)
275 RemoveHook(it.(weaponentity).hook);
276 }
277 });
278
279 // remove the ice block
280 if(targ.iceblock)
281 delete(targ.iceblock);
282 targ.iceblock = NULL;
283
284 targ.freezetag_frozen_time = 0;
285 targ.freezetag_frozen_timeout = 0;
286}
287
289{
290 if(IS_PLAYER(e) && (STAT(FROZEN, e) || IS_DEAD(e)))
291 return true;
292 return false;
293}
294
295
296// ================
297// Bot player logic
298// ================
299
302
303void havocbot_goalrating_ft_freeplayers(entity this, float ratingscale, vector org, float sradius)
304{
305 entity best_pl = NULL;
306 float best_dist2 = FLOAT_MAX;
307 FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
308 if (STAT(FROZEN, it))
309 {
310 if(vdist(it.origin - org, >, sradius))
311 continue;
312 navigation_routerating(this, it, ratingscale, 2000);
313 }
314 else if (best_dist2
315 && GetResource(it, RES_HEALTH) < GetResource(this, RES_HEALTH) + 30
316 && vlen2(it.origin - org) < best_dist2)
317 {
318 // If teamate is not frozen still seek them out as fight better
319 // in a group.
320 best_dist2 = vlen2(it.origin - org);
321 if (best_dist2 < 700 ** 2)
322 {
323 best_pl = NULL;
324 best_dist2 = 0; // already close to a teammate
325 }
326 else
327 best_pl = it;
328 }
329 });
330 if (best_pl)
331 navigation_routerating(this, best_pl, ratingscale / 2, 2000);
332}
333
335{
336 if(IS_DEAD(this))
337 return;
338
339 if (!this.havocbot_role_timeout)
340 this.havocbot_role_timeout = time + random() * 10 + 20;
341
342 // Count how many players on team are unfrozen.
343 int unfrozen = 0;
344 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !STAT(FROZEN, it), {
345 ++unfrozen;
346 });
347
348 // If only one left on team or if role has timed out then start trying to free players.
349 if ((!unfrozen && !STAT(FROZEN, this)) || time > this.havocbot_role_timeout)
350 {
351 LOG_TRACE("changing role to freeing");
352 this.havocbot_role = havocbot_role_ft_freeing;
353 this.havocbot_role_timeout = 0;
354 return;
355 }
356
358 {
360 havocbot_goalrating_items(this, 12000, this.origin, 10000);
361 havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
362 havocbot_goalrating_ft_freeplayers(this, 9000, this.origin, 10000);
363 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
365
367 }
368}
369
371{
372 if(IS_DEAD(this))
373 return;
374
375 if (!this.havocbot_role_timeout)
376 this.havocbot_role_timeout = time + random() * 10 + 20;
377
378 if (time > this.havocbot_role_timeout)
379 {
380 LOG_TRACE("changing role to offense");
381 this.havocbot_role = havocbot_role_ft_offense;
382 this.havocbot_role_timeout = 0;
383 return;
384 }
385
387 {
389 havocbot_goalrating_items(this, 10000, this.origin, 10000);
390 havocbot_goalrating_enemyplayers(this, 5000, this.origin, 10000);
391 havocbot_goalrating_ft_freeplayers(this, 20000, this.origin, 10000);
392 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
394
396 }
397}
398
399
400// ==============
401// Hook Functions
402// ==============
403
405{
406 if (!STAT(FROZEN, this))
408 freezetag_Unfreeze(this, false);
409
410 SetResourceExplicit(this, RES_HEALTH, 0); // neccessary to correctly count alive players
412}
413
415{
416 entity player = M_ARGV(0, entity);
417
418 ft_RemovePlayer(player);
419 return true;
420}
421
422MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver)
423{
424 entity player = M_ARGV(0, entity);
425
426 ft_RemovePlayer(player);
427}
428
430{
431 entity spectatee = M_ARGV(0, entity);
432 entity client = M_ARGV(1, entity);
433
434 STAT(FROZEN, client) = STAT(FROZEN, spectatee);
435 STAT(REVIVE_PROGRESS, client) = STAT(REVIVE_PROGRESS, spectatee);
436}
437
439{
440 entity player = M_ARGV(0, entity);
441
442 return STAT(FROZEN, player);
443}
444
445MUTATOR_HOOKFUNCTION(ft, PlayerDied)
446{
447 entity player = M_ARGV(0, entity);
448
449 freezetag_Unfreeze(player, false);
450}
451
452MUTATOR_HOOKFUNCTION(ft, PlayerDies)
453{
454 entity frag_attacker = M_ARGV(1, entity);
456 float frag_deathtype = M_ARGV(3, float);
457
460 {
461 if (STAT(FROZEN, frag_target))
464 frag_target.respawn_time = time;
465 frag_target.respawn_flags |= RESPAWN_FORCE;
466 return true;
467 }
468
469 frag_target.respawn_time = time + 1;
470 frag_target.respawn_flags |= RESPAWN_FORCE;
471
472 // let the player die, they will be automatically frozen when they respawn
473 // it fixes a bug where you both really die (gibbing) and get frozen
474 // if you succeed changing team through the menu
475 if (frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id || frag_target.killindicator_teamchange == -2)
476 {
477 freezetag_Add_Score(frag_target, frag_attacker);
480 frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
481 return true;
482 }
483
484 if(ITEM_DAMAGE_NEEDKILL(frag_deathtype))
485 {
486 // can't use freezetag_Add_Score here since it doesn't assign any points
487 // if the attacker is not a player (e.g. triggerhurt) by design
488 if (!STAT(FROZEN, frag_target) && !IS_PLAYER(frag_attacker))
490
491 // by restoring some health right after player death (soft-kill)
492 // weapons and ammo won't be reset
493 SetResourceExplicit(frag_target, RES_HEALTH, 1);
494 // restore armor as it was removed in PlayerDamage
495 SetResourceExplicit(frag_target, RES_ARMOR, frag_target.freezetag_frozen_armor);
496
497 // relocate
498 entity spot = SelectSpawnPoint(frag_target, true);
499 setorigin(frag_target, spot.origin);
500 frag_target.oldorigin = frag_target.origin;
501 frag_target.fixangle = true; // turn this way immediately
502 frag_target.angles = vec2(spot.angles);
503 frag_target.velocity = '0 0 0';
504 frag_target.oldvelocity = frag_target.velocity; // prevents fall damage, see CreatureFrame_FallDamage
505 frag_target.avelocity = '0 0 0';
506 frag_target.punchangle = '0 0 0';
507 frag_target.punchvector = '0 0 0';
508 }
509
510 if (STAT(FROZEN, frag_target))
511 return true;
512
513 freezetag_Freeze(frag_target, frag_attacker);
515
516 if(frag_attacker == frag_target || frag_attacker == NULL)
517 {
519 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
520 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
521 }
522 else
523 {
524 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
525 }
526
527 return true;
528}
529
530MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
531{
532 entity player = M_ARGV(0, entity);
533
534 freezetag_Unfreeze(player, false);
535
536 if(player.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
537 return true; // do nothing, round is starting right now
538
539 if(player.freezetag_frozen_timeout <= -2) // player was dead
540 {
541 freezetag_Freeze(player, NULL);
542 return true;
543 }
544
546
549 {
550 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
551 freezetag_Freeze(player, NULL);
552 }
553
554 return true;
555}
556
558{
559 eliminatedPlayers.SendFlags = 0xFFFFFF;
560}
561
562MUTATOR_HOOKFUNCTION(ft, PlayerAnim)
563{
564 entity player = M_ARGV(0, entity);
565
566 if(STAT(FROZEN, player))
567 M_ARGV(1, int) |= ANIMSTATE_FROZEN;
568}
569
570MUTATOR_HOOKFUNCTION(ft, reset_map_global)
571{
572 FOREACH_CLIENT(IS_PLAYER(it) && STAT(FROZEN, it), {
573 freezetag_Unfreeze(it, false);
574 });
575}
576
577MUTATOR_HOOKFUNCTION(ft, reset_map_players)
578{
580 CS(it).killcount = 0;
581 it.freezetag_revive_time = 0;
582 it.freezetag_frozen_timeout = -1;
584 it.freezetag_frozen_timeout = 0;
585 STAT(REVIVE_PROGRESS, it) = 0;
586 });
588 return true;
589}
590
592{
593 M_ARGV(2, float) = 0; // no frags counted in Freeze Tag
594 return true;
595}
596
597MUTATOR_HOOKFUNCTION(ft, LockWeapon)
598{
599 entity player = M_ARGV(0, entity);
600 return STAT(FROZEN, player);
601}
602
603MUTATOR_HOOKFUNCTION(ft, PlayerDamaged)
604{
606 return STAT(FROZEN, frag_target);
607}
608
609MUTATOR_HOOKFUNCTION(ft, AccuracyTargetValid)
610{
612
613 // damage to frozen players is good only if it happens in the frame they get frozen
614 if (STAT(FROZEN, frag_target) && time > frag_target.freeze_time)
616 return MUT_ACCADD_VALID;
617}
618
619MUTATOR_HOOKFUNCTION(ft, Damage_Calculate)
620{
621 entity frag_attacker = M_ARGV(1, entity);
623 float frag_deathtype = M_ARGV(3, float);
624 float frag_damage = M_ARGV(4, float);
626
627 frag_target.freezetag_frozen_armor = GetResource(frag_target, RES_ARMOR);
628
631 {
632 float t = 0;
634 && frag_target.freezetag_frozen_timeout > time)
635 {
637 {
639 t = vlen(frag_force);
640 // limit hit force considered at once, e.g when you have the Strength
641 // powerup but also with weapons that fire multiple projectiles at once (crylink)
642 if (frag_target.freezetag_frozen_force + t > maxforce)
643 {
644 t = max(0, maxforce - frag_target.freezetag_frozen_force);
645 frag_target.freezetag_frozen_force = maxforce;
646 }
647 else
648 frag_target.freezetag_frozen_force += t;
650 }
651 frag_target.freezetag_frozen_timeout -= t;
652 if (frag_target.freezetag_frozen_timeout < time)
653 frag_target.freezetag_frozen_timeout = time;
654 }
655 }
656
657 if(STAT(FROZEN, frag_target) && !ITEM_DAMAGE_NEEDKILL(frag_deathtype)
658 && frag_deathtype != DEATH_TEAMCHANGE.m_id && frag_deathtype != DEATH_AUTOTEAMCHANGE.m_id)
659 {
660 if(autocvar_g_frozen_revive_falldamage > 0 && frag_deathtype == DEATH_FALL.m_id && frag_damage >= autocvar_g_frozen_revive_falldamage)
661 {
664 Send_Effect(EFFECT_ICEORGLASS, frag_target.origin, '0 0 0', 3);
665 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, frag_target.netname);
666 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
667 }
668
669 frag_damage = 0;
671 }
672
673 if(IS_PLAYER(frag_target) && STAT(FROZEN, frag_target)
675 {
676 Send_Effect(EFFECT_TELEPORT, frag_target.origin, '0 0 0', 1);
677
678 entity spot = SelectSpawnPoint(frag_target, false);
679 if(spot)
680 {
681 frag_damage = 0;
682 frag_target.deadflag = DEAD_NO;
683
684 frag_target.angles = spot.angles;
685
686 frag_target.effects = 0;
687 frag_target.effects |= EF_TELEPORT_BIT;
688
689 frag_target.angles_z = 0; // never spawn tilted even if the spot says to
690 frag_target.fixangle = true; // turn this way immediately
691 frag_target.velocity = '0 0 0';
692 frag_target.avelocity = '0 0 0';
693 frag_target.punchangle = '0 0 0';
694 frag_target.punchvector = '0 0 0';
695 frag_target.oldvelocity = frag_target.velocity;
696
697 frag_target.spawnorigin = spot.origin;
698 setorigin(frag_target, spot.origin + '0 0 1' * (1 - frag_target.mins.z - 24));
699 // don't reset back to last position, even if new position is stuck in solid
700 frag_target.oldorigin = frag_target.origin;
701
702 Send_Effect(EFFECT_TELEPORT, frag_target.origin, '0 0 0', 1);
703 }
704 }
705
706 M_ARGV(4, float) = frag_damage;
708}
709
710#ifdef IN_REVIVING_RANGE
711 #undef IN_REVIVING_RANGE
712#endif
713
714#define IN_REVIVING_RANGE(player, it, revive_extra_size) \
715 (it != player && !IS_DEAD(it) && SAME_TEAM(it, player) \
716 && boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
717
719{
720 if(game_stopped)
721 return true;
722
725 return true;
726
727 entity player = M_ARGV(0, entity);
728 //if (STAT(FROZEN, player))
729 //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout)
730 //player.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (player.freezetag_frozen_timeout - time) / (player.freezetag_frozen_timeout - player.freezetag_frozen_time);
731
732 player.freezetag_frozen_force = 0;
733
734 if (!(frametime && IS_PLAYER(player)))
735 return true;
736
737 entity revivers_last = NULL;
738 entity revivers_first = NULL;
739
740 bool player_is_reviving = false;
741 int n = 0;
742 vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
744 // check if player is reviving anyone
745 if (STAT(FROZEN, it))
746 {
747 if ((STAT(FROZEN, player)))
748 continue;
749 if (!IN_REVIVING_RANGE(player, it, revive_extra_size))
750 continue;
751 player_is_reviving = true;
752 break;
753 }
754
755 if (!(STAT(FROZEN, player)))
756 continue; // both player and it are NOT frozen
757 if (!IN_REVIVING_RANGE(player, it, revive_extra_size))
758 continue;
759
760 // found a teammate that is reviving player
761 if (autocvar_g_freezetag_revive_time_to_score > 0 && STAT(FROZEN, player))
762 {
763 it.freezetag_revive_time += frametime / autocvar_g_freezetag_revive_time_to_score;
764 while (it.freezetag_revive_time > 1)
765 {
766 GameRules_scoring_add(it, SCORE, +1);
767 --it.freezetag_revive_time;
768 }
769 }
770 if (revivers_last)
771 revivers_last.chain = it;
772 revivers_last = it;
773 if (!revivers_first)
774 revivers_first = it;
775 ++n;
776 });
777 if (revivers_last)
778 revivers_last.chain = NULL;
779
780 // allow normal revival during automatic revival
781 // (if we wouldn't allow it then freezetag_frozen_timeout should be checked too in the previous loop)
782 //if (STAT(FROZEN, player)) // redundant check
783 if (!n && player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
784 n = -1;
785
786 float base_progress = 0;
787 if (STAT(FROZEN, player) && autocvar_g_freezetag_revive_auto
789 {
790 // NOTE if auto-revival is in progress, manual revive speed is reduced so that it always takes the same amount of time
791 base_progress = bound(0, (1 - (player.freezetag_frozen_timeout - time) / autocvar_g_freezetag_frozen_maxtime), 1);
792 }
793
794 if (!n) // no teammate nearby
795 {
797 if (STAT(FROZEN, player))
798 {
800 {
801 if (STAT(REVIVE_PROGRESS, player) > base_progress)
802 {
803 // reduce auto-revival time based on manual revival progress
804 base_progress = STAT(REVIVE_PROGRESS, player);
805 player.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime * (1 - STAT(REVIVE_PROGRESS, player));
806 }
807 // don't clear revive progress, it would allow stacking points
808 // by entering and exiting the revival zone many times
809 STAT(REVIVE_PROGRESS, player) = base_progress;
810 }
811 else
812 STAT(REVIVE_PROGRESS, player) = bound(base_progress, STAT(REVIVE_PROGRESS, player) - frametime * clearspeed * (1 - base_progress), 1);
813 }
814 else if (!STAT(FROZEN, player) && !player_is_reviving)
815 STAT(REVIVE_PROGRESS, player) = base_progress; // thawing nobody
816 }
817 else if (STAT(FROZEN, player)) // OK, there is at least one teammate reviving us
818 {
821 spd = autocvar_g_freezetag_revive_speed * (1 - base_progress);
822 STAT(REVIVE_PROGRESS, player) = bound(base_progress, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, spd), 1);
823
824 if(STAT(REVIVE_PROGRESS, player) >= 1)
825 {
826 float frozen_time = time - player.freezetag_frozen_time;
827 freezetag_Unfreeze(player, false);
829 player.spawnshieldtime = time + autocvar_g_freezetag_revive_spawnshield;
831
832 if(n == -1)
833 {
835 GameLogEcho(strcat(":ft:autorevival:", ftos(player.playerid)));
836 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, frozen_time);
837 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, player.netname, frozen_time);
838 return true;
839 }
840
841 // EVERY teammate nearby gets a point (even if multiple!)
842 for(entity it = revivers_first; it; it = it.chain)
843 {
844 GameRules_scoring_add(it, FREEZETAG_REVIVALS, +1);
846 GameRules_scoring_add(it, SCORE, +1);
848 }
849
850 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, revivers_first.netname);
851 Send_Notification(NOTIF_ONE, revivers_first, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
852 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED, player.netname, revivers_first.netname);
854 {
855 string revivers = "";
856 for(entity it = revivers_first; it; it = it.chain)
857 revivers = strcat(revivers, ftos(it.playerid), ",");
858 revivers = substring(revivers, 0, strlen(revivers) - 1);
859 GameLogEcho(strcat(":ft:revival:", ftos(player.playerid), ":", revivers));
860 }
861 }
862
863 for(entity it = revivers_first; it; it = it.chain)
864 STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
865 }
866
867 if (STAT(FROZEN, player))
868 {
869 entity player_wp = player.waypointsprite_attached;
870 if (n > 0 || (n == 0 && STAT(REVIVE_PROGRESS, player) > 0.95))
871 {
872 WaypointSprite_UpdateSprites(player_wp, WP_Reviving, WP_Null, WP_Null);
873 WaypointSprite_UpdateTeamRadar(player_wp, RADARICON_WAYPOINT, WP_REVIVING_COLOR);
874 }
875 else
876 {
877 WaypointSprite_UpdateSprites(player_wp, WP_Frozen, WP_Null, WP_Null);
878 WaypointSprite_UpdateTeamRadar(player_wp, RADARICON_WAYPOINT, WP_FROZEN_COLOR);
879 }
880
881 WaypointSprite_UpdateMaxHealth(player_wp, 1);
882 WaypointSprite_UpdateHealth(player_wp, STAT(REVIVE_PROGRESS, player));
883 }
884
885 return true;
886}
887
888MUTATOR_HOOKFUNCTION(ft, PlayerRegen)
889{
890 entity player = M_ARGV(0, entity);
891
892 return STAT(FROZEN, player);
893}
894
896{
897 if(MUTATOR_RETURNVALUE) return false;
898
900
901 if(STAT(FROZEN, toucher))
904}
905
907{
909
910 return STAT(FROZEN, toucher);
911}
912
927
928MUTATOR_HOOKFUNCTION(ft, AllowMobSpawning)
929{
930 M_ARGV(1, string) = "You can't spawn monsters while frozen";
931 return true;
932}
933
934MUTATOR_HOOKFUNCTION(ft, MonsterValidTarget)
935{
936 entity targ = M_ARGV(1, entity);
937
938 return !STAT(FROZEN, targ);
939}
940
941MUTATOR_HOOKFUNCTION(ft, TurretValidateTarget)
942{
943 entity targ = M_ARGV(1, entity);
944
945 if(STAT(FROZEN, targ))
946 {
947 M_ARGV(3, float) = -6;
948 return true;
949 }
950 return false;
951}
952
953MUTATOR_HOOKFUNCTION(ft, Bot_ForbidAttack)
954{
955 entity targ = M_ARGV(1, entity);
956
957 return STAT(FROZEN, targ);
958}
959
960MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
961{
962 entity bot = M_ARGV(0, entity);
963
964 if (!IS_DEAD(bot))
965 {
966 if (random() < 0.5)
967 bot.havocbot_role = havocbot_role_ft_freeing;
968 else
969 bot.havocbot_role = havocbot_role_ft_offense;
970 }
971
972 // if bots spawn all at once assign them a more appropriated role after a while
973 if (time < CS(bot).jointime + 1)
974 bot.havocbot_role_timeout = time + 10 + random() * 10;
975
976 return true;
977}
978
979MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
980{
981 if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
983}
984
985MUTATOR_HOOKFUNCTION(ft, FragCenterMessage)
986{
987 entity frag_attacker = M_ARGV(0, entity);
989 //float frag_deathtype = M_ARGV(2, float);
990 int kill_count_to_attacker = M_ARGV(3, int);
991 int kill_count_to_target = M_ARGV(4, int);
992
993 if(STAT(FROZEN, frag_target))
994 return; // target was already frozen, so this is just pushing them off the cliff
995
996 Send_Notification(NOTIF_ONE, frag_attacker, MSG_CHOICE, CHOICE_FRAG_FREEZE, frag_target.netname, kill_count_to_attacker, (IS_BOT_CLIENT(frag_target) ? -1 : CS(frag_target).ping));
997 Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target,
998 GetResource(frag_attacker, RES_HEALTH), GetResource(frag_attacker, RES_ARMOR), (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping));
999
1000 return true;
1001}
1002
1003MUTATOR_HOOKFUNCTION(ft, SV_ParseServerCommand)
1004{
1005 string cmd_name = M_ARGV(0, string);
1006 if (cmd_name == "shuffleteams")
1008 return false;
1009}
1010
1011MUTATOR_HOOKFUNCTION(ft, Scores_CountFragsRemaining)
1012{
1013 // announce remaining frags
1014 return true;
1015}
1016
1018{
1019 int freezetag_teams = autocvar_g_freezetag_teams_override;
1020 if(freezetag_teams < 2)
1021 freezetag_teams = cvar("g_freezetag_teams"); // read the cvar directly as it gets written earlier in the same frame
1022
1023 freezetag_teams = BITS(bound(2, freezetag_teams, 4));
1024 GameRules_scoring(freezetag_teams, SFL_SORT_PRIO_PRIMARY, 0, {
1025 field_team(ST_FT_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY);
1026 field(SP_FREEZETAG_REVIVALS, "revivals", 0);
1027 });
1028
1031
1033}
const int ANIMSTATE_FROZEN
void havocbot_goalrating_waypoints(entity this, float ratingscale, vector org, float sradius)
Definition roles.qc:16
void navigation_goalrating_start(entity this)
void navigation_goalrating_timeout_set(entity this)
Definition navigation.qc:20
bool navigation_goalrating_timeout(entity this)
Definition navigation.qc:44
void navigation_routerating(entity this, entity e, float f, float rangebias)
IntrusiveList g_bot_targets
Definition api.qh:149
void navigation_goalrating_end(entity this)
float havocbot_role_timeout
Definition api.qh:46
const int CBC_ORDER_FIRST
Definition base.qh:10
#define MUTATOR_HOOKFUNCTION(...)
Definition base.qh:335
#define MUTATOR_RETURNVALUE
Definition base.qh:328
#define BITS(n)
Definition bits.qh:9
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
void SetResource(entity e, Resource res_type, float amount)
Sets the current amount of resource the given entity will have.
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
float ping
Definition main.qh:169
entity owner
Definition main.qh:87
bool warmup_stage
Definition main.qh:120
const int IT_UNLIMITED_AMMO
Definition item.qh:23
const int IT_UNLIMITED_SUPERWEAPONS
Definition item.qh:24
#define setmodel(this, m)
Definition model.qh:26
#define M_ARGV(x, type)
Definition events.qh:17
#define IS_DEAD(s)
Definition player.qh:244
#define IS_PLAYER(s)
Definition player.qh:242
const int SFL_SORT_PRIO_PRIMARY
Definition scores.qh:134
int missing_teams_mask
Definition stats.qh:85
float game_stopped
Definition stats.qh:81
float frametime
float time
float nextthink
vector origin
const int EF_TELEPORT_BIT
#define strlen
void Send_Effect(entity eff, vector eff_loc, vector eff_vel, int eff_cnt)
Definition all.qc:155
void EliminatedPlayers_Init(float(entity) isEliminated_func)
entity eliminatedPlayers
Definition elimination.qh:3
RES_ARMOR
Definition ent_cs.qc:155
SetResourceExplicit(ent, RES_ARMOR, ReadByte() *DEC_FACTOR)) ENTCS_PROP(NAME
const float FLOAT_MAX
Definition float.qh:3
void GameLogEcho(string s)
Definition gamelog.qc:15
bool autocvar_sv_eventlog
Definition gamelog.qh:3
ERASEABLE void IL_REMOVE(IntrusiveList this, entity it)
Remove any element, anywhere in the list.
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
#define ClientKill
Definition _all.inc:254
#define PlayerPreThink
Definition _all.inc:258
#define PutClientInServer
Definition _all.inc:250
#define ClientDisconnect
Definition _all.inc:246
#define STAT(...)
Definition stats.qh:94
#define LOG_TRACE(...)
Definition log.qh:74
string cmd_name
Definition events.qh:12
float bound(float min, float value, float max)
string substring(string s, float start, float length)
float cvar(string name)
float random(void)
float vlen(vector v)
float min(float f,...)
string ftos(float f)
float fabs(float f)
float floor(float f)
float max(float f,...)
const vector WP_FROZEN_COLOR
Definition all.inc:8
const vector WP_REVIVING_COLOR
Definition all.inc:9
var void func_null()
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
void Send_Notification(NOTIF broadcast, entity client, MSG net_type, Notification net_name,...count)
Definition all.qc:1500
#define APP_TEAM_NUM(num, prefix)
Definition all.qh:88
#define NULL
Definition post.qh:14
float DEAD_NO
Definition progsdefs.qc:274
void round_handler_Init(float the_delay, float the_count, float the_round_timelimit)
void round_handler_Spawn(bool() canRoundStart_func, bool() canRoundEnd_func, void() roundStart_func)
#define round_handler_IsActive()
#define round_handler_GetEndTime()
#define round_handler_SetEndDelayTime(t)
#define round_handler_GetEndDelayTime()
#define round_handler_IsRoundStarted()
#define round_handler_ResetEndDelayTime()
#define round_handler_CountdownRunning()
float TeamScore_AddToTeam(int t, float scorefield, float score)
Adds a score to the given team.
Definition scores.qc:108
#define AVAILABLE_TEAMS
Number of teams that exist currently.
#define setthink(e, f)
vector
Definition self.qh:96
vector org
Definition self.qh:96
entity entity toucher
Definition self.qh:76
void
Definition self.qh:76
void SpectateCopy(entity this, entity spectatee)
Definition client.qc:1800
float jointime
Definition client.qh:66
const int RESPAWN_FORCE
Definition client.qh:326
void RemoveHook(entity this)
Definition hook.qc:48
void RemoveGrapplingHooks(entity pl)
Definition hook.qc:30
#define ITEM_DAMAGE_NEEDKILL(dt)
Definition items.qh:123
@ MUT_ACCADD_INDIFFERENT
Definition events.qh:862
@ MUT_ACCADD_VALID
Definition events.qh:860
@ MUT_ITEMTOUCH_RETURN
Definition events.qh:735
@ MUT_ITEMTOUCH_CONTINUE
Definition events.qh:734
entity SelectSpawnPoint(entity this, bool anypoint)
ClientState CS(Client this)
Definition state.qh:47
bool shuffleteams_on_reset_map
Definition sv_cmd.qh:7
void nades_GiveBonus(entity player, float score)
Definition sv_nades.qc:425
float frag_damage
Definition sv_ctf.qc:2316
vector frag_force
Definition sv_ctf.qc:2317
entity frag_target
Definition sv_ctf.qc:2315
void havocbot_role_ft_offense(entity this)
#define IN_REVIVING_RANGE(player, it, revive_extra_size)
float autocvar_g_ft_start_ammo_nails
void freezetag_count_alive_players()
void freezetag_Unfreeze(entity targ, bool reset_health)
float autocvar_g_freezetag_round_enddelay
float autocvar_g_ft_start_health
float autocvar_g_ft_start_ammo_fuel
float autocvar_g_freezetag_revive_clearspeed
float autocvar_g_ft_start_ammo_rockets
float autocvar_g_freezetag_frozen_maxtime
float autocvar_g_ft_start_ammo_shells
void freezetag_LastPlayerForTeam_Notify(entity this)
void havocbot_goalrating_ft_freeplayers(entity this, float ratingscale, vector org, float sradius)
void freezetag_Ice_Think(entity this)
void freezetag_Add_Score(entity targ, entity attacker)
void freezetag_Initialize()
int autocvar_g_freezetag_teams_override
void havocbot_role_ft_freeing(entity this)
float autocvar_g_freezetag_warmup
bool freezetag_isEliminated(entity e)
void freezetag_Freeze(entity targ, entity attacker)
bool freezetag_CheckTeams()
float autocvar_g_ft_start_armor
bool freezetag_CheckWinner()
float autocvar_g_freezetag_round_timelimit
float autocvar_g_ft_start_ammo_cells
entity freezetag_LastPlayerForTeam(entity this)
void ft_RemovePlayer(entity this)
bool autocvar_g_frozen_damage_trigger
int autocvar_g_freezetag_revive_auto_reducible
float autocvar_g_freezetag_revive_speed
float autocvar_g_freezetag_revive_spawnshield
float autocvar_g_freezetag_revive_auto_reducible_forcefactor
float autocvar_g_frozen_force
float autocvar_g_freezetag_revive_extra_size
const int ST_FT_ROUNDS
float autocvar_g_freezetag_revive_speed_t2s
int autocvar_g_freezetag_revive_auto_progress
float autocvar_g_frozen_revive_falldamage
string autocvar_g_freezetag_weaponarena
bool autocvar_g_freezetag_revive_auto
float autocvar_g_freezetag_revive_auto_reducible_maxforce
float autocvar_g_freezetag_revive_time_to_score
int autocvar_g_frozen_revive_falldamage_health
void nades_RemovePlayer(entity this)
Remove nades and bonus nades from a player.
Definition sv_nades.qc:894
int autocvar_g_nades_bonus_score_low
Definition sv_nades.qh:37
void havocbot_goalrating_items(entity this, float ratingscale, vector org, float sradius)
Definition roles.qc:106
void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org, float sradius)
Definition roles.qc:176
Header file that describes the resource system.
float autocvar_g_balance_pause_health_regen
#define GameRules_scoring_add(client, fld, value)
Definition sv_rules.qh:85
int total_players
Definition sv_rules.qh:12
#define GameRules_scoring(teams, spprio, stprio, fields)
Definition sv_rules.qh:58
int Team_GetNumberOfAlivePlayers(entity team_ent)
Returns the number of alive players in a team.
Definition teamplay.qc:114
entity Entity_GetTeam(entity this)
Returns the team entity of the given entity.
Definition teamplay.qc:215
int Team_GetWinnerAliveTeam()
Returns the winner team.
Definition teamplay.qc:124
bool Entity_HasValidTeam(entity this)
Returns whether the given entity belongs to a valid team.
Definition teamplay.qc:205
int Team_GetNumberOfAliveTeams()
Returns the number of alive teams.
Definition teamplay.qc:139
void Team_SetNumberOfAlivePlayers(entity team_ent, int number)
Sets the number of alive players in a team.
Definition teamplay.qc:119
entity Team_GetTeamFromIndex(int index)
Returns the global team entity at the given index.
Definition teamplay.qc:86
int teamplay_bitmask
The set of currently available teams (AVAILABLE_TEAMS is the number of them).
Definition teamplay.qh:18
#define SAME_TEAM(a, b)
Definition teams.qh:241
vector Team_ColorRGB(int teamid)
Definition teams.qh:76
#define DIFF_TEAM(a, b)
Definition teams.qh:242
int Team_IndexToBit(int index)
Converts team index into bit value that is used in team bitmasks.
Definition teams.qh:211
#define IS_REAL_CLIENT(v)
Definition utils.qh:17
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:52
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition utils.qh:15
#define vlen2(v)
Definition vector.qh:4
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt().
Definition vector.qh:8
#define vec2(...)
Definition vector.qh:95
void WaypointSprite_Kill(entity wp)
void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
void WaypointSprite_UpdateMaxHealth(entity e, float f)
entity WaypointSprite_Spawn(entity spr, float _lifetime, float maxdistance, entity ref, vector ofs, entity showto, float t, entity own,.entity ownfield, float hideable, entity icon)
void WaypointSprite_UpdateHealth(entity e, float f)
entity waypointsprite_attached
const int MAX_WEAPONSLOTS
Definition weapon.qh:16
entity weaponentities[MAX_WEAPONSLOTS]
Definition weapon.qh:17
float warmup_start_ammo_cells
Definition world.qh:105
float start_ammo_shells
Definition world.qh:84
float warmup_start_ammo_rockets
Definition world.qh:104
float warmup_start_ammo_shells
Definition world.qh:102
float start_ammo_fuel
Definition world.qh:88
int start_items
Definition world.qh:83
float warmup_start_ammo_nails
Definition world.qh:103
float start_ammo_cells
Definition world.qh:87
float warmup_start_health
Definition world.qh:107
float start_ammo_rockets
Definition world.qh:86
float start_armorvalue
Definition world.qh:97
float warmup_start_ammo_fuel
Definition world.qh:106
float start_health
Definition world.qh:96
float warmup_start_armorvalue
Definition world.qh:108
float start_ammo_nails
Definition world.qh:85