1 #ifndef GAMEMODE_DOMINATION_H
2 #define GAMEMODE_DOMINATION_H
4 #define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
5 bool autocvar_g_domination_roundbased;
6 int autocvar_g_domination_roundbased_point_limit;
7 int autocvar_g_domination_point_leadlimit;
11 REGISTER_MUTATOR(dom, false)
15 if (time > 1) // game loads at time 1
16 error("This is a game type and it cannot be added at runtime.");
19 int fraglimit_override = autocvar_g_domination_point_limit;
20 if (autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
21 fraglimit_override = autocvar_g_domination_roundbased_point_limit;
24 SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, -1, -1);
25 have_team_spawns = -1; // request team spawns
30 LOG_INFO("This is a game type and it cannot be removed at runtime.");
37 // score rule declarations
38 const float ST_DOM_TICKS = 1;
39 const float SP_DOM_TICKS = 4;
40 const float SP_DOM_TAKES = 5;
41 const float ST_DOM_CAPS = 1;
42 const float SP_DOM_CAPS = 4;
44 // pps: points per second
45 .float dom_total_pps = _STAT(DOM_TOTAL_PPS);
46 .float dom_pps_red = _STAT(DOM_PPS_RED);
47 .float dom_pps_blue = _STAT(DOM_PPS_BLUE);
48 .float dom_pps_yellow = _STAT(DOM_PPS_YELLOW);
49 .float dom_pps_pink = _STAT(DOM_PPS_PINK);
56 // capture declarations
57 .float enemy_playerid;
62 float domination_roundbased;
63 float domination_teams;
68 #include "../../teamplay.qh"
72 int autocvar_g_domination_default_teams;
73 bool autocvar_g_domination_disable_frags;
74 int autocvar_g_domination_point_amt;
75 bool autocvar_g_domination_point_fullbright;
76 float autocvar_g_domination_round_timelimit;
77 float autocvar_g_domination_warmup;
78 float autocvar_g_domination_point_rate;
79 int autocvar_g_domination_teams_override;
81 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
83 if(autocvar_sv_eventlog)
84 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
87 void set_dom_state(entity e)
89 e.dom_total_pps = total_pps;
90 e.dom_pps_red = pps_red;
91 e.dom_pps_blue = pps_blue;
92 if(domination_teams >= 3)
93 e.dom_pps_yellow = pps_yellow;
94 if(domination_teams >= 4)
95 e.dom_pps_pink = pps_pink;
98 void dompoint_captured ()
101 float old_delay, old_team, real_team;
103 // now that the delay has expired, switch to the latest team to lay claim to this point
106 real_team = self.cnt;
109 dom_EventLog("taken", self.team, self.dmg_inflictor);
110 self.dmg_inflictor = world;
112 self.goalentity = head;
113 self.model = head.mdl;
114 self.modelindex = head.dmg;
115 self.skin = head.skin;
117 float points, wait_time;
118 if (autocvar_g_domination_point_amt)
119 points = autocvar_g_domination_point_amt;
122 if (autocvar_g_domination_point_rate)
123 wait_time = autocvar_g_domination_point_rate;
125 wait_time = self.wait;
127 if(domination_roundbased)
128 bprint(sprintf("^3%s^3%s\n", head.netname, self.message));
130 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time);
132 if(self.enemy.playerid == self.enemy_playerid)
133 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
137 if (head.noise != "")
139 _sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
141 _sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
142 if (head.noise1 != "")
143 play2all(head.noise1);
145 self.delay = time + wait_time;
148 old_delay = self.delay;
149 old_team = self.team;
150 self.team = real_team;
154 self.delay = old_delay;
155 self.team = old_team;
157 entity msg = WP_DomNeut;
160 case NUM_TEAM_1: msg = WP_DomRed; break;
161 case NUM_TEAM_2: msg = WP_DomBlue; break;
162 case NUM_TEAM_3: msg = WP_DomYellow; break;
163 case NUM_TEAM_4: msg = WP_DomPink; break;
166 WaypointSprite_UpdateSprites(self.sprite, msg, WP_Null, WP_Null);
168 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
169 for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
170 FOREACH_ENTITY_CLASS("dom_controlpoint", true, LAMBDA(
171 if (autocvar_g_domination_point_amt)
172 points = autocvar_g_domination_point_amt;
175 if (autocvar_g_domination_point_rate)
176 wait_time = autocvar_g_domination_point_rate;
179 switch(it.goalentity.team)
181 case NUM_TEAM_1: pps_red += points/wait_time; break;
182 case NUM_TEAM_2: pps_blue += points/wait_time; break;
183 case NUM_TEAM_3: pps_yellow += points/wait_time; break;
184 case NUM_TEAM_4: pps_pink += points/wait_time; break;
186 total_pps += points/wait_time;
189 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
190 WaypointSprite_Ping(self.sprite);
194 FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(set_dom_state(it)));
197 void AnimateDomPoint()
199 if(self.pain_finished > time)
201 self.pain_finished = time + self.t_width;
202 if(self.nextthink > self.pain_finished)
203 self.nextthink = self.pain_finished;
205 self.frame = self.frame + 1;
206 if(self.frame > self.t_length)
214 self.nextthink = time + 0.1;
216 //self.frame = self.frame + 1;
217 //if(self.frame > 119)
223 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
226 if(autocvar_g_domination_point_rate)
227 self.delay = time + autocvar_g_domination_point_rate;
229 self.delay = time + self.wait;
231 // give credit to the team
232 // NOTE: this defaults to 0
233 if (!domination_roundbased)
234 if (self.goalentity.netname != "")
236 if(autocvar_g_domination_point_amt)
237 fragamt = autocvar_g_domination_point_amt;
239 fragamt = self.frags;
240 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
241 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
243 // give credit to the individual player, if he is still there
244 if (self.enemy.playerid == self.enemy_playerid)
246 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
247 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
257 if (!IS_PLAYER(other))
259 if (other.health < 1)
262 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
265 if(time < self.captime + 0.3)
268 // only valid teams can claim it
269 head = find(world, classname, "dom_team");
270 while (head && head.team != other.team)
271 head = find(head, classname, "dom_team");
272 if (!head || head.netname == "" || head == self.goalentity)
277 self.team = self.goalentity.team; // this stores the PREVIOUS team!
279 self.cnt = other.team;
280 self.owner = head; // team to switch to after the delay
281 self.dmg_inflictor = other;
284 // self.delay = time + cvar("g_domination_point_capturetime");
285 //self.nextthink = time + cvar("g_domination_point_capturetime");
286 //self.think = dompoint_captured;
288 // go to neutral team in the mean time
289 head = find(world, classname, "dom_team");
290 while (head && head.netname != "")
291 head = find(head, classname, "dom_team");
295 WaypointSprite_UpdateSprites(self.sprite, WP_DomNeut, WP_Null, WP_Null);
296 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
297 WaypointSprite_Ping(self.sprite);
299 self.goalentity = head;
300 self.model = head.mdl;
301 self.modelindex = head.dmg;
302 self.skin = head.skin;
304 self.enemy = other; // individual player scoring
305 self.enemy_playerid = other.playerid;
309 void dom_controlpoint_setup(entity this);
310 void dom_controlpoint_setup_self() { SELFPARAM(); dom_controlpoint_setup(this); }
311 void dom_controlpoint_setup(entity this)
314 // find the spawnfunc_dom_team representing unclaimed points
315 head = find(world, classname, "dom_team");
316 while(head && head.netname != "")
317 head = find(head, classname, "dom_team");
319 objerror("no spawnfunc_dom_team with netname \"\" found\n");
321 // copy important properties from spawnfunc_dom_team entity
322 self.goalentity = head;
323 _setmodel(self, head.mdl); // precision already set
324 self.skin = head.skin;
328 if(self.message == "")
329 self.message = " has captured a control point";
336 float points, waittime;
337 if (autocvar_g_domination_point_amt)
338 points = autocvar_g_domination_point_amt;
341 if (autocvar_g_domination_point_rate)
342 waittime = autocvar_g_domination_point_rate;
344 waittime = self.wait;
346 total_pps += points/waittime;
349 self.t_width = 0.02; // frame animation rate
351 self.t_length = 239; // maximum frame
353 self.think = dompointthink;
354 self.nextthink = time;
355 self.touch = dompointtouch;
356 self.solid = SOLID_TRIGGER;
357 self.flags = FL_ITEM;
358 setsize(self, '-32 -32 -32', '32 32 32');
359 setorigin(self, self.origin + '0 0 20');
362 waypoint_spawnforitem(self);
363 WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT);
366 float total_controlpoints;
367 void Domination_count_controlpoints()
370 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
371 for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
373 ++total_controlpoints;
374 redowned += (e.goalentity.team == NUM_TEAM_1);
375 blueowned += (e.goalentity.team == NUM_TEAM_2);
376 yellowowned += (e.goalentity.team == NUM_TEAM_3);
377 pinkowned += (e.goalentity.team == NUM_TEAM_4);
381 float Domination_GetWinnerTeam()
383 float winner_team = 0;
384 if(redowned == total_controlpoints)
385 winner_team = NUM_TEAM_1;
386 if(blueowned == total_controlpoints)
388 if(winner_team) return 0;
389 winner_team = NUM_TEAM_2;
391 if(yellowowned == total_controlpoints)
393 if(winner_team) return 0;
394 winner_team = NUM_TEAM_3;
396 if(pinkowned == total_controlpoints)
398 if(winner_team) return 0;
399 winner_team = NUM_TEAM_4;
403 return -1; // no control points left?
406 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
407 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
408 float Domination_CheckWinner()
410 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
412 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
413 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
414 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
418 Domination_count_controlpoints();
420 float winner_team = Domination_GetWinnerTeam();
422 if(winner_team == -1)
427 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
428 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
429 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
431 else if(winner_team == -1)
433 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
434 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
437 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
442 float Domination_CheckPlayers()
447 void Domination_RoundStart()
449 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(it.player_blocked = false));
452 //go to best items, or control points you don't own
453 void havocbot_role_dom()
455 if(self.deadflag != DEAD_NO)
458 if (self.bot_strategytime < time)
460 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
461 navigation_goalrating_start();
462 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
463 havocbot_goalrating_items(8000, self.origin, 8000);
464 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
465 //havocbot_goalrating_waypoints(1, self.origin, 1000);
466 navigation_goalrating_end();
470 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
473 ret_float = domination_teams;
474 ret_string = "dom_team";
476 entity head = find(world, classname, ret_string);
479 if(head.netname != "")
483 case NUM_TEAM_1: c1 = 0; break;
484 case NUM_TEAM_2: c2 = 0; break;
485 case NUM_TEAM_3: c3 = 0; break;
486 case NUM_TEAM_4: c4 = 0; break;
490 head = find(head, classname, ret_string);
493 ret_string = string_null;
498 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
500 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
501 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
504 self.player_blocked = 1;
505 if(IS_REAL_CLIENT(self))
511 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
513 if(domination_roundbased)
514 if(!round_handler_IsRoundStarted())
515 self.player_blocked = 1;
517 self.player_blocked = 0;
521 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
527 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
529 self.havocbot_role = havocbot_role_dom;
533 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
534 Control point for Domination gameplay.
536 spawnfunc(dom_controlpoint)
543 self.think = dom_controlpoint_setup_self;
544 self.nextthink = time + 0.1;
545 self.reset = dom_controlpoint_setup;
550 self.effects = self.effects | EF_LOWPRECISION;
551 if (autocvar_g_domination_point_fullbright)
552 self.effects |= EF_FULLBRIGHT;
555 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
556 Team declaration for Domination gameplay, this allows you to decide what team
557 names and control point models are used in your map.
559 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
560 can have netname set! The nameless team owns all control points at start.
564 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
566 Scoreboard color of the team (for example 4 is red and 13 is blue)
568 Model to use for control points owned by this team (for example
569 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
572 Skin of the model to use (for team skins on a single model)
574 Sound to play when this team captures a point.
575 (this is a localized sound, like a small alarm or other effect)
577 Narrator speech to play when this team captures a point.
578 (this is a global sound, like "Red team has captured a control point")
583 if(!g_domination || autocvar_g_domination_teams_override >= 2)
588 precache_model(self.model);
589 if (self.noise != "")
590 precache_sound(self.noise);
591 if (self.noise1 != "")
592 precache_sound(self.noise1);
593 self.classname = "dom_team";
594 _setmodel(self, self.model); // precision not needed
595 self.mdl = self.model;
596 self.dmg = self.modelindex;
599 // this would have to be changed if used in quakeworld
601 self.team = self.cnt + 1; // WHY are these different anyway?
605 void ScoreRules_dom(float teams)
607 if(domination_roundbased)
609 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
610 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
611 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
612 ScoreRules_basics_end();
616 float sp_domticks, sp_score;
617 sp_score = sp_domticks = 0;
618 if(autocvar_g_domination_disable_frags)
619 sp_domticks = SFL_SORT_PRIO_PRIMARY;
621 sp_score = SFL_SORT_PRIO_PRIMARY;
622 ScoreRules_basics(teams, sp_score, sp_score, true);
623 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
624 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
625 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
626 ScoreRules_basics_end();
630 // code from here on is just to support maps that don't have control point and team entities
631 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
634 self.classname = "dom_team";
635 self.netname = strzone(teamname);
636 self.cnt = teamcolor;
637 self.model = pointmodel;
638 self.skin = pointskin;
639 self.noise = strzone(capsound);
640 self.noise1 = strzone(capnarration);
641 self.message = strzone(capmessage);
643 // this code is identical to spawnfunc_dom_team
644 _setmodel(self, self.model); // precision not needed
645 self.mdl = self.model;
646 self.dmg = self.modelindex;
649 // this would have to be changed if used in quakeworld
650 self.team = self.cnt + 1;
656 void self_spawnfunc_dom_controlpoint() { SELFPARAM(); spawnfunc_dom_controlpoint(self); }
657 void dom_spawnpoint(vector org)
660 self.classname = "dom_controlpoint";
661 self.think = self_spawnfunc_dom_controlpoint;
662 self.nextthink = time;
663 setorigin(self, org);
664 spawnfunc_dom_controlpoint(this);
668 // spawn some default teams if the map is not set up for domination
669 void dom_spawnteams(float teams)
671 dom_spawnteam(Team_ColoredFullName(NUM_TEAM_1), NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND(DOM_CLAIM), "", "Red team has captured a control point");
672 dom_spawnteam(Team_ColoredFullName(NUM_TEAM_2), NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND(DOM_CLAIM), "", "Blue team has captured a control point");
674 dom_spawnteam(Team_ColoredFullName(NUM_TEAM_3), NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND(DOM_CLAIM), "", "Yellow team has captured a control point");
676 dom_spawnteam(Team_ColoredFullName(NUM_TEAM_4), NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND(DOM_CLAIM), "", "Pink team has captured a control point");
677 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
680 void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
682 // if no teams are found, spawn defaults
683 if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
685 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.\n");
686 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
687 dom_spawnteams(domination_teams);
690 CheckAllowedTeams(world);
691 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
693 domination_roundbased = autocvar_g_domination_roundbased;
695 ScoreRules_dom(domination_teams);
697 if(domination_roundbased)
699 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
700 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
704 void dom_Initialize()
707 InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);