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