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)
13 int fraglimit_override = autocvar_g_domination_point_limit;
14 if (autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
15 fraglimit_override = autocvar_g_domination_roundbased_point_limit;
18 SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, -1, -1);
19 have_team_spawns = -1; // request team spawns
23 if (time > 1) // game loads at time 1
24 error("This is a game type and it cannot be added at runtime.");
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
48 .float dom_pps_yellow;
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()
321 // find the spawnfunc_dom_team representing unclaimed points
322 head = find(world, classname, "dom_team");
323 while(head && head.netname != "")
324 head = find(head, classname, "dom_team");
326 objerror("no spawnfunc_dom_team with netname \"\" found\n");
328 // copy important properties from spawnfunc_dom_team entity
329 self.goalentity = head;
330 _setmodel(self, head.mdl); // precision already set
331 self.skin = head.skin;
335 if(self.message == "")
336 self.message = " has captured a control point";
343 float points, waittime;
344 if (autocvar_g_domination_point_amt)
345 points = autocvar_g_domination_point_amt;
348 if (autocvar_g_domination_point_rate)
349 waittime = autocvar_g_domination_point_rate;
351 waittime = self.wait;
353 total_pps += points/waittime;
356 self.t_width = 0.02; // frame animation rate
358 self.t_length = 239; // maximum frame
360 self.think = dompointthink;
361 self.nextthink = time;
362 self.touch = dompointtouch;
363 self.solid = SOLID_TRIGGER;
364 self.flags = FL_ITEM;
365 setsize(self, '-32 -32 -32', '32 32 32');
366 setorigin(self, self.origin + '0 0 20');
369 waypoint_spawnforitem(self);
370 WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT);
373 float total_controlpoints;
374 void Domination_count_controlpoints()
377 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
378 for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
380 ++total_controlpoints;
381 redowned += (e.goalentity.team == NUM_TEAM_1);
382 blueowned += (e.goalentity.team == NUM_TEAM_2);
383 yellowowned += (e.goalentity.team == NUM_TEAM_3);
384 pinkowned += (e.goalentity.team == NUM_TEAM_4);
388 float Domination_GetWinnerTeam()
390 float winner_team = 0;
391 if(redowned == total_controlpoints)
392 winner_team = NUM_TEAM_1;
393 if(blueowned == total_controlpoints)
395 if(winner_team) return 0;
396 winner_team = NUM_TEAM_2;
398 if(yellowowned == total_controlpoints)
400 if(winner_team) return 0;
401 winner_team = NUM_TEAM_3;
403 if(pinkowned == total_controlpoints)
405 if(winner_team) return 0;
406 winner_team = NUM_TEAM_4;
410 return -1; // no control points left?
413 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
414 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
415 float Domination_CheckWinner()
417 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
419 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
420 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
421 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
425 Domination_count_controlpoints();
427 float winner_team = Domination_GetWinnerTeam();
429 if(winner_team == -1)
434 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
435 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
436 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
438 else if(winner_team == -1)
440 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
441 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
444 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
449 float Domination_CheckPlayers()
454 void Domination_RoundStart()
458 e.player_blocked = 0;
461 //go to best items, or control points you don't own
462 void havocbot_role_dom()
464 if(self.deadflag != DEAD_NO)
467 if (self.bot_strategytime < time)
469 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
470 navigation_goalrating_start();
471 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
472 havocbot_goalrating_items(8000, self.origin, 8000);
473 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
474 //havocbot_goalrating_waypoints(1, self.origin, 1000);
475 navigation_goalrating_end();
479 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
482 ret_float = domination_teams;
483 ret_string = "dom_team";
485 entity head = find(world, classname, ret_string);
488 if(head.netname != "")
492 case NUM_TEAM_1: c1 = 0; break;
493 case NUM_TEAM_2: c2 = 0; break;
494 case NUM_TEAM_3: c3 = 0; break;
495 case NUM_TEAM_4: c4 = 0; break;
499 head = find(head, classname, ret_string);
502 ret_string = string_null;
507 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
509 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
515 self.player_blocked = 1;
516 if(IS_REAL_CLIENT(self))
522 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
524 if(domination_roundbased)
525 if(!round_handler_IsRoundStarted())
526 self.player_blocked = 1;
528 self.player_blocked = 0;
532 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
538 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
540 self.havocbot_role = havocbot_role_dom;
544 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
545 Control point for Domination gameplay.
547 spawnfunc(dom_controlpoint)
554 self.think = dom_controlpoint_setup;
555 self.nextthink = time + 0.1;
556 self.reset = dom_controlpoint_setup;
561 self.effects = self.effects | EF_LOWPRECISION;
562 if (autocvar_g_domination_point_fullbright)
563 self.effects |= EF_FULLBRIGHT;
566 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
567 Team declaration for Domination gameplay, this allows you to decide what team
568 names and control point models are used in your map.
570 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
571 can have netname set! The nameless team owns all control points at start.
575 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
577 Scoreboard color of the team (for example 4 is red and 13 is blue)
579 Model to use for control points owned by this team (for example
580 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
583 Skin of the model to use (for team skins on a single model)
585 Sound to play when this team captures a point.
586 (this is a localized sound, like a small alarm or other effect)
588 Narrator speech to play when this team captures a point.
589 (this is a global sound, like "Red team has captured a control point")
594 if(!g_domination || autocvar_g_domination_teams_override >= 2)
599 precache_model(self.model);
600 if (self.noise != "")
601 precache_sound(self.noise);
602 if (self.noise1 != "")
603 precache_sound(self.noise1);
604 self.classname = "dom_team";
605 _setmodel(self, self.model); // precision not needed
606 self.mdl = self.model;
607 self.dmg = self.modelindex;
610 // this would have to be changed if used in quakeworld
612 self.team = self.cnt + 1; // WHY are these different anyway?
616 void ScoreRules_dom(float teams)
618 if(domination_roundbased)
620 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
621 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
622 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
623 ScoreRules_basics_end();
627 float sp_domticks, sp_score;
628 sp_score = sp_domticks = 0;
629 if(autocvar_g_domination_disable_frags)
630 sp_domticks = SFL_SORT_PRIO_PRIMARY;
632 sp_score = SFL_SORT_PRIO_PRIMARY;
633 ScoreRules_basics(teams, sp_score, sp_score, true);
634 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
635 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
636 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
637 ScoreRules_basics_end();
641 // code from here on is just to support maps that don't have control point and team entities
642 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
645 self.classname = "dom_team";
646 self.netname = teamname;
647 self.cnt = teamcolor;
648 self.model = pointmodel;
649 self.skin = pointskin;
650 self.noise = capsound;
651 self.noise1 = capnarration;
652 self.message = capmessage;
654 // this code is identical to spawnfunc_dom_team
655 _setmodel(self, self.model); // precision not needed
656 self.mdl = self.model;
657 self.dmg = self.modelindex;
660 // this would have to be changed if used in quakeworld
661 self.team = self.cnt + 1;
667 void _spawnfunc_dom_controlpoint() { SELFPARAM(); spawnfunc_dom_controlpoint(self); }
668 void dom_spawnpoint(vector org)
671 self.classname = "dom_controlpoint";
672 self.think = _spawnfunc_dom_controlpoint;
673 self.nextthink = time;
674 setorigin(self, org);
675 spawnfunc_dom_controlpoint(this);
679 // spawn some default teams if the map is not set up for domination
680 void dom_spawnteams(float teams)
682 dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND(DOM_CLAIM), "", "Red team has captured a control point");
683 dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND(DOM_CLAIM), "", "Blue team has captured a control point");
685 dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND(DOM_CLAIM), "", "Yellow team has captured a control point");
687 dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND(DOM_CLAIM), "", "Pink team has captured a control point");
688 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
691 void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
693 // if no teams are found, spawn defaults
694 if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
696 LOG_INFO("No ""dom_team"" entities found on this map, creating them anyway.\n");
697 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
698 dom_spawnteams(domination_teams);
701 CheckAllowedTeams(world);
702 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
704 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
705 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
706 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
707 if(domination_teams >= 3) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
708 if(domination_teams >= 4) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
710 domination_roundbased = autocvar_g_domination_roundbased;
712 ScoreRules_dom(domination_teams);
714 if(domination_roundbased)
716 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
717 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
721 void dom_Initialize()
724 InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);