1 #include "gamemode_domination.qh"
6 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
8 if(autocvar_sv_eventlog)
9 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
12 void set_dom_state(entity e)
14 e.dom_total_pps = total_pps;
15 e.dom_pps_red = pps_red;
16 e.dom_pps_blue = pps_blue;
17 if(domination_teams >= 3)
18 e.dom_pps_yellow = pps_yellow;
19 if(domination_teams >= 4)
20 e.dom_pps_pink = pps_pink;
23 void dompoint_captured ()
26 float old_delay, old_team, real_team;
28 // now that the delay has expired, switch to the latest team to lay claim to this point
34 dom_EventLog("taken", self.team, self.dmg_inflictor);
35 self.dmg_inflictor = world;
37 self.goalentity = head;
38 self.model = head.mdl;
39 self.modelindex = head.dmg;
40 self.skin = head.skin;
42 float points, wait_time;
43 if (autocvar_g_domination_point_amt)
44 points = autocvar_g_domination_point_amt;
47 if (autocvar_g_domination_point_rate)
48 wait_time = autocvar_g_domination_point_rate;
50 wait_time = self.wait;
52 if(domination_roundbased)
53 bprint(sprintf("^3%s^3%s\n", head.netname, self.message));
55 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time);
57 if(self.enemy.playerid == self.enemy_playerid)
58 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
64 sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
66 sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
67 if (head.noise1 != "")
68 play2all(head.noise1);
70 self.delay = time + wait_time;
73 old_delay = self.delay;
75 self.team = real_team;
79 self.delay = old_delay;
82 entity msg = WP_DomNeut;
85 case NUM_TEAM_1: msg = WP_DomRed; break;
86 case NUM_TEAM_2: msg = WP_DomBlue; break;
87 case NUM_TEAM_3: msg = WP_DomYellow; break;
88 case NUM_TEAM_4: msg = WP_DomPink; break;
91 WaypointSprite_UpdateSprites(self.sprite, msg, WP_Null, WP_Null);
93 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
94 for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
96 if (autocvar_g_domination_point_amt)
97 points = autocvar_g_domination_point_amt;
100 if (autocvar_g_domination_point_rate)
101 wait_time = autocvar_g_domination_point_rate;
103 wait_time = head.wait;
104 switch(head.goalentity.team)
107 pps_red += points/wait_time;
110 pps_blue += points/wait_time;
113 pps_yellow += points/wait_time;
116 pps_pink += points/wait_time;
119 total_pps += points/wait_time;
122 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
123 WaypointSprite_Ping(self.sprite);
127 FOR_EACH_REALCLIENT(head)
131 void AnimateDomPoint()
133 if(self.pain_finished > time)
135 self.pain_finished = time + self.t_width;
136 if(self.nextthink > self.pain_finished)
137 self.nextthink = self.pain_finished;
139 self.frame = self.frame + 1;
140 if(self.frame > self.t_length)
148 self.nextthink = time + 0.1;
150 //self.frame = self.frame + 1;
151 //if(self.frame > 119)
157 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
160 if(autocvar_g_domination_point_rate)
161 self.delay = time + autocvar_g_domination_point_rate;
163 self.delay = time + self.wait;
165 // give credit to the team
166 // NOTE: this defaults to 0
167 if (!domination_roundbased)
168 if (self.goalentity.netname != "")
170 if(autocvar_g_domination_point_amt)
171 fragamt = autocvar_g_domination_point_amt;
173 fragamt = self.frags;
174 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
175 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
177 // give credit to the individual player, if he is still there
178 if (self.enemy.playerid == self.enemy_playerid)
180 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
181 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
191 if (!IS_PLAYER(other))
193 if (other.health < 1)
196 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
199 if(time < self.captime + 0.3)
202 // only valid teams can claim it
203 head = find(world, classname, "dom_team");
204 while (head && head.team != other.team)
205 head = find(head, classname, "dom_team");
206 if (!head || head.netname == "" || head == self.goalentity)
211 self.team = self.goalentity.team; // this stores the PREVIOUS team!
213 self.cnt = other.team;
214 self.owner = head; // team to switch to after the delay
215 self.dmg_inflictor = other;
218 // self.delay = time + cvar("g_domination_point_capturetime");
219 //self.nextthink = time + cvar("g_domination_point_capturetime");
220 //self.think = dompoint_captured;
222 // go to neutral team in the mean time
223 head = find(world, classname, "dom_team");
224 while (head && head.netname != "")
225 head = find(head, classname, "dom_team");
229 WaypointSprite_UpdateSprites(self.sprite, WP_DomNeut, WP_Null, WP_Null);
230 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
231 WaypointSprite_Ping(self.sprite);
233 self.goalentity = head;
234 self.model = head.mdl;
235 self.modelindex = head.dmg;
236 self.skin = head.skin;
238 self.enemy = other; // individual player scoring
239 self.enemy_playerid = other.playerid;
243 void dom_controlpoint_setup()
246 // find the spawnfunc_dom_team representing unclaimed points
247 head = find(world, classname, "dom_team");
248 while(head && head.netname != "")
249 head = find(head, classname, "dom_team");
251 objerror("no spawnfunc_dom_team with netname \"\" found\n");
253 // copy important properties from spawnfunc_dom_team entity
254 self.goalentity = head;
255 _setmodel(self, head.mdl); // precision already set
256 self.skin = head.skin;
260 if(self.message == "")
261 self.message = " has captured a control point";
268 float points, waittime;
269 if (autocvar_g_domination_point_amt)
270 points = autocvar_g_domination_point_amt;
273 if (autocvar_g_domination_point_rate)
274 waittime = autocvar_g_domination_point_rate;
276 waittime = self.wait;
278 total_pps += points/waittime;
281 self.t_width = 0.02; // frame animation rate
283 self.t_length = 239; // maximum frame
285 self.think = dompointthink;
286 self.nextthink = time;
287 self.touch = dompointtouch;
288 self.solid = SOLID_TRIGGER;
289 self.flags = FL_ITEM;
290 setsize(self, '-32 -32 -32', '32 32 32');
291 setorigin(self, self.origin + '0 0 20');
294 waypoint_spawnforitem(self);
295 WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT);
298 float total_controlpoints, redowned, blueowned, yellowowned, pinkowned;
299 void Domination_count_controlpoints()
302 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
303 for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
305 ++total_controlpoints;
306 redowned += (e.goalentity.team == NUM_TEAM_1);
307 blueowned += (e.goalentity.team == NUM_TEAM_2);
308 yellowowned += (e.goalentity.team == NUM_TEAM_3);
309 pinkowned += (e.goalentity.team == NUM_TEAM_4);
313 float Domination_GetWinnerTeam()
315 float winner_team = 0;
316 if(redowned == total_controlpoints)
317 winner_team = NUM_TEAM_1;
318 if(blueowned == total_controlpoints)
320 if(winner_team) return 0;
321 winner_team = NUM_TEAM_2;
323 if(yellowowned == total_controlpoints)
325 if(winner_team) return 0;
326 winner_team = NUM_TEAM_3;
328 if(pinkowned == total_controlpoints)
330 if(winner_team) return 0;
331 winner_team = NUM_TEAM_4;
335 return -1; // no control points left?
338 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
339 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
340 float Domination_CheckWinner()
342 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
344 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
345 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
346 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
350 Domination_count_controlpoints();
352 float winner_team = Domination_GetWinnerTeam();
354 if(winner_team == -1)
359 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
360 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
361 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
363 else if(winner_team == -1)
365 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
366 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
369 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
374 float Domination_CheckPlayers()
379 void Domination_RoundStart()
383 e.player_blocked = 0;
386 //go to best items, or control points you don't own
387 void havocbot_role_dom()
389 if(self.deadflag != DEAD_NO)
392 if (self.bot_strategytime < time)
394 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
395 navigation_goalrating_start();
396 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
397 havocbot_goalrating_items(8000, self.origin, 8000);
398 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
399 //havocbot_goalrating_waypoints(1, self.origin, 1000);
400 navigation_goalrating_end();
404 MUTATOR_HOOKFUNCTION(dom_GetTeamCount)
406 ret_float = domination_teams;
410 MUTATOR_HOOKFUNCTION(dom_ResetMap)
412 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
418 self.player_blocked = 1;
419 if(IS_REAL_CLIENT(self))
425 MUTATOR_HOOKFUNCTION(dom_PlayerSpawn)
427 if(domination_roundbased)
428 if(!round_handler_IsRoundStarted())
429 self.player_blocked = 1;
431 self.player_blocked = 0;
435 MUTATOR_HOOKFUNCTION(dom_ClientConnect)
441 MUTATOR_HOOKFUNCTION(dom_BotRoles)
443 self.havocbot_role = havocbot_role_dom;
447 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
448 Control point for Domination gameplay.
450 void spawnfunc_dom_controlpoint()
457 self.think = dom_controlpoint_setup;
458 self.nextthink = time + 0.1;
459 self.reset = dom_controlpoint_setup;
464 self.effects = self.effects | EF_LOWPRECISION;
465 if (autocvar_g_domination_point_fullbright)
466 self.effects |= EF_FULLBRIGHT;
469 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
470 Team declaration for Domination gameplay, this allows you to decide what team
471 names and control point models are used in your map.
473 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
474 can have netname set! The nameless team owns all control points at start.
478 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
480 Scoreboard color of the team (for example 4 is red and 13 is blue)
482 Model to use for control points owned by this team (for example
483 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
486 Skin of the model to use (for team skins on a single model)
488 Sound to play when this team captures a point.
489 (this is a localized sound, like a small alarm or other effect)
491 Narrator speech to play when this team captures a point.
492 (this is a global sound, like "Red team has captured a control point")
495 void spawnfunc_dom_team()
497 if(!g_domination || autocvar_g_domination_teams_override >= 2)
502 precache_model(self.model);
503 if (self.noise != "")
504 precache_sound(self.noise);
505 if (self.noise1 != "")
506 precache_sound(self.noise1);
507 self.classname = "dom_team";
508 _setmodel(self, self.model); // precision not needed
509 self.mdl = self.model;
510 self.dmg = self.modelindex;
513 // this would have to be changed if used in quakeworld
515 self.team = self.cnt + 1; // WHY are these different anyway?
519 void ScoreRules_dom(float teams)
521 if(domination_roundbased)
523 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
524 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
525 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
526 ScoreRules_basics_end();
530 float sp_domticks, sp_score;
531 sp_score = sp_domticks = 0;
532 if(autocvar_g_domination_disable_frags)
533 sp_domticks = SFL_SORT_PRIO_PRIMARY;
535 sp_score = SFL_SORT_PRIO_PRIMARY;
536 ScoreRules_basics(teams, sp_score, sp_score, true);
537 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
538 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
539 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
540 ScoreRules_basics_end();
544 // code from here on is just to support maps that don't have control point and team entities
545 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
548 self.classname = "dom_team";
549 self.netname = teamname;
550 self.cnt = teamcolor;
551 self.model = pointmodel;
552 self.skin = pointskin;
553 self.noise = capsound;
554 self.noise1 = capnarration;
555 self.message = capmessage;
557 // this code is identical to spawnfunc_dom_team
558 _setmodel(self, self.model); // precision not needed
559 self.mdl = self.model;
560 self.dmg = self.modelindex;
563 // this would have to be changed if used in quakeworld
564 self.team = self.cnt + 1;
570 void dom_spawnpoint(vector org)
573 self.classname = "dom_controlpoint";
574 self.think = spawnfunc_dom_controlpoint;
575 self.nextthink = time;
576 setorigin(self, org);
577 spawnfunc_dom_controlpoint();
581 // spawn some default teams if the map is not set up for domination
582 void dom_spawnteams(float teams)
584 dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
585 dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
587 dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
589 dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
590 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
593 void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
595 // if no teams are found, spawn defaults
596 if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
598 LOG_INFO("No ""dom_team"" entities found on this map, creating them anyway.\n");
599 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
600 dom_spawnteams(domination_teams);
603 CheckAllowedTeams(world);
604 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
606 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
607 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
608 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
609 if(domination_teams >= 3) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
610 if(domination_teams >= 4) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
612 domination_roundbased = autocvar_g_domination_roundbased;
614 ScoreRules_dom(domination_teams);
616 if(domination_roundbased)
618 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
619 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
623 void dom_Initialize()
625 precache_sound("domination/claim.wav");
627 InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);
631 MUTATOR_DEFINITION(gamemode_domination)
633 MUTATOR_HOOK(GetTeamCount, dom_GetTeamCount, CBC_ORDER_ANY);
634 MUTATOR_HOOK(reset_map_players, dom_ResetMap, CBC_ORDER_ANY);
635 MUTATOR_HOOK(PlayerSpawn, dom_PlayerSpawn, CBC_ORDER_ANY);
636 MUTATOR_HOOK(ClientConnect, dom_ClientConnect, CBC_ORDER_ANY);
637 MUTATOR_HOOK(HavocBot_ChooseRole, dom_BotRoles, CBC_ORDER_ANY);
641 if(time > 1) // game loads at time 1
642 error("This is a game type and it cannot be added at runtime.");
648 LOG_INFO("This is a game type and it cannot be removed at runtime.");