]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_freezetag.qc
b02679e7a11e81acb3282524d7144d6f1d18791a
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_freezetag.qc
1 .float freezetag_frozen_time;
2 .float freezetag_frozen_timeout;
3 .float freezetag_revive_progress;
4 #define ICE_MAX_ALPHA 1
5 #define ICE_MIN_ALPHA 0.1
6 float freezetag_teams;
7
8 void freezetag_count_alive_players()
9 {
10         entity e;
11         total_players = redalive = bluealive = yellowalive = pinkalive = 0;
12         FOR_EACH_PLAYER(e)
13         {
14                 switch(e.team)
15                 {
16                         case NUM_TEAM_1: ++total_players; if(e.health >= 1 && e.frozen != 1) ++redalive; break;
17                         case NUM_TEAM_2: ++total_players; if(e.health >= 1 && e.frozen != 1) ++bluealive; break;
18                         case NUM_TEAM_3: ++total_players; if(e.health >= 1 && e.frozen != 1) ++yellowalive; break;
19                         case NUM_TEAM_4: ++total_players; if(e.health >= 1 && e.frozen != 1) ++pinkalive; break;
20                 }
21         }
22         FOR_EACH_REALCLIENT(e)
23         {
24                 e.redalive_stat = redalive;
25                 e.bluealive_stat = bluealive;
26                 e.yellowalive_stat = yellowalive;
27                 e.pinkalive_stat = pinkalive;
28         }
29 }
30 #define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
31 #define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == freezetag_teams)
32
33 float prev_missing_teams_mask;
34 float freezetag_CheckTeams()
35 {
36         if(FREEZETAG_ALIVE_TEAMS_OK())
37         {
38                 if(prev_missing_teams_mask > 0)
39                         Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
40                 prev_missing_teams_mask = -1;
41                 return 1;
42         }
43         if(total_players == 0)
44         {
45                 if(prev_missing_teams_mask > 0)
46                         Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
47                 prev_missing_teams_mask = -1;
48                 return 0;
49         }
50         float missing_teams_mask = (!redalive) + (!bluealive) * 2;
51         if(freezetag_teams >= 3) missing_teams_mask += (!yellowalive) * 4;
52         if(freezetag_teams >= 4) missing_teams_mask += (!pinkalive) * 8;
53         if(prev_missing_teams_mask != missing_teams_mask)
54         {
55                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
56                 prev_missing_teams_mask = missing_teams_mask;
57         }
58         return 0;
59 }
60
61 float freezetag_getWinnerTeam()
62 {
63         float winner_team = 0;
64         if(redalive >= 1)
65                 winner_team = NUM_TEAM_1;
66         if(bluealive >= 1)
67         {
68                 if(winner_team) return 0;
69                 winner_team = NUM_TEAM_2;
70         }
71         if(yellowalive >= 1)
72         {
73                 if(winner_team) return 0;
74                 winner_team = NUM_TEAM_3;
75         }
76         if(pinkalive >= 1)
77         {
78                 if(winner_team) return 0;
79                 winner_team = NUM_TEAM_4;
80         }
81         if(winner_team)
82                 return winner_team;
83         return -1; // no player left
84 }
85
86 float freezetag_CheckWinner()
87 {
88         entity e;
89         if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
90         {
91                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
92                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
93                 FOR_EACH_PLAYER(e)
94                 {
95                         e.freezetag_frozen_timeout = 0;
96                         nades_Clear(e);
97                 }
98                 round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
99                 return 1;
100         }
101
102         if(FREEZETAG_ALIVE_TEAMS() > 1)
103                 return 0;
104
105         float winner_team;
106         winner_team = freezetag_getWinnerTeam();
107         if(winner_team > 0)
108         {
109                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
110                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
111                 TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
112         }
113         else if(winner_team == -1)
114         {
115                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
116                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
117         }
118
119         FOR_EACH_PLAYER(e)
120         {
121                 e.freezetag_frozen_timeout = 0;
122                 nades_Clear(e);
123         }
124         round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
125         return 1;
126 }
127
128 entity freezetag_LastPlayerForTeam()
129 {
130         entity pl, last_pl = world;
131         FOR_EACH_PLAYER(pl)
132         {
133                 if(pl.health >= 1)
134                 if(!pl.frozen)
135                 if(pl != self)
136                 if(pl.team == self.team)
137                 if(!last_pl)
138                         last_pl = pl;
139                 else
140                         return world;
141         }
142         return last_pl;
143 }
144
145 void freezetag_LastPlayerForTeam_Notify()
146 {
147         if(round_handler_IsActive())
148         if(round_handler_IsRoundStarted())
149         {
150                 entity pl = freezetag_LastPlayerForTeam();
151                 if(pl)
152                         Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
153         }
154 }
155
156 void freezetag_Add_Score(entity attacker)
157 {
158         if(attacker == self)
159         {
160                 // you froze your own dumb self
161                 // counted as "suicide" already
162                 PlayerScore_Add(self, SP_SCORE, -1);
163         }
164         else if(IS_PLAYER(attacker))
165         {
166                 // got frozen by an enemy
167                 // counted as "kill" and "death" already
168                 PlayerScore_Add(self, SP_SCORE, -1);
169                 PlayerScore_Add(attacker, SP_SCORE, +1);
170         }
171         // else nothing - got frozen by the game type rules themselves
172 }
173
174 void freezetag_Freeze(entity attacker)
175 {
176         if(self.frozen)
177                 return;
178
179         if(autocvar_g_freezetag_frozen_maxtime > 0)
180                 self.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
181
182         Freeze(self, 0, 1, TRUE);
183
184         freezetag_count_alive_players();
185
186         freezetag_Add_Score(attacker);
187 }
188
189 void freezetag_Unfreeze(entity attacker)
190 {
191         self.freezetag_frozen_time = 0;
192         self.freezetag_frozen_timeout = 0;
193
194         Unfreeze(self);
195 }
196
197
198 // ================
199 // Bot player logic
200 // ================
201
202 void() havocbot_role_ft_freeing;
203 void() havocbot_role_ft_offense;
204
205 void havocbot_goalrating_freeplayers(float ratingscale, vector org, float sradius)
206 {
207         entity head;
208         float distance;
209
210         FOR_EACH_PLAYER(head)
211         {
212                 if ((head != self) && (head.team == self.team))
213                 {
214                         if (head.frozen == 1)
215                         {
216                                 distance = vlen(head.origin - org);
217                                 if (distance > sradius)
218                                         continue;
219                                 navigation_routerating(head, ratingscale, 2000);
220                         }
221                         else
222                         {
223                                 // If teamate is not frozen still seek them out as fight better
224                                 // in a group.
225                                 navigation_routerating(head, ratingscale/3, 2000);
226                         }
227                 }
228         }
229 }
230
231 void havocbot_role_ft_offense()
232 {
233         entity head;
234         float unfrozen;
235
236         if(self.deadflag != DEAD_NO)
237                 return;
238
239         if (!self.havocbot_role_timeout)
240                 self.havocbot_role_timeout = time + random() * 10 + 20;
241
242         // Count how many players on team are unfrozen.
243         unfrozen = 0;
244         FOR_EACH_PLAYER(head)
245         {
246                 if ((head.team == self.team) && (head.frozen != 1))
247                         unfrozen++;
248         }
249
250         // If only one left on team or if role has timed out then start trying to free players.
251         if (((unfrozen == 0) && (!self.frozen)) || (time > self.havocbot_role_timeout))
252         {
253                 dprint("changing role to freeing\n");
254                 self.havocbot_role = havocbot_role_ft_freeing;
255                 self.havocbot_role_timeout = 0;
256                 return;
257         }
258
259         if (time > self.bot_strategytime)
260         {
261                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
262
263                 navigation_goalrating_start();
264                 havocbot_goalrating_items(10000, self.origin, 10000);
265                 havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
266                 havocbot_goalrating_freeplayers(9000, self.origin, 10000);
267                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
268                 navigation_goalrating_end();
269         }
270 }
271
272 void havocbot_role_ft_freeing()
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         if (time > self.havocbot_role_timeout)
281         {
282                 dprint("changing role to offense\n");
283                 self.havocbot_role = havocbot_role_ft_offense;
284                 self.havocbot_role_timeout = 0;
285                 return;
286         }
287
288         if (time > self.bot_strategytime)
289         {
290                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
291
292                 navigation_goalrating_start();
293                 havocbot_goalrating_items(8000, self.origin, 10000);
294                 havocbot_goalrating_enemyplayers(10000, self.origin, 10000);
295                 havocbot_goalrating_freeplayers(20000, self.origin, 10000);
296                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
297                 navigation_goalrating_end();
298         }
299 }
300
301
302 // ==============
303 // Hook Functions
304 // ==============
305
306 MUTATOR_HOOKFUNCTION(freezetag_RemovePlayer)
307 {
308         self.health = 0; // neccessary to update correctly alive stats
309         if(!self.frozen)
310                 freezetag_LastPlayerForTeam_Notify();
311         freezetag_Unfreeze(world);
312         freezetag_count_alive_players();
313         return 1;
314 }
315
316 MUTATOR_HOOKFUNCTION(freezetag_PlayerDies)
317 {
318         if(round_handler_IsActive())
319         if(round_handler_CountdownRunning())
320         {
321                 if(self.frozen)
322                         freezetag_Unfreeze(world);
323                 freezetag_count_alive_players();
324                 return 1; // let the player die so that he can respawn whenever he wants
325         }
326
327         // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
328         // you succeed changing team through the menu: you both really die (gibbing) and get frozen
329         if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
330                 || frag_deathtype == DEATH_TEAMCHANGE || frag_deathtype == DEATH_AUTOTEAMCHANGE)
331         {
332                 // let the player die, he will be automatically frozen when he respawns
333                 if(self.frozen != 1)
334                 {
335                         freezetag_Add_Score(frag_attacker);
336                         freezetag_count_alive_players();
337                         freezetag_LastPlayerForTeam_Notify();
338                 }
339                 else
340                         freezetag_Unfreeze(world); // remove ice
341                 self.health = 0; // Unfreeze resets health
342                 self.freezetag_frozen_timeout = -2; // freeze on respawn
343                 return 1;
344         }
345
346         if(self.frozen)
347                 return 1;
348
349         freezetag_Freeze(frag_attacker);
350         freezetag_LastPlayerForTeam_Notify();
351
352         if(frag_attacker == frag_target || frag_attacker == world)
353         {
354                 if(IS_PLAYER(frag_target))
355                         Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
356                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
357         }
358         else
359         {
360                 if(IS_PLAYER(frag_target))
361                         Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_FROZEN, frag_attacker.netname);
362                 if(IS_PLAYER(frag_attacker))
363                         Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_FREEZETAG_FREEZE, frag_target.netname);
364                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
365         }
366
367         return 1;
368 }
369
370 MUTATOR_HOOKFUNCTION(freezetag_PlayerSpawn)
371 {
372         if(self.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
373                 return 1; // do nothing, round is starting right now
374
375         if(self.freezetag_frozen_timeout == -2) // player was dead
376         {
377                 freezetag_Freeze(world);
378                 return 1;
379         }
380
381         freezetag_count_alive_players();
382
383         if(round_handler_IsActive())
384         if(round_handler_IsRoundStarted())
385         {
386                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
387                 freezetag_Freeze(world);
388         }
389
390         return 1;
391 }
392
393 MUTATOR_HOOKFUNCTION(freezetag_reset_map_players)
394 {
395         FOR_EACH_PLAYER(self)
396         {
397                 self.killcount = 0;
398                 self.freezetag_frozen_timeout = -1;
399                 PutClientInServer();
400                 self.freezetag_frozen_timeout = 0;
401         }
402         freezetag_count_alive_players();
403         return 1;
404 }
405
406 MUTATOR_HOOKFUNCTION(freezetag_GiveFragsForKill)
407 {
408         frag_score = 0; // no frags counted in Freeze Tag
409         return 1;
410 }
411
412 .float reviving; // temp var
413 MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink)
414 {
415         float n;
416
417         if(gameover)
418                 return 1;
419
420         if(self.frozen == 1)
421         {
422                 // keep health = 1
423                 self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
424         }
425
426         if(round_handler_IsActive())
427         if(!round_handler_IsRoundStarted())
428                 return 1;
429
430         entity o;
431         o = world;
432         //if(self.frozen)
433         //if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout)
434                 //self.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (self.freezetag_frozen_timeout - time) / (self.freezetag_frozen_timeout - self.freezetag_frozen_time);
435
436         if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout)
437                 n = -1;
438         else
439         {
440                 vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
441                 n = 0;
442                 FOR_EACH_PLAYER(other)
443                 if(self != other)
444                 if(other.frozen == 0)
445                 if(other.deadflag == DEAD_NO)
446                 if(SAME_TEAM(other, self))
447                 if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
448                 {
449                         if(!o)
450                                 o = other;
451                         if(self.frozen == 1)
452                                 other.reviving = TRUE;
453                         ++n;
454                 }
455         }
456
457         if(n && self.frozen == 1) // OK, there is at least one teammate reviving us
458         {
459                 self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
460                 self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health));
461
462                 if(self.revive_progress >= 1)
463                 {
464                         freezetag_Unfreeze(self);
465                         freezetag_count_alive_players();
466
467                         if(n == -1)
468                         {
469                                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
470                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, self.netname, autocvar_g_freezetag_frozen_maxtime);
471                                 return 1;
472                         }
473
474                         // EVERY team mate nearby gets a point (even if multiple!)
475                         FOR_EACH_PLAYER(other)
476                         {
477                                 if(other.reviving)
478                                 {
479                                         PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1);
480                                         PlayerScore_Add(other, SP_SCORE, +1);
481
482                                         nades_GiveBonus(other,autocvar_g_nades_bonus_score_low);
483                                 }
484                         }
485
486                         Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
487                         Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname);
488                         Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED, self.netname, o.netname);
489                 }
490
491                 FOR_EACH_PLAYER(other)
492                 {
493                         if(other.reviving)
494                         {
495                                 other.revive_progress = self.revive_progress;
496                                 other.reviving = FALSE;
497                         }
498                 }
499         }
500         else if(!n && self.frozen == 1) // only if no teammate is nearby will we reset
501         {
502                 self.revive_progress = bound(0, self.revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
503                 self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health));
504         }
505         else if(!n && !self.frozen)
506         {
507                 self.revive_progress = 0; // thawing nobody
508         }
509
510         return 1;
511 }
512
513 MUTATOR_HOOKFUNCTION(freezetag_BotRoles)
514 {
515         if (!self.deadflag)
516         {
517                 if (random() < 0.5)
518                         self.havocbot_role = havocbot_role_ft_freeing;
519                 else
520                         self.havocbot_role = havocbot_role_ft_offense;
521         }
522
523         return TRUE;
524 }
525
526 MUTATOR_HOOKFUNCTION(freezetag_GetTeamCount)
527 {
528         ret_float = freezetag_teams;
529         return 0;
530 }
531
532 void freezetag_Initialize()
533 {
534         freezetag_teams = autocvar_g_freezetag_teams_override;
535         if(freezetag_teams < 2)
536                 freezetag_teams = autocvar_g_freezetag_teams;
537         freezetag_teams = bound(2, freezetag_teams, 4);
538         ScoreRules_freezetag(freezetag_teams);
539
540         round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
541         round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
542
543         addstat(STAT_REDALIVE, AS_INT, redalive_stat);
544         addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
545         addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
546         addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
547 }
548
549 MUTATOR_DEFINITION(gamemode_freezetag)
550 {
551         MUTATOR_HOOK(MakePlayerObserver, freezetag_RemovePlayer, CBC_ORDER_ANY);
552         MUTATOR_HOOK(ClientDisconnect, freezetag_RemovePlayer, CBC_ORDER_ANY);
553         MUTATOR_HOOK(PlayerDies, freezetag_PlayerDies, CBC_ORDER_ANY);
554         MUTATOR_HOOK(PlayerSpawn, freezetag_PlayerSpawn, CBC_ORDER_ANY);
555         MUTATOR_HOOK(reset_map_players, freezetag_reset_map_players, CBC_ORDER_ANY);
556         MUTATOR_HOOK(GiveFragsForKill, freezetag_GiveFragsForKill, CBC_ORDER_FIRST);
557         MUTATOR_HOOK(PlayerPreThink, freezetag_PlayerPreThink, CBC_ORDER_FIRST);
558         MUTATOR_HOOK(HavocBot_ChooseRole, freezetag_BotRoles, CBC_ORDER_ANY);
559         MUTATOR_HOOK(GetTeamCount, freezetag_GetTeamCount, CBC_ORDER_EXCLUSIVE);
560
561         MUTATOR_ONADD
562         {
563                 if(time > 1) // game loads at time 1
564                         error("This is a game type and it cannot be added at runtime.");
565                 freezetag_Initialize();
566         }
567
568         MUTATOR_ONROLLBACK_OR_REMOVE
569         {
570                 // we actually cannot roll back freezetag_Initialize here
571                 // BUT: we don't need to! If this gets called, adding always
572                 // succeeds.
573         }
574
575         MUTATOR_ONREMOVE
576         {
577                 print("This is a game type and it cannot be removed at runtime.");
578                 return -1;
579         }
580
581         return 0;
582 }