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