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 ST_DOM_CAPS = 1;
42 // pps: points per second
43 .float dom_total_pps = _STAT(DOM_TOTAL_PPS);
44 .float dom_pps_red = _STAT(DOM_PPS_RED);
45 .float dom_pps_blue = _STAT(DOM_PPS_BLUE);
46 .float dom_pps_yellow = _STAT(DOM_PPS_YELLOW);
47 .float dom_pps_pink = _STAT(DOM_PPS_PINK);
54 // capture declarations
55 .float enemy_playerid;
60 float domination_roundbased;
61 float domination_teams;
66 #include <server/teamplay.qh>
70 int autocvar_g_domination_default_teams;
71 bool autocvar_g_domination_disable_frags;
72 int autocvar_g_domination_point_amt;
73 bool autocvar_g_domination_point_fullbright;
74 float autocvar_g_domination_round_timelimit;
75 float autocvar_g_domination_warmup;
76 float autocvar_g_domination_point_rate;
77 int autocvar_g_domination_teams_override;
79 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
81 if(autocvar_sv_eventlog)
82 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
85 void set_dom_state(entity e)
87 e.dom_total_pps = total_pps;
88 e.dom_pps_red = pps_red;
89 e.dom_pps_blue = pps_blue;
90 if(domination_teams >= 3)
91 e.dom_pps_yellow = pps_yellow;
92 if(domination_teams >= 4)
93 e.dom_pps_pink = pps_pink;
96 void dompoint_captured(entity this)
98 float old_delay, old_team, real_team;
100 // now that the delay has expired, switch to the latest team to lay claim to this point
101 entity head = this.owner;
103 real_team = this.cnt;
106 dom_EventLog("taken", this.team, this.dmg_inflictor);
107 this.dmg_inflictor = NULL;
109 this.goalentity = head;
110 this.model = head.mdl;
111 this.modelindex = head.dmg;
112 this.skin = head.skin;
114 float points, wait_time;
115 if (autocvar_g_domination_point_amt)
116 points = autocvar_g_domination_point_amt;
119 if (autocvar_g_domination_point_rate)
120 wait_time = autocvar_g_domination_point_rate;
122 wait_time = this.wait;
124 if(domination_roundbased)
125 bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
127 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
129 if(this.enemy.playerid == this.enemy_playerid)
130 PlayerScore_Add(this.enemy, SP_DOM_TAKES, 1);
134 if (head.noise != "")
136 _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
138 _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
139 if (head.noise1 != "")
140 play2all(head.noise1);
142 this.delay = time + wait_time;
145 old_delay = this.delay;
146 old_team = this.team;
147 this.team = real_team;
149 SUB_UseTargets (this, this, NULL);
150 this.delay = old_delay;
151 this.team = old_team;
153 entity msg = WP_DomNeut;
156 case NUM_TEAM_1: msg = WP_DomRed; break;
157 case NUM_TEAM_2: msg = WP_DomBlue; break;
158 case NUM_TEAM_3: msg = WP_DomYellow; break;
159 case NUM_TEAM_4: msg = WP_DomPink; break;
162 WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
164 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
165 for(head = NULL; (head = find(head, classname, "dom_controlpoint")) != NULL; )
166 FOREACH_ENTITY_CLASS("dom_controlpoint", true, LAMBDA(
167 if (autocvar_g_domination_point_amt)
168 points = autocvar_g_domination_point_amt;
171 if (autocvar_g_domination_point_rate)
172 wait_time = autocvar_g_domination_point_rate;
175 switch(it.goalentity.team)
177 case NUM_TEAM_1: pps_red += points/wait_time; break;
178 case NUM_TEAM_2: pps_blue += points/wait_time; break;
179 case NUM_TEAM_3: pps_yellow += points/wait_time; break;
180 case NUM_TEAM_4: pps_pink += points/wait_time; break;
182 total_pps += points/wait_time;
185 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
186 WaypointSprite_Ping(this.sprite);
190 FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(set_dom_state(it)));
193 void AnimateDomPoint(entity this)
195 if(this.pain_finished > time)
197 this.pain_finished = time + this.t_width;
198 if(this.nextthink > this.pain_finished)
199 this.nextthink = this.pain_finished;
201 this.frame = this.frame + 1;
202 if(this.frame > this.t_length)
206 void dompointthink(entity this)
210 this.nextthink = time + 0.1;
212 //this.frame = this.frame + 1;
213 //if(this.frame > 119)
215 AnimateDomPoint(this);
219 if (gameover || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
222 if(autocvar_g_domination_point_rate)
223 this.delay = time + autocvar_g_domination_point_rate;
225 this.delay = time + this.wait;
227 // give credit to the team
228 // NOTE: this defaults to 0
229 if (!domination_roundbased)
230 if (this.goalentity.netname != "")
232 if(autocvar_g_domination_point_amt)
233 fragamt = autocvar_g_domination_point_amt;
235 fragamt = this.frags;
236 TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
237 TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
239 // give credit to the individual player, if he is still there
240 if (this.enemy.playerid == this.enemy_playerid)
242 PlayerScore_Add(this.enemy, SP_SCORE, fragamt);
243 PlayerScore_Add(this.enemy, SP_DOM_TICKS, fragamt);
250 void dompointtouch(entity this)
252 if (!IS_PLAYER(other))
254 if (other.health < 1)
257 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
260 if(time < this.captime + 0.3)
263 // only valid teams can claim it
264 entity head = find(NULL, classname, "dom_team");
265 while (head && head.team != other.team)
266 head = find(head, classname, "dom_team");
267 if (!head || head.netname == "" || head == this.goalentity)
272 this.team = this.goalentity.team; // this stores the PREVIOUS team!
274 this.cnt = other.team;
275 this.owner = head; // team to switch to after the delay
276 this.dmg_inflictor = other;
279 // this.delay = time + cvar("g_domination_point_capturetime");
280 //this.nextthink = time + cvar("g_domination_point_capturetime");
281 //this.think = dompoint_captured;
283 // go to neutral team in the mean time
284 head = find(NULL, classname, "dom_team");
285 while (head && head.netname != "")
286 head = find(head, classname, "dom_team");
290 WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
291 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
292 WaypointSprite_Ping(this.sprite);
294 this.goalentity = head;
295 this.model = head.mdl;
296 this.modelindex = head.dmg;
297 this.skin = head.skin;
299 this.enemy = other; // individual player scoring
300 this.enemy_playerid = other.playerid;
301 dompoint_captured(this);
304 void dom_controlpoint_setup(entity this)
307 // find the spawnfunc_dom_team representing unclaimed points
308 head = find(NULL, classname, "dom_team");
309 while(head && head.netname != "")
310 head = find(head, classname, "dom_team");
312 objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
314 // copy important properties from spawnfunc_dom_team entity
315 this.goalentity = head;
316 _setmodel(this, head.mdl); // precision already set
317 this.skin = head.skin;
321 if(this.message == "")
322 this.message = " has captured a control point";
329 float points, waittime;
330 if (autocvar_g_domination_point_amt)
331 points = autocvar_g_domination_point_amt;
334 if (autocvar_g_domination_point_rate)
335 waittime = autocvar_g_domination_point_rate;
337 waittime = this.wait;
339 total_pps += points/waittime;
342 this.t_width = 0.02; // frame animation rate
344 this.t_length = 239; // maximum frame
346 setthink(this, dompointthink);
347 this.nextthink = time;
348 settouch(this, dompointtouch);
349 this.solid = SOLID_TRIGGER;
350 this.flags = FL_ITEM;
351 setsize(this, '-32 -32 -32', '32 32 32');
352 setorigin(this, this.origin + '0 0 20');
355 waypoint_spawnforitem(this);
356 WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
359 float total_controlpoints;
360 void Domination_count_controlpoints()
363 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
364 for(e = NULL; (e = find(e, classname, "dom_controlpoint")) != NULL; )
366 ++total_controlpoints;
367 redowned += (e.goalentity.team == NUM_TEAM_1);
368 blueowned += (e.goalentity.team == NUM_TEAM_2);
369 yellowowned += (e.goalentity.team == NUM_TEAM_3);
370 pinkowned += (e.goalentity.team == NUM_TEAM_4);
374 float Domination_GetWinnerTeam()
376 float winner_team = 0;
377 if(redowned == total_controlpoints)
378 winner_team = NUM_TEAM_1;
379 if(blueowned == total_controlpoints)
381 if(winner_team) return 0;
382 winner_team = NUM_TEAM_2;
384 if(yellowowned == total_controlpoints)
386 if(winner_team) return 0;
387 winner_team = NUM_TEAM_3;
389 if(pinkowned == total_controlpoints)
391 if(winner_team) return 0;
392 winner_team = NUM_TEAM_4;
396 return -1; // no control points left?
399 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
400 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
401 float Domination_CheckWinner()
403 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
405 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
406 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
407 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
411 Domination_count_controlpoints();
413 float winner_team = Domination_GetWinnerTeam();
415 if(winner_team == -1)
420 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
421 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
422 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
424 else if(winner_team == -1)
426 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
427 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
430 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
435 float Domination_CheckPlayers()
440 void Domination_RoundStart()
442 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(it.player_blocked = false));
445 //go to best items, or control points you don't own
446 void havocbot_role_dom(entity this)
451 if (this.bot_strategytime < time)
453 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
454 navigation_goalrating_start(this);
455 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
456 havocbot_goalrating_items(this, 8000, this.origin, 8000);
457 //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
458 //havocbot_goalrating_waypoints(1, this.origin, 1000);
459 navigation_goalrating_end(this);
463 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
466 M_ARGV(0, float) = domination_teams;
467 string ret_string = "dom_team";
469 entity head = find(NULL, classname, ret_string);
472 if(head.netname != "")
476 case NUM_TEAM_1: c1 = 0; break;
477 case NUM_TEAM_2: c2 = 0; break;
478 case NUM_TEAM_3: c3 = 0; break;
479 case NUM_TEAM_4: c4 = 0; break;
483 head = find(head, classname, ret_string);
486 M_ARGV(1, string) = string_null;
491 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
493 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
494 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
495 WITHSELF(it, PutClientInServer());
496 if(domination_roundbased)
497 it.player_blocked = 1;
498 if(IS_REAL_CLIENT(it))
504 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
506 entity player = M_ARGV(0, entity);
508 if(domination_roundbased)
509 if(!round_handler_IsRoundStarted())
510 player.player_blocked = 1;
512 player.player_blocked = 0;
515 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
517 entity player = M_ARGV(0, entity);
519 set_dom_state(player);
522 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
524 entity bot = M_ARGV(0, entity);
526 bot.havocbot_role = havocbot_role_dom;
530 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
531 Control point for Domination gameplay.
533 spawnfunc(dom_controlpoint)
540 setthink(this, dom_controlpoint_setup);
541 this.nextthink = time + 0.1;
542 this.reset = dom_controlpoint_setup;
547 this.effects = this.effects | EF_LOWPRECISION;
548 if (autocvar_g_domination_point_fullbright)
549 this.effects |= EF_FULLBRIGHT;
552 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
553 Team declaration for Domination gameplay, this allows you to decide what team
554 names and control point models are used in your map.
556 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
557 can have netname set! The nameless team owns all control points at start.
561 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
563 Scoreboard color of the team (for example 4 is red and 13 is blue)
565 Model to use for control points owned by this team (for example
566 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
569 Skin of the model to use (for team skins on a single model)
571 Sound to play when this team captures a point.
572 (this is a localized sound, like a small alarm or other effect)
574 Narrator speech to play when this team captures a point.
575 (this is a global sound, like "Red team has captured a control point")
580 if(!g_domination || autocvar_g_domination_teams_override >= 2)
585 precache_model(this.model);
586 if (this.noise != "")
587 precache_sound(this.noise);
588 if (this.noise1 != "")
589 precache_sound(this.noise1);
590 this.classname = "dom_team";
591 _setmodel(this, this.model); // precision not needed
592 this.mdl = this.model;
593 this.dmg = this.modelindex;
596 // this would have to be changed if used in quakeworld
598 this.team = this.cnt + 1; // WHY are these different anyway?
602 void ScoreRules_dom(float teams)
604 if(domination_roundbased)
606 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
607 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
608 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
609 ScoreRules_basics_end();
613 float sp_domticks, sp_score;
614 sp_score = sp_domticks = 0;
615 if(autocvar_g_domination_disable_frags)
616 sp_domticks = SFL_SORT_PRIO_PRIMARY;
618 sp_score = SFL_SORT_PRIO_PRIMARY;
619 ScoreRules_basics(teams, sp_score, sp_score, true);
620 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
621 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
622 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
623 ScoreRules_basics_end();
627 // code from here on is just to support maps that don't have control point and team entities
628 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
631 entity e = new_pure(dom_team);
632 e.netname = strzone(teamname);
634 e.model = pointmodel;
636 e.noise = strzone(Sound_fixpath(capsound));
637 e.noise1 = strzone(capnarration);
638 e.message = strzone(capmessage);
640 // this code is identical to spawnfunc_dom_team
641 _setmodel(e, e.model); // precision not needed
643 e.dmg = e.modelindex;
646 // this would have to be changed if used in quakeworld
652 void dom_spawnpoint(vector org)
655 e.classname = "dom_controlpoint";
656 setthink(e, spawnfunc_dom_controlpoint);
659 spawnfunc_dom_controlpoint(e);
662 // spawn some default teams if the map is not set up for domination
663 void dom_spawnteams(int teams)
666 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");
667 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");
669 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");
671 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");
672 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
675 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
677 // if no teams are found, spawn defaults
678 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
680 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.\n");
681 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
682 dom_spawnteams(domination_teams);
685 CheckAllowedTeams(NULL);
686 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
688 domination_roundbased = autocvar_g_domination_roundbased;
690 ScoreRules_dom(domination_teams);
692 if(domination_roundbased)
694 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
695 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
699 void dom_Initialize()
702 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);