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 float total_controlpoints;
299 void Domination_count_controlpoints()
301 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
302 IL_EACH(g_dompoints, true,
304 ++total_controlpoints;
305 redowned += (it.goalentity.team == NUM_TEAM_1);
306 blueowned += (it.goalentity.team == NUM_TEAM_2);
307 yellowowned += (it.goalentity.team == NUM_TEAM_3);
308 pinkowned += (it.goalentity.team == NUM_TEAM_4);
312 float Domination_GetWinnerTeam()
314 float winner_team = 0;
315 if(redowned == total_controlpoints)
316 winner_team = NUM_TEAM_1;
317 if(blueowned == total_controlpoints)
319 if(winner_team) return 0;
320 winner_team = NUM_TEAM_2;
322 if(yellowowned == total_controlpoints)
324 if(winner_team) return 0;
325 winner_team = NUM_TEAM_3;
327 if(pinkowned == total_controlpoints)
329 if(winner_team) return 0;
330 winner_team = NUM_TEAM_4;
334 return -1; // no control points left?
337 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
338 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
339 float Domination_CheckWinner()
341 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
343 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
344 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
347 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
351 Domination_count_controlpoints();
353 float winner_team = Domination_GetWinnerTeam();
355 if(winner_team == -1)
360 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
361 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
362 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
364 else if(winner_team == -1)
366 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
367 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
371 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
376 float Domination_CheckPlayers()
381 void Domination_RoundStart()
383 FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
386 //go to best items, or control points you don't own
387 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
389 IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
391 if(it.cnt > -1) // this is just being fought
392 navigation_routerating(this, it, ratingscale, 5000);
393 else if(it.goalentity.cnt == 0) // unclaimed
394 navigation_routerating(this, it, ratingscale * 0.5, 5000);
395 else if(it.goalentity.team != this.team) // other team's point
396 navigation_routerating(this, it, ratingscale * 0.2, 5000);
400 void havocbot_role_dom(entity this)
405 if (navigation_goalrating_timeout(this))
407 navigation_goalrating_start(this);
408 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
409 havocbot_goalrating_items(this, 8000, this.origin, 8000);
410 //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
411 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
412 navigation_goalrating_end(this);
414 navigation_goalrating_timeout_set(this);
418 MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
421 M_ARGV(0, float) = domination_teams;
422 string ret_string = "dom_team";
424 entity head = find(NULL, classname, ret_string);
427 if(head.netname != "")
429 if (Team_IsValidTeam(head.team))
431 M_ARGV(0, float) |= Team_TeamToBit(head.team);
435 head = find(head, classname, ret_string);
438 M_ARGV(1, string) = string_null;
443 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
445 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
446 FOREACH_CLIENT(IS_PLAYER(it), {
447 PutClientInServer(it);
448 if(domination_roundbased)
449 it.player_blocked = 1;
450 if(IS_REAL_CLIENT(it))
456 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
458 entity player = M_ARGV(0, entity);
460 if(domination_roundbased)
461 if(!round_handler_IsRoundStarted())
462 player.player_blocked = 1;
464 player.player_blocked = 0;
467 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
469 entity player = M_ARGV(0, entity);
471 set_dom_state(player);
474 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
476 entity bot = M_ARGV(0, entity);
478 bot.havocbot_role = havocbot_role_dom;
482 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
483 Control point for Domination gameplay.
485 spawnfunc(dom_controlpoint)
492 setthink(this, dom_controlpoint_setup);
493 this.nextthink = time + 0.1;
494 this.reset = dom_controlpoint_setup;
499 this.effects = this.effects | EF_LOWPRECISION;
500 if (autocvar_g_domination_point_fullbright)
501 this.effects |= EF_FULLBRIGHT;
503 IL_PUSH(g_dompoints, this);
506 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
507 Team declaration for Domination gameplay, this allows you to decide what team
508 names and control point models are used in your map.
510 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
511 can have netname set! The nameless team owns all control points at start.
515 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
517 Scoreboard color of the team (for example 4 is red and 13 is blue)
519 Model to use for control points owned by this team (for example
520 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
523 Skin of the model to use (for team skins on a single model)
525 Sound to play when this team captures a point.
526 (this is a localized sound, like a small alarm or other effect)
528 Narrator speech to play when this team captures a point.
529 (this is a global sound, like "Red team has captured a control point")
534 if(!g_domination || autocvar_g_domination_teams_override >= 2)
539 precache_model(this.model);
540 if (this.noise != "")
541 precache_sound(this.noise);
542 if (this.noise1 != "")
543 precache_sound(this.noise1);
544 this.classname = "dom_team";
545 _setmodel(this, this.model); // precision not needed
546 this.mdl = this.model;
547 this.dmg = this.modelindex;
550 // this would have to be changed if used in quakeworld
552 this.team = this.cnt + 1; // WHY are these different anyway?
556 void ScoreRules_dom(int teams)
558 if(domination_roundbased)
560 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
561 field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
562 field(SP_DOM_TAKES, "takes", 0);
567 float sp_domticks, sp_score;
568 sp_score = sp_domticks = 0;
569 if(autocvar_g_domination_disable_frags)
570 sp_domticks = SFL_SORT_PRIO_PRIMARY;
572 sp_score = SFL_SORT_PRIO_PRIMARY;
573 GameRules_scoring(teams, sp_score, sp_score, {
574 field_team(ST_DOM_TICKS, "ticks", sp_domticks);
575 field(SP_DOM_TICKS, "ticks", sp_domticks);
576 field(SP_DOM_TAKES, "takes", 0);
581 // code from here on is just to support maps that don't have control point and team entities
582 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
585 entity e = new_pure(dom_team);
586 e.netname = strzone(teamname);
588 e.model = pointmodel;
590 e.noise = strzone(Sound_fixpath(capsound));
591 e.noise1 = strzone(capnarration);
592 e.message = strzone(capmessage);
594 // this code is identical to spawnfunc_dom_team
595 _setmodel(e, e.model); // precision not needed
597 e.dmg = e.modelindex;
600 // this would have to be changed if used in quakeworld
606 void dom_spawnpoint(vector org)
609 e.classname = "dom_controlpoint";
610 setthink(e, spawnfunc_dom_controlpoint);
613 spawnfunc_dom_controlpoint(e);
616 // spawn some default teams if the map is not set up for domination
617 void dom_spawnteams(int teams)
620 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");
621 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");
623 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");
625 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");
626 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
629 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
631 // if no teams are found, spawn defaults
632 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
634 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
635 domination_teams = autocvar_g_domination_teams_override;
636 if (domination_teams < 2)
637 domination_teams = autocvar_g_domination_default_teams;
638 domination_teams = bound(2, domination_teams, 4);
639 dom_spawnteams(domination_teams);
642 entity balance = TeamBalance_CheckAllowedTeams(NULL);
643 int teams = TeamBalance_GetAllowedTeams(balance);
644 TeamBalance_Destroy(balance);
645 domination_teams = teams;
647 domination_roundbased = autocvar_g_domination_roundbased;
649 ScoreRules_dom(domination_teams);
651 if(domination_roundbased)
653 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
654 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
658 void dom_Initialize()
661 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);