1 #include "sv_domination.qh"
3 #include <server/command/vote.qh>
4 #include <server/gamelog.qh>
5 #include <server/teamplay.qh>
6 #include <common/mapobjects/platforms.qh>
10 int autocvar_g_domination_default_teams;
11 bool autocvar_g_domination_disable_frags;
12 int autocvar_g_domination_point_amt;
13 bool autocvar_g_domination_point_fullbright;
14 float autocvar_g_domination_round_timelimit;
15 float autocvar_g_domination_warmup;
16 float autocvar_g_domination_point_rate;
17 int autocvar_g_domination_teams_override;
19 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
21 if(autocvar_sv_eventlog)
22 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
25 void set_dom_state(entity e)
27 STAT(DOM_TOTAL_PPS, e) = total_pps;
28 STAT(DOM_PPS_RED, e) = pps_red;
29 STAT(DOM_PPS_BLUE, e) = pps_blue;
30 if(domination_teams >= 3)
31 STAT(DOM_PPS_YELLOW, e) = pps_yellow;
32 if(domination_teams >= 4)
33 STAT(DOM_PPS_PINK, e) = pps_pink;
36 void dompoint_captured(entity this)
38 float old_delay, old_team, real_team;
40 // now that the delay has expired, switch to the latest team to lay claim to this point
41 entity head = this.owner;
46 dom_EventLog("taken", this.team, this.dmg_inflictor);
47 this.dmg_inflictor = NULL;
49 this.goalentity = head;
50 this.model = head.mdl;
51 this.modelindex = head.dmg;
52 this.skin = head.skin;
54 float points, wait_time;
55 if (autocvar_g_domination_point_amt)
56 points = autocvar_g_domination_point_amt;
59 if (autocvar_g_domination_point_rate)
60 wait_time = autocvar_g_domination_point_rate;
62 wait_time = this.wait;
64 if(domination_roundbased)
65 bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
67 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
69 if(this.enemy.playerid == this.enemy_playerid)
70 GameRules_scoring_add(this.enemy, DOM_TAKES, 1);
77 _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
79 _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
81 if (head.noise1 != "")
82 play2all(head.noise1);
84 this.delay = time + wait_time;
87 old_delay = this.delay;
89 this.team = real_team;
91 SUB_UseTargets (this, this, NULL);
92 this.delay = old_delay;
95 entity msg = WP_DomNeut;
98 case NUM_TEAM_1: msg = WP_DomRed; break;
99 case NUM_TEAM_2: msg = WP_DomBlue; break;
100 case NUM_TEAM_3: msg = WP_DomYellow; break;
101 case NUM_TEAM_4: msg = WP_DomPink; break;
104 WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
106 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
107 IL_EACH(g_dompoints, true,
109 if (autocvar_g_domination_point_amt)
110 points = autocvar_g_domination_point_amt;
113 if (autocvar_g_domination_point_rate)
114 wait_time = autocvar_g_domination_point_rate;
117 switch(it.goalentity.team)
119 case NUM_TEAM_1: pps_red += points/wait_time; break;
120 case NUM_TEAM_2: pps_blue += points/wait_time; break;
121 case NUM_TEAM_3: pps_yellow += points/wait_time; break;
122 case NUM_TEAM_4: pps_pink += points/wait_time; break;
124 total_pps += points/wait_time;
127 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
128 WaypointSprite_Ping(this.sprite);
132 FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
135 void AnimateDomPoint(entity this)
137 if(this.pain_finished > time)
139 this.pain_finished = time + this.t_width;
140 if(this.nextthink > this.pain_finished)
141 this.nextthink = this.pain_finished;
143 this.frame = this.frame + 1;
144 if(this.frame > this.t_length)
148 void dompointthink(entity this)
152 this.nextthink = time + 0.1;
154 //this.frame = this.frame + 1;
155 //if(this.frame > 119)
157 AnimateDomPoint(this);
161 if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
164 if(autocvar_g_domination_point_rate)
165 this.delay = time + autocvar_g_domination_point_rate;
167 this.delay = time + this.wait;
169 // give credit to the team
170 // NOTE: this defaults to 0
171 if (!domination_roundbased)
172 if (this.goalentity.netname != "")
174 if(autocvar_g_domination_point_amt)
175 fragamt = autocvar_g_domination_point_amt;
177 fragamt = this.frags;
178 TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
179 TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
181 // give credit to the individual player, if he is still there
182 if (this.enemy.playerid == this.enemy_playerid)
184 GameRules_scoring_add(this.enemy, SCORE, fragamt);
185 GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
192 void dompointtouch(entity this, entity toucher)
194 if(!IS_PLAYER(toucher))
196 if(GetResource(toucher, RES_HEALTH) < 1)
199 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
202 if(time < this.captime + 0.3)
205 // only valid teams can claim it
206 entity head = find(NULL, classname, "dom_team");
207 while (head && head.team != toucher.team)
208 head = find(head, classname, "dom_team");
209 if (!head || head.netname == "" || head == this.goalentity)
214 this.team = this.goalentity.team; // this stores the PREVIOUS team!
216 this.cnt = toucher.team;
217 this.owner = head; // team to switch to after the delay
218 this.dmg_inflictor = toucher;
221 // this.delay = time + cvar("g_domination_point_capturetime");
222 //this.nextthink = time + cvar("g_domination_point_capturetime");
223 //this.think = dompoint_captured;
225 // go to neutral team in the mean time
226 head = find(NULL, classname, "dom_team");
227 while (head && head.netname != "")
228 head = find(head, classname, "dom_team");
232 WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
233 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
234 WaypointSprite_Ping(this.sprite);
236 this.goalentity = head;
237 this.model = head.mdl;
238 this.modelindex = head.dmg;
239 this.skin = head.skin;
241 this.enemy = toucher; // individual player scoring
242 this.enemy_playerid = toucher.playerid;
243 dompoint_captured(this);
246 void dom_controlpoint_setup(entity this)
249 // find the spawnfunc_dom_team representing unclaimed points
250 head = find(NULL, classname, "dom_team");
251 while(head && head.netname != "")
252 head = find(head, classname, "dom_team");
254 objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
256 // copy important properties from spawnfunc_dom_team entity
257 this.goalentity = head;
258 _setmodel(this, head.mdl); // precision already set
259 this.skin = head.skin;
263 if(this.message == "")
264 this.message = " has captured a control point";
271 float points, waittime;
272 if (autocvar_g_domination_point_amt)
273 points = autocvar_g_domination_point_amt;
276 if (autocvar_g_domination_point_rate)
277 waittime = autocvar_g_domination_point_rate;
279 waittime = this.wait;
281 total_pps += points/waittime;
284 this.t_width = 0.02; // frame animation rate
286 this.t_length = 239; // maximum frame
288 setthink(this, dompointthink);
289 this.nextthink = time;
290 settouch(this, dompointtouch);
291 this.solid = SOLID_TRIGGER;
292 if(!this.flags & FL_ITEM)
293 IL_PUSH(g_items, this);
294 this.flags = FL_ITEM;
295 setsize(this, '-32 -32 -32', '32 32 32');
296 setorigin(this, this.origin + '0 0 20');
299 waypoint_spawnforitem(this);
300 WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
303 int total_control_points;
304 void Domination_count_controlpoints()
306 total_control_points = 0;
307 for (int i = 1; i <= NUM_TEAMS; ++i)
309 Team_SetNumberOfControlPoints(Team_GetTeamFromIndex(i), 0);
311 IL_EACH(g_dompoints, true,
313 ++total_control_points;
314 if (!Entity_HasValidTeam(it.goalentity))
318 entity team_ = Entity_GetTeam(it.goalentity);
319 int num_control_points = Team_GetNumberOfControlPoints(team_);
320 ++num_control_points;
321 Team_SetNumberOfControlPoints(team_, num_control_points);
325 int Domination_GetWinnerTeam()
328 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) ==
329 total_control_points)
331 winner_team = NUM_TEAM_1;
333 for (int i = 2; i <= NUM_TEAMS; ++i)
335 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) ==
336 total_control_points)
338 if (winner_team != 0)
342 winner_team = Team_IndexToTeam(i);
349 return -1; // no control points left?
352 bool Domination_CheckWinner()
354 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
356 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
357 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
360 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
364 Domination_count_controlpoints();
366 float winner_team = Domination_GetWinnerTeam();
368 if(winner_team == -1)
373 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
374 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
375 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
377 else if(winner_team == -1)
379 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
380 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
384 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
389 bool Domination_CheckPlayers()
394 void Domination_RoundStart()
396 FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
399 //go to best items, or control points you don't own
400 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
402 IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
404 if(it.cnt > -1) // this is just being fought
405 navigation_routerating(this, it, ratingscale, 5000);
406 else if(it.goalentity.cnt == 0) // unclaimed
407 navigation_routerating(this, it, ratingscale, 5000);
408 else if(it.goalentity.team != this.team) // other team's point
409 navigation_routerating(this, it, ratingscale, 5000);
413 void havocbot_role_dom(entity this)
418 if (navigation_goalrating_timeout(this))
420 navigation_goalrating_start(this);
421 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
422 havocbot_goalrating_items(this, 20000, this.origin, 8000);
423 //havocbot_goalrating_enemyplayers(this, 1500, this.origin, 2000);
424 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
425 navigation_goalrating_end(this);
427 navigation_goalrating_timeout_set(this);
431 MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
434 M_ARGV(0, float) = domination_teams;
435 string ret_string = "dom_team";
437 entity head = find(NULL, classname, ret_string);
440 if(head.netname != "")
442 if (Team_IsValidTeam(head.team))
444 M_ARGV(0, float) |= Team_TeamToBit(head.team);
448 head = find(head, classname, ret_string);
451 M_ARGV(1, string) = string_null;
456 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
458 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
459 FOREACH_CLIENT(IS_PLAYER(it), {
460 PutClientInServer(it);
461 if(domination_roundbased)
462 it.player_blocked = 1;
463 if(IS_REAL_CLIENT(it))
469 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
471 entity player = M_ARGV(0, entity);
473 if(domination_roundbased)
474 player.player_blocked = !round_handler_IsRoundStarted();
477 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
479 entity player = M_ARGV(0, entity);
481 set_dom_state(player);
484 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
486 entity bot = M_ARGV(0, entity);
488 bot.havocbot_role = havocbot_role_dom;
492 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
493 Control point for Domination gameplay.
495 spawnfunc(dom_controlpoint)
502 setthink(this, dom_controlpoint_setup);
503 this.nextthink = time + 0.1;
504 this.reset = dom_controlpoint_setup;
509 this.effects = this.effects | EF_LOWPRECISION;
510 if (autocvar_g_domination_point_fullbright)
511 this.effects |= EF_FULLBRIGHT;
513 IL_PUSH(g_dompoints, this);
516 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
517 Team declaration for Domination gameplay, this allows you to decide what team
518 names and control point models are used in your map.
520 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
521 can have netname set! The nameless team owns all control points at start.
525 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
527 Scoreboard color of the team (for example 4 is red and 13 is blue)
529 Model to use for control points owned by this team (for example
530 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
533 Skin of the model to use (for team skins on a single model)
535 Sound to play when this team captures a point.
536 (this is a localized sound, like a small alarm or other effect)
538 Narrator speech to play when this team captures a point.
539 (this is a global sound, like "Red team has captured a control point")
544 if(!g_domination || autocvar_g_domination_teams_override >= 2)
549 precache_model(this.model);
550 if (this.noise != "")
551 precache_sound(this.noise);
552 if (this.noise1 != "")
553 precache_sound(this.noise1);
554 this.classname = "dom_team";
555 _setmodel(this, this.model); // precision not needed
556 this.mdl = this.model;
557 this.dmg = this.modelindex;
560 // this would have to be changed if used in quakeworld
562 this.team = this.cnt + 1; // WHY are these different anyway?
566 void ScoreRules_dom(int teams)
568 if(domination_roundbased)
570 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
571 field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
572 field(SP_DOM_TAKES, "takes", 0);
577 float sp_domticks, sp_score;
578 sp_score = sp_domticks = 0;
579 if(autocvar_g_domination_disable_frags)
580 sp_domticks = SFL_SORT_PRIO_PRIMARY;
582 sp_score = SFL_SORT_PRIO_PRIMARY;
583 GameRules_scoring(teams, sp_score, sp_score, {
584 field_team(ST_DOM_TICKS, "ticks", sp_domticks);
585 field(SP_DOM_TICKS, "ticks", sp_domticks);
586 field(SP_DOM_TAKES, "takes", 0);
591 // code from here on is just to support maps that don't have control point and team entities
592 void dom_spawnteam(string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
595 entity e = new_pure(dom_team);
596 e.netname = strzone(teamname);
598 e.model = pointmodel;
600 e.noise = strzone(Sound_fixpath(capsound));
601 e.noise1 = strzone(capnarration);
602 e.message = strzone(capmessage);
604 // this code is identical to spawnfunc_dom_team
605 _setmodel(e, e.model); // precision not needed
607 e.dmg = e.modelindex;
610 // this would have to be changed if used in quakeworld
616 void dom_spawnpoint(vector org)
619 e.classname = "dom_controlpoint";
620 setthink(e, spawnfunc_dom_controlpoint);
623 spawnfunc_dom_controlpoint(e);
626 // spawn some default teams if the map is not set up for domination
627 void dom_spawnteams(int teams)
630 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");
631 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");
633 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");
635 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");
636 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
639 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
641 // if no teams are found, spawn defaults
642 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
644 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
645 domination_teams = autocvar_g_domination_teams_override;
646 if (domination_teams < 2)
647 domination_teams = autocvar_g_domination_default_teams;
648 domination_teams = bound(2, domination_teams, 4);
649 dom_spawnteams(domination_teams);
652 entity balance = TeamBalance_CheckAllowedTeams(NULL);
653 int teams = TeamBalance_GetAllowedTeams(balance);
654 TeamBalance_Destroy(balance);
655 domination_teams = teams;
657 domination_roundbased = autocvar_g_domination_roundbased;
659 ScoreRules_dom(domination_teams);
661 if(domination_roundbased)
663 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
664 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
668 void dom_Initialize()
671 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);