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_SetNumberOfControlPoints(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_GetNumberOfControlPoints(team_);
325 ++num_control_points;
326 Team_SetNumberOfControlPoints(team_, num_control_points);
330 int Domination_GetWinnerTeam()
333 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) ==
334 total_control_points)
336 winner_team = NUM_TEAM_1;
338 for (int i = 2; i <= NUM_TEAMS; ++i)
340 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) ==
341 total_control_points)
343 if (winner_team != 0)
347 winner_team = Team_IndexToTeam(i);
354 return -1; // no control points left?
357 bool Domination_CheckWinner()
359 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
361 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
362 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
365 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
369 Domination_count_controlpoints();
371 float winner_team = Domination_GetWinnerTeam();
373 if(winner_team == -1)
378 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
379 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
380 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
382 else if(winner_team == -1)
384 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
385 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
389 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
394 bool Domination_CheckPlayers()
399 void Domination_RoundStart()
401 FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
404 //go to best items, or control points you don't own
405 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
407 IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
409 if(it.cnt > -1) // this is just being fought
410 navigation_routerating(this, it, ratingscale, 5000);
411 else if(it.goalentity.cnt == 0) // unclaimed
412 navigation_routerating(this, it, ratingscale, 5000);
413 else if(it.goalentity.team != this.team) // other team's point
414 navigation_routerating(this, it, ratingscale, 5000);
418 void havocbot_role_dom(entity this)
423 if (navigation_goalrating_timeout(this))
425 navigation_goalrating_start(this);
426 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
427 havocbot_goalrating_items(this, 20000, this.origin, 8000);
428 //havocbot_goalrating_enemyplayers(this, 1500, this.origin, 2000);
429 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
430 navigation_goalrating_end(this);
432 navigation_goalrating_timeout_set(this);
436 MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
439 M_ARGV(0, float) = domination_teams;
440 string ret_string = "dom_team";
442 entity head = find(NULL, classname, ret_string);
445 if(head.netname != "")
447 if (Team_IsValidTeam(head.team))
449 M_ARGV(0, float) |= Team_TeamToBit(head.team);
453 head = find(head, classname, ret_string);
456 M_ARGV(1, string) = string_null;
461 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
463 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
464 FOREACH_CLIENT(IS_PLAYER(it), {
465 PutClientInServer(it);
466 if(domination_roundbased)
467 it.player_blocked = 1;
468 if(IS_REAL_CLIENT(it))
474 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
476 entity player = M_ARGV(0, entity);
478 if(domination_roundbased)
479 player.player_blocked = !round_handler_IsRoundStarted();
482 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
484 entity player = M_ARGV(0, entity);
486 set_dom_state(player);
489 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
491 entity bot = M_ARGV(0, entity);
493 bot.havocbot_role = havocbot_role_dom;
497 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
498 Control point for Domination gameplay.
500 spawnfunc(dom_controlpoint)
507 setthink(this, dom_controlpoint_setup);
508 this.nextthink = time + 0.1;
509 this.reset = dom_controlpoint_setup;
514 this.effects = this.effects | EF_LOWPRECISION;
515 if (autocvar_g_domination_point_fullbright)
516 this.effects |= EF_FULLBRIGHT;
518 IL_PUSH(g_dompoints, this);
521 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
522 Team declaration for Domination gameplay, this allows you to decide what team
523 names and control point models are used in your map.
525 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
526 can have netname set! The nameless team owns all control points at start.
530 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
532 Scoreboard color of the team (for example 4 is red and 13 is blue)
534 Model to use for control points owned by this team (for example
535 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
538 Skin of the model to use (for team skins on a single model)
540 Sound to play when this team captures a point.
541 (this is a localized sound, like a small alarm or other effect)
543 Narrator speech to play when this team captures a point.
544 (this is a global sound, like "Red team has captured a control point")
549 if(!g_domination || autocvar_g_domination_teams_override >= 2)
554 precache_model(this.model);
555 if (this.noise != "")
556 precache_sound(this.noise);
557 if (this.noise1 != "")
558 precache_sound(this.noise1);
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 setthink(e, spawnfunc_dom_controlpoint);
626 spawnfunc_dom_controlpoint(e);
629 // spawn some default teams if the map is not set up for domination
630 void dom_spawnteams(int teams)
633 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");
634 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");
636 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");
638 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");
639 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
642 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
644 // if no teams are found, spawn defaults
645 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
647 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
648 domination_teams = autocvar_g_domination_teams_override;
649 if (domination_teams < 2)
650 domination_teams = autocvar_g_domination_default_teams;
651 domination_teams = bound(2, domination_teams, 4);
652 dom_spawnteams(domination_teams);
655 entity balance = TeamBalance_CheckAllowedTeams(NULL);
656 int teams = TeamBalance_GetAllowedTeams(balance);
657 TeamBalance_Destroy(balance);
658 domination_teams = teams;
660 domination_roundbased = autocvar_g_domination_roundbased;
662 ScoreRules_dom(domination_teams);
664 if(domination_roundbased)
666 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
667 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
671 void dom_Initialize()
674 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);