1 #include "domination.qh"
5 #include <server/teamplay.qh>
9 int autocvar_g_domination_default_teams;
10 bool autocvar_g_domination_disable_frags;
11 int autocvar_g_domination_point_amt;
12 bool autocvar_g_domination_point_fullbright;
13 float autocvar_g_domination_round_timelimit;
14 float autocvar_g_domination_warmup;
15 float autocvar_g_domination_point_rate;
16 int autocvar_g_domination_teams_override;
18 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
20 if(autocvar_sv_eventlog)
21 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
24 void set_dom_state(entity e)
26 STAT(DOM_TOTAL_PPS, e) = total_pps;
27 STAT(DOM_PPS_RED, e) = pps_red;
28 STAT(DOM_PPS_BLUE, e) = pps_blue;
29 if(domination_teams >= 3)
30 STAT(DOM_PPS_YELLOW, e) = pps_yellow;
31 if(domination_teams >= 4)
32 STAT(DOM_PPS_PINK, e) = pps_pink;
35 void dompoint_captured(entity this)
37 float old_delay, old_team, real_team;
39 // now that the delay has expired, switch to the latest team to lay claim to this point
40 entity head = this.owner;
45 dom_EventLog("taken", this.team, this.dmg_inflictor);
46 this.dmg_inflictor = NULL;
48 this.goalentity = head;
49 this.model = head.mdl;
50 this.modelindex = head.dmg;
51 this.skin = head.skin;
53 float points, wait_time;
54 if (autocvar_g_domination_point_amt)
55 points = autocvar_g_domination_point_amt;
58 if (autocvar_g_domination_point_rate)
59 wait_time = autocvar_g_domination_point_rate;
61 wait_time = this.wait;
63 if(domination_roundbased)
64 bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
66 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
68 if(this.enemy.playerid == this.enemy_playerid)
69 GameRules_scoring_add(this.enemy, DOM_TAKES, 1);
75 _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
77 _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
78 if (head.noise1 != "")
79 play2all(head.noise1);
81 this.delay = time + wait_time;
84 old_delay = this.delay;
86 this.team = real_team;
88 SUB_UseTargets (this, this, NULL);
89 this.delay = old_delay;
92 entity msg = WP_DomNeut;
95 case NUM_TEAM_1: msg = WP_DomRed; break;
96 case NUM_TEAM_2: msg = WP_DomBlue; break;
97 case NUM_TEAM_3: msg = WP_DomYellow; break;
98 case NUM_TEAM_4: msg = WP_DomPink; break;
101 WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
103 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
104 IL_EACH(g_dompoints, true,
106 if (autocvar_g_domination_point_amt)
107 points = autocvar_g_domination_point_amt;
110 if (autocvar_g_domination_point_rate)
111 wait_time = autocvar_g_domination_point_rate;
114 switch(it.goalentity.team)
116 case NUM_TEAM_1: pps_red += points/wait_time; break;
117 case NUM_TEAM_2: pps_blue += points/wait_time; break;
118 case NUM_TEAM_3: pps_yellow += points/wait_time; break;
119 case NUM_TEAM_4: pps_pink += points/wait_time; break;
121 total_pps += points/wait_time;
124 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
125 WaypointSprite_Ping(this.sprite);
129 FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
132 void AnimateDomPoint(entity this)
134 if(this.pain_finished > time)
136 this.pain_finished = time + this.t_width;
137 if(this.nextthink > this.pain_finished)
138 this.nextthink = this.pain_finished;
140 this.frame = this.frame + 1;
141 if(this.frame > this.t_length)
145 void dompointthink(entity this)
149 this.nextthink = time + 0.1;
151 //this.frame = this.frame + 1;
152 //if(this.frame > 119)
154 AnimateDomPoint(this);
158 if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
161 if(autocvar_g_domination_point_rate)
162 this.delay = time + autocvar_g_domination_point_rate;
164 this.delay = time + this.wait;
166 // give credit to the team
167 // NOTE: this defaults to 0
168 if (!domination_roundbased)
169 if (this.goalentity.netname != "")
171 if(autocvar_g_domination_point_amt)
172 fragamt = autocvar_g_domination_point_amt;
174 fragamt = this.frags;
175 TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
176 TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
178 // give credit to the individual player, if he is still there
179 if (this.enemy.playerid == this.enemy_playerid)
181 GameRules_scoring_add(this.enemy, SCORE, fragamt);
182 GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
189 void dompointtouch(entity this, entity toucher)
191 if(!IS_PLAYER(toucher))
193 if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
196 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
199 if(time < this.captime + 0.3)
202 // only valid teams can claim it
203 entity head = find(NULL, classname, "dom_team");
204 while (head && head.team != toucher.team)
205 head = find(head, classname, "dom_team");
206 if (!head || head.netname == "" || head == this.goalentity)
211 this.team = this.goalentity.team; // this stores the PREVIOUS team!
213 this.cnt = toucher.team;
214 this.owner = head; // team to switch to after the delay
215 this.dmg_inflictor = toucher;
218 // this.delay = time + cvar("g_domination_point_capturetime");
219 //this.nextthink = time + cvar("g_domination_point_capturetime");
220 //this.think = dompoint_captured;
222 // go to neutral team in the mean time
223 head = find(NULL, classname, "dom_team");
224 while (head && head.netname != "")
225 head = find(head, classname, "dom_team");
229 WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
230 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
231 WaypointSprite_Ping(this.sprite);
233 this.goalentity = head;
234 this.model = head.mdl;
235 this.modelindex = head.dmg;
236 this.skin = head.skin;
238 this.enemy = toucher; // individual player scoring
239 this.enemy_playerid = toucher.playerid;
240 dompoint_captured(this);
243 void dom_controlpoint_setup(entity this)
246 // find the spawnfunc_dom_team representing unclaimed points
247 head = find(NULL, classname, "dom_team");
248 while(head && head.netname != "")
249 head = find(head, classname, "dom_team");
251 objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
253 // copy important properties from spawnfunc_dom_team entity
254 this.goalentity = head;
255 _setmodel(this, head.mdl); // precision already set
256 this.skin = head.skin;
260 if(this.message == "")
261 this.message = " has captured a control point";
268 float points, waittime;
269 if (autocvar_g_domination_point_amt)
270 points = autocvar_g_domination_point_amt;
273 if (autocvar_g_domination_point_rate)
274 waittime = autocvar_g_domination_point_rate;
276 waittime = this.wait;
278 total_pps += points/waittime;
281 this.t_width = 0.02; // frame animation rate
283 this.t_length = 239; // maximum frame
285 setthink(this, dompointthink);
286 this.nextthink = time;
287 settouch(this, dompointtouch);
288 this.solid = SOLID_TRIGGER;
289 if(!this.flags & FL_ITEM)
290 IL_PUSH(g_items, this);
291 this.flags = FL_ITEM;
292 setsize(this, '-32 -32 -32', '32 32 32');
293 setorigin(this, this.origin + '0 0 20');
296 waypoint_spawnforitem(this);
297 WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
300 int total_control_points;
301 void Domination_count_controlpoints()
303 total_control_points = 0;
304 for (int i = 1; i <= NUM_TEAMS; ++i)
306 Team_SetNumberOfControlPoints(Team_GetTeamFromIndex(i), 0);
308 IL_EACH(g_dompoints, true,
310 ++total_control_points;
311 if (!Entity_HasValidTeam(it.goalentity))
315 entity team_ = Entity_GetTeam(it.goalentity);
316 int num_control_points = Team_GetNumberOfControlPoints(team_);
317 ++num_control_points;
318 Team_SetNumberOfControlPoints(team_, num_control_points);
322 int Domination_GetWinnerTeam()
325 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) ==
326 total_control_points)
328 winner_team = NUM_TEAM_1;
330 for (int i = 2; i <= NUM_TEAMS; ++i)
332 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) ==
333 total_control_points)
335 if (winner_team != 0)
339 winner_team = Team_IndexToTeam(i);
346 return -1; // no control points left?
349 float Domination_CheckWinner()
351 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
353 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
354 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
357 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
361 Domination_count_controlpoints();
363 float winner_team = Domination_GetWinnerTeam();
365 if(winner_team == -1)
370 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
371 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
372 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
374 else if(winner_team == -1)
376 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
377 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
381 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
386 float Domination_CheckPlayers()
391 void Domination_RoundStart()
393 FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
396 //go to best items, or control points you don't own
397 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
399 IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
401 if(it.cnt > -1) // this is just being fought
402 navigation_routerating(this, it, ratingscale, 5000);
403 else if(it.goalentity.cnt == 0) // unclaimed
404 navigation_routerating(this, it, ratingscale, 5000);
405 else if(it.goalentity.team != this.team) // other team's point
406 navigation_routerating(this, it, ratingscale, 5000);
410 void havocbot_role_dom(entity this)
415 if (navigation_goalrating_timeout(this))
417 navigation_goalrating_start(this);
418 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
419 havocbot_goalrating_items(this, 20000, this.origin, 8000);
420 //havocbot_goalrating_enemyplayers(this, 1500, this.origin, 2000);
421 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
422 navigation_goalrating_end(this);
424 navigation_goalrating_timeout_set(this);
428 MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
431 M_ARGV(0, float) = domination_teams;
432 string ret_string = "dom_team";
434 entity head = find(NULL, classname, ret_string);
437 if(head.netname != "")
439 if (Team_IsValidTeam(head.team))
441 M_ARGV(0, float) |= Team_TeamToBit(head.team);
445 head = find(head, classname, ret_string);
448 M_ARGV(1, string) = string_null;
453 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
455 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
456 FOREACH_CLIENT(IS_PLAYER(it), {
457 PutClientInServer(it);
458 if(domination_roundbased)
459 it.player_blocked = 1;
460 if(IS_REAL_CLIENT(it))
466 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
468 entity player = M_ARGV(0, entity);
470 if(domination_roundbased)
471 if(!round_handler_IsRoundStarted())
472 player.player_blocked = 1;
474 player.player_blocked = 0;
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);