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 & BIT(2))
36 STAT(DOM_PPS_YELLOW, e) = pps_yellow;
37 if(domination_teams & BIT(3))
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 they are 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, '-48 -48 -32', '48 48 32'); // 0.8.6 used '-32 -32 -32', '32 32 32' with sv_legacy_bbox_expand 1 and FL_ITEM
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);
491 /*QUAKED team_dom_point (0 .2 1) (-16 -16 0) (16 16 88)
492 Domination capture point.
493 -------- KEYS --------
494 identifier : Set to 1, 2, or 3 to match to point 'A', 'B', or 'C'.
495 count : Adjust the range of the capture point (in units, eg: 64, 128... etc).
496 target : Target name for multiple info_player_deathmatch entities (to allow spawning near that particular dom point).
497 -------- NOTES --------
498 Do not assign a 'gametype' key to this item. It is used in all four team game types. The game will call for it as needed.
499 -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY --------
500 model="models/powerups/domination/dompoint.md3"
502 spawnfunc(team_dom_point)
504 spawnfunc_dom_controlpoint(this);
507 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
508 Team declaration for Domination gameplay, this allows you to decide what team
509 names and control point models are used in your map.
511 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
512 can have netname set! The nameless team owns all control points at start.
516 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
518 Scoreboard color of the team (for example 4 is red and 13 is blue)
520 Model to use for control points owned by this team (for example
521 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
524 Skin of the model to use (for team skins on a single model)
526 Sound to play when this team captures a point.
527 (this is a localized sound, like a small alarm or other effect)
529 Narrator speech to play when this team captures a point.
530 (this is a global sound, like "Red team has captured a control point")
535 if(!g_domination || autocvar_g_domination_teams_override >= 2)
540 precache_model(this.model);
541 if (this.noise != "")
542 precache_sound(this.noise);
543 if (this.noise1 != "")
544 precache_sound(this.noise1);
545 _setmodel(this, this.model); // precision not needed
546 this.mdl = this.model;
547 this.dmg = this.modelindex;
550 // this would have to be changed if used in quakeworld
552 this.team = this.cnt + 1; // WHY are these different anyway?
556 void ScoreRules_dom(int teams)
558 if(domination_roundbased)
560 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
561 field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
562 field(SP_DOM_TAKES, "takes", 0);
567 float sp_domticks, sp_score;
568 sp_score = sp_domticks = 0;
569 if(autocvar_g_domination_disable_frags)
570 sp_domticks = SFL_SORT_PRIO_PRIMARY;
572 sp_score = SFL_SORT_PRIO_PRIMARY;
573 GameRules_scoring(teams, sp_score, sp_score, {
574 field_team(ST_DOM_TICKS, "ticks", sp_domticks);
575 field(SP_DOM_TICKS, "ticks", sp_domticks);
576 field(SP_DOM_TAKES, "takes", 0);
581 // code from here on is just to support maps that don't have control point and team entities
582 void dom_spawnteam(string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
585 entity e = new_pure(dom_team);
586 e.netname = strzone(teamname);
588 e.model = pointmodel;
590 e.noise = strzone(Sound_fixpath(capsound));
591 e.noise1 = strzone(capnarration);
592 e.message = strzone(capmessage);
594 // this code is identical to spawnfunc_dom_team
595 _setmodel(e, e.model); // precision not needed
597 e.dmg = e.modelindex;
600 // this would have to be changed if used in quakeworld
606 void dom_spawnpoint(vector org)
609 setthink(e, spawnfunc_dom_controlpoint);
612 spawnfunc_dom_controlpoint(e);
615 // spawn some default teams if the map is not set up for domination
616 void dom_spawnteams(int teams)
619 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");
620 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");
622 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");
624 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");
625 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
628 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
630 // if no teams are found, spawn defaults
631 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
633 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
634 domination_teams = autocvar_g_domination_teams_override;
635 if (domination_teams < 2)
636 domination_teams = autocvar_g_domination_default_teams;
637 domination_teams = BITS(bound(2, domination_teams, 4));
638 dom_spawnteams(domination_teams);
642 entity balance = TeamBalance_CheckAllowedTeams(NULL);
643 domination_teams = TeamBalance_GetAllowedTeams(balance);
644 TeamBalance_Destroy(balance);
647 domination_roundbased = autocvar_g_domination_roundbased;
649 ScoreRules_dom(domination_teams);
651 if(domination_roundbased)
653 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
654 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
658 void dom_Initialize()
661 g_dompoints = IL_NEW();
663 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);