1 #include "sv_domination.qh"
3 #include <server/client.qh>
4 #include <server/command/vote.qh>
5 #include <server/g_damage.qh>
6 #include <server/gamelog.qh>
7 #include <server/teamplay.qh>
8 #include <common/mapobjects/platforms.qh>
9 #include <common/mapobjects/triggers.qh>
13 int autocvar_g_domination_default_teams;
14 bool autocvar_g_domination_disable_frags;
15 int autocvar_g_domination_point_amt;
16 bool autocvar_g_domination_point_fullbright;
17 float autocvar_g_domination_round_timelimit;
18 float autocvar_g_domination_warmup;
19 float autocvar_g_domination_point_rate;
20 int autocvar_g_domination_teams_override;
22 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
24 if(autocvar_sv_eventlog)
25 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
28 void set_dom_state(entity e)
30 STAT(DOM_TOTAL_PPS, e) = total_pps;
31 STAT(DOM_PPS_RED, e) = pps_red;
32 STAT(DOM_PPS_BLUE, e) = pps_blue;
33 if(domination_teams >= 3)
34 STAT(DOM_PPS_YELLOW, e) = pps_yellow;
35 if(domination_teams >= 4)
36 STAT(DOM_PPS_PINK, e) = pps_pink;
39 void dompoint_captured(entity this)
41 float old_delay, old_team, real_team;
43 // now that the delay has expired, switch to the latest team to lay claim to this point
44 entity head = this.owner;
49 dom_EventLog("taken", this.team, this.dmg_inflictor);
50 this.dmg_inflictor = NULL;
52 this.goalentity = head;
53 this.model = head.mdl;
54 this.modelindex = head.dmg;
55 this.skin = head.skin;
57 float points, wait_time;
58 if (autocvar_g_domination_point_amt)
59 points = autocvar_g_domination_point_amt;
62 if (autocvar_g_domination_point_rate)
63 wait_time = autocvar_g_domination_point_rate;
65 wait_time = this.wait;
67 if(domination_roundbased)
68 bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
70 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
72 if(this.enemy.playerid == this.enemy_playerid)
73 GameRules_scoring_add(this.enemy, DOM_TAKES, 1);
80 _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
82 _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
84 if (head.noise1 != "")
85 play2all(head.noise1);
87 this.delay = time + wait_time;
90 old_delay = this.delay;
92 this.team = real_team;
94 SUB_UseTargets (this, this, NULL);
95 this.delay = old_delay;
98 entity msg = WP_DomNeut;
101 case NUM_TEAM_1: msg = WP_DomRed; break;
102 case NUM_TEAM_2: msg = WP_DomBlue; break;
103 case NUM_TEAM_3: msg = WP_DomYellow; break;
104 case NUM_TEAM_4: msg = WP_DomPink; break;
107 WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
109 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
110 IL_EACH(g_dompoints, true,
112 if (autocvar_g_domination_point_amt)
113 points = autocvar_g_domination_point_amt;
116 if (autocvar_g_domination_point_rate)
117 wait_time = autocvar_g_domination_point_rate;
120 switch(it.goalentity.team)
122 case NUM_TEAM_1: pps_red += points/wait_time; break;
123 case NUM_TEAM_2: pps_blue += points/wait_time; break;
124 case NUM_TEAM_3: pps_yellow += points/wait_time; break;
125 case NUM_TEAM_4: pps_pink += points/wait_time; break;
127 total_pps += points/wait_time;
130 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
131 WaypointSprite_Ping(this.sprite);
135 FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
138 void AnimateDomPoint(entity this)
140 if(this.pain_finished > time)
142 this.pain_finished = time + this.t_width;
143 if(this.nextthink > this.pain_finished)
144 this.nextthink = this.pain_finished;
146 this.frame = this.frame + 1;
147 if(this.frame > this.t_length)
151 void dompointthink(entity this)
155 this.nextthink = time + 0.1;
157 //this.frame = this.frame + 1;
158 //if(this.frame > 119)
160 AnimateDomPoint(this);
164 if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
167 if(autocvar_g_domination_point_rate)
168 this.delay = time + autocvar_g_domination_point_rate;
170 this.delay = time + this.wait;
172 // give credit to the team
173 // NOTE: this defaults to 0
174 if (!domination_roundbased)
175 if (this.goalentity.netname != "")
177 if(autocvar_g_domination_point_amt)
178 fragamt = autocvar_g_domination_point_amt;
180 fragamt = this.frags;
181 TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
182 TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
184 // give credit to the individual player, if he is still there
185 if (this.enemy.playerid == this.enemy_playerid)
187 GameRules_scoring_add(this.enemy, SCORE, fragamt);
188 GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
195 void dompointtouch(entity this, entity toucher)
197 if(!IS_PLAYER(toucher))
199 if(GetResource(toucher, RES_HEALTH) < 1)
202 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
205 if(time < this.captime + 0.3)
208 // only valid teams can claim it
209 entity head = find(NULL, classname, "dom_team");
210 while (head && head.team != toucher.team)
211 head = find(head, classname, "dom_team");
212 if (!head || head.netname == "" || head == this.goalentity)
217 this.team = this.goalentity.team; // this stores the PREVIOUS team!
219 this.cnt = toucher.team;
220 this.owner = head; // team to switch to after the delay
221 this.dmg_inflictor = toucher;
224 // this.delay = time + cvar("g_domination_point_capturetime");
225 //this.nextthink = time + cvar("g_domination_point_capturetime");
226 //this.think = dompoint_captured;
228 // go to neutral team in the mean time
229 head = find(NULL, classname, "dom_team");
230 while (head && head.netname != "")
231 head = find(head, classname, "dom_team");
235 WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
236 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
237 WaypointSprite_Ping(this.sprite);
239 this.goalentity = head;
240 this.model = head.mdl;
241 this.modelindex = head.dmg;
242 this.skin = head.skin;
244 this.enemy = toucher; // individual player scoring
245 this.enemy_playerid = toucher.playerid;
246 dompoint_captured(this);
249 void dom_controlpoint_setup(entity this)
252 // find the spawnfunc_dom_team representing unclaimed points
253 head = find(NULL, classname, "dom_team");
254 while(head && head.netname != "")
255 head = find(head, classname, "dom_team");
257 objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
259 // copy important properties from spawnfunc_dom_team entity
260 this.goalentity = head;
261 _setmodel(this, head.mdl); // precision already set
262 this.skin = head.skin;
266 if(this.message == "")
267 this.message = " has captured a control point";
274 float points, waittime;
275 if (autocvar_g_domination_point_amt)
276 points = autocvar_g_domination_point_amt;
279 if (autocvar_g_domination_point_rate)
280 waittime = autocvar_g_domination_point_rate;
282 waittime = this.wait;
284 total_pps += points/waittime;
287 this.t_width = 0.02; // frame animation rate
289 this.t_length = 239; // maximum frame
291 setthink(this, dompointthink);
292 this.nextthink = time;
293 settouch(this, dompointtouch);
294 this.solid = SOLID_TRIGGER;
295 if(!this.flags & FL_ITEM)
296 IL_PUSH(g_items, this);
297 this.flags = FL_ITEM;
298 setsize(this, '-32 -32 -32', '32 32 32');
299 setorigin(this, this.origin + '0 0 20');
302 waypoint_spawnforitem(this);
303 WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
306 int total_control_points;
307 void Domination_count_controlpoints()
309 total_control_points = 0;
310 for (int i = 1; i <= NUM_TEAMS; ++i)
312 Team_SetNumberOfControlPoints(Team_GetTeamFromIndex(i), 0);
314 IL_EACH(g_dompoints, true,
316 ++total_control_points;
317 if (!Entity_HasValidTeam(it.goalentity))
321 entity team_ = Entity_GetTeam(it.goalentity);
322 int num_control_points = Team_GetNumberOfControlPoints(team_);
323 ++num_control_points;
324 Team_SetNumberOfControlPoints(team_, num_control_points);
328 int Domination_GetWinnerTeam()
331 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) ==
332 total_control_points)
334 winner_team = NUM_TEAM_1;
336 for (int i = 2; i <= NUM_TEAMS; ++i)
338 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) ==
339 total_control_points)
341 if (winner_team != 0)
345 winner_team = Team_IndexToTeam(i);
352 return -1; // no control points left?
355 bool Domination_CheckWinner()
357 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
359 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
360 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
363 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
367 Domination_count_controlpoints();
369 float winner_team = Domination_GetWinnerTeam();
371 if(winner_team == -1)
376 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
377 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
378 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
380 else if(winner_team == -1)
382 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
383 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
387 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
392 bool Domination_CheckPlayers()
397 void Domination_RoundStart()
399 FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
402 //go to best items, or control points you don't own
403 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
405 IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
407 if(it.cnt > -1) // this is just being fought
408 navigation_routerating(this, it, ratingscale, 5000);
409 else if(it.goalentity.cnt == 0) // unclaimed
410 navigation_routerating(this, it, ratingscale, 5000);
411 else if(it.goalentity.team != this.team) // other team's point
412 navigation_routerating(this, it, ratingscale, 5000);
416 void havocbot_role_dom(entity this)
421 if (navigation_goalrating_timeout(this))
423 navigation_goalrating_start(this);
424 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
425 havocbot_goalrating_items(this, 20000, this.origin, 8000);
426 //havocbot_goalrating_enemyplayers(this, 1500, this.origin, 2000);
427 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
428 navigation_goalrating_end(this);
430 navigation_goalrating_timeout_set(this);
434 MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
437 M_ARGV(0, float) = domination_teams;
438 string ret_string = "dom_team";
440 entity head = find(NULL, classname, ret_string);
443 if(head.netname != "")
445 if (Team_IsValidTeam(head.team))
447 M_ARGV(0, float) |= Team_TeamToBit(head.team);
451 head = find(head, classname, ret_string);
454 M_ARGV(1, string) = string_null;
459 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
461 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
462 FOREACH_CLIENT(IS_PLAYER(it), {
463 PutClientInServer(it);
464 if(domination_roundbased)
465 it.player_blocked = 1;
466 if(IS_REAL_CLIENT(it))
472 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
474 entity player = M_ARGV(0, entity);
476 if(domination_roundbased)
477 player.player_blocked = !round_handler_IsRoundStarted();
480 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
482 entity player = M_ARGV(0, entity);
484 set_dom_state(player);
487 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
489 entity bot = M_ARGV(0, entity);
491 bot.havocbot_role = havocbot_role_dom;
495 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
496 Control point for Domination gameplay.
498 spawnfunc(dom_controlpoint)
505 setthink(this, dom_controlpoint_setup);
506 this.nextthink = time + 0.1;
507 this.reset = dom_controlpoint_setup;
512 this.effects = this.effects | EF_LOWPRECISION;
513 if (autocvar_g_domination_point_fullbright)
514 this.effects |= EF_FULLBRIGHT;
516 IL_PUSH(g_dompoints, this);
519 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
520 Team declaration for Domination gameplay, this allows you to decide what team
521 names and control point models are used in your map.
523 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
524 can have netname set! The nameless team owns all control points at start.
528 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
530 Scoreboard color of the team (for example 4 is red and 13 is blue)
532 Model to use for control points owned by this team (for example
533 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
536 Skin of the model to use (for team skins on a single model)
538 Sound to play when this team captures a point.
539 (this is a localized sound, like a small alarm or other effect)
541 Narrator speech to play when this team captures a point.
542 (this is a global sound, like "Red team has captured a control point")
547 if(!g_domination || autocvar_g_domination_teams_override >= 2)
552 precache_model(this.model);
553 if (this.noise != "")
554 precache_sound(this.noise);
555 if (this.noise1 != "")
556 precache_sound(this.noise1);
557 this.classname = "dom_team";
558 _setmodel(this, this.model); // precision not needed
559 this.mdl = this.model;
560 this.dmg = this.modelindex;
563 // this would have to be changed if used in quakeworld
565 this.team = this.cnt + 1; // WHY are these different anyway?
569 void ScoreRules_dom(int teams)
571 if(domination_roundbased)
573 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
574 field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
575 field(SP_DOM_TAKES, "takes", 0);
580 float sp_domticks, sp_score;
581 sp_score = sp_domticks = 0;
582 if(autocvar_g_domination_disable_frags)
583 sp_domticks = SFL_SORT_PRIO_PRIMARY;
585 sp_score = SFL_SORT_PRIO_PRIMARY;
586 GameRules_scoring(teams, sp_score, sp_score, {
587 field_team(ST_DOM_TICKS, "ticks", sp_domticks);
588 field(SP_DOM_TICKS, "ticks", sp_domticks);
589 field(SP_DOM_TAKES, "takes", 0);
594 // code from here on is just to support maps that don't have control point and team entities
595 void dom_spawnteam(string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
598 entity e = new_pure(dom_team);
599 e.netname = strzone(teamname);
601 e.model = pointmodel;
603 e.noise = strzone(Sound_fixpath(capsound));
604 e.noise1 = strzone(capnarration);
605 e.message = strzone(capmessage);
607 // this code is identical to spawnfunc_dom_team
608 _setmodel(e, e.model); // precision not needed
610 e.dmg = e.modelindex;
613 // this would have to be changed if used in quakeworld
619 void dom_spawnpoint(vector org)
622 e.classname = "dom_controlpoint";
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);