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 != world) ? (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 = world;
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, world, 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 = world; (head = find(head, classname, "dom_controlpoint")) != world; )
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(world, 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(world, 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(world, classname, "dom_team");
312 while(head && head.netname != "")
313 head = find(head, classname, "dom_team");
315 objerror("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 = world; (e = find(e, classname, "dom_controlpoint")) != world; )
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, world, MSG_CENTER, CENTER_ROUND_OVER);
409 Send_Notification(NOTIF_ALL, world, 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, world, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
424 Send_Notification(NOTIF_ALL, world, 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, world, MSG_CENTER, CENTER_ROUND_TIED);
430 Send_Notification(NOTIF_ALL, world, 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(world, 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;
519 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
521 entity player = M_ARGV(0, entity);
523 set_dom_state(player);
526 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
528 entity bot = M_ARGV(0, entity);
530 bot.havocbot_role = havocbot_role_dom;
534 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
535 Control point for Domination gameplay.
537 spawnfunc(dom_controlpoint)
544 setthink(this, dom_controlpoint_setup);
545 this.nextthink = time + 0.1;
546 this.reset = dom_controlpoint_setup;
551 this.effects = this.effects | EF_LOWPRECISION;
552 if (autocvar_g_domination_point_fullbright)
553 this.effects |= EF_FULLBRIGHT;
556 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
557 Team declaration for Domination gameplay, this allows you to decide what team
558 names and control point models are used in your map.
560 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
561 can have netname set! The nameless team owns all control points at start.
565 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
567 Scoreboard color of the team (for example 4 is red and 13 is blue)
569 Model to use for control points owned by this team (for example
570 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
573 Skin of the model to use (for team skins on a single model)
575 Sound to play when this team captures a point.
576 (this is a localized sound, like a small alarm or other effect)
578 Narrator speech to play when this team captures a point.
579 (this is a global sound, like "Red team has captured a control point")
584 if(!g_domination || autocvar_g_domination_teams_override >= 2)
589 precache_model(this.model);
590 if (this.noise != "")
591 precache_sound(this.noise);
592 if (this.noise1 != "")
593 precache_sound(this.noise1);
594 this.classname = "dom_team";
595 _setmodel(this, this.model); // precision not needed
596 this.mdl = this.model;
597 this.dmg = this.modelindex;
600 // this would have to be changed if used in quakeworld
602 this.team = this.cnt + 1; // WHY are these different anyway?
606 void ScoreRules_dom(float teams)
608 if(domination_roundbased)
610 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
611 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
612 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
613 ScoreRules_basics_end();
617 float sp_domticks, sp_score;
618 sp_score = sp_domticks = 0;
619 if(autocvar_g_domination_disable_frags)
620 sp_domticks = SFL_SORT_PRIO_PRIMARY;
622 sp_score = SFL_SORT_PRIO_PRIMARY;
623 ScoreRules_basics(teams, sp_score, sp_score, true);
624 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
625 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
626 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
627 ScoreRules_basics_end();
631 // code from here on is just to support maps that don't have control point and team entities
632 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
635 entity e = new_pure(dom_team);
636 e.netname = strzone(teamname);
638 e.model = pointmodel;
640 e.noise = strzone(Sound_fixpath(capsound));
641 e.noise1 = strzone(capnarration);
642 e.message = strzone(capmessage);
644 // this code is identical to spawnfunc_dom_team
645 _setmodel(e, e.model); // precision not needed
647 e.dmg = e.modelindex;
650 // this would have to be changed if used in quakeworld
656 void dom_spawnpoint(vector org)
659 e.classname = "dom_controlpoint";
660 setthink(e, spawnfunc_dom_controlpoint);
663 spawnfunc_dom_controlpoint(e);
666 // spawn some default teams if the map is not set up for domination
667 void dom_spawnteams(int teams)
670 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");
671 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");
673 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");
675 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");
676 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
679 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
681 // if no teams are found, spawn defaults
682 if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
684 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.\n");
685 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
686 dom_spawnteams(domination_teams);
689 CheckAllowedTeams(world);
690 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
692 domination_roundbased = autocvar_g_domination_roundbased;
694 ScoreRules_dom(domination_teams);
696 if(domination_roundbased)
698 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
699 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
703 void dom_Initialize()
706 InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);