]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_freezetag.qc
Fix another function full of gamemode specific blocks
[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 void ft_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 }
328
329 MUTATOR_HOOKFUNCTION(ft, ClientDisconnect)
330 {SELFPARAM();
331         ft_RemovePlayer();
332         return 1;
333 }
334
335 MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver)
336 {SELFPARAM();
337         ft_RemovePlayer();
338         return 1;
339 }
340
341 MUTATOR_HOOKFUNCTION(ft, PlayerDies)
342 {SELFPARAM();
343         if(round_handler_IsActive())
344         if(round_handler_CountdownRunning())
345         {
346                 if(self.frozen)
347                         freezetag_Unfreeze(world);
348                 freezetag_count_alive_players();
349                 return 1; // let the player die so that he can respawn whenever he wants
350         }
351
352         // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
353         // you succeed changing team through the menu: you both really die (gibbing) and get frozen
354         if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
355                 || frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id)
356         {
357                 // let the player die, he will be automatically frozen when he respawns
358                 if(self.frozen != 1)
359                 {
360                         freezetag_Add_Score(frag_attacker);
361                         freezetag_count_alive_players();
362                         freezetag_LastPlayerForTeam_Notify();
363                 }
364                 else
365                         freezetag_Unfreeze(world); // remove ice
366                 self.health = 0; // Unfreeze resets health
367                 self.freezetag_frozen_timeout = -2; // freeze on respawn
368                 return 1;
369         }
370
371         if(self.frozen)
372                 return 1;
373
374         freezetag_Freeze(frag_attacker);
375         freezetag_LastPlayerForTeam_Notify();
376
377         if(frag_attacker == frag_target || frag_attacker == world)
378         {
379                 if(IS_PLAYER(frag_target))
380                         Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
381                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
382         }
383         else
384         {
385                 if(IS_PLAYER(frag_target))
386                         Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_FROZEN, frag_attacker.netname);
387                 if(IS_PLAYER(frag_attacker))
388                         Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_FREEZETAG_FREEZE, frag_target.netname);
389                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
390         }
391
392         return 1;
393 }
394
395 MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
396 {SELFPARAM();
397         if(self.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
398                 return 1; // do nothing, round is starting right now
399
400         if(self.freezetag_frozen_timeout == -2) // player was dead
401         {
402                 freezetag_Freeze(world);
403                 return 1;
404         }
405
406         freezetag_count_alive_players();
407
408         if(round_handler_IsActive())
409         if(round_handler_IsRoundStarted())
410         {
411                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
412                 freezetag_Freeze(world);
413         }
414
415         return 1;
416 }
417
418 MUTATOR_HOOKFUNCTION(ft, reset_map_players)
419 {SELFPARAM();
420         entity e;
421         FOR_EACH_PLAYER(e)
422         {
423                 e.killcount = 0;
424                 e.freezetag_frozen_timeout = -1;
425                 setself(e);
426                 PutClientInServer();
427                 e.freezetag_frozen_timeout = 0;
428         }
429         freezetag_count_alive_players();
430         return 1;
431 }
432
433 MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST)
434 {
435         frag_score = 0; // no frags counted in Freeze Tag
436         return 1;
437 }
438
439 MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST)
440 {SELFPARAM();
441         float n;
442
443         if(gameover)
444                 return 1;
445
446         if(self.frozen == 1)
447         {
448                 // keep health = 1
449                 self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
450         }
451
452         if(round_handler_IsActive())
453         if(!round_handler_IsRoundStarted())
454                 return 1;
455
456         entity o;
457         o = world;
458         //if(self.frozen)
459         //if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout)
460                 //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);
461
462         if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout)
463                 n = -1;
464         else
465         {
466                 vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
467                 n = 0;
468                 FOR_EACH_PLAYER(other)
469                 if(self != other)
470                 if(other.frozen == 0)
471                 if(other.deadflag == DEAD_NO)
472                 if(SAME_TEAM(other, self))
473                 if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
474                 {
475                         if(!o)
476                                 o = other;
477                         if(self.frozen == 1)
478                                 other.reviving = true;
479                         ++n;
480                 }
481         }
482
483         if(n && self.frozen == 1) // OK, there is at least one teammate reviving us
484         {
485                 self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
486                 self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health));
487
488                 if(self.revive_progress >= 1)
489                 {
490                         freezetag_Unfreeze(self);
491                         freezetag_count_alive_players();
492
493                         if(n == -1)
494                         {
495                                 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
496                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, self.netname, autocvar_g_freezetag_frozen_maxtime);
497                                 return 1;
498                         }
499
500                         // EVERY team mate nearby gets a point (even if multiple!)
501                         FOR_EACH_PLAYER(other)
502                         {
503                                 if(other.reviving)
504                                 {
505                                         PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1);
506                                         PlayerScore_Add(other, SP_SCORE, +1);
507
508                                         nades_GiveBonus(other,autocvar_g_nades_bonus_score_low);
509                                 }
510                         }
511
512                         Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
513                         Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname);
514                         Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED, self.netname, o.netname);
515                 }
516
517                 FOR_EACH_PLAYER(other)
518                 {
519                         if(other.reviving)
520                         {
521                                 other.revive_progress = self.revive_progress;
522                                 other.reviving = false;
523                         }
524                 }
525         }
526         else if(!n && self.frozen == 1) // only if no teammate is nearby will we reset
527         {
528                 self.revive_progress = bound(0, self.revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
529                 self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health));
530         }
531         else if(!n && !self.frozen)
532         {
533                 self.revive_progress = 0; // thawing nobody
534         }
535
536         return 1;
537 }
538
539 MUTATOR_HOOKFUNCTION(ft, SetStartItems)
540 {
541         start_items &= ~IT_UNLIMITED_AMMO;
542         //start_health       = warmup_start_health       = cvar("g_lms_start_health");
543         //start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
544         start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
545         start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
546         start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
547         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
548         start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
549         start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
550
551         return 0;
552 }
553
554 MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
555 {SELFPARAM();
556         if (!self.deadflag)
557         {
558                 if (random() < 0.5)
559                         self.havocbot_role = havocbot_role_ft_freeing;
560                 else
561                         self.havocbot_role = havocbot_role_ft_offense;
562         }
563
564         return true;
565 }
566
567 MUTATOR_HOOKFUNCTION(ft, GetTeamCount, CBC_ORDER_EXCLUSIVE)
568 {
569         ret_float = freezetag_teams;
570         return false;
571 }
572
573 void freezetag_Initialize()
574 {
575         freezetag_teams = autocvar_g_freezetag_teams_override;
576         if(freezetag_teams < 2)
577                 freezetag_teams = autocvar_g_freezetag_teams;
578         freezetag_teams = bound(2, freezetag_teams, 4);
579         freezetag_ScoreRules(freezetag_teams);
580
581         round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
582         round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
583
584         addstat(STAT_REDALIVE, AS_INT, redalive_stat);
585         addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
586         addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
587         addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
588
589         EliminatedPlayers_Init(freezetag_isEliminated);
590 }
591
592 REGISTER_MUTATOR(ft, g_freezetag)
593 {
594         ActivateTeamplay();
595         SetLimits(autocvar_g_freezetag_point_limit, autocvar_g_freezetag_point_leadlimit, -1, -1);
596
597         if(autocvar_g_freezetag_team_spawns)
598                 have_team_spawns = -1; // request team spawns
599
600         MUTATOR_ONADD
601         {
602                 if(time > 1) // game loads at time 1
603                         error("This is a game type and it cannot be added at runtime.");
604                 freezetag_Initialize();
605         }
606
607         MUTATOR_ONROLLBACK_OR_REMOVE
608         {
609                 // we actually cannot roll back freezetag_Initialize here
610                 // BUT: we don't need to! If this gets called, adding always
611                 // succeeds.
612         }
613
614         MUTATOR_ONREMOVE
615         {
616                 LOG_INFO("This is a game type and it cannot be removed at runtime.");
617                 return -1;
618         }
619
620         return 0;
621 }