]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_freezetag.qc
Do not unfreeze automatically any player if the round is over
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_freezetag.qc
1 .float freezetag_frozen_timeout;
2 .float freezetag_revive_progress;
3 float freezetag_teams;
4 float freezetag_CheckTeams();
5 float freezetag_CheckWinner();
6 void freezetag_Initialize()
7 {
8         precache_model("models/ice/ice.md3");
9         ScoreRules_freezetag();
10
11         round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null, 5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
12
13         addstat(STAT_REDALIVE, AS_INT, redalive_stat);
14         addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
15         addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
16         addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
17
18         addstat(STAT_FROZEN, AS_INT, freezetag_frozen);
19         addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, freezetag_revive_progress);
20 }
21
22 void freezetag_count_alive_players()
23 {
24         entity e;
25         total_players = redalive = bluealive = yellowalive = pinkalive = 0;
26         FOR_EACH_PLAYER(e) {
27                 if(e.team == COLOR_TEAM1 && e.health >= 1)
28                 {
29                         ++total_players;
30                         if (!e.freezetag_frozen) ++redalive;
31                 }
32                 else if(e.team == COLOR_TEAM2 && e.health >= 1)
33                 {
34                         ++total_players;
35                         if (!e.freezetag_frozen) ++bluealive;
36                 }
37                 else if(e.team == COLOR_TEAM3 && e.health >= 1)
38                 {
39                         ++total_players;
40                         if (!e.freezetag_frozen) ++yellowalive;
41                 }
42                 else if(e.team == COLOR_TEAM4 && e.health >= 1)
43                 {
44                         ++total_players;
45                         if (!e.freezetag_frozen) ++pinkalive;
46                 }
47         }
48         FOR_EACH_REALCLIENT(e) {
49                 e.redalive_stat = redalive;
50                 e.bluealive_stat = bluealive;
51                 e.yellowalive_stat = yellowalive;
52                 e.pinkalive_stat = pinkalive;
53         }
54 }
55 #define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
56 #define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == freezetag_teams)
57
58 float prev_total_players;
59 float freezetag_CheckTeams()
60 {
61         entity e;
62         if(FREEZETAG_ALIVE_TEAMS_OK())
63         {
64                 if(prev_total_players != -1)
65                 {
66                         FOR_EACH_REALCLIENT(e)
67                                 Send_CSQC_Centerprint_Generic_Expire(e, CPID_WAITING_PLAYERS);
68                 }
69                 prev_total_players = -1;
70                 return 1;
71         }
72         if(prev_total_players != total_players)
73         {
74                 string teams_missing = "";
75                 if(!redalive)   teams_missing = strcat(teams_missing, ColoredTeamName(COLOR_TEAM1), ", ");
76                 if(!bluealive)  teams_missing = strcat(teams_missing, ColoredTeamName(COLOR_TEAM2), ", ");
77                 if(freezetag_teams >= 3)
78                 if(!yellowalive)        teams_missing = strcat(teams_missing, ColoredTeamName(COLOR_TEAM3), ", ");
79                 if(freezetag_teams == 4)
80                 if(!pinkalive)  teams_missing = strcat(teams_missing, ColoredTeamName(COLOR_TEAM4), ", ");
81                 teams_missing = substring(teams_missing, 0, strlen(teams_missing)-2);
82
83                 FOR_EACH_REALCLIENT(e)
84                         Send_CSQC_Centerprint_Generic(e, CPID_WAITING_PLAYERS, strcat("Waiting for players to join...\n\nNeed active players for: ", teams_missing), -1, 0);
85                 prev_total_players = total_players;
86         }
87         return 0;
88 }
89
90 float freezetag_getWinnerTeam()
91 {
92         float winner_team = 0;
93         if(redalive >= 1)
94                 winner_team = COLOR_TEAM1;
95         if(bluealive >= 1)
96         {
97                 if(winner_team) return 0;
98                 winner_team = COLOR_TEAM2;
99         }
100         if(yellowalive >= 1)
101         {
102                 if(winner_team) return 0;
103                 winner_team = COLOR_TEAM3;
104         }
105         if(pinkalive >= 1)
106         {
107                 if(winner_team) return 0;
108                 winner_team = COLOR_TEAM4;
109         }
110         if(winner_team)
111                 return winner_team;
112         return -1; // no player left
113 }
114
115 float freezetag_CheckWinner()
116 {
117         entity e;
118         if(round_handler_GetTimeLeft() <= 0)
119         {
120                 FOR_EACH_REALCLIENT(e)
121                         centerprint(e, "Round over, there's no winner");
122                 bprint("Round over, there's no winner.\n");
123                 FOR_EACH_PLAYER(e)
124                         e.freezetag_frozen_timeout = 0;
125                 return 1;
126         }
127
128         if(FREEZETAG_ALIVE_TEAMS() > 1)
129                 return 0;
130
131         float winner_team;
132         string teamname;
133         winner_team = freezetag_getWinnerTeam();
134         if(winner_team > 0)
135         {
136                 teamname = ColoredTeamName(winner_team);
137                 FOR_EACH_REALCLIENT(e)
138                         centerprint(e, strcat(teamname, "^5 wins the round, all other teams were frozen."));
139                 bprint(teamname, "^5 wins the round since all the other teams were frozen.\n");
140                 TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
141         }
142         else if(winner_team == -1)
143         {
144                 FOR_EACH_REALCLIENT(e)
145                         centerprint(e, "^5Round tied! All teams were frozen.");
146                 bprint("^5Round tied! All teams were frozen.\n");
147         }
148
149         FOR_EACH_PLAYER(e)
150                 e.freezetag_frozen_timeout = 0;
151         return 1;
152 }
153
154 // this is needed to allow the player to turn his view around (fixangle can't
155 // be used to freeze his view, as that also changes the angles), while not
156 // turning that ice object with the player
157 void freezetag_Ice_Think()
158 {
159         setorigin(self, self.owner.origin - '0 0 16');
160         self.nextthink = time;
161 }
162
163 void freezetag_Add_Score(entity attacker)
164 {
165         if(attacker == self)
166         {
167                 // you froze your own dumb self
168                 // counted as "suicide" already
169                 PlayerScore_Add(self, SP_SCORE, -1);
170         }
171         else if(attacker.classname == "player")
172         {
173                 // got frozen by an enemy
174                 // counted as "kill" and "death" already
175                 PlayerScore_Add(self, SP_SCORE, -1);
176                 PlayerScore_Add(attacker, SP_SCORE, +1);
177         }
178         // else nothing - got frozen by the game type rules themselves
179 }
180
181 void freezetag_Freeze(entity attacker)
182 {
183         if(self.freezetag_frozen)
184                 return;
185         self.freezetag_frozen = 1;
186         self.freezetag_revive_progress = 0;
187         self.health = 1;
188         if(autocvar_g_freezetag_frozen_timeout > 0)
189                 self.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_timeout;
190
191         freezetag_count_alive_players();
192
193         entity ice;
194         ice = spawn();
195         ice.owner = self;
196         ice.classname = "freezetag_ice";
197         ice.think = freezetag_Ice_Think;
198         ice.nextthink = time;
199         ice.frame = floor(random() * 21); // ice model has 20 different looking frames
200         setmodel(ice, "models/ice/ice.md3");
201
202         entity oldself;
203         oldself = self;
204         self = ice;
205         freezetag_Ice_Think();
206         self = oldself;
207
208         RemoveGrapplingHook(self);
209
210         // add waypoint
211         WaypointSprite_Spawn("freezetag_frozen", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attached, TRUE, RADARICON_WAYPOINT, '0.25 0.90 1');
212
213         freezetag_Add_Score(attacker);
214 }
215
216 void freezetag_Unfreeze(entity attacker)
217 {
218         self.freezetag_frozen = 0;
219         self.freezetag_frozen_timeout = 0;
220         self.freezetag_revive_progress = 0;
221
222         // remove the ice block
223         entity ice;
224         for(ice = world; (ice = find(ice, classname, "freezetag_ice")); ) if(ice.owner == self)
225         {
226                 remove(ice);
227                 break;
228         }
229
230         // remove waypoint
231         if(self.waypointsprite_attached)
232                 WaypointSprite_Kill(self.waypointsprite_attached);
233 }
234
235
236 // ================
237 // Bot player logic
238 // ================
239
240 void() havocbot_role_ft_freeing;
241 void() havocbot_role_ft_offense;
242
243 void havocbot_goalrating_freeplayers(float ratingscale, vector org, float sradius)
244 {
245         entity head;
246         float distance;
247
248         FOR_EACH_PLAYER(head)
249         {
250                 if ((head != self) && (head.team == self.team))
251                 {
252                         if (head.freezetag_frozen)
253                         {
254                                 distance = vlen(head.origin - org);
255                                 if (distance > sradius)
256                                         continue;
257                                 navigation_routerating(head, ratingscale, 2000);
258                         }
259                         else
260                         {
261                                 // If teamate is not frozen still seek them out as fight better
262                                 // in a group.
263                                 navigation_routerating(head, ratingscale/3, 2000);
264                         }
265                 }
266         }
267 }
268
269 void havocbot_role_ft_offense()
270 {
271         entity head;
272         float unfrozen;
273
274         if(self.deadflag != DEAD_NO)
275                 return;
276
277         if (!self.havocbot_role_timeout)
278                 self.havocbot_role_timeout = time + random() * 10 + 20;
279
280         // Count how many players on team are unfrozen.
281         unfrozen = 0;
282         FOR_EACH_PLAYER(head)
283         {
284                 if ((head.team == self.team) && (!head.freezetag_frozen))
285                         unfrozen++;
286         }
287
288         // If only one left on team or if role has timed out then start trying to free players.
289         if (((unfrozen == 0) && (!self.freezetag_frozen)) || (time > self.havocbot_role_timeout))
290         {
291                 dprint("changing role to freeing\n");
292                 self.havocbot_role = havocbot_role_ft_freeing;
293                 self.havocbot_role_timeout = 0;
294                 return;
295         }
296
297         if (time > self.bot_strategytime)
298         {
299                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
300
301                 navigation_goalrating_start();
302                 havocbot_goalrating_items(10000, self.origin, 10000);
303                 havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
304                 havocbot_goalrating_freeplayers(9000, self.origin, 10000);
305                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
306                 navigation_goalrating_end();
307         }
308 }
309
310 void havocbot_role_ft_freeing()
311 {
312         if(self.deadflag != DEAD_NO)
313                 return;
314
315         if (!self.havocbot_role_timeout)
316                 self.havocbot_role_timeout = time + random() * 10 + 20;
317
318         if (time > self.havocbot_role_timeout)
319         {
320                 dprint("changing role to offense\n");
321                 self.havocbot_role = havocbot_role_ft_offense;
322                 self.havocbot_role_timeout = 0;
323                 return;
324         }
325
326         if (time > self.bot_strategytime)
327         {
328                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
329
330                 navigation_goalrating_start();
331                 havocbot_goalrating_items(8000, self.origin, 10000);
332                 havocbot_goalrating_enemyplayers(10000, self.origin, 10000);
333                 havocbot_goalrating_freeplayers(20000, self.origin, 10000);
334                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
335                 navigation_goalrating_end();
336         }
337 }
338
339
340 // ==============
341 // Hook Functions
342 // ==============
343
344 MUTATOR_HOOKFUNCTION(freezetag_RemovePlayer)
345 {
346         self.health = 0; // neccessary to update correctly alive stats
347         freezetag_Unfreeze(world);
348         freezetag_count_alive_players();
349         return 1;
350 }
351
352 MUTATOR_HOOKFUNCTION(freezetag_PlayerDies)
353 {
354         if(round_handler_IsActive())
355         if(round_handler_CountdownRunning())
356         {
357                 if(self.freezetag_frozen)
358                         freezetag_Unfreeze(world);
359                 freezetag_count_alive_players();
360                 return 1; // let the player die so that he can respawn whenever he wants
361         }
362
363         // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
364         // you succeed changing team through the menu: you both really die (gibbing) and get frozen
365         if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
366                 || frag_deathtype == DEATH_TEAMCHANGE || frag_deathtype == DEATH_AUTOTEAMCHANGE)
367         {
368                 // let the player die, he will be automatically frozen when he respawns
369                 if(!self.freezetag_frozen)
370                 {
371                         freezetag_Add_Score(frag_attacker);
372                         freezetag_count_alive_players();
373                 }
374                 else
375                         freezetag_Unfreeze(world); // remove ice
376                 self.freezetag_frozen_timeout = -2; // freeze on respawn
377                 return 1;
378         }
379
380         if(self.freezetag_frozen)
381                 return 1;
382
383         freezetag_Freeze(frag_attacker);
384
385         if(frag_attacker == frag_target || frag_attacker == world)
386         {
387                 if(frag_target.classname == STR_PLAYER)
388                         centerprint(frag_target, "^1You froze yourself.\n");
389                 bprint("^7", frag_target.netname, "^1 froze himself.\n");
390         }
391         else
392         {
393                 if(frag_target.classname == STR_PLAYER)
394                         centerprint(frag_target, strcat("^1You were frozen by ^7", frag_attacker.netname, ".\n"));
395                 if(frag_attacker.classname == STR_PLAYER)
396                         centerprint(frag_attacker, strcat("^2You froze ^7", frag_target.netname, ".\n"));
397                 bprint("^7", frag_target.netname, "^1 was frozen by ^7", frag_attacker.netname, ".\n");
398         }
399
400         frag_target.health = 1; // "respawn" the player :P
401
402         return 1;
403 }
404
405 MUTATOR_HOOKFUNCTION(freezetag_PlayerSpawn)
406 {
407         if(self.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
408                 return 1; // do nothing, round is starting right now
409
410         if(self.freezetag_frozen_timeout == -2) // player was dead
411         {
412                 freezetag_Freeze(world);
413                 return 1;
414         }
415
416         freezetag_count_alive_players();
417
418         if(round_handler_IsActive())
419         if(round_handler_IsRoundStarted())
420         {
421                 centerprint(self, "^1Round already started, you spawn as frozen.");
422                 freezetag_Freeze(world);
423         }
424
425         return 1;
426 }
427
428 MUTATOR_HOOKFUNCTION(freezetag_reset_map_players)
429 {
430         FOR_EACH_PLAYER(self)
431         {
432                 if (self.freezetag_frozen)
433                         freezetag_Unfreeze(world);
434                 self.freezetag_frozen_timeout = -1;
435                 PutClientInServer();
436                 self.freezetag_frozen_timeout = 0;
437         }
438         freezetag_count_alive_players();
439         return 1;
440 }
441
442 MUTATOR_HOOKFUNCTION(freezetag_GiveFragsForKill)
443 {
444         frag_score = 0; // no frags counted in Freeze Tag
445         return 1;
446 }
447
448 MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink)
449 {
450         float n;
451         vector revive_extra_size;
452
453         if(gameover)
454                 return 1;
455
456         if(self.freezetag_frozen)
457         {
458                 // keep health = 1
459                 self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
460
461                 if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout)
462                 {
463                         self.health = autocvar_g_balance_health_start;
464                         freezetag_Unfreeze(world);
465                         freezetag_count_alive_players();
466                         return 1;
467                 }
468         }
469
470         if(round_handler_IsActive())
471         if(!round_handler_IsRoundStarted())
472                 return 1;
473
474         revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
475
476         entity o;
477         o = world;
478         n = 0;
479         FOR_EACH_PLAYER(other) if(self != other)
480         {
481                 if(other.freezetag_frozen == 0)
482                 {
483                         if(other.team == self.team)
484                         {
485                                 if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
486                                 {
487                                         if(!o)
488                                                 o = other;
489                                         ++n;
490                                 }
491                         }
492                 }
493         }
494
495         if(n && self.freezetag_frozen) // OK, there is at least one teammate reviving us
496         {
497                 self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress + frametime * autocvar_g_freezetag_revive_speed, 1);
498                 self.health = max(1, self.freezetag_revive_progress * autocvar_g_balance_health_start);
499
500                 if(self.freezetag_revive_progress >= 1)
501                 {
502                         freezetag_Unfreeze(self);
503                         freezetag_count_alive_players();
504
505                         // EVERY team mate nearby gets a point (even if multiple!)
506                         FOR_EACH_PLAYER(other) if(self != other)
507                         {
508                                 if(other.freezetag_frozen == 0)
509                                 {
510                                         if(other.team == self.team)
511                                         {
512                                                 if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
513                                                 {
514                                                         PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1);
515                                                         PlayerScore_Add(other, SP_SCORE, +1);
516                                                 }
517                                         }
518                                 }
519                         }
520
521                         if(n > 1)
522                                 centerprint(self, strcat("^5You were revived by ^7", o.netname, "^5 et al.\n"));
523                         else
524                                 centerprint(self, strcat("^5You were revived by ^7", o.netname, "^5.\n"));
525                         centerprint(o, strcat("^5You revived ^7", self.netname, "^5.\n"));
526                         if(n > 1)
527                                 bprint("^7", o.netname, "^5 et al revived ^7", self.netname, "^5.\n");
528                         else
529                                 bprint("^7", o.netname, "^5 revived ^7", self.netname, "^5.\n");
530                 }
531
532                 // now find EVERY teammate within reviving radius, set their revive_progress values correct
533                 FOR_EACH_PLAYER(other) if(self != other)
534                 {
535                         if(other.freezetag_frozen == 0)
536                         {
537                                 if(other.team == self.team)
538                                 {
539                                         if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
540                                                 other.freezetag_revive_progress = self.freezetag_revive_progress;
541                                 }
542                         }
543                 }
544         }
545         else if(!n && self.freezetag_frozen) // only if no teammate is nearby will we reset
546         {
547                 self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
548                 self.health = max(1, self.freezetag_revive_progress * autocvar_g_balance_health_start);
549         }
550         else if(!n)
551         {
552                 self.freezetag_revive_progress = 0; // thawing nobody
553         }
554
555         return 1;
556 }
557
558 MUTATOR_HOOKFUNCTION(freezetag_PlayerPhysics)
559 {
560         if(self.freezetag_frozen)
561         {
562                 self.movement = '0 0 0';
563                 self.disableclientprediction = 1;
564         }
565         return 1;
566 }
567
568 MUTATOR_HOOKFUNCTION(freezetag_PlayerDamage_Calculate)
569 {
570         if(frag_target.freezetag_frozen && frag_deathtype != DEATH_HURTTRIGGER)
571         {
572                 frag_damage = 0;
573                 frag_force = frag_force * autocvar_g_freezetag_frozen_force;
574         }
575         return 1;
576 }
577
578 MUTATOR_HOOKFUNCTION(freezetag_ForbidThrowCurrentWeapon)
579 {
580         if (self.freezetag_frozen)
581                 return 1;
582         return 0;
583 }
584
585 MUTATOR_HOOKFUNCTION(freezetag_BotRoles)
586 {
587         if not(self.deadflag)
588         {
589                 if (random() < 0.5)
590                         self.havocbot_role = havocbot_role_ft_freeing;
591                 else
592                         self.havocbot_role = havocbot_role_ft_offense;
593         }
594
595         return TRUE;
596 }
597
598 MUTATOR_HOOKFUNCTION(freezetag_SpectateCopy)
599 {
600         self.freezetag_frozen = other.freezetag_frozen;
601         self.freezetag_revive_progress = other.freezetag_revive_progress;
602         return 0;
603 }
604
605 MUTATOR_HOOKFUNCTION(freezetag_GetTeamCount)
606 {
607         freezetag_teams = autocvar_g_freezetag_teams_override;
608         if(freezetag_teams < 2)
609                 freezetag_teams = autocvar_g_freezetag_teams;
610         freezetag_teams = bound(2, freezetag_teams, 4);
611         ret_float = freezetag_teams;
612         return 0;
613 }
614
615 MUTATOR_DEFINITION(gamemode_freezetag)
616 {
617         MUTATOR_HOOK(MakePlayerObserver, freezetag_RemovePlayer, CBC_ORDER_ANY);
618         MUTATOR_HOOK(ClientDisconnect, freezetag_RemovePlayer, CBC_ORDER_ANY);
619         MUTATOR_HOOK(PlayerDies, freezetag_PlayerDies, CBC_ORDER_ANY);
620         MUTATOR_HOOK(PlayerSpawn, freezetag_PlayerSpawn, CBC_ORDER_ANY);
621         MUTATOR_HOOK(reset_map_players, freezetag_reset_map_players, CBC_ORDER_ANY);
622         MUTATOR_HOOK(GiveFragsForKill, freezetag_GiveFragsForKill, CBC_ORDER_FIRST);
623         MUTATOR_HOOK(PlayerPreThink, freezetag_PlayerPreThink, CBC_ORDER_FIRST);
624         MUTATOR_HOOK(PlayerPhysics, freezetag_PlayerPhysics, CBC_ORDER_FIRST);
625         MUTATOR_HOOK(PlayerDamage_Calculate, freezetag_PlayerDamage_Calculate, CBC_ORDER_ANY);
626         MUTATOR_HOOK(ForbidThrowCurrentWeapon, freezetag_ForbidThrowCurrentWeapon, CBC_ORDER_ANY);
627         MUTATOR_HOOK(HavocBot_ChooseRule, freezetag_BotRoles, CBC_ORDER_ANY);
628         MUTATOR_HOOK(SpectateCopy, freezetag_SpectateCopy, CBC_ORDER_ANY);
629         MUTATOR_HOOK(GetTeamCount, freezetag_GetTeamCount, CBC_ORDER_EXCLUSIVE);
630
631         MUTATOR_ONADD
632         {
633                 if(time > 1) // game loads at time 1
634                         error("This is a game type and it cannot be added at runtime.");
635                 freezetag_Initialize();
636         }
637
638         MUTATOR_ONROLLBACK_OR_REMOVE
639         {
640                 // we actually cannot roll back freezetag_Initialize here
641                 // BUT: we don't need to! If this gets called, adding always
642                 // succeeds.
643         }
644
645         MUTATOR_ONREMOVE
646         {
647                 print("This is a game type and it cannot be removed at runtime.");
648                 return -1;
649         }
650
651         return 0;
652 }