1 #include "gamemode_domination.qh"
5 #include "../teamplay.qh"
7 int autocvar_g_domination_default_teams;
8 bool autocvar_g_domination_disable_frags;
9 int autocvar_g_domination_point_amt;
10 bool autocvar_g_domination_point_fullbright;
11 int autocvar_g_domination_point_leadlimit;
12 bool autocvar_g_domination_roundbased;
13 int autocvar_g_domination_roundbased_point_limit;
14 float autocvar_g_domination_round_timelimit;
15 float autocvar_g_domination_warmup;
16 #define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
17 float autocvar_g_domination_point_rate;
18 int autocvar_g_domination_teams_override;
20 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
22 if(autocvar_sv_eventlog)
23 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
26 void set_dom_state(entity e)
28 e.dom_total_pps = total_pps;
29 e.dom_pps_red = pps_red;
30 e.dom_pps_blue = pps_blue;
31 if(domination_teams >= 3)
32 e.dom_pps_yellow = pps_yellow;
33 if(domination_teams >= 4)
34 e.dom_pps_pink = pps_pink;
37 void dompoint_captured ()
40 float old_delay, old_team, real_team;
42 // now that the delay has expired, switch to the latest team to lay claim to this point
48 dom_EventLog("taken", self.team, self.dmg_inflictor);
49 self.dmg_inflictor = world;
51 self.goalentity = head;
52 self.model = head.mdl;
53 self.modelindex = head.dmg;
54 self.skin = head.skin;
56 float points, wait_time;
57 if (autocvar_g_domination_point_amt)
58 points = autocvar_g_domination_point_amt;
61 if (autocvar_g_domination_point_rate)
62 wait_time = autocvar_g_domination_point_rate;
64 wait_time = self.wait;
66 if(domination_roundbased)
67 bprint(sprintf("^3%s^3%s\n", head.netname, self.message));
69 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time);
71 if(self.enemy.playerid == self.enemy_playerid)
72 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
78 _sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
80 _sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
81 if (head.noise1 != "")
82 play2all(head.noise1);
84 self.delay = time + wait_time;
87 old_delay = self.delay;
89 self.team = real_team;
93 self.delay = old_delay;
96 entity msg = WP_DomNeut;
99 case NUM_TEAM_1: msg = WP_DomRed; break;
100 case NUM_TEAM_2: msg = WP_DomBlue; break;
101 case NUM_TEAM_3: msg = WP_DomYellow; break;
102 case NUM_TEAM_4: msg = WP_DomPink; break;
105 WaypointSprite_UpdateSprites(self.sprite, msg, WP_Null, WP_Null);
107 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
108 for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
110 if (autocvar_g_domination_point_amt)
111 points = autocvar_g_domination_point_amt;
114 if (autocvar_g_domination_point_rate)
115 wait_time = autocvar_g_domination_point_rate;
117 wait_time = head.wait;
118 switch(head.goalentity.team)
121 pps_red += points/wait_time;
124 pps_blue += points/wait_time;
127 pps_yellow += points/wait_time;
130 pps_pink += points/wait_time;
133 total_pps += points/wait_time;
136 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
137 WaypointSprite_Ping(self.sprite);
141 FOR_EACH_REALCLIENT(head)
145 void AnimateDomPoint()
147 if(self.pain_finished > time)
149 self.pain_finished = time + self.t_width;
150 if(self.nextthink > self.pain_finished)
151 self.nextthink = self.pain_finished;
153 self.frame = self.frame + 1;
154 if(self.frame > self.t_length)
162 self.nextthink = time + 0.1;
164 //self.frame = self.frame + 1;
165 //if(self.frame > 119)
171 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
174 if(autocvar_g_domination_point_rate)
175 self.delay = time + autocvar_g_domination_point_rate;
177 self.delay = time + self.wait;
179 // give credit to the team
180 // NOTE: this defaults to 0
181 if (!domination_roundbased)
182 if (self.goalentity.netname != "")
184 if(autocvar_g_domination_point_amt)
185 fragamt = autocvar_g_domination_point_amt;
187 fragamt = self.frags;
188 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
189 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
191 // give credit to the individual player, if he is still there
192 if (self.enemy.playerid == self.enemy_playerid)
194 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
195 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
205 if (!IS_PLAYER(other))
207 if (other.health < 1)
210 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
213 if(time < self.captime + 0.3)
216 // only valid teams can claim it
217 head = find(world, classname, "dom_team");
218 while (head && head.team != other.team)
219 head = find(head, classname, "dom_team");
220 if (!head || head.netname == "" || head == self.goalentity)
225 self.team = self.goalentity.team; // this stores the PREVIOUS team!
227 self.cnt = other.team;
228 self.owner = head; // team to switch to after the delay
229 self.dmg_inflictor = other;
232 // self.delay = time + cvar("g_domination_point_capturetime");
233 //self.nextthink = time + cvar("g_domination_point_capturetime");
234 //self.think = dompoint_captured;
236 // go to neutral team in the mean time
237 head = find(world, classname, "dom_team");
238 while (head && head.netname != "")
239 head = find(head, classname, "dom_team");
243 WaypointSprite_UpdateSprites(self.sprite, WP_DomNeut, WP_Null, WP_Null);
244 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
245 WaypointSprite_Ping(self.sprite);
247 self.goalentity = head;
248 self.model = head.mdl;
249 self.modelindex = head.dmg;
250 self.skin = head.skin;
252 self.enemy = other; // individual player scoring
253 self.enemy_playerid = other.playerid;
257 void dom_controlpoint_setup()
260 // find the spawnfunc_dom_team representing unclaimed points
261 head = find(world, classname, "dom_team");
262 while(head && head.netname != "")
263 head = find(head, classname, "dom_team");
265 objerror("no spawnfunc_dom_team with netname \"\" found\n");
267 // copy important properties from spawnfunc_dom_team entity
268 self.goalentity = head;
269 _setmodel(self, head.mdl); // precision already set
270 self.skin = head.skin;
274 if(self.message == "")
275 self.message = " has captured a control point";
282 float points, waittime;
283 if (autocvar_g_domination_point_amt)
284 points = autocvar_g_domination_point_amt;
287 if (autocvar_g_domination_point_rate)
288 waittime = autocvar_g_domination_point_rate;
290 waittime = self.wait;
292 total_pps += points/waittime;
295 self.t_width = 0.02; // frame animation rate
297 self.t_length = 239; // maximum frame
299 self.think = dompointthink;
300 self.nextthink = time;
301 self.touch = dompointtouch;
302 self.solid = SOLID_TRIGGER;
303 self.flags = FL_ITEM;
304 setsize(self, '-32 -32 -32', '32 32 32');
305 setorigin(self, self.origin + '0 0 20');
308 waypoint_spawnforitem(self);
309 WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT);
312 float total_controlpoints;
313 void Domination_count_controlpoints()
316 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
317 for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
319 ++total_controlpoints;
320 redowned += (e.goalentity.team == NUM_TEAM_1);
321 blueowned += (e.goalentity.team == NUM_TEAM_2);
322 yellowowned += (e.goalentity.team == NUM_TEAM_3);
323 pinkowned += (e.goalentity.team == NUM_TEAM_4);
327 float Domination_GetWinnerTeam()
329 float winner_team = 0;
330 if(redowned == total_controlpoints)
331 winner_team = NUM_TEAM_1;
332 if(blueowned == total_controlpoints)
334 if(winner_team) return 0;
335 winner_team = NUM_TEAM_2;
337 if(yellowowned == total_controlpoints)
339 if(winner_team) return 0;
340 winner_team = NUM_TEAM_3;
342 if(pinkowned == total_controlpoints)
344 if(winner_team) return 0;
345 winner_team = NUM_TEAM_4;
349 return -1; // no control points left?
352 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
353 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
354 float Domination_CheckWinner()
356 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
358 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
359 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
360 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
364 Domination_count_controlpoints();
366 float winner_team = Domination_GetWinnerTeam();
368 if(winner_team == -1)
373 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
374 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
375 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
377 else if(winner_team == -1)
379 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
380 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
383 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
388 float Domination_CheckPlayers()
393 void Domination_RoundStart()
397 e.player_blocked = 0;
400 //go to best items, or control points you don't own
401 void havocbot_role_dom()
403 if(self.deadflag != DEAD_NO)
406 if (self.bot_strategytime < time)
408 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
409 navigation_goalrating_start();
410 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
411 havocbot_goalrating_items(8000, self.origin, 8000);
412 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
413 //havocbot_goalrating_waypoints(1, self.origin, 1000);
414 navigation_goalrating_end();
418 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
421 ret_float = domination_teams;
422 ret_string = "dom_team";
424 entity head = find(world, classname, ret_string);
427 if(head.netname != "")
431 case NUM_TEAM_1: c1 = 0; break;
432 case NUM_TEAM_2: c2 = 0; break;
433 case NUM_TEAM_3: c3 = 0; break;
434 case NUM_TEAM_4: c4 = 0; break;
438 head = find(head, classname, ret_string);
441 ret_string = string_null;
446 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
448 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
454 self.player_blocked = 1;
455 if(IS_REAL_CLIENT(self))
461 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
463 if(domination_roundbased)
464 if(!round_handler_IsRoundStarted())
465 self.player_blocked = 1;
467 self.player_blocked = 0;
471 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
477 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
479 self.havocbot_role = havocbot_role_dom;
483 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
484 Control point for Domination gameplay.
486 spawnfunc(dom_controlpoint)
493 self.think = dom_controlpoint_setup;
494 self.nextthink = time + 0.1;
495 self.reset = dom_controlpoint_setup;
500 self.effects = self.effects | EF_LOWPRECISION;
501 if (autocvar_g_domination_point_fullbright)
502 self.effects |= EF_FULLBRIGHT;
505 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
506 Team declaration for Domination gameplay, this allows you to decide what team
507 names and control point models are used in your map.
509 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
510 can have netname set! The nameless team owns all control points at start.
514 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
516 Scoreboard color of the team (for example 4 is red and 13 is blue)
518 Model to use for control points owned by this team (for example
519 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
522 Skin of the model to use (for team skins on a single model)
524 Sound to play when this team captures a point.
525 (this is a localized sound, like a small alarm or other effect)
527 Narrator speech to play when this team captures a point.
528 (this is a global sound, like "Red team has captured a control point")
533 if(!g_domination || autocvar_g_domination_teams_override >= 2)
538 precache_model(self.model);
539 if (self.noise != "")
540 precache_sound(self.noise);
541 if (self.noise1 != "")
542 precache_sound(self.noise1);
543 self.classname = "dom_team";
544 _setmodel(self, self.model); // precision not needed
545 self.mdl = self.model;
546 self.dmg = self.modelindex;
549 // this would have to be changed if used in quakeworld
551 self.team = self.cnt + 1; // WHY are these different anyway?
555 void ScoreRules_dom(float teams)
557 if(domination_roundbased)
559 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
560 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
561 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
562 ScoreRules_basics_end();
566 float sp_domticks, sp_score;
567 sp_score = sp_domticks = 0;
568 if(autocvar_g_domination_disable_frags)
569 sp_domticks = SFL_SORT_PRIO_PRIMARY;
571 sp_score = SFL_SORT_PRIO_PRIMARY;
572 ScoreRules_basics(teams, sp_score, sp_score, true);
573 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
574 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
575 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
576 ScoreRules_basics_end();
580 // code from here on is just to support maps that don't have control point and team entities
581 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
584 self.classname = "dom_team";
585 self.netname = teamname;
586 self.cnt = teamcolor;
587 self.model = pointmodel;
588 self.skin = pointskin;
589 self.noise = capsound;
590 self.noise1 = capnarration;
591 self.message = capmessage;
593 // this code is identical to spawnfunc_dom_team
594 _setmodel(self, self.model); // precision not needed
595 self.mdl = self.model;
596 self.dmg = self.modelindex;
599 // this would have to be changed if used in quakeworld
600 self.team = self.cnt + 1;
606 void _spawnfunc_dom_controlpoint() { SELFPARAM(); spawnfunc_dom_controlpoint(self); }
607 void dom_spawnpoint(vector org)
610 self.classname = "dom_controlpoint";
611 self.think = _spawnfunc_dom_controlpoint;
612 self.nextthink = time;
613 setorigin(self, org);
614 spawnfunc_dom_controlpoint(this);
618 // spawn some default teams if the map is not set up for domination
619 void dom_spawnteams(float teams)
621 dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND(DOM_CLAIM), "", "Red team has captured a control point");
622 dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND(DOM_CLAIM), "", "Blue team has captured a control point");
624 dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND(DOM_CLAIM), "", "Yellow team has captured a control point");
626 dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND(DOM_CLAIM), "", "Pink team has captured a control point");
627 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
630 void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
632 // if no teams are found, spawn defaults
633 if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
635 LOG_INFO("No ""dom_team"" entities found on this map, creating them anyway.\n");
636 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
637 dom_spawnteams(domination_teams);
640 CheckAllowedTeams(world);
641 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
643 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
644 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
645 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
646 if(domination_teams >= 3) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
647 if(domination_teams >= 4) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
649 domination_roundbased = autocvar_g_domination_roundbased;
651 ScoreRules_dom(domination_teams);
653 if(domination_roundbased)
655 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
656 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
660 void dom_Initialize()
662 InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);
666 REGISTER_MUTATOR(dom, g_domination)
668 int fraglimit_override = autocvar_g_domination_point_limit;
669 if(autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
670 fraglimit_override = autocvar_g_domination_roundbased_point_limit;
673 SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, -1, -1);
674 have_team_spawns = -1; // request team spawns
678 if(time > 1) // game loads at time 1
679 error("This is a game type and it cannot be added at runtime.");
685 LOG_INFO("This is a game type and it cannot be removed at runtime.");