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