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 (toucher.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 entity team_ = Entity_GetTeam(it.goalentity);
312 int num_control_points = Team_GetNumberOfControlPoints(team_);
313 ++num_control_points;
314 Team_SetNumberOfControlPoints(team_, num_control_points);
318 int Domination_GetWinnerTeam()
321 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) ==
322 total_control_points)
324 winner_team = NUM_TEAM_1;
326 for (int i = 2; i <= NUM_TEAMS; ++i)
328 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) ==
329 total_control_points)
331 if (winner_team != 0)
335 winner_team = Team_IndexToTeam(i);
342 return -1; // no control points left?
345 float Domination_CheckWinner()
347 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
349 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
350 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
353 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
357 Domination_count_controlpoints();
359 float winner_team = Domination_GetWinnerTeam();
361 if(winner_team == -1)
366 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
367 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
368 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
370 else if(winner_team == -1)
372 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
373 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
377 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
382 float Domination_CheckPlayers()
387 void Domination_RoundStart()
389 FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
392 //go to best items, or control points you don't own
393 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
395 IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
397 if(it.cnt > -1) // this is just being fought
398 navigation_routerating(this, it, ratingscale, 5000);
399 else if(it.goalentity.cnt == 0) // unclaimed
400 navigation_routerating(this, it, ratingscale * 0.5, 5000);
401 else if(it.goalentity.team != this.team) // other team's point
402 navigation_routerating(this, it, ratingscale * 0.2, 5000);
406 void havocbot_role_dom(entity this)
411 if (navigation_goalrating_timeout(this))
413 navigation_goalrating_start(this);
414 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
415 havocbot_goalrating_items(this, 8000, this.origin, 8000);
416 //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
417 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
418 navigation_goalrating_end(this);
420 navigation_goalrating_timeout_set(this);
424 MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
427 M_ARGV(0, float) = domination_teams;
428 string ret_string = "dom_team";
430 entity head = find(NULL, classname, ret_string);
433 if(head.netname != "")
435 if (Team_IsValidTeam(head.team))
437 M_ARGV(0, float) |= Team_TeamToBit(head.team);
441 head = find(head, classname, ret_string);
444 M_ARGV(1, string) = string_null;
449 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
451 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
452 FOREACH_CLIENT(IS_PLAYER(it), {
453 PutClientInServer(it);
454 if(domination_roundbased)
455 it.player_blocked = 1;
456 if(IS_REAL_CLIENT(it))
462 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
464 entity player = M_ARGV(0, entity);
466 if(domination_roundbased)
467 if(!round_handler_IsRoundStarted())
468 player.player_blocked = 1;
470 player.player_blocked = 0;
473 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
475 entity player = M_ARGV(0, entity);
477 set_dom_state(player);
480 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
482 entity bot = M_ARGV(0, entity);
484 bot.havocbot_role = havocbot_role_dom;
488 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
489 Control point for Domination gameplay.
491 spawnfunc(dom_controlpoint)
498 setthink(this, dom_controlpoint_setup);
499 this.nextthink = time + 0.1;
500 this.reset = dom_controlpoint_setup;
505 this.effects = this.effects | EF_LOWPRECISION;
506 if (autocvar_g_domination_point_fullbright)
507 this.effects |= EF_FULLBRIGHT;
509 IL_PUSH(g_dompoints, this);
512 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
513 Team declaration for Domination gameplay, this allows you to decide what team
514 names and control point models are used in your map.
516 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
517 can have netname set! The nameless team owns all control points at start.
521 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
523 Scoreboard color of the team (for example 4 is red and 13 is blue)
525 Model to use for control points owned by this team (for example
526 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
529 Skin of the model to use (for team skins on a single model)
531 Sound to play when this team captures a point.
532 (this is a localized sound, like a small alarm or other effect)
534 Narrator speech to play when this team captures a point.
535 (this is a global sound, like "Red team has captured a control point")
540 if(!g_domination || autocvar_g_domination_teams_override >= 2)
545 precache_model(this.model);
546 if (this.noise != "")
547 precache_sound(this.noise);
548 if (this.noise1 != "")
549 precache_sound(this.noise1);
550 this.classname = "dom_team";
551 _setmodel(this, this.model); // precision not needed
552 this.mdl = this.model;
553 this.dmg = this.modelindex;
556 // this would have to be changed if used in quakeworld
558 this.team = this.cnt + 1; // WHY are these different anyway?
562 void ScoreRules_dom(int teams)
564 if(domination_roundbased)
566 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
567 field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
568 field(SP_DOM_TAKES, "takes", 0);
573 float sp_domticks, sp_score;
574 sp_score = sp_domticks = 0;
575 if(autocvar_g_domination_disable_frags)
576 sp_domticks = SFL_SORT_PRIO_PRIMARY;
578 sp_score = SFL_SORT_PRIO_PRIMARY;
579 GameRules_scoring(teams, sp_score, sp_score, {
580 field_team(ST_DOM_TICKS, "ticks", sp_domticks);
581 field(SP_DOM_TICKS, "ticks", sp_domticks);
582 field(SP_DOM_TAKES, "takes", 0);
587 // code from here on is just to support maps that don't have control point and team entities
588 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
591 entity e = new_pure(dom_team);
592 e.netname = strzone(teamname);
594 e.model = pointmodel;
596 e.noise = strzone(Sound_fixpath(capsound));
597 e.noise1 = strzone(capnarration);
598 e.message = strzone(capmessage);
600 // this code is identical to spawnfunc_dom_team
601 _setmodel(e, e.model); // precision not needed
603 e.dmg = e.modelindex;
606 // this would have to be changed if used in quakeworld
612 void dom_spawnpoint(vector org)
615 e.classname = "dom_controlpoint";
616 setthink(e, spawnfunc_dom_controlpoint);
619 spawnfunc_dom_controlpoint(e);
622 // spawn some default teams if the map is not set up for domination
623 void dom_spawnteams(int teams)
626 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");
627 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");
629 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");
631 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");
632 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
635 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
637 // if no teams are found, spawn defaults
638 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
640 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
641 domination_teams = autocvar_g_domination_teams_override;
642 if (domination_teams < 2)
643 domination_teams = autocvar_g_domination_default_teams;
644 domination_teams = bound(2, domination_teams, 4);
645 dom_spawnteams(domination_teams);
648 entity balance = TeamBalance_CheckAllowedTeams(NULL);
649 int teams = TeamBalance_GetAllowedTeams(balance);
650 TeamBalance_Destroy(balance);
651 domination_teams = teams;
653 domination_roundbased = autocvar_g_domination_roundbased;
655 ScoreRules_dom(domination_teams);
657 if(domination_roundbased)
659 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
660 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
664 void dom_Initialize()
667 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);