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 <= NUM_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 |= 1;
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 <= NUM_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)
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
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 });
587 return true;
588}
589
591{
592 M_ARGV(2, float) = 0; // no frags counted in Freeze Tag
593 return true;
594}
595
596MUTATOR_HOOKFUNCTION(ft, LockWeapon)
597{
598 entity player = M_ARGV(0, entity);
599 return STAT(FROZEN, player);
600}
601
602MUTATOR_HOOKFUNCTION(ft, PlayerDamaged)
603{
605 return STAT(FROZEN, frag_target);
606}
607
608MUTATOR_HOOKFUNCTION(ft, AccuracyTargetValid)
609{
611
612 // damage to frozen players is good only if it happens in the frame they get frozen
613 if (STAT(FROZEN, frag_target) && time > frag_target.freeze_time)
615 return MUT_ACCADD_VALID;
616}
617
618MUTATOR_HOOKFUNCTION(ft, Damage_Calculate)
619{
620 entity frag_attacker = M_ARGV(1, entity);
622 float frag_deathtype = M_ARGV(3, float);
623 float frag_damage = M_ARGV(4, float);
625
626 frag_target.freezetag_frozen_armor = GetResource(frag_target, RES_ARMOR);
627
630 {
631 float t = 0;
633 && frag_target.freezetag_frozen_timeout > time)
634 {
636 {
638 t = vlen(frag_force);
639 // limit hit force considered at once, e.g when you have the Strength
640 // powerup but also with weapons that fire multiple projectiles at once (crylink)
641 if (frag_target.freezetag_frozen_force + t > maxforce)
642 {
643 t = max(0, maxforce - frag_target.freezetag_frozen_force);
644 frag_target.freezetag_frozen_force = maxforce;
645 }
646 else
647 frag_target.freezetag_frozen_force += t;
649 }
650 frag_target.freezetag_frozen_timeout -= t;
651 if (frag_target.freezetag_frozen_timeout < time)
652 frag_target.freezetag_frozen_timeout = time;
653 }
654 }
655
656 if(STAT(FROZEN, frag_target) && !ITEM_DAMAGE_NEEDKILL(frag_deathtype)
657 && frag_deathtype != DEATH_TEAMCHANGE.m_id && frag_deathtype != DEATH_AUTOTEAMCHANGE.m_id)
658 {
659 if(autocvar_g_frozen_revive_falldamage > 0 && frag_deathtype == DEATH_FALL.m_id && frag_damage >= autocvar_g_frozen_revive_falldamage)
660 {
663 Send_Effect(EFFECT_ICEORGLASS, frag_target.origin, '0 0 0', 3);
664 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, frag_target.netname);
665 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
666 }
667
668 frag_damage = 0;
670 }
671
672 if(IS_PLAYER(frag_target) && STAT(FROZEN, frag_target)
674 {
675 Send_Effect(EFFECT_TELEPORT, frag_target.origin, '0 0 0', 1);
676
677 entity spot = SelectSpawnPoint(frag_target, false);
678 if(spot)
679 {
680 frag_damage = 0;
681 frag_target.deadflag = DEAD_NO;
682
683 frag_target.angles = spot.angles;
684
685 frag_target.effects = 0;
686 frag_target.effects |= EF_TELEPORT_BIT;
687
688 frag_target.angles_z = 0; // never spawn tilted even if the spot says to
689 frag_target.fixangle = true; // turn this way immediately
690 frag_target.velocity = '0 0 0';
691 frag_target.avelocity = '0 0 0';
692 frag_target.punchangle = '0 0 0';
693 frag_target.punchvector = '0 0 0';
694 frag_target.oldvelocity = frag_target.velocity;
695
696 frag_target.spawnorigin = spot.origin;
697 setorigin(frag_target, spot.origin + '0 0 1' * (1 - frag_target.mins.z - 24));
698 // don't reset back to last position, even if new position is stuck in solid
699 frag_target.oldorigin = frag_target.origin;
700
701 Send_Effect(EFFECT_TELEPORT, frag_target.origin, '0 0 0', 1);
702 }
703 }
704
705 M_ARGV(4, float) = frag_damage;
707}
708
709#ifdef IN_REVIVING_RANGE
710 #undef IN_REVIVING_RANGE
711#endif
712
713#define IN_REVIVING_RANGE(player, it, revive_extra_size) \
714 (it != player && !IS_DEAD(it) && SAME_TEAM(it, player) \
715 && boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
716
718{
719 if(game_stopped)
720 return true;
721
724 return true;
725
726 entity player = M_ARGV(0, entity);
727 //if (STAT(FROZEN, player))
728 //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout)
729 //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);
730
731 player.freezetag_frozen_force = 0;
732
733 if (!(frametime && IS_PLAYER(player)))
734 return true;
735
736 entity revivers_last = NULL;
737 entity revivers_first = NULL;
738
739 bool player_is_reviving = false;
740 int n = 0;
741 vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
743 // check if player is reviving anyone
744 if (STAT(FROZEN, it))
745 {
746 if ((STAT(FROZEN, player)))
747 continue;
748 if (!IN_REVIVING_RANGE(player, it, revive_extra_size))
749 continue;
750 player_is_reviving = true;
751 break;
752 }
753
754 if (!(STAT(FROZEN, player)))
755 continue; // both player and it are NOT frozen
756 if (!IN_REVIVING_RANGE(player, it, revive_extra_size))
757 continue;
758
759 // found a teammate that is reviving player
760 if (autocvar_g_freezetag_revive_time_to_score > 0 && STAT(FROZEN, player))
761 {
762 it.freezetag_revive_time += frametime / autocvar_g_freezetag_revive_time_to_score;
763 while (it.freezetag_revive_time > 1)
764 {
765 GameRules_scoring_add(it, SCORE, +1);
766 it.freezetag_revive_time -= 1;
767 }
768 }
769 if (revivers_last)
770 revivers_last.chain = it;
771 revivers_last = it;
772 if (!revivers_first)
773 revivers_first = it;
774 ++n;
775 });
776 if (revivers_last)
777 revivers_last.chain = NULL;
778
779 // allow normal revival during automatic revival
780 // (if we wouldn't allow it then freezetag_frozen_timeout should be checked too in the previous loop)
781 //if (STAT(FROZEN, player)) // redundant check
782 if (!n && player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
783 n = -1;
784
785 float base_progress = 0;
786 if (STAT(FROZEN, player) && autocvar_g_freezetag_revive_auto
788 {
789 // NOTE if auto-revival is in progress, manual revive speed is reduced so that it always takes the same amount of time
790 base_progress = bound(0, (1 - (player.freezetag_frozen_timeout - time) / autocvar_g_freezetag_frozen_maxtime), 1);
791 }
792
793 if (!n) // no teammate nearby
794 {
796 if (STAT(FROZEN, player))
797 {
799 {
800 if (STAT(REVIVE_PROGRESS, player) > base_progress)
801 {
802 // reduce auto-revival time based on manual revival progress
803 base_progress = STAT(REVIVE_PROGRESS, player);
804 player.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime * (1 - STAT(REVIVE_PROGRESS, player));
805 }
806 // don't clear revive progress, it would allow stacking points
807 // by entering and exiting the revival zone many times
808 STAT(REVIVE_PROGRESS, player) = base_progress;
809 }
810 else
811 STAT(REVIVE_PROGRESS, player) = bound(base_progress, STAT(REVIVE_PROGRESS, player) - frametime * clearspeed * (1 - base_progress), 1);
812 }
813 else if (!STAT(FROZEN, player) && !player_is_reviving)
814 STAT(REVIVE_PROGRESS, player) = base_progress; // thawing nobody
815 }
816 else if (STAT(FROZEN, player)) // OK, there is at least one teammate reviving us
817 {
820 spd = autocvar_g_freezetag_revive_speed * (1 - base_progress);
821 STAT(REVIVE_PROGRESS, player) = bound(base_progress, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, spd), 1);
822
823 if(STAT(REVIVE_PROGRESS, player) >= 1)
824 {
825 float frozen_time = time - player.freezetag_frozen_time;
826 freezetag_Unfreeze(player, false);
828 player.spawnshieldtime = time + autocvar_g_freezetag_revive_spawnshield;
830
831 if(n == -1)
832 {
834 GameLogEcho(strcat(":ft:autorevival:", ftos(player.playerid)));
835 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, frozen_time);
836 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, player.netname, frozen_time);
837 return true;
838 }
839
840 // EVERY teammate nearby gets a point (even if multiple!)
841 for(entity it = revivers_first; it; it = it.chain)
842 {
843 GameRules_scoring_add(it, FREEZETAG_REVIVALS, +1);
845 GameRules_scoring_add(it, SCORE, +1);
847 }
848
849 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, revivers_first.netname);
850 Send_Notification(NOTIF_ONE, revivers_first, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
851 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED, player.netname, revivers_first.netname);
853 {
854 string revivers = "";
855 for(entity it = revivers_first; it; it = it.chain)
856 revivers = strcat(revivers, ftos(it.playerid), ",");
857 revivers = substring(revivers, 0, strlen(revivers) - 1);
858 GameLogEcho(strcat(":ft:revival:", ftos(player.playerid), ":", revivers));
859 }
860 }
861
862 for(entity it = revivers_first; it; it = it.chain)
863 STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
864 }
865
866 if (STAT(FROZEN, player))
867 {
868 entity player_wp = player.waypointsprite_attached;
869 if (n > 0 || (n == 0 && STAT(REVIVE_PROGRESS, player) > 0.95))
870 {
871 WaypointSprite_UpdateSprites(player_wp, WP_Reviving, WP_Null, WP_Null);
872 WaypointSprite_UpdateTeamRadar(player_wp, RADARICON_WAYPOINT, WP_REVIVING_COLOR);
873 }
874 else
875 {
876 WaypointSprite_UpdateSprites(player_wp, WP_Frozen, WP_Null, WP_Null);
877 WaypointSprite_UpdateTeamRadar(player_wp, RADARICON_WAYPOINT, WP_FROZEN_COLOR);
878 }
879
880 WaypointSprite_UpdateMaxHealth(player_wp, 1);
881 WaypointSprite_UpdateHealth(player_wp, STAT(REVIVE_PROGRESS, player));
882 }
883
884 return true;
885}
886
887MUTATOR_HOOKFUNCTION(ft, PlayerRegen)
888{
889 entity player = M_ARGV(0, entity);
890
891 return STAT(FROZEN, player);
892}
893
895{
896 if(MUTATOR_RETURNVALUE) return false;
897
899
900 if(STAT(FROZEN, toucher))
903}
904
906{
908
909 return STAT(FROZEN, toucher);
910}
911
926
927MUTATOR_HOOKFUNCTION(ft, AllowMobSpawning)
928{
929 M_ARGV(1, string) = "You can't spawn monsters while frozen";
930 return true;
931}
932
933MUTATOR_HOOKFUNCTION(ft, MonsterValidTarget)
934{
935 entity targ = M_ARGV(1, entity);
936
937 return !STAT(FROZEN, targ);
938}
939
940MUTATOR_HOOKFUNCTION(ft, TurretValidateTarget)
941{
942 entity targ = M_ARGV(1, entity);
943
944 if(STAT(FROZEN, targ))
945 {
946 M_ARGV(3, float) = -6;
947 return true;
948 }
949 return false;
950}
951
952MUTATOR_HOOKFUNCTION(ft, BotShouldAttack)
953{
954 entity targ = M_ARGV(1, entity);
955
956 return STAT(FROZEN, targ);
957}
958
959MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
960{
961 entity bot = M_ARGV(0, entity);
962
963 if (!IS_DEAD(bot))
964 {
965 if (random() < 0.5)
966 bot.havocbot_role = havocbot_role_ft_freeing;
967 else
968 bot.havocbot_role = havocbot_role_ft_offense;
969 }
970
971 // if bots spawn all at once assign them a more appropriated role after a while
972 if (time < CS(bot).jointime + 1)
973 bot.havocbot_role_timeout = time + 10 + random() * 10;
974
975 return true;
976}
977
983
984MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
985{
986 if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
988}
989
990MUTATOR_HOOKFUNCTION(ft, FragCenterMessage)
991{
992 entity frag_attacker = M_ARGV(0, entity);
994 //float frag_deathtype = M_ARGV(2, float);
995 int kill_count_to_attacker = M_ARGV(3, int);
996 int kill_count_to_target = M_ARGV(4, int);
997
998 if(STAT(FROZEN, frag_target))
999 return; // target was already frozen, so this is just pushing them off the cliff
1000
1001 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));
1002 Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target,
1003 GetResource(frag_attacker, RES_HEALTH), GetResource(frag_attacker, RES_ARMOR), (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping));
1004
1005 return true;
1006}
1007
1008MUTATOR_HOOKFUNCTION(ft, SV_ParseServerCommand)
1009{
1010 string cmd_name = M_ARGV(0, string);
1011 if (cmd_name == "shuffleteams")
1013 return false;
1014}
1015
1016MUTATOR_HOOKFUNCTION(ft, Scores_CountFragsRemaining)
1017{
1018 // announce remaining frags
1019 return true;
1020}
1021
1023{
1025 if(freezetag_teams < 2)
1026 freezetag_teams = cvar("g_freezetag_teams"); // read the cvar directly as it gets written earlier in the same frame
1027
1030 field_team(ST_FT_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY);
1031 field(SP_FREEZETAG_REVIVALS, "revivals", 0);
1032 });
1033
1036
1038}
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
const int CBC_ORDER_EXCLUSIVE
Definition base.qh:12
#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.
bool SetResourceExplicit(entity e, Resource res_type, float amount)
Sets the resource amount of an entity without calling any hooks.
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:245
#define IS_PLAYER(s)
Definition player.qh:243
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:124
void EliminatedPlayers_Init(float(entity) isEliminated_func)
entity eliminatedPlayers
Definition elimination.qh:3
RES_ARMOR
Definition ent_cs.qc:130
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:250
#define PlayerPreThink
Definition _all.inc:254
#define PutClientInServer
Definition _all.inc:246
#define ClientDisconnect
Definition _all.inc:242
#define STAT(...)
Definition stats.qh:82
#define LOG_TRACE(...)
Definition log.qh:76
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:1573
#define APP_TEAM_NUM(num, prefix)
Definition all.qh:84
#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:107
int NumTeams(int teams)
#define setthink(e, f)
vector
Definition self.qh:92
vector org
Definition self.qh:92
entity entity toucher
Definition self.qh:72
void
Definition self.qh:72
void SpectateCopy(entity this, entity spectatee)
Definition client.qc:1793
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:129
@ 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:434
float frag_damage
Definition sv_ctf.qc:2322
vector frag_force
Definition sv_ctf.qc:2323
entity frag_target
Definition sv_ctf.qc:2321
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
float freezetag_teams
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)
Definition sv_nades.qc:885
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:85
entity Entity_GetTeam(entity this)
Returns the team entity of the given entity.
Definition teamplay.qc:186
int Team_GetWinnerAliveTeam()
Returns the winner team.
Definition teamplay.qc:95
bool Entity_HasValidTeam(entity this)
Returns whether the given entity belongs to a valid team.
Definition teamplay.qc:176
entity TeamBalance_CheckAllowedTeams(entity for_whom)
Checks whether the player can join teams according to global configuration and mutator settings.
Definition teamplay.qc:459
int Team_GetNumberOfAliveTeams()
Returns the number of alive teams.
Definition teamplay.qc:110
void Team_SetNumberOfAlivePlayers(entity team_ent, int number)
Sets the number of alive players in a team.
Definition teamplay.qc:90
entity Team_GetTeamFromIndex(int index)
Returns the global team entity at the given index.
Definition teamplay.qc:57
#define SAME_TEAM(a, b)
Definition teams.qh:241
vector Team_ColorRGB(int teamid)
Definition teams.qh:76
const int NUM_TEAMS
Number of teams in the game.
Definition teams.qh:3
#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:50
#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:90
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