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; )
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;
178 wait_time = head.wait;
179 switch(head.goalentity.team)
182 pps_red += points/wait_time;
185 pps_blue += points/wait_time;
188 pps_yellow += points/wait_time;
191 pps_pink += points/wait_time;
194 total_pps += points/wait_time;
197 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
198 WaypointSprite_Ping(self.sprite);
202 FOR_EACH_REALCLIENT(head)
206 void AnimateDomPoint()
208 if(self.pain_finished > time)
210 self.pain_finished = time + self.t_width;
211 if(self.nextthink > self.pain_finished)
212 self.nextthink = self.pain_finished;
214 self.frame = self.frame + 1;
215 if(self.frame > self.t_length)
223 self.nextthink = time + 0.1;
225 //self.frame = self.frame + 1;
226 //if(self.frame > 119)
232 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
235 if(autocvar_g_domination_point_rate)
236 self.delay = time + autocvar_g_domination_point_rate;
238 self.delay = time + self.wait;
240 // give credit to the team
241 // NOTE: this defaults to 0
242 if (!domination_roundbased)
243 if (self.goalentity.netname != "")
245 if(autocvar_g_domination_point_amt)
246 fragamt = autocvar_g_domination_point_amt;
248 fragamt = self.frags;
249 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
250 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
252 // give credit to the individual player, if he is still there
253 if (self.enemy.playerid == self.enemy_playerid)
255 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
256 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
266 if (!IS_PLAYER(other))
268 if (other.health < 1)
271 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
274 if(time < self.captime + 0.3)
277 // only valid teams can claim it
278 head = find(world, classname, "dom_team");
279 while (head && head.team != other.team)
280 head = find(head, classname, "dom_team");
281 if (!head || head.netname == "" || head == self.goalentity)
286 self.team = self.goalentity.team; // this stores the PREVIOUS team!
288 self.cnt = other.team;
289 self.owner = head; // team to switch to after the delay
290 self.dmg_inflictor = other;
293 // self.delay = time + cvar("g_domination_point_capturetime");
294 //self.nextthink = time + cvar("g_domination_point_capturetime");
295 //self.think = dompoint_captured;
297 // go to neutral team in the mean time
298 head = find(world, classname, "dom_team");
299 while (head && head.netname != "")
300 head = find(head, classname, "dom_team");
304 WaypointSprite_UpdateSprites(self.sprite, WP_DomNeut, WP_Null, WP_Null);
305 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
306 WaypointSprite_Ping(self.sprite);
308 self.goalentity = head;
309 self.model = head.mdl;
310 self.modelindex = head.dmg;
311 self.skin = head.skin;
313 self.enemy = other; // individual player scoring
314 self.enemy_playerid = other.playerid;
318 void dom_controlpoint_setup(entity this);
319 void dom_controlpoint_setup_self() { SELFPARAM(); dom_controlpoint_setup(this); }
320 void dom_controlpoint_setup(entity this)
323 // find the spawnfunc_dom_team representing unclaimed points
324 head = find(world, classname, "dom_team");
325 while(head && head.netname != "")
326 head = find(head, classname, "dom_team");
328 objerror("no spawnfunc_dom_team with netname \"\" found\n");
330 // copy important properties from spawnfunc_dom_team entity
331 self.goalentity = head;
332 _setmodel(self, head.mdl); // precision already set
333 self.skin = head.skin;
337 if(self.message == "")
338 self.message = " has captured a control point";
345 float points, waittime;
346 if (autocvar_g_domination_point_amt)
347 points = autocvar_g_domination_point_amt;
350 if (autocvar_g_domination_point_rate)
351 waittime = autocvar_g_domination_point_rate;
353 waittime = self.wait;
355 total_pps += points/waittime;
358 self.t_width = 0.02; // frame animation rate
360 self.t_length = 239; // maximum frame
362 self.think = dompointthink;
363 self.nextthink = time;
364 self.touch = dompointtouch;
365 self.solid = SOLID_TRIGGER;
366 self.flags = FL_ITEM;
367 setsize(self, '-32 -32 -32', '32 32 32');
368 setorigin(self, self.origin + '0 0 20');
371 waypoint_spawnforitem(self);
372 WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT);
375 float total_controlpoints;
376 void Domination_count_controlpoints()
379 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
380 for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
382 ++total_controlpoints;
383 redowned += (e.goalentity.team == NUM_TEAM_1);
384 blueowned += (e.goalentity.team == NUM_TEAM_2);
385 yellowowned += (e.goalentity.team == NUM_TEAM_3);
386 pinkowned += (e.goalentity.team == NUM_TEAM_4);
390 float Domination_GetWinnerTeam()
392 float winner_team = 0;
393 if(redowned == total_controlpoints)
394 winner_team = NUM_TEAM_1;
395 if(blueowned == total_controlpoints)
397 if(winner_team) return 0;
398 winner_team = NUM_TEAM_2;
400 if(yellowowned == total_controlpoints)
402 if(winner_team) return 0;
403 winner_team = NUM_TEAM_3;
405 if(pinkowned == total_controlpoints)
407 if(winner_team) return 0;
408 winner_team = NUM_TEAM_4;
412 return -1; // no control points left?
415 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
416 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
417 float Domination_CheckWinner()
419 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
421 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
422 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
423 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
427 Domination_count_controlpoints();
429 float winner_team = Domination_GetWinnerTeam();
431 if(winner_team == -1)
436 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
437 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
438 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
440 else if(winner_team == -1)
442 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
443 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
446 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
451 float Domination_CheckPlayers()
456 void Domination_RoundStart()
460 e.player_blocked = 0;
463 //go to best items, or control points you don't own
464 void havocbot_role_dom()
466 if(self.deadflag != DEAD_NO)
469 if (self.bot_strategytime < time)
471 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
472 navigation_goalrating_start();
473 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
474 havocbot_goalrating_items(8000, self.origin, 8000);
475 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
476 //havocbot_goalrating_waypoints(1, self.origin, 1000);
477 navigation_goalrating_end();
481 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
484 ret_float = domination_teams;
485 ret_string = "dom_team";
487 entity head = find(world, classname, ret_string);
490 if(head.netname != "")
494 case NUM_TEAM_1: c1 = 0; break;
495 case NUM_TEAM_2: c2 = 0; break;
496 case NUM_TEAM_3: c3 = 0; break;
497 case NUM_TEAM_4: c4 = 0; break;
501 head = find(head, classname, ret_string);
504 ret_string = string_null;
509 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
511 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
517 self.player_blocked = 1;
518 if(IS_REAL_CLIENT(self))
524 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
526 if(domination_roundbased)
527 if(!round_handler_IsRoundStarted())
528 self.player_blocked = 1;
530 self.player_blocked = 0;
534 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
540 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
542 self.havocbot_role = havocbot_role_dom;
546 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
547 Control point for Domination gameplay.
549 spawnfunc(dom_controlpoint)
556 self.think = dom_controlpoint_setup_self;
557 self.nextthink = time + 0.1;
558 self.reset = dom_controlpoint_setup;
563 self.effects = self.effects | EF_LOWPRECISION;
564 if (autocvar_g_domination_point_fullbright)
565 self.effects |= EF_FULLBRIGHT;
568 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
569 Team declaration for Domination gameplay, this allows you to decide what team
570 names and control point models are used in your map.
572 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
573 can have netname set! The nameless team owns all control points at start.
577 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
579 Scoreboard color of the team (for example 4 is red and 13 is blue)
581 Model to use for control points owned by this team (for example
582 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
585 Skin of the model to use (for team skins on a single model)
587 Sound to play when this team captures a point.
588 (this is a localized sound, like a small alarm or other effect)
590 Narrator speech to play when this team captures a point.
591 (this is a global sound, like "Red team has captured a control point")
596 if(!g_domination || autocvar_g_domination_teams_override >= 2)
601 precache_model(self.model);
602 if (self.noise != "")
603 precache_sound(self.noise);
604 if (self.noise1 != "")
605 precache_sound(self.noise1);
606 self.classname = "dom_team";
607 _setmodel(self, self.model); // precision not needed
608 self.mdl = self.model;
609 self.dmg = self.modelindex;
612 // this would have to be changed if used in quakeworld
614 self.team = self.cnt + 1; // WHY are these different anyway?
618 void ScoreRules_dom(float teams)
620 if(domination_roundbased)
622 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
623 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
624 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
625 ScoreRules_basics_end();
629 float sp_domticks, sp_score;
630 sp_score = sp_domticks = 0;
631 if(autocvar_g_domination_disable_frags)
632 sp_domticks = SFL_SORT_PRIO_PRIMARY;
634 sp_score = SFL_SORT_PRIO_PRIMARY;
635 ScoreRules_basics(teams, sp_score, sp_score, true);
636 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
637 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
638 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
639 ScoreRules_basics_end();
643 // code from here on is just to support maps that don't have control point and team entities
644 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
647 self.classname = "dom_team";
648 self.netname = teamname;
649 self.cnt = teamcolor;
650 self.model = pointmodel;
651 self.skin = pointskin;
652 self.noise = capsound;
653 self.noise1 = capnarration;
654 self.message = capmessage;
656 // this code is identical to spawnfunc_dom_team
657 _setmodel(self, self.model); // precision not needed
658 self.mdl = self.model;
659 self.dmg = self.modelindex;
662 // this would have to be changed if used in quakeworld
663 self.team = self.cnt + 1;
669 void self_spawnfunc_dom_controlpoint() { SELFPARAM(); spawnfunc_dom_controlpoint(self); }
670 void dom_spawnpoint(vector org)
673 self.classname = "dom_controlpoint";
674 self.think = self_spawnfunc_dom_controlpoint;
675 self.nextthink = time;
676 setorigin(self, org);
677 spawnfunc_dom_controlpoint(this);
681 // spawn some default teams if the map is not set up for domination
682 void dom_spawnteams(float teams)
684 dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND(DOM_CLAIM), "", "Red team has captured a control point");
685 dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND(DOM_CLAIM), "", "Blue team has captured a control point");
687 dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND(DOM_CLAIM), "", "Yellow team has captured a control point");
689 dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND(DOM_CLAIM), "", "Pink team has captured a control point");
690 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
693 void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
695 // if no teams are found, spawn defaults
696 if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
698 LOG_INFO("No \"dom_team\" entities found on this map, creating them anyway.\n");
699 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
700 dom_spawnteams(domination_teams);
703 CheckAllowedTeams(world);
704 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
706 domination_roundbased = autocvar_g_domination_roundbased;
708 ScoreRules_dom(domination_teams);
710 if(domination_roundbased)
712 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
713 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
717 void dom_Initialize()
720 InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);