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 <server/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(winner_team, CENTER_ROUND_TEAM_WIN));
428 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM(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()
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 if(domination_roundbased)
505 self.player_blocked = 1;
506 if(IS_REAL_CLIENT(self))
512 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
514 if(domination_roundbased)
515 if(!round_handler_IsRoundStarted())
516 self.player_blocked = 1;
518 self.player_blocked = 0;
522 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
528 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
530 self.havocbot_role = havocbot_role_dom;
534 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
535 Control point for Domination gameplay.
537 spawnfunc(dom_controlpoint)
544 self.think = dom_controlpoint_setup_self;
545 self.nextthink = time + 0.1;
546 self.reset = dom_controlpoint_setup;
551 self.effects = self.effects | EF_LOWPRECISION;
552 if (autocvar_g_domination_point_fullbright)
553 self.effects |= EF_FULLBRIGHT;
556 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
557 Team declaration for Domination gameplay, this allows you to decide what team
558 names and control point models are used in your map.
560 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
561 can have netname set! The nameless team owns all control points at start.
565 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
567 Scoreboard color of the team (for example 4 is red and 13 is blue)
569 Model to use for control points owned by this team (for example
570 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
573 Skin of the model to use (for team skins on a single model)
575 Sound to play when this team captures a point.
576 (this is a localized sound, like a small alarm or other effect)
578 Narrator speech to play when this team captures a point.
579 (this is a global sound, like "Red team has captured a control point")
584 if(!g_domination || autocvar_g_domination_teams_override >= 2)
589 precache_model(self.model);
590 if (self.noise != "")
591 precache_sound(self.noise);
592 if (self.noise1 != "")
593 precache_sound(self.noise1);
594 self.classname = "dom_team";
595 _setmodel(self, self.model); // precision not needed
596 self.mdl = self.model;
597 self.dmg = self.modelindex;
600 // this would have to be changed if used in quakeworld
602 self.team = self.cnt + 1; // WHY are these different anyway?
606 void ScoreRules_dom(float teams)
608 if(domination_roundbased)
610 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
611 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
612 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
613 ScoreRules_basics_end();
617 float sp_domticks, sp_score;
618 sp_score = sp_domticks = 0;
619 if(autocvar_g_domination_disable_frags)
620 sp_domticks = SFL_SORT_PRIO_PRIMARY;
622 sp_score = SFL_SORT_PRIO_PRIMARY;
623 ScoreRules_basics(teams, sp_score, sp_score, true);
624 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
625 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
626 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
627 ScoreRules_basics_end();
631 // code from here on is just to support maps that don't have control point and team entities
632 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
635 self.classname = "dom_team";
636 self.netname = strzone(teamname);
637 self.cnt = teamcolor;
638 self.model = pointmodel;
639 self.skin = pointskin;
640 self.noise = strzone(capsound);
641 self.noise1 = strzone(capnarration);
642 self.message = strzone(capmessage);
644 // this code is identical to spawnfunc_dom_team
645 _setmodel(self, self.model); // precision not needed
646 self.mdl = self.model;
647 self.dmg = self.modelindex;
650 // this would have to be changed if used in quakeworld
651 self.team = self.cnt + 1;
657 void self_spawnfunc_dom_controlpoint() { SELFPARAM(); spawnfunc_dom_controlpoint(self); }
658 void dom_spawnpoint(vector org)
661 self.classname = "dom_controlpoint";
662 self.think = self_spawnfunc_dom_controlpoint;
663 self.nextthink = time;
664 setorigin(self, org);
665 spawnfunc_dom_controlpoint(this);
669 // spawn some default teams if the map is not set up for domination
670 void dom_spawnteams(float teams)
672 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");
673 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");
675 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");
677 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");
678 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
681 void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
683 // if no teams are found, spawn defaults
684 if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
686 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.\n");
687 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
688 dom_spawnteams(domination_teams);
691 CheckAllowedTeams(world);
692 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
694 domination_roundbased = autocvar_g_domination_roundbased;
696 ScoreRules_dom(domination_teams);
698 if(domination_roundbased)
700 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
701 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
705 void dom_Initialize()
708 InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);