1 #include "gamemode_domination.qh"
5 #include "../teamplay.qh"
7 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
9 if(autocvar_sv_eventlog)
10 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
13 void set_dom_state(entity e)
15 e.dom_total_pps = total_pps;
16 e.dom_pps_red = pps_red;
17 e.dom_pps_blue = pps_blue;
18 if(domination_teams >= 3)
19 e.dom_pps_yellow = pps_yellow;
20 if(domination_teams >= 4)
21 e.dom_pps_pink = pps_pink;
24 void dompoint_captured ()
27 float old_delay, old_team, real_team;
29 // now that the delay has expired, switch to the latest team to lay claim to this point
35 dom_EventLog("taken", self.team, self.dmg_inflictor);
36 self.dmg_inflictor = world;
38 self.goalentity = head;
39 self.model = head.mdl;
40 self.modelindex = head.dmg;
41 self.skin = head.skin;
43 float points, wait_time;
44 if (autocvar_g_domination_point_amt)
45 points = autocvar_g_domination_point_amt;
48 if (autocvar_g_domination_point_rate)
49 wait_time = autocvar_g_domination_point_rate;
51 wait_time = self.wait;
53 if(domination_roundbased)
54 bprint(sprintf("^3%s^3%s\n", head.netname, self.message));
56 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time);
58 if(self.enemy.playerid == self.enemy_playerid)
59 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
65 _sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
67 _sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
68 if (head.noise1 != "")
69 play2all(head.noise1);
71 self.delay = time + wait_time;
74 old_delay = self.delay;
76 self.team = real_team;
80 self.delay = old_delay;
83 entity msg = WP_DomNeut;
86 case NUM_TEAM_1: msg = WP_DomRed; break;
87 case NUM_TEAM_2: msg = WP_DomBlue; break;
88 case NUM_TEAM_3: msg = WP_DomYellow; break;
89 case NUM_TEAM_4: msg = WP_DomPink; break;
92 WaypointSprite_UpdateSprites(self.sprite, msg, WP_Null, WP_Null);
94 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
95 for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
97 if (autocvar_g_domination_point_amt)
98 points = autocvar_g_domination_point_amt;
101 if (autocvar_g_domination_point_rate)
102 wait_time = autocvar_g_domination_point_rate;
104 wait_time = head.wait;
105 switch(head.goalentity.team)
108 pps_red += points/wait_time;
111 pps_blue += points/wait_time;
114 pps_yellow += points/wait_time;
117 pps_pink += points/wait_time;
120 total_pps += points/wait_time;
123 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
124 WaypointSprite_Ping(self.sprite);
128 FOR_EACH_REALCLIENT(head)
132 void AnimateDomPoint()
134 if(self.pain_finished > time)
136 self.pain_finished = time + self.t_width;
137 if(self.nextthink > self.pain_finished)
138 self.nextthink = self.pain_finished;
140 self.frame = self.frame + 1;
141 if(self.frame > self.t_length)
149 self.nextthink = time + 0.1;
151 //self.frame = self.frame + 1;
152 //if(self.frame > 119)
158 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
161 if(autocvar_g_domination_point_rate)
162 self.delay = time + autocvar_g_domination_point_rate;
164 self.delay = time + self.wait;
166 // give credit to the team
167 // NOTE: this defaults to 0
168 if (!domination_roundbased)
169 if (self.goalentity.netname != "")
171 if(autocvar_g_domination_point_amt)
172 fragamt = autocvar_g_domination_point_amt;
174 fragamt = self.frags;
175 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
176 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
178 // give credit to the individual player, if he is still there
179 if (self.enemy.playerid == self.enemy_playerid)
181 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
182 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
192 if (!IS_PLAYER(other))
194 if (other.health < 1)
197 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
200 if(time < self.captime + 0.3)
203 // only valid teams can claim it
204 head = find(world, classname, "dom_team");
205 while (head && head.team != other.team)
206 head = find(head, classname, "dom_team");
207 if (!head || head.netname == "" || head == self.goalentity)
212 self.team = self.goalentity.team; // this stores the PREVIOUS team!
214 self.cnt = other.team;
215 self.owner = head; // team to switch to after the delay
216 self.dmg_inflictor = other;
219 // self.delay = time + cvar("g_domination_point_capturetime");
220 //self.nextthink = time + cvar("g_domination_point_capturetime");
221 //self.think = dompoint_captured;
223 // go to neutral team in the mean time
224 head = find(world, classname, "dom_team");
225 while (head && head.netname != "")
226 head = find(head, classname, "dom_team");
230 WaypointSprite_UpdateSprites(self.sprite, WP_DomNeut, WP_Null, WP_Null);
231 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
232 WaypointSprite_Ping(self.sprite);
234 self.goalentity = head;
235 self.model = head.mdl;
236 self.modelindex = head.dmg;
237 self.skin = head.skin;
239 self.enemy = other; // individual player scoring
240 self.enemy_playerid = other.playerid;
244 void dom_controlpoint_setup()
247 // find the spawnfunc_dom_team representing unclaimed points
248 head = find(world, classname, "dom_team");
249 while(head && head.netname != "")
250 head = find(head, classname, "dom_team");
252 objerror("no spawnfunc_dom_team with netname \"\" found\n");
254 // copy important properties from spawnfunc_dom_team entity
255 self.goalentity = head;
256 _setmodel(self, head.mdl); // precision already set
257 self.skin = head.skin;
261 if(self.message == "")
262 self.message = " has captured a control point";
269 float points, waittime;
270 if (autocvar_g_domination_point_amt)
271 points = autocvar_g_domination_point_amt;
274 if (autocvar_g_domination_point_rate)
275 waittime = autocvar_g_domination_point_rate;
277 waittime = self.wait;
279 total_pps += points/waittime;
282 self.t_width = 0.02; // frame animation rate
284 self.t_length = 239; // maximum frame
286 self.think = dompointthink;
287 self.nextthink = time;
288 self.touch = dompointtouch;
289 self.solid = SOLID_TRIGGER;
290 self.flags = FL_ITEM;
291 setsize(self, '-32 -32 -32', '32 32 32');
292 setorigin(self, self.origin + '0 0 20');
295 waypoint_spawnforitem(self);
296 WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT);
299 float total_controlpoints;
300 void Domination_count_controlpoints()
303 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
304 for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
306 ++total_controlpoints;
307 redowned += (e.goalentity.team == NUM_TEAM_1);
308 blueowned += (e.goalentity.team == NUM_TEAM_2);
309 yellowowned += (e.goalentity.team == NUM_TEAM_3);
310 pinkowned += (e.goalentity.team == NUM_TEAM_4);
314 float Domination_GetWinnerTeam()
316 float winner_team = 0;
317 if(redowned == total_controlpoints)
318 winner_team = NUM_TEAM_1;
319 if(blueowned == total_controlpoints)
321 if(winner_team) return 0;
322 winner_team = NUM_TEAM_2;
324 if(yellowowned == total_controlpoints)
326 if(winner_team) return 0;
327 winner_team = NUM_TEAM_3;
329 if(pinkowned == total_controlpoints)
331 if(winner_team) return 0;
332 winner_team = NUM_TEAM_4;
336 return -1; // no control points left?
339 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
340 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
341 float Domination_CheckWinner()
343 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
345 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
346 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
347 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
351 Domination_count_controlpoints();
353 float winner_team = Domination_GetWinnerTeam();
355 if(winner_team == -1)
360 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
361 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
362 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
364 else if(winner_team == -1)
366 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
367 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
370 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
375 float Domination_CheckPlayers()
380 void Domination_RoundStart()
384 e.player_blocked = 0;
387 //go to best items, or control points you don't own
388 void havocbot_role_dom()
390 if(self.deadflag != DEAD_NO)
393 if (self.bot_strategytime < time)
395 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
396 navigation_goalrating_start();
397 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
398 havocbot_goalrating_items(8000, self.origin, 8000);
399 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
400 //havocbot_goalrating_waypoints(1, self.origin, 1000);
401 navigation_goalrating_end();
405 MUTATOR_HOOKFUNCTION(dom_GetTeamCount)
407 ret_float = domination_teams;
411 MUTATOR_HOOKFUNCTION(dom_ResetMap)
413 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
419 self.player_blocked = 1;
420 if(IS_REAL_CLIENT(self))
426 MUTATOR_HOOKFUNCTION(dom_PlayerSpawn)
428 if(domination_roundbased)
429 if(!round_handler_IsRoundStarted())
430 self.player_blocked = 1;
432 self.player_blocked = 0;
436 MUTATOR_HOOKFUNCTION(dom_ClientConnect)
442 MUTATOR_HOOKFUNCTION(dom_BotRoles)
444 self.havocbot_role = havocbot_role_dom;
448 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
449 Control point for Domination gameplay.
451 spawnfunc(dom_controlpoint)
458 self.think = dom_controlpoint_setup;
459 self.nextthink = time + 0.1;
460 self.reset = dom_controlpoint_setup;
465 self.effects = self.effects | EF_LOWPRECISION;
466 if (autocvar_g_domination_point_fullbright)
467 self.effects |= EF_FULLBRIGHT;
470 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
471 Team declaration for Domination gameplay, this allows you to decide what team
472 names and control point models are used in your map.
474 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
475 can have netname set! The nameless team owns all control points at start.
479 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
481 Scoreboard color of the team (for example 4 is red and 13 is blue)
483 Model to use for control points owned by this team (for example
484 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
487 Skin of the model to use (for team skins on a single model)
489 Sound to play when this team captures a point.
490 (this is a localized sound, like a small alarm or other effect)
492 Narrator speech to play when this team captures a point.
493 (this is a global sound, like "Red team has captured a control point")
498 if(!g_domination || autocvar_g_domination_teams_override >= 2)
503 precache_model(self.model);
504 if (self.noise != "")
505 precache_sound(self.noise);
506 if (self.noise1 != "")
507 precache_sound(self.noise1);
508 self.classname = "dom_team";
509 _setmodel(self, self.model); // precision not needed
510 self.mdl = self.model;
511 self.dmg = self.modelindex;
514 // this would have to be changed if used in quakeworld
516 self.team = self.cnt + 1; // WHY are these different anyway?
520 void ScoreRules_dom(float teams)
522 if(domination_roundbased)
524 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
525 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
526 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
527 ScoreRules_basics_end();
531 float sp_domticks, sp_score;
532 sp_score = sp_domticks = 0;
533 if(autocvar_g_domination_disable_frags)
534 sp_domticks = SFL_SORT_PRIO_PRIMARY;
536 sp_score = SFL_SORT_PRIO_PRIMARY;
537 ScoreRules_basics(teams, sp_score, sp_score, true);
538 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
539 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
540 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
541 ScoreRules_basics_end();
545 // code from here on is just to support maps that don't have control point and team entities
546 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
549 self.classname = "dom_team";
550 self.netname = teamname;
551 self.cnt = teamcolor;
552 self.model = pointmodel;
553 self.skin = pointskin;
554 self.noise = capsound;
555 self.noise1 = capnarration;
556 self.message = capmessage;
558 // this code is identical to spawnfunc_dom_team
559 _setmodel(self, self.model); // precision not needed
560 self.mdl = self.model;
561 self.dmg = self.modelindex;
564 // this would have to be changed if used in quakeworld
565 self.team = self.cnt + 1;
571 void _spawnfunc_dom_controlpoint() { SELFPARAM(); spawnfunc_dom_controlpoint(self); }
572 void dom_spawnpoint(vector org)
575 self.classname = "dom_controlpoint";
576 self.think = _spawnfunc_dom_controlpoint;
577 self.nextthink = time;
578 setorigin(self, org);
579 spawnfunc_dom_controlpoint(this);
583 // spawn some default teams if the map is not set up for domination
584 void dom_spawnteams(float teams)
586 dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND(DOM_CLAIM), "", "Red team has captured a control point");
587 dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND(DOM_CLAIM), "", "Blue team has captured a control point");
589 dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND(DOM_CLAIM), "", "Yellow team has captured a control point");
591 dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND(DOM_CLAIM), "", "Pink team has captured a control point");
592 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
595 void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
597 // if no teams are found, spawn defaults
598 if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
600 LOG_INFO("No ""dom_team"" entities found on this map, creating them anyway.\n");
601 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
602 dom_spawnteams(domination_teams);
605 CheckAllowedTeams(world);
606 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
608 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
609 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
610 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
611 if(domination_teams >= 3) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
612 if(domination_teams >= 4) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
614 domination_roundbased = autocvar_g_domination_roundbased;
616 ScoreRules_dom(domination_teams);
618 if(domination_roundbased)
620 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
621 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
625 void dom_Initialize()
627 InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);
631 MUTATOR_DEFINITION(gamemode_domination)
633 MUTATOR_HOOK(GetTeamCount, dom_GetTeamCount, CBC_ORDER_ANY);
634 MUTATOR_HOOK(reset_map_players, dom_ResetMap, CBC_ORDER_ANY);
635 MUTATOR_HOOK(PlayerSpawn, dom_PlayerSpawn, CBC_ORDER_ANY);
636 MUTATOR_HOOK(ClientConnect, dom_ClientConnect, CBC_ORDER_ANY);
637 MUTATOR_HOOK(HavocBot_ChooseRole, dom_BotRoles, CBC_ORDER_ANY);
641 if(time > 1) // game loads at time 1
642 error("This is a game type and it cannot be added at runtime.");
648 LOG_INFO("This is a game type and it cannot be removed at runtime.");