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