1 #include "sv_domination.qh"
3 #include <server/client.qh>
4 #include <server/command/vote.qh>
5 #include <server/g_damage.qh>
6 #include <server/gamelog.qh>
7 #include <server/items/items.qh>
8 #include <server/teamplay.qh>
9 #include <common/mapobjects/platforms.qh>
10 #include <common/mapobjects/triggers.qh>
14 int autocvar_g_domination_default_teams;
15 bool autocvar_g_domination_disable_frags;
16 int autocvar_g_domination_point_amt;
17 bool autocvar_g_domination_point_fullbright;
18 float autocvar_g_domination_round_timelimit;
19 float autocvar_g_domination_warmup;
20 float autocvar_g_domination_point_rate;
21 int autocvar_g_domination_teams_override;
23 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
25 if(autocvar_sv_eventlog)
26 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
29 void set_dom_state(entity e)
31 STAT(DOM_TOTAL_PPS, e) = total_pps;
32 STAT(DOM_PPS_RED, e) = pps_red;
33 STAT(DOM_PPS_BLUE, e) = pps_blue;
34 if(domination_teams >= 3)
35 STAT(DOM_PPS_YELLOW, e) = pps_yellow;
36 if(domination_teams >= 4)
37 STAT(DOM_PPS_PINK, e) = pps_pink;
40 void dompoint_captured(entity this)
42 float old_delay, old_team, real_team;
44 // now that the delay has expired, switch to the latest team to lay claim to this point
45 entity head = this.owner;
50 dom_EventLog("taken", this.team, this.dmg_inflictor);
51 this.dmg_inflictor = NULL;
53 this.goalentity = head;
54 this.model = head.mdl;
55 this.modelindex = head.dmg;
56 this.skin = head.skin;
58 float points, wait_time;
59 if (autocvar_g_domination_point_amt)
60 points = autocvar_g_domination_point_amt;
63 if (autocvar_g_domination_point_rate)
64 wait_time = autocvar_g_domination_point_rate;
66 wait_time = this.wait;
68 if(domination_roundbased)
69 bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
71 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
73 if(this.enemy.playerid == this.enemy_playerid)
74 GameRules_scoring_add(this.enemy, DOM_TAKES, 1);
81 _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
83 _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
85 if (head.noise1 != "")
86 play2all(head.noise1);
88 this.delay = time + wait_time;
91 old_delay = this.delay;
93 this.team = real_team;
95 SUB_UseTargets (this, this, NULL);
96 this.delay = old_delay;
99 entity msg = WP_DomNeut;
102 case NUM_TEAM_1: msg = WP_DomRed; break;
103 case NUM_TEAM_2: msg = WP_DomBlue; break;
104 case NUM_TEAM_3: msg = WP_DomYellow; break;
105 case NUM_TEAM_4: msg = WP_DomPink; break;
108 WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
110 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
111 IL_EACH(g_dompoints, true,
113 if (autocvar_g_domination_point_amt)
114 points = autocvar_g_domination_point_amt;
117 if (autocvar_g_domination_point_rate)
118 wait_time = autocvar_g_domination_point_rate;
121 switch(it.goalentity.team)
123 case NUM_TEAM_1: pps_red += points/wait_time; break;
124 case NUM_TEAM_2: pps_blue += points/wait_time; break;
125 case NUM_TEAM_3: pps_yellow += points/wait_time; break;
126 case NUM_TEAM_4: pps_pink += points/wait_time; break;
128 total_pps += points/wait_time;
131 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
132 WaypointSprite_Ping(this.sprite);
136 FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
139 void AnimateDomPoint(entity this)
141 if(this.pain_finished > time)
143 this.pain_finished = time + this.t_width;
144 if(this.nextthink > this.pain_finished)
145 this.nextthink = this.pain_finished;
147 this.frame = this.frame + 1;
148 if(this.frame > this.t_length)
152 void dompointthink(entity this)
156 this.nextthink = time + 0.1;
158 //this.frame = this.frame + 1;
159 //if(this.frame > 119)
161 AnimateDomPoint(this);
165 if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
168 if(autocvar_g_domination_point_rate)
169 this.delay = time + autocvar_g_domination_point_rate;
171 this.delay = time + this.wait;
173 // give credit to the team
174 // NOTE: this defaults to 0
175 if (!domination_roundbased)
176 if (this.goalentity.netname != "")
178 if(autocvar_g_domination_point_amt)
179 fragamt = autocvar_g_domination_point_amt;
181 fragamt = this.frags;
182 TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
183 TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
185 // give credit to the individual player, if he is still there
186 if (this.enemy.playerid == this.enemy_playerid)
188 GameRules_scoring_add(this.enemy, SCORE, fragamt);
189 GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
196 void dompointtouch(entity this, entity toucher)
198 if(!IS_PLAYER(toucher))
200 if(GetResource(toucher, RES_HEALTH) < 1)
203 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
206 if(time < this.captime + 0.3)
209 // only valid teams can claim it
210 entity head = find(NULL, classname, "dom_team");
211 while (head && head.team != toucher.team)
212 head = find(head, classname, "dom_team");
213 if (!head || head.netname == "" || head == this.goalentity)
218 this.team = this.goalentity.team; // this stores the PREVIOUS team!
220 this.cnt = toucher.team;
221 this.owner = head; // team to switch to after the delay
222 this.dmg_inflictor = toucher;
225 // this.delay = time + cvar("g_domination_point_capturetime");
226 //this.nextthink = time + cvar("g_domination_point_capturetime");
227 //this.think = dompoint_captured;
229 // go to neutral team in the mean time
230 head = find(NULL, classname, "dom_team");
231 while (head && head.netname != "")
232 head = find(head, classname, "dom_team");
236 WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
237 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
238 WaypointSprite_Ping(this.sprite);
240 this.goalentity = head;
241 this.model = head.mdl;
242 this.modelindex = head.dmg;
243 this.skin = head.skin;
245 this.enemy = toucher; // individual player scoring
246 this.enemy_playerid = toucher.playerid;
247 dompoint_captured(this);
250 void dom_controlpoint_setup(entity this)
253 // find the spawnfunc_dom_team representing unclaimed points
254 head = find(NULL, classname, "dom_team");
255 while(head && head.netname != "")
256 head = find(head, classname, "dom_team");
258 objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
260 // copy important properties from spawnfunc_dom_team entity
261 this.goalentity = head;
262 _setmodel(this, head.mdl); // precision already set
263 this.skin = head.skin;
267 if(this.message == "")
268 this.message = " has captured a control point";
275 float points, waittime;
276 if (autocvar_g_domination_point_amt)
277 points = autocvar_g_domination_point_amt;
280 if (autocvar_g_domination_point_rate)
281 waittime = autocvar_g_domination_point_rate;
283 waittime = this.wait;
285 total_pps += points/waittime;
288 this.t_width = 0.02; // frame animation rate
290 this.t_length = 239; // maximum frame
292 setthink(this, dompointthink);
293 this.nextthink = time;
294 settouch(this, dompointtouch);
295 this.solid = SOLID_TRIGGER;
296 if(!this.flags & FL_ITEM)
297 IL_PUSH(g_items, this);
298 this.flags = FL_ITEM;
299 setsize(this, '-32 -32 -32', '32 32 32');
300 setorigin(this, this.origin + '0 0 20');
303 waypoint_spawnforitem(this);
304 WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
307 int total_control_points;
308 void Domination_count_controlpoints()
310 total_control_points = 0;
311 for (int i = 1; i <= NUM_TEAMS; ++i)
313 Team_SetNumberOfControlPoints(Team_GetTeamFromIndex(i), 0);
315 IL_EACH(g_dompoints, true,
317 ++total_control_points;
318 if (!Entity_HasValidTeam(it.goalentity))
322 entity team_ = Entity_GetTeam(it.goalentity);
323 int num_control_points = Team_GetNumberOfControlPoints(team_);
324 ++num_control_points;
325 Team_SetNumberOfControlPoints(team_, num_control_points);
329 int Domination_GetWinnerTeam()
332 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) ==
333 total_control_points)
335 winner_team = NUM_TEAM_1;
337 for (int i = 2; i <= NUM_TEAMS; ++i)
339 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) ==
340 total_control_points)
342 if (winner_team != 0)
346 winner_team = Team_IndexToTeam(i);
353 return -1; // no control points left?
356 bool Domination_CheckWinner()
358 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
360 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
361 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
364 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
368 Domination_count_controlpoints();
370 float winner_team = Domination_GetWinnerTeam();
372 if(winner_team == -1)
377 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
378 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
379 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
381 else if(winner_team == -1)
383 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
384 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
388 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
393 bool Domination_CheckPlayers()
398 void Domination_RoundStart()
400 FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
403 //go to best items, or control points you don't own
404 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
406 IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
408 if(it.cnt > -1) // this is just being fought
409 navigation_routerating(this, it, ratingscale, 5000);
410 else if(it.goalentity.cnt == 0) // unclaimed
411 navigation_routerating(this, it, ratingscale, 5000);
412 else if(it.goalentity.team != this.team) // other team's point
413 navigation_routerating(this, it, ratingscale, 5000);
417 void havocbot_role_dom(entity this)
422 if (navigation_goalrating_timeout(this))
424 navigation_goalrating_start(this);
425 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
426 havocbot_goalrating_items(this, 20000, this.origin, 8000);
427 //havocbot_goalrating_enemyplayers(this, 1500, this.origin, 2000);
428 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
429 navigation_goalrating_end(this);
431 navigation_goalrating_timeout_set(this);
435 MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
438 M_ARGV(0, float) = domination_teams;
439 string ret_string = "dom_team";
441 entity head = find(NULL, classname, ret_string);
444 if(head.netname != "")
446 if (Team_IsValidTeam(head.team))
448 M_ARGV(0, float) |= Team_TeamToBit(head.team);
452 head = find(head, classname, ret_string);
455 M_ARGV(1, string) = string_null;
460 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
462 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
463 FOREACH_CLIENT(IS_PLAYER(it), {
464 PutClientInServer(it);
465 if(domination_roundbased)
466 it.player_blocked = 1;
467 if(IS_REAL_CLIENT(it))
473 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
475 entity player = M_ARGV(0, entity);
477 if(domination_roundbased)
478 player.player_blocked = !round_handler_IsRoundStarted();
481 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
483 entity player = M_ARGV(0, entity);
485 set_dom_state(player);
488 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
490 entity bot = M_ARGV(0, entity);
492 bot.havocbot_role = havocbot_role_dom;
496 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
497 Control point for Domination gameplay.
499 spawnfunc(dom_controlpoint)
506 setthink(this, dom_controlpoint_setup);
507 this.nextthink = time + 0.1;
508 this.reset = dom_controlpoint_setup;
513 this.effects = this.effects | EF_LOWPRECISION;
514 if (autocvar_g_domination_point_fullbright)
515 this.effects |= EF_FULLBRIGHT;
517 IL_PUSH(g_dompoints, this);
520 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
521 Team declaration for Domination gameplay, this allows you to decide what team
522 names and control point models are used in your map.
524 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
525 can have netname set! The nameless team owns all control points at start.
529 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
531 Scoreboard color of the team (for example 4 is red and 13 is blue)
533 Model to use for control points owned by this team (for example
534 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
537 Skin of the model to use (for team skins on a single model)
539 Sound to play when this team captures a point.
540 (this is a localized sound, like a small alarm or other effect)
542 Narrator speech to play when this team captures a point.
543 (this is a global sound, like "Red team has captured a control point")
548 if(!g_domination || autocvar_g_domination_teams_override >= 2)
553 precache_model(this.model);
554 if (this.noise != "")
555 precache_sound(this.noise);
556 if (this.noise1 != "")
557 precache_sound(this.noise1);
558 this.classname = "dom_team";
559 _setmodel(this, this.model); // precision not needed
560 this.mdl = this.model;
561 this.dmg = this.modelindex;
564 // this would have to be changed if used in quakeworld
566 this.team = this.cnt + 1; // WHY are these different anyway?
570 void ScoreRules_dom(int teams)
572 if(domination_roundbased)
574 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
575 field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
576 field(SP_DOM_TAKES, "takes", 0);
581 float sp_domticks, sp_score;
582 sp_score = sp_domticks = 0;
583 if(autocvar_g_domination_disable_frags)
584 sp_domticks = SFL_SORT_PRIO_PRIMARY;
586 sp_score = SFL_SORT_PRIO_PRIMARY;
587 GameRules_scoring(teams, sp_score, sp_score, {
588 field_team(ST_DOM_TICKS, "ticks", sp_domticks);
589 field(SP_DOM_TICKS, "ticks", sp_domticks);
590 field(SP_DOM_TAKES, "takes", 0);
595 // code from here on is just to support maps that don't have control point and team entities
596 void dom_spawnteam(string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
599 entity e = new_pure(dom_team);
600 e.netname = strzone(teamname);
602 e.model = pointmodel;
604 e.noise = strzone(Sound_fixpath(capsound));
605 e.noise1 = strzone(capnarration);
606 e.message = strzone(capmessage);
608 // this code is identical to spawnfunc_dom_team
609 _setmodel(e, e.model); // precision not needed
611 e.dmg = e.modelindex;
614 // this would have to be changed if used in quakeworld
620 void dom_spawnpoint(vector org)
623 e.classname = "dom_controlpoint";
624 setthink(e, spawnfunc_dom_controlpoint);
627 spawnfunc_dom_controlpoint(e);
630 // spawn some default teams if the map is not set up for domination
631 void dom_spawnteams(int teams)
634 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");
635 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");
637 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");
639 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");
640 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
643 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
645 // if no teams are found, spawn defaults
646 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
648 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
649 domination_teams = autocvar_g_domination_teams_override;
650 if (domination_teams < 2)
651 domination_teams = autocvar_g_domination_default_teams;
652 domination_teams = bound(2, domination_teams, 4);
653 dom_spawnteams(domination_teams);
656 entity balance = TeamBalance_CheckAllowedTeams(NULL);
657 int teams = TeamBalance_GetAllowedTeams(balance);
658 TeamBalance_Destroy(balance);
659 domination_teams = teams;
661 domination_roundbased = autocvar_g_domination_roundbased;
663 ScoreRules_dom(domination_teams);
665 if(domination_roundbased)
667 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
668 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
672 void dom_Initialize()
675 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);