1 #ifndef GAMEMODE_DOMINATION_H
2 #define GAMEMODE_DOMINATION_H
4 // score rule declarations
5 const float ST_DOM_TICKS = 1;
6 const float SP_DOM_TICKS = 4;
7 const float SP_DOM_TAKES = 5;
8 const float ST_DOM_CAPS = 1;
9 const float SP_DOM_CAPS = 4;
11 // pps: points per second
15 .float dom_pps_yellow;
23 // capture declarations
24 .float enemy_playerid;
29 float domination_roundbased;
30 float domination_teams;
35 #include "../../teamplay.qh"
39 int autocvar_g_domination_default_teams;
40 bool autocvar_g_domination_disable_frags;
41 int autocvar_g_domination_point_amt;
42 bool autocvar_g_domination_point_fullbright;
43 int autocvar_g_domination_point_leadlimit;
44 bool autocvar_g_domination_roundbased;
45 int autocvar_g_domination_roundbased_point_limit;
46 float autocvar_g_domination_round_timelimit;
47 float autocvar_g_domination_warmup;
48 #define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
49 float autocvar_g_domination_point_rate;
50 int autocvar_g_domination_teams_override;
52 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
54 if(autocvar_sv_eventlog)
55 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
58 void set_dom_state(entity e)
60 e.dom_total_pps = total_pps;
61 e.dom_pps_red = pps_red;
62 e.dom_pps_blue = pps_blue;
63 if(domination_teams >= 3)
64 e.dom_pps_yellow = pps_yellow;
65 if(domination_teams >= 4)
66 e.dom_pps_pink = pps_pink;
69 void dompoint_captured ()
72 float old_delay, old_team, real_team;
74 // now that the delay has expired, switch to the latest team to lay claim to this point
80 dom_EventLog("taken", self.team, self.dmg_inflictor);
81 self.dmg_inflictor = world;
83 self.goalentity = head;
84 self.model = head.mdl;
85 self.modelindex = head.dmg;
86 self.skin = head.skin;
88 float points, wait_time;
89 if (autocvar_g_domination_point_amt)
90 points = autocvar_g_domination_point_amt;
93 if (autocvar_g_domination_point_rate)
94 wait_time = autocvar_g_domination_point_rate;
96 wait_time = self.wait;
98 if(domination_roundbased)
99 bprint(sprintf("^3%s^3%s\n", head.netname, self.message));
101 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time);
103 if(self.enemy.playerid == self.enemy_playerid)
104 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
108 if (head.noise != "")
110 _sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
112 _sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
113 if (head.noise1 != "")
114 play2all(head.noise1);
116 self.delay = time + wait_time;
119 old_delay = self.delay;
120 old_team = self.team;
121 self.team = real_team;
125 self.delay = old_delay;
126 self.team = old_team;
128 entity msg = WP_DomNeut;
131 case NUM_TEAM_1: msg = WP_DomRed; break;
132 case NUM_TEAM_2: msg = WP_DomBlue; break;
133 case NUM_TEAM_3: msg = WP_DomYellow; break;
134 case NUM_TEAM_4: msg = WP_DomPink; break;
137 WaypointSprite_UpdateSprites(self.sprite, msg, WP_Null, WP_Null);
139 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
140 for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
142 if (autocvar_g_domination_point_amt)
143 points = autocvar_g_domination_point_amt;
146 if (autocvar_g_domination_point_rate)
147 wait_time = autocvar_g_domination_point_rate;
149 wait_time = head.wait;
150 switch(head.goalentity.team)
153 pps_red += points/wait_time;
156 pps_blue += points/wait_time;
159 pps_yellow += points/wait_time;
162 pps_pink += points/wait_time;
165 total_pps += points/wait_time;
168 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
169 WaypointSprite_Ping(self.sprite);
173 FOR_EACH_REALCLIENT(head)
177 void AnimateDomPoint()
179 if(self.pain_finished > time)
181 self.pain_finished = time + self.t_width;
182 if(self.nextthink > self.pain_finished)
183 self.nextthink = self.pain_finished;
185 self.frame = self.frame + 1;
186 if(self.frame > self.t_length)
194 self.nextthink = time + 0.1;
196 //self.frame = self.frame + 1;
197 //if(self.frame > 119)
203 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
206 if(autocvar_g_domination_point_rate)
207 self.delay = time + autocvar_g_domination_point_rate;
209 self.delay = time + self.wait;
211 // give credit to the team
212 // NOTE: this defaults to 0
213 if (!domination_roundbased)
214 if (self.goalentity.netname != "")
216 if(autocvar_g_domination_point_amt)
217 fragamt = autocvar_g_domination_point_amt;
219 fragamt = self.frags;
220 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
221 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
223 // give credit to the individual player, if he is still there
224 if (self.enemy.playerid == self.enemy_playerid)
226 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
227 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
237 if (!IS_PLAYER(other))
239 if (other.health < 1)
242 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
245 if(time < self.captime + 0.3)
248 // only valid teams can claim it
249 head = find(world, classname, "dom_team");
250 while (head && head.team != other.team)
251 head = find(head, classname, "dom_team");
252 if (!head || head.netname == "" || head == self.goalentity)
257 self.team = self.goalentity.team; // this stores the PREVIOUS team!
259 self.cnt = other.team;
260 self.owner = head; // team to switch to after the delay
261 self.dmg_inflictor = other;
264 // self.delay = time + cvar("g_domination_point_capturetime");
265 //self.nextthink = time + cvar("g_domination_point_capturetime");
266 //self.think = dompoint_captured;
268 // go to neutral team in the mean time
269 head = find(world, classname, "dom_team");
270 while (head && head.netname != "")
271 head = find(head, classname, "dom_team");
275 WaypointSprite_UpdateSprites(self.sprite, WP_DomNeut, WP_Null, WP_Null);
276 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
277 WaypointSprite_Ping(self.sprite);
279 self.goalentity = head;
280 self.model = head.mdl;
281 self.modelindex = head.dmg;
282 self.skin = head.skin;
284 self.enemy = other; // individual player scoring
285 self.enemy_playerid = other.playerid;
289 void dom_controlpoint_setup()
292 // find the spawnfunc_dom_team representing unclaimed points
293 head = find(world, classname, "dom_team");
294 while(head && head.netname != "")
295 head = find(head, classname, "dom_team");
297 objerror("no spawnfunc_dom_team with netname \"\" found\n");
299 // copy important properties from spawnfunc_dom_team entity
300 self.goalentity = head;
301 _setmodel(self, head.mdl); // precision already set
302 self.skin = head.skin;
306 if(self.message == "")
307 self.message = " has captured a control point";
314 float points, waittime;
315 if (autocvar_g_domination_point_amt)
316 points = autocvar_g_domination_point_amt;
319 if (autocvar_g_domination_point_rate)
320 waittime = autocvar_g_domination_point_rate;
322 waittime = self.wait;
324 total_pps += points/waittime;
327 self.t_width = 0.02; // frame animation rate
329 self.t_length = 239; // maximum frame
331 self.think = dompointthink;
332 self.nextthink = time;
333 self.touch = dompointtouch;
334 self.solid = SOLID_TRIGGER;
335 self.flags = FL_ITEM;
336 setsize(self, '-32 -32 -32', '32 32 32');
337 setorigin(self, self.origin + '0 0 20');
340 waypoint_spawnforitem(self);
341 WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT);
344 float total_controlpoints;
345 void Domination_count_controlpoints()
348 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
349 for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
351 ++total_controlpoints;
352 redowned += (e.goalentity.team == NUM_TEAM_1);
353 blueowned += (e.goalentity.team == NUM_TEAM_2);
354 yellowowned += (e.goalentity.team == NUM_TEAM_3);
355 pinkowned += (e.goalentity.team == NUM_TEAM_4);
359 float Domination_GetWinnerTeam()
361 float winner_team = 0;
362 if(redowned == total_controlpoints)
363 winner_team = NUM_TEAM_1;
364 if(blueowned == total_controlpoints)
366 if(winner_team) return 0;
367 winner_team = NUM_TEAM_2;
369 if(yellowowned == total_controlpoints)
371 if(winner_team) return 0;
372 winner_team = NUM_TEAM_3;
374 if(pinkowned == total_controlpoints)
376 if(winner_team) return 0;
377 winner_team = NUM_TEAM_4;
381 return -1; // no control points left?
384 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
385 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
386 float Domination_CheckWinner()
388 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
390 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
391 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
392 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
396 Domination_count_controlpoints();
398 float winner_team = Domination_GetWinnerTeam();
400 if(winner_team == -1)
405 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
406 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
407 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
409 else if(winner_team == -1)
411 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
412 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
415 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
420 float Domination_CheckPlayers()
425 void Domination_RoundStart()
429 e.player_blocked = 0;
432 //go to best items, or control points you don't own
433 void havocbot_role_dom()
435 if(self.deadflag != DEAD_NO)
438 if (self.bot_strategytime < time)
440 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
441 navigation_goalrating_start();
442 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
443 havocbot_goalrating_items(8000, self.origin, 8000);
444 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
445 //havocbot_goalrating_waypoints(1, self.origin, 1000);
446 navigation_goalrating_end();
450 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
453 ret_float = domination_teams;
454 ret_string = "dom_team";
456 entity head = find(world, classname, ret_string);
459 if(head.netname != "")
463 case NUM_TEAM_1: c1 = 0; break;
464 case NUM_TEAM_2: c2 = 0; break;
465 case NUM_TEAM_3: c3 = 0; break;
466 case NUM_TEAM_4: c4 = 0; break;
470 head = find(head, classname, ret_string);
473 ret_string = string_null;
478 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
480 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
486 self.player_blocked = 1;
487 if(IS_REAL_CLIENT(self))
493 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
495 if(domination_roundbased)
496 if(!round_handler_IsRoundStarted())
497 self.player_blocked = 1;
499 self.player_blocked = 0;
503 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
509 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
511 self.havocbot_role = havocbot_role_dom;
515 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
516 Control point for Domination gameplay.
518 spawnfunc(dom_controlpoint)
525 self.think = dom_controlpoint_setup;
526 self.nextthink = time + 0.1;
527 self.reset = dom_controlpoint_setup;
532 self.effects = self.effects | EF_LOWPRECISION;
533 if (autocvar_g_domination_point_fullbright)
534 self.effects |= EF_FULLBRIGHT;
537 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
538 Team declaration for Domination gameplay, this allows you to decide what team
539 names and control point models are used in your map.
541 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
542 can have netname set! The nameless team owns all control points at start.
546 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
548 Scoreboard color of the team (for example 4 is red and 13 is blue)
550 Model to use for control points owned by this team (for example
551 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
554 Skin of the model to use (for team skins on a single model)
556 Sound to play when this team captures a point.
557 (this is a localized sound, like a small alarm or other effect)
559 Narrator speech to play when this team captures a point.
560 (this is a global sound, like "Red team has captured a control point")
565 if(!g_domination || autocvar_g_domination_teams_override >= 2)
570 precache_model(self.model);
571 if (self.noise != "")
572 precache_sound(self.noise);
573 if (self.noise1 != "")
574 precache_sound(self.noise1);
575 self.classname = "dom_team";
576 _setmodel(self, self.model); // precision not needed
577 self.mdl = self.model;
578 self.dmg = self.modelindex;
581 // this would have to be changed if used in quakeworld
583 self.team = self.cnt + 1; // WHY are these different anyway?
587 void ScoreRules_dom(float teams)
589 if(domination_roundbased)
591 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
592 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
593 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
594 ScoreRules_basics_end();
598 float sp_domticks, sp_score;
599 sp_score = sp_domticks = 0;
600 if(autocvar_g_domination_disable_frags)
601 sp_domticks = SFL_SORT_PRIO_PRIMARY;
603 sp_score = SFL_SORT_PRIO_PRIMARY;
604 ScoreRules_basics(teams, sp_score, sp_score, true);
605 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
606 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
607 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
608 ScoreRules_basics_end();
612 // code from here on is just to support maps that don't have control point and team entities
613 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
616 self.classname = "dom_team";
617 self.netname = teamname;
618 self.cnt = teamcolor;
619 self.model = pointmodel;
620 self.skin = pointskin;
621 self.noise = capsound;
622 self.noise1 = capnarration;
623 self.message = capmessage;
625 // this code is identical to spawnfunc_dom_team
626 _setmodel(self, self.model); // precision not needed
627 self.mdl = self.model;
628 self.dmg = self.modelindex;
631 // this would have to be changed if used in quakeworld
632 self.team = self.cnt + 1;
638 void _spawnfunc_dom_controlpoint() { SELFPARAM(); spawnfunc_dom_controlpoint(self); }
639 void dom_spawnpoint(vector org)
642 self.classname = "dom_controlpoint";
643 self.think = _spawnfunc_dom_controlpoint;
644 self.nextthink = time;
645 setorigin(self, org);
646 spawnfunc_dom_controlpoint(this);
650 // spawn some default teams if the map is not set up for domination
651 void dom_spawnteams(float teams)
653 dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND(DOM_CLAIM), "", "Red team has captured a control point");
654 dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND(DOM_CLAIM), "", "Blue team has captured a control point");
656 dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND(DOM_CLAIM), "", "Yellow team has captured a control point");
658 dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND(DOM_CLAIM), "", "Pink team has captured a control point");
659 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
662 void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
664 // if no teams are found, spawn defaults
665 if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
667 LOG_INFO("No ""dom_team"" entities found on this map, creating them anyway.\n");
668 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
669 dom_spawnteams(domination_teams);
672 CheckAllowedTeams(world);
673 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
675 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
676 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
677 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
678 if(domination_teams >= 3) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
679 if(domination_teams >= 4) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
681 domination_roundbased = autocvar_g_domination_roundbased;
683 ScoreRules_dom(domination_teams);
685 if(domination_roundbased)
687 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
688 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
692 void dom_Initialize()
695 InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);
699 REGISTER_MUTATOR(dom, IS_GAMETYPE(DOMINATION))
701 int fraglimit_override = autocvar_g_domination_point_limit;
702 if(autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
703 fraglimit_override = autocvar_g_domination_roundbased_point_limit;
706 SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, -1, -1);
707 have_team_spawns = -1; // request team spawns
711 if(time > 1) // game loads at time 1
712 error("This is a game type and it cannot be added at runtime.");
718 LOG_INFO("This is a game type and it cannot be removed at runtime.");