1 #include "gamemode_domination.qh"
2 #ifndef GAMEMODE_DOMINATION_H
3 #define GAMEMODE_DOMINATION_H
5 #define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
6 bool autocvar_g_domination_roundbased;
7 int autocvar_g_domination_roundbased_point_limit;
8 int autocvar_g_domination_point_leadlimit;
10 void dom_Initialize();
12 REGISTER_MUTATOR(dom, false)
16 if (time > 1) // game loads at time 1
17 error("This is a game type and it cannot be added at runtime.");
20 int fraglimit_override = autocvar_g_domination_point_limit;
21 if (autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
22 fraglimit_override = autocvar_g_domination_roundbased_point_limit;
25 SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, autocvar_timelimit_override, -1);
26 have_team_spawns = -1; // request team spawns
31 LOG_INFO("This is a game type and it cannot be removed at runtime.");
38 // score rule declarations
39 const float ST_DOM_TICKS = 1;
40 const float SP_DOM_TICKS = 4;
41 const float SP_DOM_TAKES = 5;
42 const float ST_DOM_CAPS = 1;
43 const float SP_DOM_CAPS = 4;
45 // pps: points per second
46 .float dom_total_pps = _STAT(DOM_TOTAL_PPS);
47 .float dom_pps_red = _STAT(DOM_PPS_RED);
48 .float dom_pps_blue = _STAT(DOM_PPS_BLUE);
49 .float dom_pps_yellow = _STAT(DOM_PPS_YELLOW);
50 .float dom_pps_pink = _STAT(DOM_PPS_PINK);
57 // capture declarations
58 .float enemy_playerid;
63 float domination_roundbased;
64 float domination_teams;
69 #include <server/teamplay.qh>
73 int autocvar_g_domination_default_teams;
74 bool autocvar_g_domination_disable_frags;
75 int autocvar_g_domination_point_amt;
76 bool autocvar_g_domination_point_fullbright;
77 float autocvar_g_domination_round_timelimit;
78 float autocvar_g_domination_warmup;
79 float autocvar_g_domination_point_rate;
80 int autocvar_g_domination_teams_override;
82 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
84 if(autocvar_sv_eventlog)
85 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
88 void set_dom_state(entity e)
90 e.dom_total_pps = total_pps;
91 e.dom_pps_red = pps_red;
92 e.dom_pps_blue = pps_blue;
93 if(domination_teams >= 3)
94 e.dom_pps_yellow = pps_yellow;
95 if(domination_teams >= 4)
96 e.dom_pps_pink = pps_pink;
99 void dompoint_captured(entity this)
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
104 entity head = this.owner;
106 real_team = this.cnt;
109 dom_EventLog("taken", this.team, this.dmg_inflictor);
110 this.dmg_inflictor = NULL;
112 this.goalentity = head;
113 this.model = head.mdl;
114 this.modelindex = head.dmg;
115 this.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 = this.wait;
127 if(domination_roundbased)
128 bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
130 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
132 if(this.enemy.playerid == this.enemy_playerid)
133 PlayerScore_Add(this.enemy, SP_DOM_TAKES, 1);
137 if (head.noise != "")
139 _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
141 _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
142 if (head.noise1 != "")
143 play2all(head.noise1);
145 this.delay = time + wait_time;
148 old_delay = this.delay;
149 old_team = this.team;
150 this.team = real_team;
152 SUB_UseTargets (this, this, NULL);
153 this.delay = old_delay;
154 this.team = old_team;
156 entity msg = WP_DomNeut;
159 case NUM_TEAM_1: msg = WP_DomRed; break;
160 case NUM_TEAM_2: msg = WP_DomBlue; break;
161 case NUM_TEAM_3: msg = WP_DomYellow; break;
162 case NUM_TEAM_4: msg = WP_DomPink; break;
165 WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
167 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
168 FOREACH_ENTITY_CLASS("dom_controlpoint", true, LAMBDA(
169 if (autocvar_g_domination_point_amt)
170 points = autocvar_g_domination_point_amt;
173 if (autocvar_g_domination_point_rate)
174 wait_time = autocvar_g_domination_point_rate;
177 switch(it.goalentity.team)
179 case NUM_TEAM_1: pps_red += points/wait_time; break;
180 case NUM_TEAM_2: pps_blue += points/wait_time; break;
181 case NUM_TEAM_3: pps_yellow += points/wait_time; break;
182 case NUM_TEAM_4: pps_pink += points/wait_time; break;
184 total_pps += points/wait_time;
187 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
188 WaypointSprite_Ping(this.sprite);
192 FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(set_dom_state(it)));
195 void AnimateDomPoint(entity this)
197 if(this.pain_finished > time)
199 this.pain_finished = time + this.t_width;
200 if(this.nextthink > this.pain_finished)
201 this.nextthink = this.pain_finished;
203 this.frame = this.frame + 1;
204 if(this.frame > this.t_length)
208 void dompointthink(entity this)
212 this.nextthink = time + 0.1;
214 //this.frame = this.frame + 1;
215 //if(this.frame > 119)
217 AnimateDomPoint(this);
221 if (gameover || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
224 if(autocvar_g_domination_point_rate)
225 this.delay = time + autocvar_g_domination_point_rate;
227 this.delay = time + this.wait;
229 // give credit to the team
230 // NOTE: this defaults to 0
231 if (!domination_roundbased)
232 if (this.goalentity.netname != "")
234 if(autocvar_g_domination_point_amt)
235 fragamt = autocvar_g_domination_point_amt;
237 fragamt = this.frags;
238 TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
239 TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
241 // give credit to the individual player, if he is still there
242 if (this.enemy.playerid == this.enemy_playerid)
244 PlayerScore_Add(this.enemy, SP_SCORE, fragamt);
245 PlayerScore_Add(this.enemy, SP_DOM_TICKS, fragamt);
252 void dompointtouch(entity this, entity toucher)
254 if (!IS_PLAYER(toucher))
256 if (toucher.health < 1)
259 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
262 if(time < this.captime + 0.3)
265 // only valid teams can claim it
266 entity head = find(NULL, classname, "dom_team");
267 while (head && head.team != toucher.team)
268 head = find(head, classname, "dom_team");
269 if (!head || head.netname == "" || head == this.goalentity)
274 this.team = this.goalentity.team; // this stores the PREVIOUS team!
276 this.cnt = toucher.team;
277 this.owner = head; // team to switch to after the delay
278 this.dmg_inflictor = toucher;
281 // this.delay = time + cvar("g_domination_point_capturetime");
282 //this.nextthink = time + cvar("g_domination_point_capturetime");
283 //this.think = dompoint_captured;
285 // go to neutral team in the mean time
286 head = find(NULL, classname, "dom_team");
287 while (head && head.netname != "")
288 head = find(head, classname, "dom_team");
292 WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
293 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
294 WaypointSprite_Ping(this.sprite);
296 this.goalentity = head;
297 this.model = head.mdl;
298 this.modelindex = head.dmg;
299 this.skin = head.skin;
301 this.enemy = toucher; // individual player scoring
302 this.enemy_playerid = toucher.playerid;
303 dompoint_captured(this);
306 void dom_controlpoint_setup(entity this)
309 // find the spawnfunc_dom_team representing unclaimed points
310 head = find(NULL, classname, "dom_team");
311 while(head && head.netname != "")
312 head = find(head, classname, "dom_team");
314 objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
316 // copy important properties from spawnfunc_dom_team entity
317 this.goalentity = head;
318 _setmodel(this, head.mdl); // precision already set
319 this.skin = head.skin;
323 if(this.message == "")
324 this.message = " has captured a control point";
331 float points, waittime;
332 if (autocvar_g_domination_point_amt)
333 points = autocvar_g_domination_point_amt;
336 if (autocvar_g_domination_point_rate)
337 waittime = autocvar_g_domination_point_rate;
339 waittime = this.wait;
341 total_pps += points/waittime;
344 this.t_width = 0.02; // frame animation rate
346 this.t_length = 239; // maximum frame
348 setthink(this, dompointthink);
349 this.nextthink = time;
350 settouch(this, dompointtouch);
351 this.solid = SOLID_TRIGGER;
352 this.flags = FL_ITEM;
353 setsize(this, '-32 -32 -32', '32 32 32');
354 setorigin(this, this.origin + '0 0 20');
357 waypoint_spawnforitem(this);
358 WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
361 float total_controlpoints;
362 void Domination_count_controlpoints()
364 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
365 FOREACH_ENTITY_CLASS("dom_controlpoint", true,
367 ++total_controlpoints;
368 redowned += (it.goalentity.team == NUM_TEAM_1);
369 blueowned += (it.goalentity.team == NUM_TEAM_2);
370 yellowowned += (it.goalentity.team == NUM_TEAM_3);
371 pinkowned += (it.goalentity.team == NUM_TEAM_4);
375 float Domination_GetWinnerTeam()
377 float winner_team = 0;
378 if(redowned == total_controlpoints)
379 winner_team = NUM_TEAM_1;
380 if(blueowned == total_controlpoints)
382 if(winner_team) return 0;
383 winner_team = NUM_TEAM_2;
385 if(yellowowned == total_controlpoints)
387 if(winner_team) return 0;
388 winner_team = NUM_TEAM_3;
390 if(pinkowned == total_controlpoints)
392 if(winner_team) return 0;
393 winner_team = NUM_TEAM_4;
397 return -1; // no control points left?
400 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
401 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
402 float Domination_CheckWinner()
404 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
406 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
407 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
408 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
412 Domination_count_controlpoints();
414 float winner_team = Domination_GetWinnerTeam();
416 if(winner_team == -1)
421 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
422 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
423 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
425 else if(winner_team == -1)
427 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
428 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
431 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
436 float Domination_CheckPlayers()
441 void Domination_RoundStart()
443 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(it.player_blocked = false));
446 //go to best items, or control points you don't own
447 void havocbot_role_dom(entity this)
452 if (this.bot_strategytime < time)
454 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
455 navigation_goalrating_start(this);
456 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
457 havocbot_goalrating_items(this, 8000, this.origin, 8000);
458 //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
459 //havocbot_goalrating_waypoints(1, this.origin, 1000);
460 navigation_goalrating_end(this);
464 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
467 M_ARGV(0, float) = domination_teams;
468 string ret_string = "dom_team";
470 entity head = find(NULL, classname, ret_string);
473 if(head.netname != "")
477 case NUM_TEAM_1: c1 = 0; break;
478 case NUM_TEAM_2: c2 = 0; break;
479 case NUM_TEAM_3: c3 = 0; break;
480 case NUM_TEAM_4: c4 = 0; break;
484 head = find(head, classname, ret_string);
487 M_ARGV(1, string) = string_null;
492 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
494 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
495 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
496 PutClientInServer(it);
497 if(domination_roundbased)
498 it.player_blocked = 1;
499 if(IS_REAL_CLIENT(it))
505 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
507 entity player = M_ARGV(0, entity);
509 if(domination_roundbased)
510 if(!round_handler_IsRoundStarted())
511 player.player_blocked = 1;
513 player.player_blocked = 0;
516 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
518 entity player = M_ARGV(0, entity);
520 set_dom_state(player);
523 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
525 entity bot = M_ARGV(0, entity);
527 bot.havocbot_role = havocbot_role_dom;
531 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
532 Control point for Domination gameplay.
534 spawnfunc(dom_controlpoint)
541 setthink(this, dom_controlpoint_setup);
542 this.nextthink = time + 0.1;
543 this.reset = dom_controlpoint_setup;
548 this.effects = this.effects | EF_LOWPRECISION;
549 if (autocvar_g_domination_point_fullbright)
550 this.effects |= EF_FULLBRIGHT;
553 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
554 Team declaration for Domination gameplay, this allows you to decide what team
555 names and control point models are used in your map.
557 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
558 can have netname set! The nameless team owns all control points at start.
562 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
564 Scoreboard color of the team (for example 4 is red and 13 is blue)
566 Model to use for control points owned by this team (for example
567 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
570 Skin of the model to use (for team skins on a single model)
572 Sound to play when this team captures a point.
573 (this is a localized sound, like a small alarm or other effect)
575 Narrator speech to play when this team captures a point.
576 (this is a global sound, like "Red team has captured a control point")
581 if(!g_domination || autocvar_g_domination_teams_override >= 2)
586 precache_model(this.model);
587 if (this.noise != "")
588 precache_sound(this.noise);
589 if (this.noise1 != "")
590 precache_sound(this.noise1);
591 this.classname = "dom_team";
592 _setmodel(this, this.model); // precision not needed
593 this.mdl = this.model;
594 this.dmg = this.modelindex;
597 // this would have to be changed if used in quakeworld
599 this.team = this.cnt + 1; // WHY are these different anyway?
603 void ScoreRules_dom(float teams)
605 if(domination_roundbased)
607 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
608 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
609 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
610 ScoreRules_basics_end();
614 float sp_domticks, sp_score;
615 sp_score = sp_domticks = 0;
616 if(autocvar_g_domination_disable_frags)
617 sp_domticks = SFL_SORT_PRIO_PRIMARY;
619 sp_score = SFL_SORT_PRIO_PRIMARY;
620 ScoreRules_basics(teams, sp_score, sp_score, true);
621 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
622 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
623 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
624 ScoreRules_basics_end();
628 // code from here on is just to support maps that don't have control point and team entities
629 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
632 entity e = new_pure(dom_team);
633 e.netname = strzone(teamname);
635 e.model = pointmodel;
637 e.noise = strzone(Sound_fixpath(capsound));
638 e.noise1 = strzone(capnarration);
639 e.message = strzone(capmessage);
641 // this code is identical to spawnfunc_dom_team
642 _setmodel(e, e.model); // precision not needed
644 e.dmg = e.modelindex;
647 // this would have to be changed if used in quakeworld
653 void dom_spawnpoint(vector org)
656 e.classname = "dom_controlpoint";
657 setthink(e, spawnfunc_dom_controlpoint);
660 spawnfunc_dom_controlpoint(e);
663 // spawn some default teams if the map is not set up for domination
664 void dom_spawnteams(int teams)
667 dom_spawnteam(Team_ColoredFullName(NUM_TEAM_1), NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND_DOM_CLAIM, "", "Red team has captured a control point");
668 dom_spawnteam(Team_ColoredFullName(NUM_TEAM_2), NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND_DOM_CLAIM, "", "Blue team has captured a control point");
670 dom_spawnteam(Team_ColoredFullName(NUM_TEAM_3), NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND_DOM_CLAIM, "", "Yellow team has captured a control point");
672 dom_spawnteam(Team_ColoredFullName(NUM_TEAM_4), NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND_DOM_CLAIM, "", "Pink team has captured a control point");
673 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
676 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
678 // if no teams are found, spawn defaults
679 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
681 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.\n");
682 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
683 dom_spawnteams(domination_teams);
686 CheckAllowedTeams(NULL);
687 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
689 domination_roundbased = autocvar_g_domination_roundbased;
691 ScoreRules_dom(domination_teams);
693 if(domination_roundbased)
695 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
696 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
700 void dom_Initialize()
703 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);