1 #include "../../common/triggers/subs.qh"
3 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
5 if(autocvar_sv_eventlog)
6 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
9 void set_dom_state(entity e)
11 e.dom_total_pps = total_pps;
12 e.dom_pps_red = pps_red;
13 e.dom_pps_blue = pps_blue;
14 if(domination_teams >= 3)
15 e.dom_pps_yellow = pps_yellow;
16 if(domination_teams >= 4)
17 e.dom_pps_pink = pps_pink;
20 void dompoint_captured ()
23 float old_delay, old_team, real_team;
24 string msg = "dom-neut";
26 // now that the delay has expired, switch to the latest team to lay claim to this point
32 dom_EventLog("taken", self.team, self.dmg_inflictor);
33 self.dmg_inflictor = world;
35 self.goalentity = head;
36 self.model = head.mdl;
37 self.modelindex = head.dmg;
38 self.skin = head.skin;
40 float points, wait_time;
41 if (autocvar_g_domination_point_amt)
42 points = autocvar_g_domination_point_amt;
45 if (autocvar_g_domination_point_rate)
46 wait_time = autocvar_g_domination_point_rate;
48 wait_time = self.wait;
50 if(domination_roundbased)
51 bprint(sprintf("^3%s^3%s\n", head.netname, self.message));
53 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time);
55 if(self.enemy.playerid == self.enemy_playerid)
56 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
62 sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
64 sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
65 if (head.noise1 != "")
66 play2all(head.noise1);
68 self.delay = time + wait_time;
71 old_delay = self.delay;
73 self.team = real_team;
77 self.delay = old_delay;
82 case NUM_TEAM_1: msg = "dom-red"; break;
83 case NUM_TEAM_2: msg = "dom-blue"; break;
84 case NUM_TEAM_3: msg = "dom-yellow"; break;
85 case NUM_TEAM_4: msg = "dom-pink"; break;
88 WaypointSprite_UpdateSprites(self.sprite, msg, "", "");
90 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
91 for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
93 if (autocvar_g_domination_point_amt)
94 points = autocvar_g_domination_point_amt;
97 if (autocvar_g_domination_point_rate)
98 wait_time = autocvar_g_domination_point_rate;
100 wait_time = head.wait;
101 switch(head.goalentity.team)
104 pps_red += points/wait_time;
107 pps_blue += points/wait_time;
110 pps_yellow += points/wait_time;
113 pps_pink += points/wait_time;
116 total_pps += points/wait_time;
119 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
120 WaypointSprite_Ping(self.sprite);
124 FOR_EACH_REALCLIENT(head)
128 void AnimateDomPoint()
130 if(self.pain_finished > time)
132 self.pain_finished = time + self.t_width;
133 if(self.nextthink > self.pain_finished)
134 self.nextthink = self.pain_finished;
136 self.frame = self.frame + 1;
137 if(self.frame > self.t_length)
145 self.nextthink = time + 0.1;
147 //self.frame = self.frame + 1;
148 //if(self.frame > 119)
154 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
157 if(autocvar_g_domination_point_rate)
158 self.delay = time + autocvar_g_domination_point_rate;
160 self.delay = time + self.wait;
162 // give credit to the team
163 // NOTE: this defaults to 0
164 if (!domination_roundbased)
165 if (self.goalentity.netname != "")
167 if(autocvar_g_domination_point_amt)
168 fragamt = autocvar_g_domination_point_amt;
170 fragamt = self.frags;
171 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
172 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
174 // give credit to the individual player, if he is still there
175 if (self.enemy.playerid == self.enemy_playerid)
177 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
178 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
188 if (!IS_PLAYER(other))
190 if (other.health < 1)
193 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
196 if(time < self.captime + 0.3)
199 // only valid teams can claim it
200 head = find(world, classname, "dom_team");
201 while (head && head.team != other.team)
202 head = find(head, classname, "dom_team");
203 if (!head || head.netname == "" || head == self.goalentity)
208 self.team = self.goalentity.team; // this stores the PREVIOUS team!
210 self.cnt = other.team;
211 self.owner = head; // team to switch to after the delay
212 self.dmg_inflictor = other;
215 // self.delay = time + cvar("g_domination_point_capturetime");
216 //self.nextthink = time + cvar("g_domination_point_capturetime");
217 //self.think = dompoint_captured;
219 // go to neutral team in the mean time
220 head = find(world, classname, "dom_team");
221 while (head && head.netname != "")
222 head = find(head, classname, "dom_team");
226 WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
227 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
228 WaypointSprite_Ping(self.sprite);
230 self.goalentity = head;
231 self.model = head.mdl;
232 self.modelindex = head.dmg;
233 self.skin = head.skin;
235 self.enemy = other; // individual player scoring
236 self.enemy_playerid = other.playerid;
240 void dom_controlpoint_setup()
243 // find the spawnfunc_dom_team representing unclaimed points
244 head = find(world, classname, "dom_team");
245 while(head && head.netname != "")
246 head = find(head, classname, "dom_team");
248 objerror("no spawnfunc_dom_team with netname \"\" found\n");
250 // copy important properties from spawnfunc_dom_team entity
251 self.goalentity = head;
252 setmodel(self, head.mdl); // precision already set
253 self.skin = head.skin;
257 if(self.message == "")
258 self.message = " has captured a control point";
265 float points, waittime;
266 if (autocvar_g_domination_point_amt)
267 points = autocvar_g_domination_point_amt;
270 if (autocvar_g_domination_point_rate)
271 waittime = autocvar_g_domination_point_rate;
273 waittime = self.wait;
275 total_pps += points/waittime;
278 self.t_width = 0.02; // frame animation rate
280 self.t_length = 239; // maximum frame
282 self.think = dompointthink;
283 self.nextthink = time;
284 self.touch = dompointtouch;
285 self.solid = SOLID_TRIGGER;
286 self.flags = FL_ITEM;
287 setsize(self, '-32 -32 -32', '32 32 32');
288 setorigin(self, self.origin + '0 0 20');
291 waypoint_spawnforitem(self);
292 WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT, '0 1 1');
295 float total_controlpoints, redowned, blueowned, yellowowned, pinkowned;
296 void Domination_count_controlpoints()
299 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
300 for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
302 ++total_controlpoints;
303 redowned += (e.goalentity.team == NUM_TEAM_1);
304 blueowned += (e.goalentity.team == NUM_TEAM_2);
305 yellowowned += (e.goalentity.team == NUM_TEAM_3);
306 pinkowned += (e.goalentity.team == NUM_TEAM_4);
310 float Domination_GetWinnerTeam()
312 float winner_team = 0;
313 if(redowned == total_controlpoints)
314 winner_team = NUM_TEAM_1;
315 if(blueowned == total_controlpoints)
317 if(winner_team) return 0;
318 winner_team = NUM_TEAM_2;
320 if(yellowowned == total_controlpoints)
322 if(winner_team) return 0;
323 winner_team = NUM_TEAM_3;
325 if(pinkowned == total_controlpoints)
327 if(winner_team) return 0;
328 winner_team = NUM_TEAM_4;
332 return -1; // no control points left?
335 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
336 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
337 float Domination_CheckWinner()
339 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
341 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
342 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
343 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
347 Domination_count_controlpoints();
349 float winner_team = Domination_GetWinnerTeam();
351 if(winner_team == -1)
356 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
357 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
358 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
360 else if(winner_team == -1)
362 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
363 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
366 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
371 float Domination_CheckPlayers()
376 void Domination_RoundStart()
380 e.player_blocked = 0;
383 //go to best items, or control points you don't own
384 void havocbot_role_dom()
386 if(self.deadflag != DEAD_NO)
389 if (self.bot_strategytime < time)
391 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
392 navigation_goalrating_start();
393 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
394 havocbot_goalrating_items(8000, self.origin, 8000);
395 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
396 //havocbot_goalrating_waypoints(1, self.origin, 1000);
397 navigation_goalrating_end();
401 MUTATOR_HOOKFUNCTION(dom_GetTeamCount)
403 ret_float = domination_teams;
407 MUTATOR_HOOKFUNCTION(dom_ResetMap)
409 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
410 FOR_EACH_PLAYER(self)
413 self.player_blocked = 1;
414 if(IS_REAL_CLIENT(self))
420 MUTATOR_HOOKFUNCTION(dom_PlayerSpawn)
422 if(domination_roundbased)
423 if(!round_handler_IsRoundStarted())
424 self.player_blocked = 1;
426 self.player_blocked = 0;
430 MUTATOR_HOOKFUNCTION(dom_ClientConnect)
436 MUTATOR_HOOKFUNCTION(dom_BotRoles)
438 self.havocbot_role = havocbot_role_dom;
442 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
443 Control point for Domination gameplay.
445 void spawnfunc_dom_controlpoint()
452 self.think = dom_controlpoint_setup;
453 self.nextthink = time + 0.1;
454 self.reset = dom_controlpoint_setup;
459 self.effects = self.effects | EF_LOWPRECISION;
460 if (autocvar_g_domination_point_fullbright)
461 self.effects |= EF_FULLBRIGHT;
464 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
465 Team declaration for Domination gameplay, this allows you to decide what team
466 names and control point models are used in your map.
468 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
469 can have netname set! The nameless team owns all control points at start.
473 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
475 Scoreboard color of the team (for example 4 is red and 13 is blue)
477 Model to use for control points owned by this team (for example
478 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
481 Skin of the model to use (for team skins on a single model)
483 Sound to play when this team captures a point.
484 (this is a localized sound, like a small alarm or other effect)
486 Narrator speech to play when this team captures a point.
487 (this is a global sound, like "Red team has captured a control point")
490 void spawnfunc_dom_team()
492 if(!g_domination || autocvar_g_domination_teams_override >= 2)
497 precache_model(self.model);
498 if (self.noise != "")
499 precache_sound(self.noise);
500 if (self.noise1 != "")
501 precache_sound(self.noise1);
502 self.classname = "dom_team";
503 setmodel(self, self.model); // precision not needed
504 self.mdl = self.model;
505 self.dmg = self.modelindex;
508 // this would have to be changed if used in quakeworld
510 self.team = self.cnt + 1; // WHY are these different anyway?
514 void ScoreRules_dom(float teams)
516 if(domination_roundbased)
518 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
519 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
520 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
521 ScoreRules_basics_end();
525 float sp_domticks, sp_score;
526 sp_score = sp_domticks = 0;
527 if(autocvar_g_domination_disable_frags)
528 sp_domticks = SFL_SORT_PRIO_PRIMARY;
530 sp_score = SFL_SORT_PRIO_PRIMARY;
531 ScoreRules_basics(teams, sp_score, sp_score, true);
532 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
533 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
534 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
535 ScoreRules_basics_end();
539 // code from here on is just to support maps that don't have control point and team entities
540 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
545 self.classname = "dom_team";
546 self.netname = teamname;
547 self.cnt = teamcolor;
548 self.model = pointmodel;
549 self.skin = pointskin;
550 self.noise = capsound;
551 self.noise1 = capnarration;
552 self.message = capmessage;
554 // this code is identical to spawnfunc_dom_team
555 setmodel(self, self.model); // precision not needed
556 self.mdl = self.model;
557 self.dmg = self.modelindex;
560 // this would have to be changed if used in quakeworld
561 self.team = self.cnt + 1;
567 void dom_spawnpoint(vector org)
572 self.classname = "dom_controlpoint";
573 self.think = spawnfunc_dom_controlpoint;
574 self.nextthink = time;
575 setorigin(self, org);
576 spawnfunc_dom_controlpoint();
580 // spawn some default teams if the map is not set up for domination
581 void dom_spawnteams(float teams)
583 dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
584 dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
586 dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
588 dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
589 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
592 void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
594 // if no teams are found, spawn defaults
595 if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
597 print("No ""dom_team"" entities found on this map, creating them anyway.\n");
598 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
599 dom_spawnteams(domination_teams);
602 CheckAllowedTeams(world);
603 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
605 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
606 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
607 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
608 if(domination_teams >= 3) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
609 if(domination_teams >= 4) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
611 domination_roundbased = autocvar_g_domination_roundbased;
613 ScoreRules_dom(domination_teams);
615 if(domination_roundbased)
617 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
618 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
622 void dom_Initialize()
624 precache_model("models/domination/dom_red.md3");
625 precache_model("models/domination/dom_blue.md3");
626 precache_model("models/domination/dom_yellow.md3");
627 precache_model("models/domination/dom_pink.md3");
628 precache_model("models/domination/dom_unclaimed.md3");
629 precache_sound("domination/claim.wav");
631 InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);
635 MUTATOR_DEFINITION(gamemode_domination)
637 MUTATOR_HOOK(GetTeamCount, dom_GetTeamCount, CBC_ORDER_ANY);
638 MUTATOR_HOOK(reset_map_players, dom_ResetMap, CBC_ORDER_ANY);
639 MUTATOR_HOOK(PlayerSpawn, dom_PlayerSpawn, CBC_ORDER_ANY);
640 MUTATOR_HOOK(ClientConnect, dom_ClientConnect, CBC_ORDER_ANY);
641 MUTATOR_HOOK(HavocBot_ChooseRole, dom_BotRoles, CBC_ORDER_ANY);
645 if(time > 1) // game loads at time 1
646 error("This is a game type and it cannot be added at runtime.");
652 print("This is a game type and it cannot be removed at runtime.");