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;
153 SUB_UseTargets (self, self, NULL);
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)
210 void dompointthink(entity this)
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);
254 void dompointtouch(entity this)
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)
312 // find the spawnfunc_dom_team representing unclaimed points
313 head = find(world, classname, "dom_team");
314 while(head && head.netname != "")
315 head = find(head, classname, "dom_team");
317 objerror("no spawnfunc_dom_team with netname \"\" found\n");
319 // copy important properties from spawnfunc_dom_team entity
320 self.goalentity = head;
321 _setmodel(self, head.mdl); // precision already set
322 self.skin = head.skin;
326 if(self.message == "")
327 self.message = " has captured a control point";
334 float points, waittime;
335 if (autocvar_g_domination_point_amt)
336 points = autocvar_g_domination_point_amt;
339 if (autocvar_g_domination_point_rate)
340 waittime = autocvar_g_domination_point_rate;
342 waittime = self.wait;
344 total_pps += points/waittime;
347 self.t_width = 0.02; // frame animation rate
349 self.t_length = 239; // maximum frame
351 setthink(self, dompointthink);
352 self.nextthink = time;
353 settouch(self, dompointtouch);
354 self.solid = SOLID_TRIGGER;
355 self.flags = FL_ITEM;
356 setsize(self, '-32 -32 -32', '32 32 32');
357 setorigin(self, self.origin + '0 0 20');
360 waypoint_spawnforitem(self);
361 WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT);
364 float total_controlpoints;
365 void Domination_count_controlpoints()
368 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
369 for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
371 ++total_controlpoints;
372 redowned += (e.goalentity.team == NUM_TEAM_1);
373 blueowned += (e.goalentity.team == NUM_TEAM_2);
374 yellowowned += (e.goalentity.team == NUM_TEAM_3);
375 pinkowned += (e.goalentity.team == NUM_TEAM_4);
379 float Domination_GetWinnerTeam()
381 float winner_team = 0;
382 if(redowned == total_controlpoints)
383 winner_team = NUM_TEAM_1;
384 if(blueowned == total_controlpoints)
386 if(winner_team) return 0;
387 winner_team = NUM_TEAM_2;
389 if(yellowowned == total_controlpoints)
391 if(winner_team) return 0;
392 winner_team = NUM_TEAM_3;
394 if(pinkowned == total_controlpoints)
396 if(winner_team) return 0;
397 winner_team = NUM_TEAM_4;
401 return -1; // no control points left?
404 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
405 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
406 float Domination_CheckWinner()
408 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
410 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
411 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
412 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
416 Domination_count_controlpoints();
418 float winner_team = Domination_GetWinnerTeam();
420 if(winner_team == -1)
425 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
426 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
427 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
429 else if(winner_team == -1)
431 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
432 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
435 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
440 float Domination_CheckPlayers()
445 void Domination_RoundStart()
447 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(it.player_blocked = false));
450 //go to best items, or control points you don't own
451 void havocbot_role_dom(entity this)
456 if (this.bot_strategytime < time)
458 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
459 navigation_goalrating_start(this);
460 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
461 havocbot_goalrating_items(this, 8000, this.origin, 8000);
462 //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
463 //havocbot_goalrating_waypoints(1, this.origin, 1000);
464 navigation_goalrating_end(this);
468 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
471 ret_float = domination_teams;
472 ret_string = "dom_team";
474 entity head = find(world, classname, ret_string);
477 if(head.netname != "")
481 case NUM_TEAM_1: c1 = 0; break;
482 case NUM_TEAM_2: c2 = 0; break;
483 case NUM_TEAM_3: c3 = 0; break;
484 case NUM_TEAM_4: c4 = 0; break;
488 head = find(head, classname, ret_string);
491 ret_string = string_null;
496 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
498 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
499 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
500 WITHSELF(it, PutClientInServer());
501 if(domination_roundbased)
502 it.player_blocked = 1;
503 if(IS_REAL_CLIENT(it))
509 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
511 entity player = M_ARGV(0, entity);
513 if(domination_roundbased)
514 if(!round_handler_IsRoundStarted())
515 player.player_blocked = 1;
517 player.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 setthink(this, dom_controlpoint_setup);
544 this.nextthink = time + 0.1;
545 this.reset = dom_controlpoint_setup;
550 this.effects = this.effects | EF_LOWPRECISION;
551 if (autocvar_g_domination_point_fullbright)
552 this.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(this.model);
589 if (this.noise != "")
590 precache_sound(this.noise);
591 if (this.noise1 != "")
592 precache_sound(this.noise1);
593 this.classname = "dom_team";
594 _setmodel(this, this.model); // precision not needed
595 this.mdl = this.model;
596 this.dmg = this.modelindex;
599 // this would have to be changed if used in quakeworld
601 this.team = this.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, Sound capsound, string capnarration, string capmessage)
634 entity e = new_pure(dom_team);
635 e.netname = strzone(teamname);
637 e.model = pointmodel;
639 e.noise = strzone(Sound_fixpath(capsound));
640 e.noise1 = strzone(capnarration);
641 e.message = strzone(capmessage);
643 // this code is identical to spawnfunc_dom_team
644 _setmodel(e, e.model); // precision not needed
646 e.dmg = e.modelindex;
649 // this would have to be changed if used in quakeworld
655 void dom_spawnpoint(vector org)
658 e.classname = "dom_controlpoint";
659 setthink(e, spawnfunc_dom_controlpoint);
662 spawnfunc_dom_controlpoint(e);
665 // spawn some default teams if the map is not set up for domination
666 void dom_spawnteams(int teams)
669 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");
670 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");
672 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");
674 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");
675 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
678 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
680 // if no teams are found, spawn defaults
681 if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
683 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.\n");
684 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
685 dom_spawnteams(domination_teams);
688 CheckAllowedTeams(world);
689 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
691 domination_roundbased = autocvar_g_domination_roundbased;
693 ScoreRules_dom(domination_teams);
695 if(domination_roundbased)
697 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
698 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
702 void dom_Initialize()
705 InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);