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 for(head = NULL; (head = find(head, classname, "dom_controlpoint")) != NULL; )
169 FOREACH_ENTITY_CLASS("dom_controlpoint", true, LAMBDA(
170 if (autocvar_g_domination_point_amt)
171 points = autocvar_g_domination_point_amt;
174 if (autocvar_g_domination_point_rate)
175 wait_time = autocvar_g_domination_point_rate;
178 switch(it.goalentity.team)
180 case NUM_TEAM_1: pps_red += points/wait_time; break;
181 case NUM_TEAM_2: pps_blue += points/wait_time; break;
182 case NUM_TEAM_3: pps_yellow += points/wait_time; break;
183 case NUM_TEAM_4: pps_pink += points/wait_time; break;
185 total_pps += points/wait_time;
188 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
189 WaypointSprite_Ping(this.sprite);
193 FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(set_dom_state(it)));
196 void AnimateDomPoint(entity this)
198 if(this.pain_finished > time)
200 this.pain_finished = time + this.t_width;
201 if(this.nextthink > this.pain_finished)
202 this.nextthink = this.pain_finished;
204 this.frame = this.frame + 1;
205 if(this.frame > this.t_length)
209 void dompointthink(entity this)
213 this.nextthink = time + 0.1;
215 //this.frame = this.frame + 1;
216 //if(this.frame > 119)
218 AnimateDomPoint(this);
222 if (gameover || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
225 if(autocvar_g_domination_point_rate)
226 this.delay = time + autocvar_g_domination_point_rate;
228 this.delay = time + this.wait;
230 // give credit to the team
231 // NOTE: this defaults to 0
232 if (!domination_roundbased)
233 if (this.goalentity.netname != "")
235 if(autocvar_g_domination_point_amt)
236 fragamt = autocvar_g_domination_point_amt;
238 fragamt = this.frags;
239 TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
240 TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
242 // give credit to the individual player, if he is still there
243 if (this.enemy.playerid == this.enemy_playerid)
245 PlayerScore_Add(this.enemy, SP_SCORE, fragamt);
246 PlayerScore_Add(this.enemy, SP_DOM_TICKS, fragamt);
253 void dompointtouch(entity this)
255 if (!IS_PLAYER(other))
257 if (other.health < 1)
260 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
263 if(time < this.captime + 0.3)
266 // only valid teams can claim it
267 entity head = find(NULL, classname, "dom_team");
268 while (head && head.team != other.team)
269 head = find(head, classname, "dom_team");
270 if (!head || head.netname == "" || head == this.goalentity)
275 this.team = this.goalentity.team; // this stores the PREVIOUS team!
277 this.cnt = other.team;
278 this.owner = head; // team to switch to after the delay
279 this.dmg_inflictor = other;
282 // this.delay = time + cvar("g_domination_point_capturetime");
283 //this.nextthink = time + cvar("g_domination_point_capturetime");
284 //this.think = dompoint_captured;
286 // go to neutral team in the mean time
287 head = find(NULL, classname, "dom_team");
288 while (head && head.netname != "")
289 head = find(head, classname, "dom_team");
293 WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
294 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
295 WaypointSprite_Ping(this.sprite);
297 this.goalentity = head;
298 this.model = head.mdl;
299 this.modelindex = head.dmg;
300 this.skin = head.skin;
302 this.enemy = other; // individual player scoring
303 this.enemy_playerid = other.playerid;
304 dompoint_captured(this);
307 void dom_controlpoint_setup(entity this)
310 // find the spawnfunc_dom_team representing unclaimed points
311 head = find(NULL, classname, "dom_team");
312 while(head && head.netname != "")
313 head = find(head, classname, "dom_team");
315 objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
317 // copy important properties from spawnfunc_dom_team entity
318 this.goalentity = head;
319 _setmodel(this, head.mdl); // precision already set
320 this.skin = head.skin;
324 if(this.message == "")
325 this.message = " has captured a control point";
332 float points, waittime;
333 if (autocvar_g_domination_point_amt)
334 points = autocvar_g_domination_point_amt;
337 if (autocvar_g_domination_point_rate)
338 waittime = autocvar_g_domination_point_rate;
340 waittime = this.wait;
342 total_pps += points/waittime;
345 this.t_width = 0.02; // frame animation rate
347 this.t_length = 239; // maximum frame
349 setthink(this, dompointthink);
350 this.nextthink = time;
351 settouch(this, dompointtouch);
352 this.solid = SOLID_TRIGGER;
353 this.flags = FL_ITEM;
354 setsize(this, '-32 -32 -32', '32 32 32');
355 setorigin(this, this.origin + '0 0 20');
358 waypoint_spawnforitem(this);
359 WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
362 float total_controlpoints;
363 void Domination_count_controlpoints()
366 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
367 for(e = NULL; (e = find(e, classname, "dom_controlpoint")) != NULL; )
369 ++total_controlpoints;
370 redowned += (e.goalentity.team == NUM_TEAM_1);
371 blueowned += (e.goalentity.team == NUM_TEAM_2);
372 yellowowned += (e.goalentity.team == NUM_TEAM_3);
373 pinkowned += (e.goalentity.team == NUM_TEAM_4);
377 float Domination_GetWinnerTeam()
379 float winner_team = 0;
380 if(redowned == total_controlpoints)
381 winner_team = NUM_TEAM_1;
382 if(blueowned == total_controlpoints)
384 if(winner_team) return 0;
385 winner_team = NUM_TEAM_2;
387 if(yellowowned == total_controlpoints)
389 if(winner_team) return 0;
390 winner_team = NUM_TEAM_3;
392 if(pinkowned == total_controlpoints)
394 if(winner_team) return 0;
395 winner_team = NUM_TEAM_4;
399 return -1; // no control points left?
402 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
403 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
404 float Domination_CheckWinner()
406 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
408 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
409 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
410 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
414 Domination_count_controlpoints();
416 float winner_team = Domination_GetWinnerTeam();
418 if(winner_team == -1)
423 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
424 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
425 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
427 else if(winner_team == -1)
429 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
430 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
433 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
438 float Domination_CheckPlayers()
443 void Domination_RoundStart()
445 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(it.player_blocked = false));
448 //go to best items, or control points you don't own
449 void havocbot_role_dom(entity this)
454 if (this.bot_strategytime < time)
456 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
457 navigation_goalrating_start(this);
458 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
459 havocbot_goalrating_items(this, 8000, this.origin, 8000);
460 //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
461 //havocbot_goalrating_waypoints(1, this.origin, 1000);
462 navigation_goalrating_end(this);
466 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
469 M_ARGV(0, float) = domination_teams;
470 string ret_string = "dom_team";
472 entity head = find(NULL, classname, ret_string);
475 if(head.netname != "")
479 case NUM_TEAM_1: c1 = 0; break;
480 case NUM_TEAM_2: c2 = 0; break;
481 case NUM_TEAM_3: c3 = 0; break;
482 case NUM_TEAM_4: c4 = 0; break;
486 head = find(head, classname, ret_string);
489 M_ARGV(1, string) = string_null;
494 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
496 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
497 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
498 WITHSELF(it, PutClientInServer());
499 if(domination_roundbased)
500 it.player_blocked = 1;
501 if(IS_REAL_CLIENT(it))
507 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
509 entity player = M_ARGV(0, entity);
511 if(domination_roundbased)
512 if(!round_handler_IsRoundStarted())
513 player.player_blocked = 1;
515 player.player_blocked = 0;
518 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
520 entity player = M_ARGV(0, entity);
522 set_dom_state(player);
525 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
527 entity bot = M_ARGV(0, entity);
529 bot.havocbot_role = havocbot_role_dom;
533 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
534 Control point for Domination gameplay.
536 spawnfunc(dom_controlpoint)
543 setthink(this, dom_controlpoint_setup);
544 this.nextthink = time + 0.1;
545 this.reset = dom_controlpoint_setup;
550 this.effects = this.effects | EF_LOWPRECISION;
551 if (autocvar_g_domination_point_fullbright)
552 this.effects |= EF_FULLBRIGHT;
555 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
556 Team declaration for Domination gameplay, this allows you to decide what team
557 names and control point models are used in your map.
559 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
560 can have netname set! The nameless team owns all control points at start.
564 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
566 Scoreboard color of the team (for example 4 is red and 13 is blue)
568 Model to use for control points owned by this team (for example
569 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
572 Skin of the model to use (for team skins on a single model)
574 Sound to play when this team captures a point.
575 (this is a localized sound, like a small alarm or other effect)
577 Narrator speech to play when this team captures a point.
578 (this is a global sound, like "Red team has captured a control point")
583 if(!g_domination || autocvar_g_domination_teams_override >= 2)
588 precache_model(this.model);
589 if (this.noise != "")
590 precache_sound(this.noise);
591 if (this.noise1 != "")
592 precache_sound(this.noise1);
593 this.classname = "dom_team";
594 _setmodel(this, this.model); // precision not needed
595 this.mdl = this.model;
596 this.dmg = this.modelindex;
599 // this would have to be changed if used in quakeworld
601 this.team = this.cnt + 1; // WHY are these different anyway?
605 void ScoreRules_dom(float teams)
607 if(domination_roundbased)
609 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
610 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
611 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
612 ScoreRules_basics_end();
616 float sp_domticks, sp_score;
617 sp_score = sp_domticks = 0;
618 if(autocvar_g_domination_disable_frags)
619 sp_domticks = SFL_SORT_PRIO_PRIMARY;
621 sp_score = SFL_SORT_PRIO_PRIMARY;
622 ScoreRules_basics(teams, sp_score, sp_score, true);
623 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
624 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
625 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
626 ScoreRules_basics_end();
630 // code from here on is just to support maps that don't have control point and team entities
631 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
634 entity e = new_pure(dom_team);
635 e.netname = strzone(teamname);
637 e.model = pointmodel;
639 e.noise = strzone(Sound_fixpath(capsound));
640 e.noise1 = strzone(capnarration);
641 e.message = strzone(capmessage);
643 // this code is identical to spawnfunc_dom_team
644 _setmodel(e, e.model); // precision not needed
646 e.dmg = e.modelindex;
649 // this would have to be changed if used in quakeworld
655 void dom_spawnpoint(vector org)
658 e.classname = "dom_controlpoint";
659 setthink(e, spawnfunc_dom_controlpoint);
662 spawnfunc_dom_controlpoint(e);
665 // spawn some default teams if the map is not set up for domination
666 void dom_spawnteams(int teams)
669 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");
670 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");
672 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");
674 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");
675 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
678 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
680 // if no teams are found, spawn defaults
681 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
683 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.\n");
684 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
685 dom_spawnteams(domination_teams);
688 CheckAllowedTeams(NULL);
689 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
691 domination_roundbased = autocvar_g_domination_roundbased;
693 ScoreRules_dom(domination_teams);
695 if(domination_roundbased)
697 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
698 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
702 void dom_Initialize()
705 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);