1 #include "gamemode_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 (toucher.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 entity team_ = Entity_GetTeam(it.goalentity);
310 int num_control_points = Team_GetNumberOfControlPoints(team_);
311 ++num_control_points;
312 Team_SetNumberOfControlPoints(team_, num_control_points);
316 int Domination_GetWinnerTeam()
319 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) ==
320 total_control_points)
322 winner_team = NUM_TEAM_1;
324 for (int i = 2; i <= NUM_TEAMS; ++i)
326 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) ==
327 total_control_points)
329 if (winner_team != 0)
333 winner_team = Team_IndexToTeam(i);
340 return -1; // no control points left?
343 float Domination_CheckWinner()
345 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
347 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
348 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
351 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
355 Domination_count_controlpoints();
357 float winner_team = Domination_GetWinnerTeam();
359 if(winner_team == -1)
364 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
365 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
366 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
368 else if(winner_team == -1)
370 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
371 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
375 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
380 float Domination_CheckPlayers()
385 void Domination_RoundStart()
387 FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
390 //go to best items, or control points you don't own
391 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
393 IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
395 if(it.cnt > -1) // this is just being fought
396 navigation_routerating(this, it, ratingscale, 5000);
397 else if(it.goalentity.cnt == 0) // unclaimed
398 navigation_routerating(this, it, ratingscale * 0.5, 5000);
399 else if(it.goalentity.team != this.team) // other team's point
400 navigation_routerating(this, it, ratingscale * 0.2, 5000);
404 void havocbot_role_dom(entity this)
409 if (navigation_goalrating_timeout(this))
411 navigation_goalrating_start(this);
412 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
413 havocbot_goalrating_items(this, 8000, this.origin, 8000);
414 //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
415 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
416 navigation_goalrating_end(this);
418 navigation_goalrating_timeout_set(this);
422 MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
425 M_ARGV(0, float) = domination_teams;
426 string ret_string = "dom_team";
428 entity head = find(NULL, classname, ret_string);
431 if(head.netname != "")
433 if (Team_IsValidTeam(head.team))
435 M_ARGV(0, float) |= Team_TeamToBit(head.team);
439 head = find(head, classname, ret_string);
442 M_ARGV(1, string) = string_null;
447 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
449 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
450 FOREACH_CLIENT(IS_PLAYER(it), {
451 PutClientInServer(it);
452 if(domination_roundbased)
453 it.player_blocked = 1;
454 if(IS_REAL_CLIENT(it))
460 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
462 entity player = M_ARGV(0, entity);
464 if(domination_roundbased)
465 if(!round_handler_IsRoundStarted())
466 player.player_blocked = 1;
468 player.player_blocked = 0;
471 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
473 entity player = M_ARGV(0, entity);
475 set_dom_state(player);
478 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
480 entity bot = M_ARGV(0, entity);
482 bot.havocbot_role = havocbot_role_dom;
486 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
487 Control point for Domination gameplay.
489 spawnfunc(dom_controlpoint)
496 setthink(this, dom_controlpoint_setup);
497 this.nextthink = time + 0.1;
498 this.reset = dom_controlpoint_setup;
503 this.effects = this.effects | EF_LOWPRECISION;
504 if (autocvar_g_domination_point_fullbright)
505 this.effects |= EF_FULLBRIGHT;
507 IL_PUSH(g_dompoints, this);
510 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
511 Team declaration for Domination gameplay, this allows you to decide what team
512 names and control point models are used in your map.
514 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
515 can have netname set! The nameless team owns all control points at start.
519 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
521 Scoreboard color of the team (for example 4 is red and 13 is blue)
523 Model to use for control points owned by this team (for example
524 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
527 Skin of the model to use (for team skins on a single model)
529 Sound to play when this team captures a point.
530 (this is a localized sound, like a small alarm or other effect)
532 Narrator speech to play when this team captures a point.
533 (this is a global sound, like "Red team has captured a control point")
538 if(!g_domination || autocvar_g_domination_teams_override >= 2)
543 precache_model(this.model);
544 if (this.noise != "")
545 precache_sound(this.noise);
546 if (this.noise1 != "")
547 precache_sound(this.noise1);
548 this.classname = "dom_team";
549 _setmodel(this, this.model); // precision not needed
550 this.mdl = this.model;
551 this.dmg = this.modelindex;
554 // this would have to be changed if used in quakeworld
556 this.team = this.cnt + 1; // WHY are these different anyway?
560 void ScoreRules_dom(int teams)
562 if(domination_roundbased)
564 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
565 field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
566 field(SP_DOM_TAKES, "takes", 0);
571 float sp_domticks, sp_score;
572 sp_score = sp_domticks = 0;
573 if(autocvar_g_domination_disable_frags)
574 sp_domticks = SFL_SORT_PRIO_PRIMARY;
576 sp_score = SFL_SORT_PRIO_PRIMARY;
577 GameRules_scoring(teams, sp_score, sp_score, {
578 field_team(ST_DOM_TICKS, "ticks", sp_domticks);
579 field(SP_DOM_TICKS, "ticks", sp_domticks);
580 field(SP_DOM_TAKES, "takes", 0);
585 // code from here on is just to support maps that don't have control point and team entities
586 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
589 entity e = new_pure(dom_team);
590 e.netname = strzone(teamname);
592 e.model = pointmodel;
594 e.noise = strzone(Sound_fixpath(capsound));
595 e.noise1 = strzone(capnarration);
596 e.message = strzone(capmessage);
598 // this code is identical to spawnfunc_dom_team
599 _setmodel(e, e.model); // precision not needed
601 e.dmg = e.modelindex;
604 // this would have to be changed if used in quakeworld
610 void dom_spawnpoint(vector org)
613 e.classname = "dom_controlpoint";
614 setthink(e, spawnfunc_dom_controlpoint);
617 spawnfunc_dom_controlpoint(e);
620 // spawn some default teams if the map is not set up for domination
621 void dom_spawnteams(int teams)
624 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");
625 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");
627 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");
629 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");
630 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
633 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
635 // if no teams are found, spawn defaults
636 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
638 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
639 domination_teams = autocvar_g_domination_teams_override;
640 if (domination_teams < 2)
641 domination_teams = autocvar_g_domination_default_teams;
642 domination_teams = bound(2, domination_teams, 4);
643 dom_spawnteams(domination_teams);
646 entity balance = TeamBalance_CheckAllowedTeams(NULL);
647 int teams = TeamBalance_GetAllowedTeams(balance);
648 TeamBalance_Destroy(balance);
649 domination_teams = teams;
651 domination_roundbased = autocvar_g_domination_roundbased;
653 ScoreRules_dom(domination_teams);
655 if(domination_roundbased)
657 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
658 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
662 void dom_Initialize()
665 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);