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(IS_PLAYER(it), {
431 PutClientInServer(it);
432 if(domination_roundbased)
433 it.player_blocked = 1;
434 if(IS_REAL_CLIENT(it))
440 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
442 entity player = M_ARGV(0, entity);
444 if(domination_roundbased)
445 player.player_blocked = !round_handler_IsRoundStarted();
448 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
450 entity player = M_ARGV(0, entity);
452 set_dom_state(player);
455 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
457 entity bot = M_ARGV(0, entity);
459 bot.havocbot_role = havocbot_role_dom;
463 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
464 Control point for Domination gameplay.
466 spawnfunc(dom_controlpoint)
473 setthink(this, dom_controlpoint_setup);
474 this.nextthink = time + 0.1;
475 this.reset = dom_controlpoint_setup;
480 this.effects = this.effects | EF_LOWPRECISION;
481 if (autocvar_g_domination_point_fullbright)
482 this.effects |= EF_FULLBRIGHT;
484 IL_PUSH(g_dompoints, this);
487 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
488 Team declaration for Domination gameplay, this allows you to decide what team
489 names and control point models are used in your map.
491 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
492 can have netname set! The nameless team owns all control points at start.
496 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
498 Scoreboard color of the team (for example 4 is red and 13 is blue)
500 Model to use for control points owned by this team (for example
501 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
504 Skin of the model to use (for team skins on a single model)
506 Sound to play when this team captures a point.
507 (this is a localized sound, like a small alarm or other effect)
509 Narrator speech to play when this team captures a point.
510 (this is a global sound, like "Red team has captured a control point")
515 if(!g_domination || autocvar_g_domination_teams_override >= 2)
520 precache_model(this.model);
521 if (this.noise != "")
522 precache_sound(this.noise);
523 if (this.noise1 != "")
524 precache_sound(this.noise1);
525 _setmodel(this, this.model); // precision not needed
526 this.mdl = this.model;
527 this.dmg = this.modelindex;
530 // this would have to be changed if used in quakeworld
532 this.team = this.cnt + 1; // WHY are these different anyway?
536 void ScoreRules_dom(int teams)
538 if(domination_roundbased)
540 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
541 field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
542 field(SP_DOM_TAKES, "takes", 0);
547 float sp_domticks, sp_score;
548 sp_score = sp_domticks = 0;
549 if(autocvar_g_domination_disable_frags)
550 sp_domticks = SFL_SORT_PRIO_PRIMARY;
552 sp_score = SFL_SORT_PRIO_PRIMARY;
553 GameRules_scoring(teams, sp_score, sp_score, {
554 field_team(ST_DOM_TICKS, "ticks", sp_domticks);
555 field(SP_DOM_TICKS, "ticks", sp_domticks);
556 field(SP_DOM_TAKES, "takes", 0);
561 // code from here on is just to support maps that don't have control point and team entities
562 void dom_spawnteam(string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
565 entity e = new_pure(dom_team);
566 e.netname = strzone(teamname);
568 e.model = pointmodel;
570 e.noise = strzone(Sound_fixpath(capsound));
571 e.noise1 = strzone(capnarration);
572 e.message = strzone(capmessage);
574 // this code is identical to spawnfunc_dom_team
575 _setmodel(e, e.model); // precision not needed
577 e.dmg = e.modelindex;
580 // this would have to be changed if used in quakeworld
586 void dom_spawnpoint(vector org)
589 setthink(e, spawnfunc_dom_controlpoint);
592 spawnfunc_dom_controlpoint(e);
595 // spawn some default teams if the map is not set up for domination
596 void dom_spawnteams(int teams)
599 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");
600 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");
602 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");
604 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");
605 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
608 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
610 // if no teams are found, spawn defaults
611 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
613 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
614 domination_teams = autocvar_g_domination_teams_override;
615 if (domination_teams < 2)
616 domination_teams = autocvar_g_domination_default_teams;
617 domination_teams = bound(2, domination_teams, 4);
618 dom_spawnteams(domination_teams);
621 entity balance = TeamBalance_CheckAllowedTeams(NULL);
622 int teams = TeamBalance_GetAllowedTeams(balance);
623 TeamBalance_Destroy(balance);
624 domination_teams = teams;
626 domination_roundbased = autocvar_g_domination_roundbased;
628 ScoreRules_dom(domination_teams);
630 if(domination_roundbased)
632 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
633 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
637 void dom_Initialize()
640 g_dompoints = IL_NEW();
642 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);