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
8 void freezetag_count_alive_players()
11 total_players = redalive = bluealive = yellowalive = pinkalive = 0;
16 case NUM_TEAM_1: ++total_players; if(e.health >= 1 && e.frozen != 1) ++redalive; break;
17 case NUM_TEAM_2: ++total_players; if(e.health >= 1 && e.frozen != 1) ++bluealive; break;
18 case NUM_TEAM_3: ++total_players; if(e.health >= 1 && e.frozen != 1) ++yellowalive; break;
19 case NUM_TEAM_4: ++total_players; if(e.health >= 1 && e.frozen != 1) ++pinkalive; break;
22 FOR_EACH_REALCLIENT(e)
24 e.redalive_stat = redalive;
25 e.bluealive_stat = bluealive;
26 e.yellowalive_stat = yellowalive;
27 e.pinkalive_stat = pinkalive;
30 eliminatedPlayers.SendFlags |= 1;
32 #define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
33 #define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == freezetag_teams)
35 float prev_missing_teams_mask;
36 float freezetag_CheckTeams()
38 if(FREEZETAG_ALIVE_TEAMS_OK())
40 if(prev_missing_teams_mask > 0)
41 Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
42 prev_missing_teams_mask = -1;
45 if(total_players == 0)
47 if(prev_missing_teams_mask > 0)
48 Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
49 prev_missing_teams_mask = -1;
52 float missing_teams_mask = (!redalive) + (!bluealive) * 2;
53 if(freezetag_teams >= 3) missing_teams_mask += (!yellowalive) * 4;
54 if(freezetag_teams >= 4) missing_teams_mask += (!pinkalive) * 8;
55 if(prev_missing_teams_mask != missing_teams_mask)
57 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
58 prev_missing_teams_mask = missing_teams_mask;
63 float freezetag_getWinnerTeam()
65 float winner_team = 0;
67 winner_team = NUM_TEAM_1;
70 if(winner_team) return 0;
71 winner_team = NUM_TEAM_2;
75 if(winner_team) return 0;
76 winner_team = NUM_TEAM_3;
80 if(winner_team) return 0;
81 winner_team = NUM_TEAM_4;
85 return -1; // no player left
88 float freezetag_CheckWinner()
91 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
93 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
94 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
97 e.freezetag_frozen_timeout = 0;
100 round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
104 if(FREEZETAG_ALIVE_TEAMS() > 1)
108 winner_team = freezetag_getWinnerTeam();
111 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
112 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
113 TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
115 else if(winner_team == -1)
117 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
118 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
123 e.freezetag_frozen_timeout = 0;
126 round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
130 entity freezetag_LastPlayerForTeam()
132 entity pl, last_pl = world;
138 if(pl.team == self.team)
147 void freezetag_LastPlayerForTeam_Notify()
149 if(round_handler_IsActive())
150 if(round_handler_IsRoundStarted())
152 entity pl = freezetag_LastPlayerForTeam();
154 Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
158 void freezetag_Add_Score(entity attacker)
162 // you froze your own dumb self
163 // counted as "suicide" already
164 PlayerScore_Add(self, SP_SCORE, -1);
166 else if(IS_PLAYER(attacker))
168 // got frozen by an enemy
169 // counted as "kill" and "death" already
170 PlayerScore_Add(self, SP_SCORE, -1);
171 PlayerScore_Add(attacker, SP_SCORE, +1);
173 // else nothing - got frozen by the game type rules themselves
176 void freezetag_Freeze(entity attacker)
181 if(autocvar_g_freezetag_frozen_maxtime > 0)
182 self.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
184 Freeze(self, 0, 1, TRUE);
186 freezetag_count_alive_players();
188 freezetag_Add_Score(attacker);
191 void freezetag_Unfreeze(entity attacker)
193 self.freezetag_frozen_time = 0;
194 self.freezetag_frozen_timeout = 0;
199 float freezetag_isEliminated(entity e)
201 if(e.frozen == 1 || e.deadflag != DEAD_NO)
211 void() havocbot_role_ft_freeing;
212 void() havocbot_role_ft_offense;
214 void havocbot_goalrating_freeplayers(float ratingscale, vector org, float sradius)
219 FOR_EACH_PLAYER(head)
221 if ((head != self) && (head.team == self.team))
223 if (head.frozen == 1)
225 distance = vlen(head.origin - org);
226 if (distance > sradius)
228 navigation_routerating(head, ratingscale, 2000);
232 // If teamate is not frozen still seek them out as fight better
234 navigation_routerating(head, ratingscale/3, 2000);
240 void havocbot_role_ft_offense()
245 if(self.deadflag != DEAD_NO)
248 if (!self.havocbot_role_timeout)
249 self.havocbot_role_timeout = time + random() * 10 + 20;
251 // Count how many players on team are unfrozen.
253 FOR_EACH_PLAYER(head)
255 if ((head.team == self.team) && (head.frozen != 1))
259 // If only one left on team or if role has timed out then start trying to free players.
260 if (((unfrozen == 0) && (!self.frozen)) || (time > self.havocbot_role_timeout))
262 dprint("changing role to freeing\n");
263 self.havocbot_role = havocbot_role_ft_freeing;
264 self.havocbot_role_timeout = 0;
268 if (time > self.bot_strategytime)
270 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
272 navigation_goalrating_start();
273 havocbot_goalrating_items(10000, self.origin, 10000);
274 havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
275 havocbot_goalrating_freeplayers(9000, self.origin, 10000);
276 //havocbot_goalrating_waypoints(1, self.origin, 1000);
277 navigation_goalrating_end();
281 void havocbot_role_ft_freeing()
283 if(self.deadflag != DEAD_NO)
286 if (!self.havocbot_role_timeout)
287 self.havocbot_role_timeout = time + random() * 10 + 20;
289 if (time > self.havocbot_role_timeout)
291 dprint("changing role to offense\n");
292 self.havocbot_role = havocbot_role_ft_offense;
293 self.havocbot_role_timeout = 0;
297 if (time > self.bot_strategytime)
299 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
301 navigation_goalrating_start();
302 havocbot_goalrating_items(8000, self.origin, 10000);
303 havocbot_goalrating_enemyplayers(10000, self.origin, 10000);
304 havocbot_goalrating_freeplayers(20000, self.origin, 10000);
305 //havocbot_goalrating_waypoints(1, self.origin, 1000);
306 navigation_goalrating_end();
315 MUTATOR_HOOKFUNCTION(freezetag_RemovePlayer)
317 self.health = 0; // neccessary to update correctly alive stats
319 freezetag_LastPlayerForTeam_Notify();
320 freezetag_Unfreeze(world);
321 freezetag_count_alive_players();
325 MUTATOR_HOOKFUNCTION(freezetag_PlayerDies)
327 if(round_handler_IsActive())
328 if(round_handler_CountdownRunning())
331 freezetag_Unfreeze(world);
332 freezetag_count_alive_players();
333 return 1; // let the player die so that he can respawn whenever he wants
336 // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe
337 // you succeed changing team through the menu: you both really die (gibbing) and get frozen
338 if(ITEM_DAMAGE_NEEDKILL(frag_deathtype)
339 || frag_deathtype == DEATH_TEAMCHANGE || frag_deathtype == DEATH_AUTOTEAMCHANGE)
341 // let the player die, he will be automatically frozen when he respawns
344 freezetag_Add_Score(frag_attacker);
345 freezetag_count_alive_players();
346 freezetag_LastPlayerForTeam_Notify();
349 freezetag_Unfreeze(world); // remove ice
350 self.health = 0; // Unfreeze resets health
351 self.freezetag_frozen_timeout = -2; // freeze on respawn
358 freezetag_Freeze(frag_attacker);
359 freezetag_LastPlayerForTeam_Notify();
361 if(frag_attacker == frag_target || frag_attacker == world)
363 if(IS_PLAYER(frag_target))
364 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
365 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
369 if(IS_PLAYER(frag_target))
370 Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_FROZEN, frag_attacker.netname);
371 if(IS_PLAYER(frag_attacker))
372 Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_FREEZETAG_FREEZE, frag_target.netname);
373 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
379 MUTATOR_HOOKFUNCTION(freezetag_PlayerSpawn)
381 if(self.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
382 return 1; // do nothing, round is starting right now
384 if(self.freezetag_frozen_timeout == -2) // player was dead
386 freezetag_Freeze(world);
390 freezetag_count_alive_players();
392 if(round_handler_IsActive())
393 if(round_handler_IsRoundStarted())
395 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
396 freezetag_Freeze(world);
402 MUTATOR_HOOKFUNCTION(freezetag_reset_map_players)
404 FOR_EACH_PLAYER(self)
407 self.freezetag_frozen_timeout = -1;
409 self.freezetag_frozen_timeout = 0;
411 freezetag_count_alive_players();
415 MUTATOR_HOOKFUNCTION(freezetag_GiveFragsForKill)
417 frag_score = 0; // no frags counted in Freeze Tag
421 .float reviving; // temp var
422 MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink)
432 self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
435 if(round_handler_IsActive())
436 if(!round_handler_IsRoundStarted())
442 //if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout)
443 //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);
445 if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout)
449 vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
451 FOR_EACH_PLAYER(other)
453 if(other.frozen == 0)
454 if(other.deadflag == DEAD_NO)
455 if(SAME_TEAM(other, self))
456 if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax))
461 other.reviving = TRUE;
466 if(n && self.frozen == 1) // OK, there is at least one teammate reviving us
468 self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
469 self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health));
471 if(self.revive_progress >= 1)
473 freezetag_Unfreeze(self);
474 freezetag_count_alive_players();
478 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
479 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, self.netname, autocvar_g_freezetag_frozen_maxtime);
483 // EVERY team mate nearby gets a point (even if multiple!)
484 FOR_EACH_PLAYER(other)
488 PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1);
489 PlayerScore_Add(other, SP_SCORE, +1);
491 nades_GiveBonus(other,autocvar_g_nades_bonus_score_low);
495 Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
496 Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname);
497 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED, self.netname, o.netname);
500 FOR_EACH_PLAYER(other)
504 other.revive_progress = self.revive_progress;
505 other.reviving = FALSE;
509 else if(!n && self.frozen == 1) // only if no teammate is nearby will we reset
511 self.revive_progress = bound(0, self.revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
512 self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health));
514 else if(!n && !self.frozen)
516 self.revive_progress = 0; // thawing nobody
522 MUTATOR_HOOKFUNCTION(freezetag_BotRoles)
527 self.havocbot_role = havocbot_role_ft_freeing;
529 self.havocbot_role = havocbot_role_ft_offense;
535 MUTATOR_HOOKFUNCTION(freezetag_GetTeamCount)
537 ret_float = freezetag_teams;
541 void freezetag_Initialize()
543 freezetag_teams = autocvar_g_freezetag_teams_override;
544 if(freezetag_teams < 2)
545 freezetag_teams = autocvar_g_freezetag_teams;
546 freezetag_teams = bound(2, freezetag_teams, 4);
547 ScoreRules_freezetag(freezetag_teams);
549 round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
550 round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
552 addstat(STAT_REDALIVE, AS_INT, redalive_stat);
553 addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
554 addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
555 addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
557 EliminatedPlayers_Init(freezetag_isEliminated);
560 MUTATOR_DEFINITION(gamemode_freezetag)
562 MUTATOR_HOOK(MakePlayerObserver, freezetag_RemovePlayer, CBC_ORDER_ANY);
563 MUTATOR_HOOK(ClientDisconnect, freezetag_RemovePlayer, CBC_ORDER_ANY);
564 MUTATOR_HOOK(PlayerDies, freezetag_PlayerDies, CBC_ORDER_ANY);
565 MUTATOR_HOOK(PlayerSpawn, freezetag_PlayerSpawn, CBC_ORDER_ANY);
566 MUTATOR_HOOK(reset_map_players, freezetag_reset_map_players, CBC_ORDER_ANY);
567 MUTATOR_HOOK(GiveFragsForKill, freezetag_GiveFragsForKill, CBC_ORDER_FIRST);
568 MUTATOR_HOOK(PlayerPreThink, freezetag_PlayerPreThink, CBC_ORDER_FIRST);
569 MUTATOR_HOOK(HavocBot_ChooseRole, freezetag_BotRoles, CBC_ORDER_ANY);
570 MUTATOR_HOOK(GetTeamCount, freezetag_GetTeamCount, CBC_ORDER_EXCLUSIVE);
574 if(time > 1) // game loads at time 1
575 error("This is a game type and it cannot be added at runtime.");
576 freezetag_Initialize();
579 MUTATOR_ONROLLBACK_OR_REMOVE
581 // we actually cannot roll back freezetag_Initialize here
582 // BUT: we don't need to! If this gets called, adding always
588 print("This is a game type and it cannot be removed at runtime.");