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