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 //TODO: team_ seems to be NULL
313 int num_control_points = Team_GetNumberOfControlPoints(team_);
314 ++num_control_points;
315 Team_SetNumberOfControlPoints(team_, num_control_points);
319 int Domination_GetWinnerTeam()
322 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) ==
323 total_control_points)
325 winner_team = NUM_TEAM_1;
327 for (int i = 2; i <= NUM_TEAMS; ++i)
329 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) ==
330 total_control_points)
332 if (winner_team != 0)
336 winner_team = Team_IndexToTeam(i);
343 return -1; // no control points left?
346 float Domination_CheckWinner()
348 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
350 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
351 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
354 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
358 Domination_count_controlpoints();
360 float winner_team = Domination_GetWinnerTeam();
362 if(winner_team == -1)
367 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
368 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
369 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
371 else if(winner_team == -1)
373 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
374 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
378 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
383 float Domination_CheckPlayers()
388 void Domination_RoundStart()
390 FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
393 //go to best items, or control points you don't own
394 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
396 IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
398 if(it.cnt > -1) // this is just being fought
399 navigation_routerating(this, it, ratingscale, 5000);
400 else if(it.goalentity.cnt == 0) // unclaimed
401 navigation_routerating(this, it, ratingscale * 0.5, 5000);
402 else if(it.goalentity.team != this.team) // other team's point
403 navigation_routerating(this, it, ratingscale * 0.2, 5000);
407 void havocbot_role_dom(entity this)
412 if (navigation_goalrating_timeout(this))
414 navigation_goalrating_start(this);
415 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
416 havocbot_goalrating_items(this, 8000, this.origin, 8000);
417 //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
418 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
419 navigation_goalrating_end(this);
421 navigation_goalrating_timeout_set(this);
425 MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
428 M_ARGV(0, float) = domination_teams;
429 string ret_string = "dom_team";
431 entity head = find(NULL, classname, ret_string);
434 if(head.netname != "")
436 if (Team_IsValidTeam(head.team))
438 M_ARGV(0, float) |= Team_TeamToBit(head.team);
442 head = find(head, classname, ret_string);
445 M_ARGV(1, string) = string_null;
450 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
452 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
453 FOREACH_CLIENT(IS_PLAYER(it), {
454 PutClientInServer(it);
455 if(domination_roundbased)
456 it.player_blocked = 1;
457 if(IS_REAL_CLIENT(it))
463 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
465 entity player = M_ARGV(0, entity);
467 if(domination_roundbased)
468 if(!round_handler_IsRoundStarted())
469 player.player_blocked = 1;
471 player.player_blocked = 0;
474 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
476 entity player = M_ARGV(0, entity);
478 set_dom_state(player);
481 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
483 entity bot = M_ARGV(0, entity);
485 bot.havocbot_role = havocbot_role_dom;
489 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
490 Control point for Domination gameplay.
492 spawnfunc(dom_controlpoint)
499 setthink(this, dom_controlpoint_setup);
500 this.nextthink = time + 0.1;
501 this.reset = dom_controlpoint_setup;
506 this.effects = this.effects | EF_LOWPRECISION;
507 if (autocvar_g_domination_point_fullbright)
508 this.effects |= EF_FULLBRIGHT;
510 IL_PUSH(g_dompoints, this);
513 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
514 Team declaration for Domination gameplay, this allows you to decide what team
515 names and control point models are used in your map.
517 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
518 can have netname set! The nameless team owns all control points at start.
522 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
524 Scoreboard color of the team (for example 4 is red and 13 is blue)
526 Model to use for control points owned by this team (for example
527 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
530 Skin of the model to use (for team skins on a single model)
532 Sound to play when this team captures a point.
533 (this is a localized sound, like a small alarm or other effect)
535 Narrator speech to play when this team captures a point.
536 (this is a global sound, like "Red team has captured a control point")
541 if(!g_domination || autocvar_g_domination_teams_override >= 2)
546 precache_model(this.model);
547 if (this.noise != "")
548 precache_sound(this.noise);
549 if (this.noise1 != "")
550 precache_sound(this.noise1);
551 this.classname = "dom_team";
552 _setmodel(this, this.model); // precision not needed
553 this.mdl = this.model;
554 this.dmg = this.modelindex;
557 // this would have to be changed if used in quakeworld
559 this.team = this.cnt + 1; // WHY are these different anyway?
563 void ScoreRules_dom(int teams)
565 if(domination_roundbased)
567 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
568 field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
569 field(SP_DOM_TAKES, "takes", 0);
574 float sp_domticks, sp_score;
575 sp_score = sp_domticks = 0;
576 if(autocvar_g_domination_disable_frags)
577 sp_domticks = SFL_SORT_PRIO_PRIMARY;
579 sp_score = SFL_SORT_PRIO_PRIMARY;
580 GameRules_scoring(teams, sp_score, sp_score, {
581 field_team(ST_DOM_TICKS, "ticks", sp_domticks);
582 field(SP_DOM_TICKS, "ticks", sp_domticks);
583 field(SP_DOM_TAKES, "takes", 0);
588 // code from here on is just to support maps that don't have control point and team entities
589 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
592 entity e = new_pure(dom_team);
593 e.netname = strzone(teamname);
595 e.model = pointmodel;
597 e.noise = strzone(Sound_fixpath(capsound));
598 e.noise1 = strzone(capnarration);
599 e.message = strzone(capmessage);
601 // this code is identical to spawnfunc_dom_team
602 _setmodel(e, e.model); // precision not needed
604 e.dmg = e.modelindex;
607 // this would have to be changed if used in quakeworld
613 void dom_spawnpoint(vector org)
616 e.classname = "dom_controlpoint";
617 setthink(e, spawnfunc_dom_controlpoint);
620 spawnfunc_dom_controlpoint(e);
623 // spawn some default teams if the map is not set up for domination
624 void dom_spawnteams(int teams)
627 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");
628 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");
630 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");
632 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");
633 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
636 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
638 // if no teams are found, spawn defaults
639 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
641 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
642 domination_teams = autocvar_g_domination_teams_override;
643 if (domination_teams < 2)
644 domination_teams = autocvar_g_domination_default_teams;
645 domination_teams = bound(2, domination_teams, 4);
646 dom_spawnteams(domination_teams);
649 entity balance = TeamBalance_CheckAllowedTeams(NULL);
650 int teams = TeamBalance_GetAllowedTeams(balance);
651 TeamBalance_Destroy(balance);
652 domination_teams = teams;
654 domination_roundbased = autocvar_g_domination_roundbased;
656 ScoreRules_dom(domination_teams);
658 if(domination_roundbased)
660 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
661 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
665 void dom_Initialize()
668 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);