1 #include "sv_domination.qh"
3 #include <server/teamplay.qh>
7 int autocvar_g_domination_default_teams;
8 bool autocvar_g_domination_disable_frags;
9 int autocvar_g_domination_point_amt;
10 bool autocvar_g_domination_point_fullbright;
11 float autocvar_g_domination_round_timelimit;
12 float autocvar_g_domination_warmup;
13 float autocvar_g_domination_point_rate;
14 int autocvar_g_domination_teams_override;
16 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
18 if(autocvar_sv_eventlog)
19 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
22 void set_dom_state(entity e)
24 STAT(DOM_TOTAL_PPS, e) = total_pps;
25 STAT(DOM_PPS_RED, e) = pps_red;
26 STAT(DOM_PPS_BLUE, e) = pps_blue;
27 if(domination_teams >= 3)
28 STAT(DOM_PPS_YELLOW, e) = pps_yellow;
29 if(domination_teams >= 4)
30 STAT(DOM_PPS_PINK, e) = pps_pink;
33 void dompoint_captured(entity this)
35 float old_delay, old_team, real_team;
37 // now that the delay has expired, switch to the latest team to lay claim to this point
38 entity head = this.owner;
43 dom_EventLog("taken", this.team, this.dmg_inflictor);
44 this.dmg_inflictor = NULL;
46 this.goalentity = head;
47 this.model = head.mdl;
48 this.modelindex = head.dmg;
49 this.skin = head.skin;
51 float points, wait_time;
52 if (autocvar_g_domination_point_amt)
53 points = autocvar_g_domination_point_amt;
56 if (autocvar_g_domination_point_rate)
57 wait_time = autocvar_g_domination_point_rate;
59 wait_time = this.wait;
61 if(domination_roundbased)
62 bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
64 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
66 if(this.enemy.playerid == this.enemy_playerid)
67 GameRules_scoring_add(this.enemy, DOM_TAKES, 1);
73 _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
75 _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
76 if (head.noise1 != "")
77 play2all(head.noise1);
79 this.delay = time + wait_time;
82 old_delay = this.delay;
84 this.team = real_team;
86 SUB_UseTargets (this, this, NULL);
87 this.delay = old_delay;
90 entity msg = WP_DomNeut;
93 case NUM_TEAM_1: msg = WP_DomRed; break;
94 case NUM_TEAM_2: msg = WP_DomBlue; break;
95 case NUM_TEAM_3: msg = WP_DomYellow; break;
96 case NUM_TEAM_4: msg = WP_DomPink; break;
99 WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
101 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
102 IL_EACH(g_dompoints, true,
104 if (autocvar_g_domination_point_amt)
105 points = autocvar_g_domination_point_amt;
108 if (autocvar_g_domination_point_rate)
109 wait_time = autocvar_g_domination_point_rate;
112 switch(it.goalentity.team)
114 case NUM_TEAM_1: pps_red += points/wait_time; break;
115 case NUM_TEAM_2: pps_blue += points/wait_time; break;
116 case NUM_TEAM_3: pps_yellow += points/wait_time; break;
117 case NUM_TEAM_4: pps_pink += points/wait_time; break;
119 total_pps += points/wait_time;
122 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
123 WaypointSprite_Ping(this.sprite);
127 FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
130 void AnimateDomPoint(entity this)
132 if(this.pain_finished > time)
134 this.pain_finished = time + this.t_width;
135 if(this.nextthink > this.pain_finished)
136 this.nextthink = this.pain_finished;
138 this.frame = this.frame + 1;
139 if(this.frame > this.t_length)
143 void dompointthink(entity this)
147 this.nextthink = time + 0.1;
149 //this.frame = this.frame + 1;
150 //if(this.frame > 119)
152 AnimateDomPoint(this);
156 if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
159 if(autocvar_g_domination_point_rate)
160 this.delay = time + autocvar_g_domination_point_rate;
162 this.delay = time + this.wait;
164 // give credit to the team
165 // NOTE: this defaults to 0
166 if (!domination_roundbased)
167 if (this.goalentity.netname != "")
169 if(autocvar_g_domination_point_amt)
170 fragamt = autocvar_g_domination_point_amt;
172 fragamt = this.frags;
173 TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
174 TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
176 // give credit to the individual player, if he is still there
177 if (this.enemy.playerid == this.enemy_playerid)
179 GameRules_scoring_add(this.enemy, SCORE, fragamt);
180 GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
187 void dompointtouch(entity this, entity toucher)
189 if(!IS_PLAYER(toucher))
191 if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
194 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
197 if(time < this.captime + 0.3)
200 // only valid teams can claim it
201 entity head = find(NULL, classname, "dom_team");
202 while (head && head.team != toucher.team)
203 head = find(head, classname, "dom_team");
204 if (!head || head.netname == "" || head == this.goalentity)
209 this.team = this.goalentity.team; // this stores the PREVIOUS team!
211 this.cnt = toucher.team;
212 this.owner = head; // team to switch to after the delay
213 this.dmg_inflictor = toucher;
216 // this.delay = time + cvar("g_domination_point_capturetime");
217 //this.nextthink = time + cvar("g_domination_point_capturetime");
218 //this.think = dompoint_captured;
220 // go to neutral team in the mean time
221 head = find(NULL, classname, "dom_team");
222 while (head && head.netname != "")
223 head = find(head, classname, "dom_team");
227 WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
228 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
229 WaypointSprite_Ping(this.sprite);
231 this.goalentity = head;
232 this.model = head.mdl;
233 this.modelindex = head.dmg;
234 this.skin = head.skin;
236 this.enemy = toucher; // individual player scoring
237 this.enemy_playerid = toucher.playerid;
238 dompoint_captured(this);
241 void dom_controlpoint_setup(entity this)
244 // find the spawnfunc_dom_team representing unclaimed points
245 head = find(NULL, classname, "dom_team");
246 while(head && head.netname != "")
247 head = find(head, classname, "dom_team");
249 objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
251 // copy important properties from spawnfunc_dom_team entity
252 this.goalentity = head;
253 _setmodel(this, head.mdl); // precision already set
254 this.skin = head.skin;
258 if(this.message == "")
259 this.message = " has captured a control point";
266 float points, waittime;
267 if (autocvar_g_domination_point_amt)
268 points = autocvar_g_domination_point_amt;
271 if (autocvar_g_domination_point_rate)
272 waittime = autocvar_g_domination_point_rate;
274 waittime = this.wait;
276 total_pps += points/waittime;
279 this.t_width = 0.02; // frame animation rate
281 this.t_length = 239; // maximum frame
283 setthink(this, dompointthink);
284 this.nextthink = time;
285 settouch(this, dompointtouch);
286 this.solid = SOLID_TRIGGER;
287 if(!this.flags & FL_ITEM)
288 IL_PUSH(g_items, this);
289 this.flags = FL_ITEM;
290 setsize(this, '-32 -32 -32', '32 32 32');
291 setorigin(this, this.origin + '0 0 20');
294 waypoint_spawnforitem(this);
295 WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
298 int total_control_points;
299 void Domination_count_controlpoints()
301 total_control_points = 0;
302 for (int i = 1; i <= NUM_TEAMS; ++i)
304 Team_SetNumberOfControlPoints(Team_GetTeamFromIndex(i), 0);
306 IL_EACH(g_dompoints, true,
308 ++total_control_points;
309 if (!Entity_HasValidTeam(it.goalentity))
313 entity team_ = Entity_GetTeam(it.goalentity);
314 int num_control_points = Team_GetNumberOfControlPoints(team_);
315 ++num_control_points;
316 Team_SetNumberOfControlPoints(team_, num_control_points);
320 int Domination_GetWinnerTeam()
323 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) ==
324 total_control_points)
326 winner_team = NUM_TEAM_1;
328 for (int i = 2; i <= NUM_TEAMS; ++i)
330 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) ==
331 total_control_points)
333 if (winner_team != 0)
337 winner_team = Team_IndexToTeam(i);
344 return -1; // no control points left?
347 bool Domination_CheckWinner()
349 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
351 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
352 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
355 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
359 Domination_count_controlpoints();
361 float winner_team = Domination_GetWinnerTeam();
363 if(winner_team == -1)
368 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
369 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
370 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
372 else if(winner_team == -1)
374 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
375 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
379 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
384 bool Domination_CheckPlayers()
389 void Domination_RoundStart()
391 FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
394 //go to best items, or control points you don't own
395 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
397 IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
399 if(it.cnt > -1) // this is just being fought
400 navigation_routerating(this, it, ratingscale, 5000);
401 else if(it.goalentity.cnt == 0) // unclaimed
402 navigation_routerating(this, it, ratingscale, 5000);
403 else if(it.goalentity.team != this.team) // other team's point
404 navigation_routerating(this, it, ratingscale, 5000);
408 void havocbot_role_dom(entity this)
413 if (navigation_goalrating_timeout(this))
415 navigation_goalrating_start(this);
416 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
417 havocbot_goalrating_items(this, 20000, this.origin, 8000);
418 //havocbot_goalrating_enemyplayers(this, 1500, this.origin, 2000);
419 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
420 navigation_goalrating_end(this);
422 navigation_goalrating_timeout_set(this);
426 MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
429 M_ARGV(0, float) = domination_teams;
430 string ret_string = "dom_team";
432 entity head = find(NULL, classname, ret_string);
435 if(head.netname != "")
437 if (Team_IsValidTeam(head.team))
439 M_ARGV(0, float) |= Team_TeamToBit(head.team);
443 head = find(head, classname, ret_string);
446 M_ARGV(1, string) = string_null;
451 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
453 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
454 FOREACH_CLIENT(IS_PLAYER(it), {
455 PutClientInServer(it);
456 if(domination_roundbased)
457 it.player_blocked = 1;
458 if(IS_REAL_CLIENT(it))
464 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
466 entity player = M_ARGV(0, entity);
468 if(domination_roundbased)
469 if(!round_handler_IsRoundStarted())
470 player.player_blocked = 1;
472 player.player_blocked = 0;
475 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
477 entity player = M_ARGV(0, entity);
479 set_dom_state(player);
482 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
484 entity bot = M_ARGV(0, entity);
486 bot.havocbot_role = havocbot_role_dom;
490 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
491 Control point for Domination gameplay.
493 spawnfunc(dom_controlpoint)
500 setthink(this, dom_controlpoint_setup);
501 this.nextthink = time + 0.1;
502 this.reset = dom_controlpoint_setup;
507 this.effects = this.effects | EF_LOWPRECISION;
508 if (autocvar_g_domination_point_fullbright)
509 this.effects |= EF_FULLBRIGHT;
511 IL_PUSH(g_dompoints, this);
514 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
515 Team declaration for Domination gameplay, this allows you to decide what team
516 names and control point models are used in your map.
518 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
519 can have netname set! The nameless team owns all control points at start.
523 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
525 Scoreboard color of the team (for example 4 is red and 13 is blue)
527 Model to use for control points owned by this team (for example
528 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
531 Skin of the model to use (for team skins on a single model)
533 Sound to play when this team captures a point.
534 (this is a localized sound, like a small alarm or other effect)
536 Narrator speech to play when this team captures a point.
537 (this is a global sound, like "Red team has captured a control point")
542 if(!g_domination || autocvar_g_domination_teams_override >= 2)
547 precache_model(this.model);
548 if (this.noise != "")
549 precache_sound(this.noise);
550 if (this.noise1 != "")
551 precache_sound(this.noise1);
552 this.classname = "dom_team";
553 _setmodel(this, this.model); // precision not needed
554 this.mdl = this.model;
555 this.dmg = this.modelindex;
558 // this would have to be changed if used in quakeworld
560 this.team = this.cnt + 1; // WHY are these different anyway?
564 void ScoreRules_dom(int teams)
566 if(domination_roundbased)
568 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
569 field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
570 field(SP_DOM_TAKES, "takes", 0);
575 float sp_domticks, sp_score;
576 sp_score = sp_domticks = 0;
577 if(autocvar_g_domination_disable_frags)
578 sp_domticks = SFL_SORT_PRIO_PRIMARY;
580 sp_score = SFL_SORT_PRIO_PRIMARY;
581 GameRules_scoring(teams, sp_score, sp_score, {
582 field_team(ST_DOM_TICKS, "ticks", sp_domticks);
583 field(SP_DOM_TICKS, "ticks", sp_domticks);
584 field(SP_DOM_TAKES, "takes", 0);
589 // code from here on is just to support maps that don't have control point and team entities
590 void dom_spawnteam(string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
593 entity e = new_pure(dom_team);
594 e.netname = strzone(teamname);
596 e.model = pointmodel;
598 e.noise = strzone(Sound_fixpath(capsound));
599 e.noise1 = strzone(capnarration);
600 e.message = strzone(capmessage);
602 // this code is identical to spawnfunc_dom_team
603 _setmodel(e, e.model); // precision not needed
605 e.dmg = e.modelindex;
608 // this would have to be changed if used in quakeworld
614 void dom_spawnpoint(vector org)
617 e.classname = "dom_controlpoint";
618 setthink(e, spawnfunc_dom_controlpoint);
621 spawnfunc_dom_controlpoint(e);
624 // spawn some default teams if the map is not set up for domination
625 void dom_spawnteams(int teams)
628 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");
629 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");
631 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");
633 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");
634 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
637 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
639 // if no teams are found, spawn defaults
640 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
642 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
643 domination_teams = autocvar_g_domination_teams_override;
644 if (domination_teams < 2)
645 domination_teams = autocvar_g_domination_default_teams;
646 domination_teams = bound(2, domination_teams, 4);
647 dom_spawnteams(domination_teams);
650 entity balance = TeamBalance_CheckAllowedTeams(NULL);
651 int teams = TeamBalance_GetAllowedTeams(balance);
652 TeamBalance_Destroy(balance);
653 domination_teams = teams;
655 domination_roundbased = autocvar_g_domination_roundbased;
657 ScoreRules_dom(domination_teams);
659 if(domination_roundbased)
661 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
662 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
666 void dom_Initialize()
669 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);