1 #include "gamemode_domination.qh"
5 #include "../teamplay.qh"
9 int autocvar_g_domination_default_teams;
10 bool autocvar_g_domination_disable_frags;
11 int autocvar_g_domination_point_amt;
12 bool autocvar_g_domination_point_fullbright;
13 int autocvar_g_domination_point_leadlimit;
14 bool autocvar_g_domination_roundbased;
15 int autocvar_g_domination_roundbased_point_limit;
16 float autocvar_g_domination_round_timelimit;
17 float autocvar_g_domination_warmup;
18 #define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
19 float autocvar_g_domination_point_rate;
20 int autocvar_g_domination_teams_override;
22 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
24 if(autocvar_sv_eventlog)
25 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
28 void set_dom_state(entity e)
30 e.dom_total_pps = total_pps;
31 e.dom_pps_red = pps_red;
32 e.dom_pps_blue = pps_blue;
33 if(domination_teams >= 3)
34 e.dom_pps_yellow = pps_yellow;
35 if(domination_teams >= 4)
36 e.dom_pps_pink = pps_pink;
39 void dompoint_captured ()
42 float old_delay, old_team, real_team;
44 // now that the delay has expired, switch to the latest team to lay claim to this point
50 dom_EventLog("taken", self.team, self.dmg_inflictor);
51 self.dmg_inflictor = world;
53 self.goalentity = head;
54 self.model = head.mdl;
55 self.modelindex = head.dmg;
56 self.skin = head.skin;
58 float points, wait_time;
59 if (autocvar_g_domination_point_amt)
60 points = autocvar_g_domination_point_amt;
63 if (autocvar_g_domination_point_rate)
64 wait_time = autocvar_g_domination_point_rate;
66 wait_time = self.wait;
68 if(domination_roundbased)
69 bprint(sprintf("^3%s^3%s\n", head.netname, self.message));
71 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time);
73 if(self.enemy.playerid == self.enemy_playerid)
74 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
80 _sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
82 _sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
83 if (head.noise1 != "")
84 play2all(head.noise1);
86 self.delay = time + wait_time;
89 old_delay = self.delay;
91 self.team = real_team;
95 self.delay = old_delay;
98 entity msg = WP_DomNeut;
101 case NUM_TEAM_1: msg = WP_DomRed; break;
102 case NUM_TEAM_2: msg = WP_DomBlue; break;
103 case NUM_TEAM_3: msg = WP_DomYellow; break;
104 case NUM_TEAM_4: msg = WP_DomPink; break;
107 WaypointSprite_UpdateSprites(self.sprite, msg, WP_Null, WP_Null);
109 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
110 for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
112 if (autocvar_g_domination_point_amt)
113 points = autocvar_g_domination_point_amt;
116 if (autocvar_g_domination_point_rate)
117 wait_time = autocvar_g_domination_point_rate;
119 wait_time = head.wait;
120 switch(head.goalentity.team)
123 pps_red += points/wait_time;
126 pps_blue += points/wait_time;
129 pps_yellow += points/wait_time;
132 pps_pink += points/wait_time;
135 total_pps += points/wait_time;
138 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
139 WaypointSprite_Ping(self.sprite);
143 FOR_EACH_REALCLIENT(head)
147 void AnimateDomPoint()
149 if(self.pain_finished > time)
151 self.pain_finished = time + self.t_width;
152 if(self.nextthink > self.pain_finished)
153 self.nextthink = self.pain_finished;
155 self.frame = self.frame + 1;
156 if(self.frame > self.t_length)
164 self.nextthink = time + 0.1;
166 //self.frame = self.frame + 1;
167 //if(self.frame > 119)
173 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
176 if(autocvar_g_domination_point_rate)
177 self.delay = time + autocvar_g_domination_point_rate;
179 self.delay = time + self.wait;
181 // give credit to the team
182 // NOTE: this defaults to 0
183 if (!domination_roundbased)
184 if (self.goalentity.netname != "")
186 if(autocvar_g_domination_point_amt)
187 fragamt = autocvar_g_domination_point_amt;
189 fragamt = self.frags;
190 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
191 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
193 // give credit to the individual player, if he is still there
194 if (self.enemy.playerid == self.enemy_playerid)
196 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
197 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
207 if (!IS_PLAYER(other))
209 if (other.health < 1)
212 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
215 if(time < self.captime + 0.3)
218 // only valid teams can claim it
219 head = find(world, classname, "dom_team");
220 while (head && head.team != other.team)
221 head = find(head, classname, "dom_team");
222 if (!head || head.netname == "" || head == self.goalentity)
227 self.team = self.goalentity.team; // this stores the PREVIOUS team!
229 self.cnt = other.team;
230 self.owner = head; // team to switch to after the delay
231 self.dmg_inflictor = other;
234 // self.delay = time + cvar("g_domination_point_capturetime");
235 //self.nextthink = time + cvar("g_domination_point_capturetime");
236 //self.think = dompoint_captured;
238 // go to neutral team in the mean time
239 head = find(world, classname, "dom_team");
240 while (head && head.netname != "")
241 head = find(head, classname, "dom_team");
245 WaypointSprite_UpdateSprites(self.sprite, WP_DomNeut, WP_Null, WP_Null);
246 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
247 WaypointSprite_Ping(self.sprite);
249 self.goalentity = head;
250 self.model = head.mdl;
251 self.modelindex = head.dmg;
252 self.skin = head.skin;
254 self.enemy = other; // individual player scoring
255 self.enemy_playerid = other.playerid;
259 void dom_controlpoint_setup()
262 // find the spawnfunc_dom_team representing unclaimed points
263 head = find(world, classname, "dom_team");
264 while(head && head.netname != "")
265 head = find(head, classname, "dom_team");
267 objerror("no spawnfunc_dom_team with netname \"\" found\n");
269 // copy important properties from spawnfunc_dom_team entity
270 self.goalentity = head;
271 _setmodel(self, head.mdl); // precision already set
272 self.skin = head.skin;
276 if(self.message == "")
277 self.message = " has captured a control point";
284 float points, waittime;
285 if (autocvar_g_domination_point_amt)
286 points = autocvar_g_domination_point_amt;
289 if (autocvar_g_domination_point_rate)
290 waittime = autocvar_g_domination_point_rate;
292 waittime = self.wait;
294 total_pps += points/waittime;
297 self.t_width = 0.02; // frame animation rate
299 self.t_length = 239; // maximum frame
301 self.think = dompointthink;
302 self.nextthink = time;
303 self.touch = dompointtouch;
304 self.solid = SOLID_TRIGGER;
305 self.flags = FL_ITEM;
306 setsize(self, '-32 -32 -32', '32 32 32');
307 setorigin(self, self.origin + '0 0 20');
310 waypoint_spawnforitem(self);
311 WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT);
314 float total_controlpoints;
315 void Domination_count_controlpoints()
318 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
319 for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
321 ++total_controlpoints;
322 redowned += (e.goalentity.team == NUM_TEAM_1);
323 blueowned += (e.goalentity.team == NUM_TEAM_2);
324 yellowowned += (e.goalentity.team == NUM_TEAM_3);
325 pinkowned += (e.goalentity.team == NUM_TEAM_4);
329 float Domination_GetWinnerTeam()
331 float winner_team = 0;
332 if(redowned == total_controlpoints)
333 winner_team = NUM_TEAM_1;
334 if(blueowned == total_controlpoints)
336 if(winner_team) return 0;
337 winner_team = NUM_TEAM_2;
339 if(yellowowned == total_controlpoints)
341 if(winner_team) return 0;
342 winner_team = NUM_TEAM_3;
344 if(pinkowned == total_controlpoints)
346 if(winner_team) return 0;
347 winner_team = NUM_TEAM_4;
351 return -1; // no control points left?
354 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
355 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
356 float Domination_CheckWinner()
358 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
360 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
361 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
362 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
366 Domination_count_controlpoints();
368 float winner_team = Domination_GetWinnerTeam();
370 if(winner_team == -1)
375 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
376 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
377 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
379 else if(winner_team == -1)
381 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
382 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
385 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
390 float Domination_CheckPlayers()
395 void Domination_RoundStart()
399 e.player_blocked = 0;
402 //go to best items, or control points you don't own
403 void havocbot_role_dom()
405 if(self.deadflag != DEAD_NO)
408 if (self.bot_strategytime < time)
410 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
411 navigation_goalrating_start();
412 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
413 havocbot_goalrating_items(8000, self.origin, 8000);
414 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
415 //havocbot_goalrating_waypoints(1, self.origin, 1000);
416 navigation_goalrating_end();
420 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
423 ret_float = domination_teams;
424 ret_string = "dom_team";
426 entity head = find(world, classname, ret_string);
429 if(head.netname != "")
433 case NUM_TEAM_1: c1 = 0; break;
434 case NUM_TEAM_2: c2 = 0; break;
435 case NUM_TEAM_3: c3 = 0; break;
436 case NUM_TEAM_4: c4 = 0; break;
440 head = find(head, classname, ret_string);
443 ret_string = string_null;
448 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
450 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
456 self.player_blocked = 1;
457 if(IS_REAL_CLIENT(self))
463 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
465 if(domination_roundbased)
466 if(!round_handler_IsRoundStarted())
467 self.player_blocked = 1;
469 self.player_blocked = 0;
473 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
479 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
481 self.havocbot_role = havocbot_role_dom;
485 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
486 Control point for Domination gameplay.
488 spawnfunc(dom_controlpoint)
495 self.think = dom_controlpoint_setup;
496 self.nextthink = time + 0.1;
497 self.reset = dom_controlpoint_setup;
502 self.effects = self.effects | EF_LOWPRECISION;
503 if (autocvar_g_domination_point_fullbright)
504 self.effects |= EF_FULLBRIGHT;
507 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
508 Team declaration for Domination gameplay, this allows you to decide what team
509 names and control point models are used in your map.
511 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
512 can have netname set! The nameless team owns all control points at start.
516 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
518 Scoreboard color of the team (for example 4 is red and 13 is blue)
520 Model to use for control points owned by this team (for example
521 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
524 Skin of the model to use (for team skins on a single model)
526 Sound to play when this team captures a point.
527 (this is a localized sound, like a small alarm or other effect)
529 Narrator speech to play when this team captures a point.
530 (this is a global sound, like "Red team has captured a control point")
535 if(!g_domination || autocvar_g_domination_teams_override >= 2)
540 precache_model(self.model);
541 if (self.noise != "")
542 precache_sound(self.noise);
543 if (self.noise1 != "")
544 precache_sound(self.noise1);
545 self.classname = "dom_team";
546 _setmodel(self, self.model); // precision not needed
547 self.mdl = self.model;
548 self.dmg = self.modelindex;
551 // this would have to be changed if used in quakeworld
553 self.team = self.cnt + 1; // WHY are these different anyway?
557 void ScoreRules_dom(float teams)
559 if(domination_roundbased)
561 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
562 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
563 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
564 ScoreRules_basics_end();
568 float sp_domticks, sp_score;
569 sp_score = sp_domticks = 0;
570 if(autocvar_g_domination_disable_frags)
571 sp_domticks = SFL_SORT_PRIO_PRIMARY;
573 sp_score = SFL_SORT_PRIO_PRIMARY;
574 ScoreRules_basics(teams, sp_score, sp_score, true);
575 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
576 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
577 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
578 ScoreRules_basics_end();
582 // code from here on is just to support maps that don't have control point and team entities
583 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
586 self.classname = "dom_team";
587 self.netname = teamname;
588 self.cnt = teamcolor;
589 self.model = pointmodel;
590 self.skin = pointskin;
591 self.noise = capsound;
592 self.noise1 = capnarration;
593 self.message = capmessage;
595 // this code is identical to spawnfunc_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
602 self.team = self.cnt + 1;
608 void _spawnfunc_dom_controlpoint() { SELFPARAM(); spawnfunc_dom_controlpoint(self); }
609 void dom_spawnpoint(vector org)
612 self.classname = "dom_controlpoint";
613 self.think = _spawnfunc_dom_controlpoint;
614 self.nextthink = time;
615 setorigin(self, org);
616 spawnfunc_dom_controlpoint(this);
620 // spawn some default teams if the map is not set up for domination
621 void dom_spawnteams(float teams)
623 dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND(DOM_CLAIM), "", "Red team has captured a control point");
624 dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND(DOM_CLAIM), "", "Blue team has captured a control point");
626 dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND(DOM_CLAIM), "", "Yellow team has captured a control point");
628 dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND(DOM_CLAIM), "", "Pink team has captured a control point");
629 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
632 void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
634 // if no teams are found, spawn defaults
635 if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
637 LOG_INFO("No ""dom_team"" entities found on this map, creating them anyway.\n");
638 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
639 dom_spawnteams(domination_teams);
642 CheckAllowedTeams(world);
643 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
645 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
646 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
647 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
648 if(domination_teams >= 3) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
649 if(domination_teams >= 4) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
651 domination_roundbased = autocvar_g_domination_roundbased;
653 ScoreRules_dom(domination_teams);
655 if(domination_roundbased)
657 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
658 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
662 void dom_Initialize()
665 InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);
669 REGISTER_MUTATOR(dom, IS_GAMETYPE(DOMINATION))
671 int fraglimit_override = autocvar_g_domination_point_limit;
672 if(autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
673 fraglimit_override = autocvar_g_domination_roundbased_point_limit;
676 SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, -1, -1);
677 have_team_spawns = -1; // request team spawns
681 if(time > 1) // game loads at time 1
682 error("This is a game type and it cannot be added at runtime.");
688 LOG_INFO("This is a game type and it cannot be removed at runtime.");