1 #include "gamemode_domination.qh"
2 #ifndef GAMEMODE_DOMINATION_H
3 #define GAMEMODE_DOMINATION_H
5 #define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
6 bool autocvar_g_domination_roundbased;
7 int autocvar_g_domination_roundbased_point_limit;
8 int autocvar_g_domination_point_leadlimit;
10 void dom_Initialize();
12 REGISTER_MUTATOR(dom, false)
16 if (time > 1) // game loads at time 1
17 error("This is a game type and it cannot be added at runtime.");
20 int fraglimit_override = autocvar_g_domination_point_limit;
21 if (autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
22 fraglimit_override = autocvar_g_domination_roundbased_point_limit;
25 SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, autocvar_timelimit_override, -1);
26 have_team_spawns = -1; // request team spawns
31 LOG_INFO("This is a game type and it cannot be removed at runtime.");
38 // score rule declarations
39 const float ST_DOM_TICKS = 1;
40 const float SP_DOM_TICKS = 4;
41 const float SP_DOM_TAKES = 5;
42 const float ST_DOM_CAPS = 1;
43 const float SP_DOM_CAPS = 4;
45 // pps: points per second
46 .float dom_total_pps = _STAT(DOM_TOTAL_PPS);
47 .float dom_pps_red = _STAT(DOM_PPS_RED);
48 .float dom_pps_blue = _STAT(DOM_PPS_BLUE);
49 .float dom_pps_yellow = _STAT(DOM_PPS_YELLOW);
50 .float dom_pps_pink = _STAT(DOM_PPS_PINK);
57 // capture declarations
58 .float enemy_playerid;
63 float domination_roundbased;
64 float domination_teams;
69 #include <server/teamplay.qh>
73 int autocvar_g_domination_default_teams;
74 bool autocvar_g_domination_disable_frags;
75 int autocvar_g_domination_point_amt;
76 bool autocvar_g_domination_point_fullbright;
77 float autocvar_g_domination_round_timelimit;
78 float autocvar_g_domination_warmup;
79 float autocvar_g_domination_point_rate;
80 int autocvar_g_domination_teams_override;
82 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
84 if(autocvar_sv_eventlog)
85 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
88 void set_dom_state(entity e)
90 e.dom_total_pps = total_pps;
91 e.dom_pps_red = pps_red;
92 e.dom_pps_blue = pps_blue;
93 if(domination_teams >= 3)
94 e.dom_pps_yellow = pps_yellow;
95 if(domination_teams >= 4)
96 e.dom_pps_pink = pps_pink;
99 void dompoint_captured ()
102 float old_delay, old_team, real_team;
104 // now that the delay has expired, switch to the latest team to lay claim to this point
107 real_team = self.cnt;
110 dom_EventLog("taken", self.team, self.dmg_inflictor);
111 self.dmg_inflictor = world;
113 self.goalentity = head;
114 self.model = head.mdl;
115 self.modelindex = head.dmg;
116 self.skin = head.skin;
118 float points, wait_time;
119 if (autocvar_g_domination_point_amt)
120 points = autocvar_g_domination_point_amt;
123 if (autocvar_g_domination_point_rate)
124 wait_time = autocvar_g_domination_point_rate;
126 wait_time = self.wait;
128 if(domination_roundbased)
129 bprint(sprintf("^3%s^3%s\n", head.netname, self.message));
131 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time);
133 if(self.enemy.playerid == self.enemy_playerid)
134 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
138 if (head.noise != "")
140 _sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
142 _sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
143 if (head.noise1 != "")
144 play2all(head.noise1);
146 self.delay = time + wait_time;
149 old_delay = self.delay;
150 old_team = self.team;
151 self.team = real_team;
155 self.delay = old_delay;
156 self.team = old_team;
158 entity msg = WP_DomNeut;
161 case NUM_TEAM_1: msg = WP_DomRed; break;
162 case NUM_TEAM_2: msg = WP_DomBlue; break;
163 case NUM_TEAM_3: msg = WP_DomYellow; break;
164 case NUM_TEAM_4: msg = WP_DomPink; break;
167 WaypointSprite_UpdateSprites(self.sprite, msg, WP_Null, WP_Null);
169 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
170 for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
171 FOREACH_ENTITY_CLASS("dom_controlpoint", true, LAMBDA(
172 if (autocvar_g_domination_point_amt)
173 points = autocvar_g_domination_point_amt;
176 if (autocvar_g_domination_point_rate)
177 wait_time = autocvar_g_domination_point_rate;
180 switch(it.goalentity.team)
182 case NUM_TEAM_1: pps_red += points/wait_time; break;
183 case NUM_TEAM_2: pps_blue += points/wait_time; break;
184 case NUM_TEAM_3: pps_yellow += points/wait_time; break;
185 case NUM_TEAM_4: pps_pink += points/wait_time; break;
187 total_pps += points/wait_time;
190 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
191 WaypointSprite_Ping(self.sprite);
195 FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(set_dom_state(it)));
198 void AnimateDomPoint()
200 if(self.pain_finished > time)
202 self.pain_finished = time + self.t_width;
203 if(self.nextthink > self.pain_finished)
204 self.nextthink = self.pain_finished;
206 self.frame = self.frame + 1;
207 if(self.frame > self.t_length)
215 self.nextthink = time + 0.1;
217 //self.frame = self.frame + 1;
218 //if(self.frame > 119)
224 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
227 if(autocvar_g_domination_point_rate)
228 self.delay = time + autocvar_g_domination_point_rate;
230 self.delay = time + self.wait;
232 // give credit to the team
233 // NOTE: this defaults to 0
234 if (!domination_roundbased)
235 if (self.goalentity.netname != "")
237 if(autocvar_g_domination_point_amt)
238 fragamt = autocvar_g_domination_point_amt;
240 fragamt = self.frags;
241 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
242 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
244 // give credit to the individual player, if he is still there
245 if (self.enemy.playerid == self.enemy_playerid)
247 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
248 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
258 if (!IS_PLAYER(other))
260 if (other.health < 1)
263 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
266 if(time < self.captime + 0.3)
269 // only valid teams can claim it
270 head = find(world, classname, "dom_team");
271 while (head && head.team != other.team)
272 head = find(head, classname, "dom_team");
273 if (!head || head.netname == "" || head == self.goalentity)
278 self.team = self.goalentity.team; // this stores the PREVIOUS team!
280 self.cnt = other.team;
281 self.owner = head; // team to switch to after the delay
282 self.dmg_inflictor = other;
285 // self.delay = time + cvar("g_domination_point_capturetime");
286 //self.nextthink = time + cvar("g_domination_point_capturetime");
287 //self.think = dompoint_captured;
289 // go to neutral team in the mean time
290 head = find(world, classname, "dom_team");
291 while (head && head.netname != "")
292 head = find(head, classname, "dom_team");
296 WaypointSprite_UpdateSprites(self.sprite, WP_DomNeut, WP_Null, WP_Null);
297 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
298 WaypointSprite_Ping(self.sprite);
300 self.goalentity = head;
301 self.model = head.mdl;
302 self.modelindex = head.dmg;
303 self.skin = head.skin;
305 self.enemy = other; // individual player scoring
306 self.enemy_playerid = other.playerid;
310 void dom_controlpoint_setup(entity this);
311 void dom_controlpoint_setup_self() { SELFPARAM(); dom_controlpoint_setup(this); }
312 void dom_controlpoint_setup(entity this)
315 // find the spawnfunc_dom_team representing unclaimed points
316 head = find(world, classname, "dom_team");
317 while(head && head.netname != "")
318 head = find(head, classname, "dom_team");
320 objerror("no spawnfunc_dom_team with netname \"\" found\n");
322 // copy important properties from spawnfunc_dom_team entity
323 self.goalentity = head;
324 _setmodel(self, head.mdl); // precision already set
325 self.skin = head.skin;
329 if(self.message == "")
330 self.message = " has captured a control point";
337 float points, waittime;
338 if (autocvar_g_domination_point_amt)
339 points = autocvar_g_domination_point_amt;
342 if (autocvar_g_domination_point_rate)
343 waittime = autocvar_g_domination_point_rate;
345 waittime = self.wait;
347 total_pps += points/waittime;
350 self.t_width = 0.02; // frame animation rate
352 self.t_length = 239; // maximum frame
354 self.think = dompointthink;
355 self.nextthink = time;
356 self.touch = dompointtouch;
357 self.solid = SOLID_TRIGGER;
358 self.flags = FL_ITEM;
359 setsize(self, '-32 -32 -32', '32 32 32');
360 setorigin(self, self.origin + '0 0 20');
363 waypoint_spawnforitem(self);
364 WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT);
367 float total_controlpoints;
368 void Domination_count_controlpoints()
371 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
372 for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
374 ++total_controlpoints;
375 redowned += (e.goalentity.team == NUM_TEAM_1);
376 blueowned += (e.goalentity.team == NUM_TEAM_2);
377 yellowowned += (e.goalentity.team == NUM_TEAM_3);
378 pinkowned += (e.goalentity.team == NUM_TEAM_4);
382 float Domination_GetWinnerTeam()
384 float winner_team = 0;
385 if(redowned == total_controlpoints)
386 winner_team = NUM_TEAM_1;
387 if(blueowned == total_controlpoints)
389 if(winner_team) return 0;
390 winner_team = NUM_TEAM_2;
392 if(yellowowned == total_controlpoints)
394 if(winner_team) return 0;
395 winner_team = NUM_TEAM_3;
397 if(pinkowned == total_controlpoints)
399 if(winner_team) return 0;
400 winner_team = NUM_TEAM_4;
404 return -1; // no control points left?
407 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
408 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
409 float Domination_CheckWinner()
411 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
413 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
414 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
415 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
419 Domination_count_controlpoints();
421 float winner_team = Domination_GetWinnerTeam();
423 if(winner_team == -1)
428 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
429 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
430 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
432 else if(winner_team == -1)
434 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
435 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
438 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
443 float Domination_CheckPlayers()
448 void Domination_RoundStart()
450 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(it.player_blocked = false));
453 //go to best items, or control points you don't own
454 void havocbot_role_dom(entity this)
459 if (this.bot_strategytime < time)
461 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
462 navigation_goalrating_start(this);
463 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
464 havocbot_goalrating_items(this, 8000, this.origin, 8000);
465 //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
466 //havocbot_goalrating_waypoints(1, this.origin, 1000);
467 navigation_goalrating_end(this);
471 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
474 ret_float = domination_teams;
475 ret_string = "dom_team";
477 entity head = find(world, classname, ret_string);
480 if(head.netname != "")
484 case NUM_TEAM_1: c1 = 0; break;
485 case NUM_TEAM_2: c2 = 0; break;
486 case NUM_TEAM_3: c3 = 0; break;
487 case NUM_TEAM_4: c4 = 0; break;
491 head = find(head, classname, ret_string);
494 ret_string = string_null;
499 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
501 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
502 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
505 if(domination_roundbased)
506 self.player_blocked = 1;
507 if(IS_REAL_CLIENT(self))
513 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
515 if(domination_roundbased)
516 if(!round_handler_IsRoundStarted())
517 self.player_blocked = 1;
519 self.player_blocked = 0;
523 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
529 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
531 self.havocbot_role = havocbot_role_dom;
535 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
536 Control point for Domination gameplay.
538 spawnfunc(dom_controlpoint)
545 self.think = dom_controlpoint_setup_self;
546 self.nextthink = time + 0.1;
547 self.reset = dom_controlpoint_setup;
552 self.effects = self.effects | EF_LOWPRECISION;
553 if (autocvar_g_domination_point_fullbright)
554 self.effects |= EF_FULLBRIGHT;
557 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
558 Team declaration for Domination gameplay, this allows you to decide what team
559 names and control point models are used in your map.
561 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
562 can have netname set! The nameless team owns all control points at start.
566 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
568 Scoreboard color of the team (for example 4 is red and 13 is blue)
570 Model to use for control points owned by this team (for example
571 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
574 Skin of the model to use (for team skins on a single model)
576 Sound to play when this team captures a point.
577 (this is a localized sound, like a small alarm or other effect)
579 Narrator speech to play when this team captures a point.
580 (this is a global sound, like "Red team has captured a control point")
585 if(!g_domination || autocvar_g_domination_teams_override >= 2)
590 precache_model(self.model);
591 if (self.noise != "")
592 precache_sound(self.noise);
593 if (self.noise1 != "")
594 precache_sound(self.noise1);
595 self.classname = "dom_team";
596 _setmodel(self, self.model); // precision not needed
597 self.mdl = self.model;
598 self.dmg = self.modelindex;
601 // this would have to be changed if used in quakeworld
603 self.team = self.cnt + 1; // WHY are these different anyway?
607 void ScoreRules_dom(float teams)
609 if(domination_roundbased)
611 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
612 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
613 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
614 ScoreRules_basics_end();
618 float sp_domticks, sp_score;
619 sp_score = sp_domticks = 0;
620 if(autocvar_g_domination_disable_frags)
621 sp_domticks = SFL_SORT_PRIO_PRIMARY;
623 sp_score = SFL_SORT_PRIO_PRIMARY;
624 ScoreRules_basics(teams, sp_score, sp_score, true);
625 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
626 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
627 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
628 ScoreRules_basics_end();
632 // code from here on is just to support maps that don't have control point and team entities
633 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
637 self.classname = "dom_team";
638 self.netname = strzone(teamname);
639 self.cnt = teamcolor;
640 self.model = pointmodel;
641 self.skin = pointskin;
642 self.noise = strzone(Sound_fixpath(capsound));
643 self.noise1 = strzone(capnarration);
644 self.message = strzone(capmessage);
646 // this code is identical to spawnfunc_dom_team
647 _setmodel(self, self.model); // precision not needed
648 self.mdl = self.model;
649 self.dmg = self.modelindex;
652 // this would have to be changed if used in quakeworld
653 self.team = self.cnt + 1;
659 void self_spawnfunc_dom_controlpoint() { SELFPARAM(); spawnfunc_dom_controlpoint(self); }
660 void dom_spawnpoint(vector org)
663 self.classname = "dom_controlpoint";
664 self.think = self_spawnfunc_dom_controlpoint;
665 self.nextthink = time;
666 setorigin(self, org);
667 spawnfunc_dom_controlpoint(this);
671 // spawn some default teams if the map is not set up for domination
672 void dom_spawnteams(int teams)
675 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");
676 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");
678 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");
680 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");
681 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
684 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
686 // if no teams are found, spawn defaults
687 if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
689 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.\n");
690 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
691 dom_spawnteams(domination_teams);
694 CheckAllowedTeams(world);
695 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
697 domination_roundbased = autocvar_g_domination_roundbased;
699 ScoreRules_dom(domination_teams);
701 if(domination_roundbased)
703 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
704 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
708 void dom_Initialize()
711 InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);