]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_freezetag.qc
Freezetag: update player count before checking it, besides making the code more intui...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_freezetag.qc
1 void freezetag_Initialize()
2 {
3         precache_model("models/ice/ice.md3");
4         warmup = max(time, game_starttime) + autocvar_g_freezetag_warmup;
5         ScoreRules_freezetag();
6
7         addstat(STAT_REDALIVE, AS_INT, redalive_stat);
8         addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
9         addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
10         addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
11
12         addstat(STAT_FROZEN, AS_INT, freezetag_frozen);
13         addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, freezetag_revive_progress);
14 }
15
16 void freezetag_CheckWinner()
17 {
18         if(time <= game_starttime || inWarmupStage)
19                 return;
20
21         if(next_round || (time > warmup - autocvar_g_freezetag_warmup && time < warmup))
22                 return; // already waiting for next round to start
23
24         if((redalive >= 1 && bluealive >= 1)
25                 || (redalive >= 1 && yellowalive >= 1)
26                 || (redalive >= 1 && pinkalive >= 1)
27                 || (bluealive >= 1 && yellowalive >= 1)
28                 || (bluealive >= 1 && pinkalive >= 1)
29                 || (yellowalive >= 1 && pinkalive >= 1))
30                 return; // we still have active players on two or more teams, nobody won yet
31
32         entity e, winner;
33         string teamname;
34         winner = world;
35
36         FOR_EACH_PLAYER(e)
37         {
38                 if(e.freezetag_frozen == 0 && e.health >= 1) // here's one player from the winning team... good
39                 {
40                         winner = e;
41                         break; // break, we found the winner
42                 }
43         }
44
45         if(winner != world) // just in case a winner wasn't found
46         {
47                 if(winner.team == COLOR_TEAM1)
48                         teamname = "^1Red Team";
49                 else if(winner.team == COLOR_TEAM2)
50                         teamname = "^4Blue Team";
51                 else if(winner.team == COLOR_TEAM3)
52                         teamname = "^3Yellow Team";
53                 else
54                         teamname = "^6Pink Team";
55                 FOR_EACH_PLAYER(e) {
56                         centerprint(e, strcat(teamname, "^5 wins the round, all other teams were frozen.\n"));
57                 }
58                 bprint(teamname, "^5 wins the round since all the other teams were frozen.\n");
59                 TeamScore_AddToTeam(winner.team, ST_SCORE, +1);
60         }
61
62         next_round = time + 5;
63 }
64
65 // this is needed to allow the player to turn his view around (fixangle can't
66 // be used to freeze his view, as that also changes the angles), while not
67 // turning that ice object with the player
68 void freezetag_Ice_Think()
69 {
70         setorigin(self, self.owner.origin - '0 0 16');
71         self.nextthink = time;
72 }
73
74 void freezetag_count_alive_players()
75 {
76         entity e;
77         total_players = redalive = bluealive = yellowalive = pinkalive = 0;
78         FOR_EACH_PLAYER(e) {
79                 if(e.team == COLOR_TEAM1 && e.health >= 1)
80                 {
81                         ++total_players;
82                         if (!e.freezetag_frozen) ++redalive;
83                 }
84                 else if(e.team == COLOR_TEAM2 && e.health >= 1)
85                 {
86                         ++total_players;
87                         if (!e.freezetag_frozen) ++bluealive;
88                 }
89                 else if(e.team == COLOR_TEAM3 && e.health >= 1)
90                 {
91                         ++total_players;
92                         if (!e.freezetag_frozen) ++yellowalive;
93                 }
94                 else if(e.team == COLOR_TEAM4 && e.health >= 1)
95                 {
96                         ++total_players;
97                         if (!e.freezetag_frozen) ++pinkalive;
98                 }
99         }
100         FOR_EACH_REALCLIENT(e) {
101                 e.redalive_stat = redalive;
102                 e.bluealive_stat = bluealive;
103                 e.yellowalive_stat = yellowalive;
104                 e.pinkalive_stat = pinkalive;
105         }
106 }
107
108 void freezetag_Freeze(entity attacker)
109 {
110         if(self.freezetag_frozen)
111                 return;
112         self.freezetag_frozen = 1;
113         self.freezetag_revive_progress = 0;
114         self.health = 1;
115         if(inWarmupStage)
116                 self.freezetag_frozen_timeout = time + 5;
117
118         freezetag_count_alive_players();
119
120         entity ice;
121         ice = spawn();
122         ice.owner = self;
123         ice.classname = "freezetag_ice";
124         ice.think = freezetag_Ice_Think;
125         ice.nextthink = time;
126         ice.frame = floor(random() * 21); // ice model has 20 different looking frames
127         setmodel(ice, "models/ice/ice.md3");
128
129         entity oldself;
130         oldself = self;
131         self = ice;
132         freezetag_Ice_Think();
133         self = oldself;
134
135         RemoveGrapplingHook(self);
136
137         // add waypoint
138         WaypointSprite_Spawn("freezetag_frozen", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attached, TRUE, RADARICON_WAYPOINT, '0.25 0.90 1');
139
140         if(attacker == self)
141         {
142                 // you froze your own dumb self
143                 // counted as "suicide" already
144                 PlayerScore_Add(self, SP_SCORE, -1);
145         }
146         else if(attacker.classname == "player")
147         {
148                 // got frozen by an enemy
149                 // counted as "kill" and "death" already
150                 PlayerScore_Add(self, SP_SCORE, -1);
151                 PlayerScore_Add(attacker, SP_SCORE, +1);
152         }
153         else
154         {
155                 // nothing - got frozen by the game type rules themselves
156         }
157 }
158
159 void freezetag_Unfreeze(entity attacker)
160 {
161         self.freezetag_frozen = 0;
162         self.freezetag_frozen_timeout = 0;
163         self.freezetag_revive_progress = 0;
164
165         // remove the ice block
166         entity ice;
167         for(ice = world; (ice = find(ice, classname, "freezetag_ice")); ) if(ice.owner == self)
168         {
169                 remove(ice);
170                 break;
171         }
172
173         // remove waypoint
174         if(self.waypointsprite_attached)
175                 WaypointSprite_Kill(self.waypointsprite_attached);
176 }
177
178
179 // ================
180 // Bot player logic
181 // ================
182
183 void() havocbot_role_ft_freeing;
184 void() havocbot_role_ft_offense;
185
186 void havocbot_goalrating_freeplayers(float ratingscale, vector org, float sradius)
187 {
188         entity head;
189         float distance;
190
191         FOR_EACH_PLAYER(head)
192         {
193                 if ((head != self) && (head.team == self.team))
194                 {
195                         if (head.freezetag_frozen)
196                         {
197                                 distance = vlen(head.origin - org);
198                                 if (distance > sradius)
199                                         continue;
200                                 navigation_routerating(head, ratingscale, 2000);
201                         }
202                         else
203                         {
204                                 // If teamate is not frozen still seek them out as fight better
205                                 // in a group.
206                                 navigation_routerating(head, ratingscale/3, 2000);
207                         }
208                 }
209         }
210 }
211
212 void havocbot_role_ft_offense()
213 {
214         entity head;
215         float unfrozen;
216
217         if(self.deadflag != DEAD_NO)
218                 return;
219
220         if (!self.havocbot_role_timeout)
221                 self.havocbot_role_timeout = time + random() * 10 + 20;
222
223         // Count how many players on team are unfrozen.
224         unfrozen = 0;
225         FOR_EACH_PLAYER(head)
226         {
227                 if ((head.team == self.team) && (!head.freezetag_frozen))
228                         unfrozen++;
229         }
230
231         // If only one left on team or if role has timed out then start trying to free players.
232         if (((unfrozen == 0) && (!self.freezetag_frozen)) || (time > self.havocbot_role_timeout))
233         {
234                 dprint("changing role to freeing\n");
235                 self.havocbot_role = havocbot_role_ft_freeing;
236                 self.havocbot_role_timeout = 0;
237                 return;
238         }
239
240         if (time > self.bot_strategytime)
241         {
242                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
243
244                 navigation_goalrating_start();
245                 havocbot_goalrating_items(10000, self.origin, 10000);
246                 havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
247                 havocbot_goalrating_freeplayers(9000, self.origin, 10000);
248                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
249                 navigation_goalrating_end();
250         }
251 }
252
253 void havocbot_role_ft_freeing()
254 {
255         if(self.deadflag != DEAD_NO)
256                 return;
257
258         if (!self.havocbot_role_timeout)
259                 self.havocbot_role_timeout = time + random() * 10 + 20;
260
261         if (time > self.havocbot_role_timeout)
262         {
263                 dprint("changing role to offense\n");
264                 self.havocbot_role = havocbot_role_ft_offense;
265                 self.havocbot_role_timeout = 0;
266                 return;
267         }
268
269         if (time > self.bot_strategytime)
270         {
271                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
272
273                 navigation_goalrating_start();
274                 havocbot_goalrating_items(8000, self.origin, 10000);
275                 havocbot_goalrating_enemyplayers(10000, self.origin, 10000);
276                 havocbot_goalrating_freeplayers(20000, self.origin, 10000);
277                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
278                 navigation_goalrating_end();
279         }
280 }
281
282
283 // ==============
284 // Hook Functions
285 // ==============
286
287 MUTATOR_HOOKFUNCTION(freezetag_RemovePlayer)
288 {
289         self.health = 0; // neccessary to update correctly alive stats
290         freezetag_Unfreeze(world);
291
292         freezetag_count_alive_players();
293
294         if(total_players > 1) // only check for winners if we had more than two players (one of them left, don't let the other player win just because of that)
295                 freezetag_CheckWinner();
296
297         return 1;
298 }
299
300 MUTATOR_HOOKFUNCTION(freezetag_PlayerDies)
301 {
302         // let the player die in these cases
303         if(time <= game_starttime)
304                 return 1;
305         if(next_round || (time > warmup - autocvar_g_freezetag_warmup && time < warmup))
306                 return 1;
307
308         if(frag_deathtype == DEATH_HURTTRIGGER)
309         {
310                 if(!self.freezetag_frozen)
311                 {
312                         freezetag_Freeze(world);
313                         freezetag_CheckWinner();
314                 }
315                 PutClientInServer(); // respawn the player
316                 self.health = 1;
317                 self.armorvalue = 0;
318                 return 1;
319         }
320
321         if(self.freezetag_frozen)
322                 return 1;
323
324         freezetag_Freeze(frag_attacker);
325
326         if(frag_attacker == frag_target || frag_attacker == world)
327         {
328                 if(frag_target.classname == STR_PLAYER)
329                         centerprint(frag_target, "^1You froze yourself.\n");
330                 bprint("^7", frag_target.netname, "^1 froze himself.\n");
331         }
332         else
333         {
334                 if(frag_target.classname == STR_PLAYER)
335                         centerprint(frag_target, strcat("^1You were frozen by ^7", frag_attacker.netname, ".\n"));
336                 if(frag_attacker.classname == STR_PLAYER)
337                         centerprint(frag_attacker, strcat("^2You froze ^7", frag_target.netname, ".\n"));
338                 bprint("^7", frag_target.netname, "^1 was frozen by ^7", frag_attacker.netname, ".\n");
339         }
340
341         frag_target.health = 1; // "respawn" the player :P
342
343         freezetag_CheckWinner();
344
345         return 1;
346 }
347
348 MUTATOR_HOOKFUNCTION(freezetag_PlayerSpawn)
349 {
350         freezetag_count_alive_players();
351
352         if(self.freezetag_frozen) // stay frozen if respawning after death (DEATH_HURTTRIGGER)
353                 return 1;
354
355         if(time <= game_starttime || inWarmupStage || total_players == 1)
356                 return 1;
357
358         if(total_players == 2) // only one player active on server, start a new match immediately
359         if(!next_round && warmup && (time < warmup - autocvar_g_freezetag_warmup || time > warmup)) // not awaiting next round
360         {
361                 next_round = time;
362                 return 1;
363         }
364
365         if(warmup && time > warmup) // spawn too late, freeze player
366         {
367                 centerprint(self, "^1Round already started, you spawn as frozen.");
368                 freezetag_Freeze(world);
369         }
370
371         return 1;
372 }
373
374 MUTATOR_HOOKFUNCTION(freezetag_reset_map_global)
375 {
376         redalive = bluealive = yellowalive = pinkalive = 0;
377         warmup = max(time, game_starttime);
378         if(autocvar_g_freezetag_warmup > 0)
379                 warmup += autocvar_g_freezetag_warmup;
380         return 1;
381 }
382
383 MUTATOR_HOOKFUNCTION(freezetag_reset_map_players)
384 {
385         FOR_EACH_PLAYER(self)
386         {
387                 if (self.freezetag_frozen)
388                         freezetag_Unfreeze(world);
389                 PutClientInServer();
390         }
391         return 1;
392 }
393
394 MUTATOR_HOOKFUNCTION(freezetag_GiveFragsForKill)
395 {
396         frag_score = 0; // no frags counted in Freeze Tag
397         return 1;
398 }
399
400 MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink)
401 {
402         float n;
403         vector revive_extra_size;
404
405         if(gameover)
406                 return 1;
407
408         if(self.freezetag_frozen)
409         {
410                 // keep health = 1
411                 self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
412
413                 if(self.freezetag_frozen_timeout && time >= self.freezetag_frozen_timeout)
414                 {
415                         self.health = autocvar_g_balance_health_start;
416                         freezetag_Unfreeze(world);
417                         freezetag_count_alive_players();
418                         return 1;
419                 }
420         }
421         if(next_round || (time > warmup - autocvar_g_freezetag_warmup && time < warmup))
422                 return 1; // already waiting for next round to start
423
424         revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
425
426         entity o;
427         o = world;
428         n = 0;
429         FOR_EACH_PLAYER(other) if(self != other)
430         {
431                 if(other.freezetag_frozen == 0)
432                 {
433                         if(other.team == self.team)
434                         {
435                                 if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
436                                 {
437                                         if(!o)
438                                                 o = other;
439                                         ++n;
440                                 }
441                         }
442                 }
443         }
444
445         if(n && self.freezetag_frozen) // OK, there is at least one teammate reviving us
446         {
447                 self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress + frametime * autocvar_g_freezetag_revive_speed, 1);
448                 self.health = max(1, self.freezetag_revive_progress * autocvar_g_balance_health_start);
449
450                 if(self.freezetag_revive_progress >= 1)
451                 {
452                         freezetag_Unfreeze(self);
453                         freezetag_count_alive_players();
454
455                         // EVERY team mate nearby gets a point (even if multiple!)
456                         FOR_EACH_PLAYER(other) if(self != other)
457                         {
458                                 if(other.freezetag_frozen == 0)
459                                 {
460                                         if(other.team == self.team)
461                                         {
462                                                 if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
463                                                 {
464                                                         PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1);
465                                                         PlayerScore_Add(other, SP_SCORE, +1);
466                                                 }
467                                         }
468                                 }
469                         }
470
471                         if(n > 1)
472                                 centerprint(self, strcat("^5You were revived by ^7", o.netname, "^5 et al.\n"));
473                         else
474                                 centerprint(self, strcat("^5You were revived by ^7", o.netname, "^5.\n"));
475                         centerprint(o, strcat("^5You revived ^7", self.netname, "^5.\n"));
476                         if(n > 1)
477                                 bprint("^7", o.netname, "^5 et al revived ^7", self.netname, "^5.\n");
478                         else
479                                 bprint("^7", o.netname, "^5 revived ^7", self.netname, "^5.\n");
480                 }
481
482                 // now find EVERY teammate within reviving radius, set their revive_progress values correct
483                 FOR_EACH_PLAYER(other) if(self != other)
484                 {
485                         if(other.freezetag_frozen == 0)
486                         {
487                                 if(other.team == self.team)
488                                 {
489                                         if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
490                                                 other.freezetag_revive_progress = self.freezetag_revive_progress;
491                                 }
492                         }
493                 }
494         }
495         else if(!n && self.freezetag_frozen) // only if no teammate is nearby will we reset
496         {
497                 self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
498                 self.health = max(1, self.freezetag_revive_progress * autocvar_g_balance_health_start);
499         }
500         else if(!n)
501         {
502                 self.freezetag_revive_progress = 0; // thawing nobody
503         }
504
505         return 1;
506 }
507
508 MUTATOR_HOOKFUNCTION(freezetag_PlayerPhysics)
509 {
510         if(self.freezetag_frozen)
511         {
512                 self.movement = '0 0 0';
513                 self.disableclientprediction = 1;
514         }
515         return 1;
516 }
517
518 MUTATOR_HOOKFUNCTION(freezetag_PlayerDamage_Calculate)
519 {
520         if(frag_target.freezetag_frozen == 1 && frag_deathtype != DEATH_HURTTRIGGER)
521         {
522                 frag_damage = 0;
523                 frag_force = frag_force * autocvar_g_freezetag_frozen_force;
524         }
525         return 1;
526 }
527
528 MUTATOR_HOOKFUNCTION(freezetag_ForbidThrowCurrentWeapon)
529 {
530         if (self.freezetag_frozen)
531                 return 1;
532         return 0;
533 }
534
535 MUTATOR_HOOKFUNCTION(freezetag_BotRoles)
536 {
537         if not(self.deadflag)
538         {
539                 if (random() < 0.5)
540                         self.havocbot_role = havocbot_role_ft_freeing;
541                 else
542                         self.havocbot_role = havocbot_role_ft_offense;
543         }
544
545         return TRUE;
546 }
547
548 MUTATOR_DEFINITION(gamemode_freezetag)
549 {
550         MUTATOR_HOOK(MakePlayerObserver, freezetag_RemovePlayer, CBC_ORDER_ANY);
551         MUTATOR_HOOK(ClientDisconnect, freezetag_RemovePlayer, CBC_ORDER_ANY);
552         MUTATOR_HOOK(PlayerDies, freezetag_PlayerDies, CBC_ORDER_ANY);
553         MUTATOR_HOOK(PlayerSpawn, freezetag_PlayerSpawn, CBC_ORDER_ANY);
554         MUTATOR_HOOK(reset_map_global, freezetag_reset_map_global, CBC_ORDER_ANY);
555         MUTATOR_HOOK(reset_map_players, freezetag_reset_map_players, CBC_ORDER_ANY);
556         MUTATOR_HOOK(GiveFragsForKill, freezetag_GiveFragsForKill, CBC_ORDER_FIRST);
557         MUTATOR_HOOK(PlayerPreThink, freezetag_PlayerPreThink, CBC_ORDER_FIRST);
558         MUTATOR_HOOK(PlayerPhysics, freezetag_PlayerPhysics, CBC_ORDER_FIRST);
559         MUTATOR_HOOK(PlayerDamage_Calculate, freezetag_PlayerDamage_Calculate, CBC_ORDER_ANY);
560         MUTATOR_HOOK(ForbidThrowCurrentWeapon, freezetag_ForbidThrowCurrentWeapon, CBC_ORDER_FIRST); //first, last or any? dunno.
561         MUTATOR_HOOK(HavocBot_ChooseRule, freezetag_BotRoles, CBC_ORDER_ANY);
562
563         MUTATOR_ONADD
564         {
565                 if(time > 1) // game loads at time 1
566                         error("This is a game type and it cannot be added at runtime.");
567                 freezetag_Initialize();
568         }
569
570         MUTATOR_ONREMOVE
571         {
572                 error("This is a game type and it cannot be removed at runtime.");
573         }
574
575         return 0;
576 }