1 #include "sv_domination.qh"
3 #include <server/client.qh>
4 #include <server/command/vote.qh>
5 #include <server/damage.qh>
6 #include <server/gamelog.qh>
7 #include <server/items/items.qh>
8 #include <server/teamplay.qh>
9 #include <server/world.qh>
10 #include <common/mapobjects/platforms.qh>
11 #include <common/mapobjects/triggers.qh>
15 int autocvar_g_domination_default_teams;
16 bool autocvar_g_domination_disable_frags;
17 int autocvar_g_domination_point_amt;
18 bool autocvar_g_domination_point_fullbright;
19 float autocvar_g_domination_round_timelimit;
20 float autocvar_g_domination_warmup;
21 float autocvar_g_domination_point_rate;
22 int autocvar_g_domination_teams_override;
24 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
26 if(autocvar_sv_eventlog)
27 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
30 void set_dom_state(entity e)
32 STAT(DOM_TOTAL_PPS, e) = total_pps;
33 STAT(DOM_PPS_RED, e) = pps_red;
34 STAT(DOM_PPS_BLUE, e) = pps_blue;
35 if(domination_teams >= 3)
36 STAT(DOM_PPS_YELLOW, e) = pps_yellow;
37 if(domination_teams >= 4)
38 STAT(DOM_PPS_PINK, e) = pps_pink;
41 void dompoint_captured(entity this)
43 float old_delay, old_team, real_team;
45 // now that the delay has expired, switch to the latest team to lay claim to this point
46 entity head = this.owner;
51 dom_EventLog("taken", this.team, this.dmg_inflictor);
52 this.dmg_inflictor = NULL;
54 this.goalentity = head;
55 this.model = head.mdl;
56 this.modelindex = head.dmg;
57 this.skin = head.skin;
59 float points, wait_time;
60 if (autocvar_g_domination_point_amt)
61 points = autocvar_g_domination_point_amt;
64 if (autocvar_g_domination_point_rate)
65 wait_time = autocvar_g_domination_point_rate;
67 wait_time = this.wait;
69 if(domination_roundbased)
70 bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
72 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
74 if(this.enemy.playerid == this.enemy_playerid)
75 GameRules_scoring_add(this.enemy, DOM_TAKES, 1);
82 _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
84 _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
86 if (head.noise1 != "")
87 play2all(head.noise1);
89 this.delay = time + wait_time;
92 old_delay = this.delay;
94 this.team = real_team;
96 SUB_UseTargets (this, this, NULL);
97 this.delay = old_delay;
100 entity msg = WP_DomNeut;
103 case NUM_TEAM_1: msg = WP_DomRed; break;
104 case NUM_TEAM_2: msg = WP_DomBlue; break;
105 case NUM_TEAM_3: msg = WP_DomYellow; break;
106 case NUM_TEAM_4: msg = WP_DomPink; break;
109 WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
111 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
112 IL_EACH(g_dompoints, true,
114 if (autocvar_g_domination_point_amt)
115 points = autocvar_g_domination_point_amt;
118 if (autocvar_g_domination_point_rate)
119 wait_time = autocvar_g_domination_point_rate;
122 switch(it.goalentity.team)
124 case NUM_TEAM_1: pps_red += points/wait_time; break;
125 case NUM_TEAM_2: pps_blue += points/wait_time; break;
126 case NUM_TEAM_3: pps_yellow += points/wait_time; break;
127 case NUM_TEAM_4: pps_pink += points/wait_time; break;
129 total_pps += points/wait_time;
132 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
133 WaypointSprite_Ping(this.sprite);
137 FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
140 void AnimateDomPoint(entity this)
142 if(this.pain_finished > time)
144 this.pain_finished = time + this.t_width;
145 if(this.nextthink > this.pain_finished)
146 this.nextthink = this.pain_finished;
148 this.frame = this.frame + 1;
149 if(this.frame > this.t_length)
153 void dompointthink(entity this)
157 this.nextthink = time + 0.1;
159 //this.frame = this.frame + 1;
160 //if(this.frame > 119)
162 AnimateDomPoint(this);
166 if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
169 if(autocvar_g_domination_point_rate)
170 this.delay = time + autocvar_g_domination_point_rate;
172 this.delay = time + this.wait;
174 // give credit to the team
175 // NOTE: this defaults to 0
176 if (!domination_roundbased)
177 if (this.goalentity.netname != "")
179 if(autocvar_g_domination_point_amt)
180 fragamt = autocvar_g_domination_point_amt;
182 fragamt = this.frags;
183 TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
184 TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
186 // give credit to the individual player, if he is still there
187 if (this.enemy.playerid == this.enemy_playerid)
189 GameRules_scoring_add(this.enemy, SCORE, fragamt);
190 GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
197 void dompointtouch(entity this, entity toucher)
199 if(!IS_PLAYER(toucher))
201 if(GetResource(toucher, RES_HEALTH) < 1)
204 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
207 if(time < this.captime + 0.3)
210 // only valid teams can claim it
211 entity head = find(NULL, classname, "dom_team");
212 while (head && head.team != toucher.team)
213 head = find(head, classname, "dom_team");
214 if (!head || head.netname == "" || head == this.goalentity)
219 this.team = this.goalentity.team; // this stores the PREVIOUS team!
221 this.cnt = toucher.team;
222 this.owner = head; // team to switch to after the delay
223 this.dmg_inflictor = toucher;
226 // this.delay = time + cvar("g_domination_point_capturetime");
227 //this.nextthink = time + cvar("g_domination_point_capturetime");
228 //this.think = dompoint_captured;
230 // go to neutral team in the mean time
231 head = find(NULL, classname, "dom_team");
232 while (head && head.netname != "")
233 head = find(head, classname, "dom_team");
237 WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
238 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
239 WaypointSprite_Ping(this.sprite);
241 this.goalentity = head;
242 this.model = head.mdl;
243 this.modelindex = head.dmg;
244 this.skin = head.skin;
246 this.enemy = toucher; // individual player scoring
247 this.enemy_playerid = toucher.playerid;
248 dompoint_captured(this);
251 void dom_controlpoint_setup(entity this)
254 // find the spawnfunc_dom_team representing unclaimed points
255 head = find(NULL, classname, "dom_team");
256 while(head && head.netname != "")
257 head = find(head, classname, "dom_team");
259 objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
261 // copy important properties from spawnfunc_dom_team entity
262 this.goalentity = head;
263 _setmodel(this, head.mdl); // precision already set
264 this.skin = head.skin;
268 if(this.message == "")
269 this.message = " has captured a control point";
276 float points, waittime;
277 if (autocvar_g_domination_point_amt)
278 points = autocvar_g_domination_point_amt;
281 if (autocvar_g_domination_point_rate)
282 waittime = autocvar_g_domination_point_rate;
284 waittime = this.wait;
286 total_pps += points/waittime;
289 this.t_width = 0.02; // frame animation rate
291 this.t_length = 239; // maximum frame
293 setthink(this, dompointthink);
294 this.nextthink = time;
295 settouch(this, dompointtouch);
296 this.solid = SOLID_TRIGGER;
297 if(!this.flags & FL_ITEM)
298 IL_PUSH(g_items, this);
299 this.flags = FL_ITEM;
300 setsize(this, '-32 -32 -32', '32 32 32');
301 setorigin(this, this.origin + '0 0 20');
304 waypoint_spawnforitem(this);
305 WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
308 int total_control_points;
309 void Domination_count_controlpoints()
311 total_control_points = 0;
312 for (int i = 1; i <= NUM_TEAMS; ++i)
314 Team_SetNumberOfOwnedItems(Team_GetTeamFromIndex(i), 0);
316 IL_EACH(g_dompoints, true,
318 ++total_control_points;
319 if (!Entity_HasValidTeam(it.goalentity))
323 entity team_ = Entity_GetTeam(it.goalentity);
324 int num_control_points = Team_GetNumberOfOwnedItems(team_);
325 ++num_control_points;
326 Team_SetNumberOfOwnedItems(team_, num_control_points);
330 bool Domination_CheckWinner()
332 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
334 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
335 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
338 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
342 Domination_count_controlpoints();
343 int winner_team = Team_GetWinnerTeam_WithOwnedItems(total_control_points);
344 if (winner_team == -1)
349 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
350 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
351 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
355 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
360 bool Domination_CheckPlayers()
365 void Domination_RoundStart()
367 FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
370 //go to best items, or control points you don't own
371 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
373 IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
375 if(it.cnt > -1) // this is just being fought
376 navigation_routerating(this, it, ratingscale, 5000);
377 else if(it.goalentity.cnt == 0) // unclaimed
378 navigation_routerating(this, it, ratingscale, 5000);
379 else if(it.goalentity.team != this.team) // other team's point
380 navigation_routerating(this, it, ratingscale, 5000);
384 void havocbot_role_dom(entity this)
389 if (navigation_goalrating_timeout(this))
391 navigation_goalrating_start(this);
392 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
393 havocbot_goalrating_items(this, 20000, this.origin, 8000);
394 //havocbot_goalrating_enemyplayers(this, 1500, this.origin, 2000);
395 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
396 navigation_goalrating_end(this);
398 navigation_goalrating_timeout_set(this);
402 MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
405 M_ARGV(0, float) = domination_teams;
406 string ret_string = "dom_team";
408 entity head = find(NULL, classname, ret_string);
411 if(head.netname != "")
413 if (Team_IsValidTeam(head.team))
415 M_ARGV(0, float) |= Team_TeamToBit(head.team);
419 head = find(head, classname, ret_string);
422 M_ARGV(1, string) = string_null;
427 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
429 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
430 FOREACH_CLIENT(true, {
433 PutClientInServer(it);
434 if(domination_roundbased)
435 it.player_blocked = 1;
437 if(IS_REAL_CLIENT(it))
443 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
445 entity player = M_ARGV(0, entity);
447 if(domination_roundbased)
448 player.player_blocked = !round_handler_IsRoundStarted();
451 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
453 entity player = M_ARGV(0, entity);
455 set_dom_state(player);
458 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
460 entity bot = M_ARGV(0, entity);
462 bot.havocbot_role = havocbot_role_dom;
466 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
467 Control point for Domination gameplay.
469 spawnfunc(dom_controlpoint)
476 setthink(this, dom_controlpoint_setup);
477 this.nextthink = time + 0.1;
478 this.reset = dom_controlpoint_setup;
483 this.effects = this.effects | EF_LOWPRECISION;
484 if (autocvar_g_domination_point_fullbright)
485 this.effects |= EF_FULLBRIGHT;
487 IL_PUSH(g_dompoints, this);
490 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
491 Team declaration for Domination gameplay, this allows you to decide what team
492 names and control point models are used in your map.
494 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
495 can have netname set! The nameless team owns all control points at start.
499 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
501 Scoreboard color of the team (for example 4 is red and 13 is blue)
503 Model to use for control points owned by this team (for example
504 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
507 Skin of the model to use (for team skins on a single model)
509 Sound to play when this team captures a point.
510 (this is a localized sound, like a small alarm or other effect)
512 Narrator speech to play when this team captures a point.
513 (this is a global sound, like "Red team has captured a control point")
518 if(!g_domination || autocvar_g_domination_teams_override >= 2)
523 precache_model(this.model);
524 if (this.noise != "")
525 precache_sound(this.noise);
526 if (this.noise1 != "")
527 precache_sound(this.noise1);
528 _setmodel(this, this.model); // precision not needed
529 this.mdl = this.model;
530 this.dmg = this.modelindex;
533 // this would have to be changed if used in quakeworld
535 this.team = this.cnt + 1; // WHY are these different anyway?
539 void ScoreRules_dom(int teams)
541 if(domination_roundbased)
543 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
544 field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
545 field(SP_DOM_TAKES, "takes", 0);
550 float sp_domticks, sp_score;
551 sp_score = sp_domticks = 0;
552 if(autocvar_g_domination_disable_frags)
553 sp_domticks = SFL_SORT_PRIO_PRIMARY;
555 sp_score = SFL_SORT_PRIO_PRIMARY;
556 GameRules_scoring(teams, sp_score, sp_score, {
557 field_team(ST_DOM_TICKS, "ticks", sp_domticks);
558 field(SP_DOM_TICKS, "ticks", sp_domticks);
559 field(SP_DOM_TAKES, "takes", 0);
564 // code from here on is just to support maps that don't have control point and team entities
565 void dom_spawnteam(string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
568 entity e = new_pure(dom_team);
569 e.netname = strzone(teamname);
571 e.model = pointmodel;
573 e.noise = strzone(Sound_fixpath(capsound));
574 e.noise1 = strzone(capnarration);
575 e.message = strzone(capmessage);
577 // this code is identical to spawnfunc_dom_team
578 _setmodel(e, e.model); // precision not needed
580 e.dmg = e.modelindex;
583 // this would have to be changed if used in quakeworld
589 void dom_spawnpoint(vector org)
592 setthink(e, spawnfunc_dom_controlpoint);
595 spawnfunc_dom_controlpoint(e);
598 // spawn some default teams if the map is not set up for domination
599 void dom_spawnteams(int teams)
602 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");
603 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");
605 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");
607 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");
608 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
611 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
613 // if no teams are found, spawn defaults
614 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
616 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
617 domination_teams = autocvar_g_domination_teams_override;
618 if (domination_teams < 2)
619 domination_teams = autocvar_g_domination_default_teams;
620 domination_teams = bound(2, domination_teams, 4);
621 dom_spawnteams(domination_teams);
624 entity balance = TeamBalance_CheckAllowedTeams(NULL);
625 int teams = TeamBalance_GetAllowedTeams(balance);
626 TeamBalance_Destroy(balance);
627 domination_teams = teams;
629 domination_roundbased = autocvar_g_domination_roundbased;
631 ScoreRules_dom(domination_teams);
633 if(domination_roundbased)
635 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
636 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
640 void dom_Initialize()
643 g_dompoints = IL_NEW();
645 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);