1 #include "sv_domination.qh"
3 #include <server/gamelog.qh>
4 #include <server/teamplay.qh>
8 int autocvar_g_domination_default_teams;
9 bool autocvar_g_domination_disable_frags;
10 int autocvar_g_domination_point_amt;
11 bool autocvar_g_domination_point_fullbright;
12 float autocvar_g_domination_round_timelimit;
13 float autocvar_g_domination_warmup;
14 float autocvar_g_domination_point_rate;
15 int autocvar_g_domination_teams_override;
17 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
19 if(autocvar_sv_eventlog)
20 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
23 void set_dom_state(entity e)
25 STAT(DOM_TOTAL_PPS, e) = total_pps;
26 STAT(DOM_PPS_RED, e) = pps_red;
27 STAT(DOM_PPS_BLUE, e) = pps_blue;
28 if(domination_teams >= 3)
29 STAT(DOM_PPS_YELLOW, e) = pps_yellow;
30 if(domination_teams >= 4)
31 STAT(DOM_PPS_PINK, e) = pps_pink;
34 void dompoint_captured(entity this)
36 float old_delay, old_team, real_team;
38 // now that the delay has expired, switch to the latest team to lay claim to this point
39 entity head = this.owner;
44 dom_EventLog("taken", this.team, this.dmg_inflictor);
45 this.dmg_inflictor = NULL;
47 this.goalentity = head;
48 this.model = head.mdl;
49 this.modelindex = head.dmg;
50 this.skin = head.skin;
52 float points, wait_time;
53 if (autocvar_g_domination_point_amt)
54 points = autocvar_g_domination_point_amt;
57 if (autocvar_g_domination_point_rate)
58 wait_time = autocvar_g_domination_point_rate;
60 wait_time = this.wait;
62 if(domination_roundbased)
63 bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
65 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
67 if(this.enemy.playerid == this.enemy_playerid)
68 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);
79 if (head.noise1 != "")
80 play2all(head.noise1);
82 this.delay = time + wait_time;
85 old_delay = this.delay;
87 this.team = real_team;
89 SUB_UseTargets (this, this, NULL);
90 this.delay = old_delay;
93 entity msg = WP_DomNeut;
96 case NUM_TEAM_1: msg = WP_DomRed; break;
97 case NUM_TEAM_2: msg = WP_DomBlue; break;
98 case NUM_TEAM_3: msg = WP_DomYellow; break;
99 case NUM_TEAM_4: msg = WP_DomPink; break;
102 WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
104 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
105 IL_EACH(g_dompoints, true,
107 if (autocvar_g_domination_point_amt)
108 points = autocvar_g_domination_point_amt;
111 if (autocvar_g_domination_point_rate)
112 wait_time = autocvar_g_domination_point_rate;
115 switch(it.goalentity.team)
117 case NUM_TEAM_1: pps_red += points/wait_time; break;
118 case NUM_TEAM_2: pps_blue += points/wait_time; break;
119 case NUM_TEAM_3: pps_yellow += points/wait_time; break;
120 case NUM_TEAM_4: pps_pink += points/wait_time; break;
122 total_pps += points/wait_time;
125 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
126 WaypointSprite_Ping(this.sprite);
130 FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
133 void AnimateDomPoint(entity this)
135 if(this.pain_finished > time)
137 this.pain_finished = time + this.t_width;
138 if(this.nextthink > this.pain_finished)
139 this.nextthink = this.pain_finished;
141 this.frame = this.frame + 1;
142 if(this.frame > this.t_length)
146 void dompointthink(entity this)
150 this.nextthink = time + 0.1;
152 //this.frame = this.frame + 1;
153 //if(this.frame > 119)
155 AnimateDomPoint(this);
159 if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
162 if(autocvar_g_domination_point_rate)
163 this.delay = time + autocvar_g_domination_point_rate;
165 this.delay = time + this.wait;
167 // give credit to the team
168 // NOTE: this defaults to 0
169 if (!domination_roundbased)
170 if (this.goalentity.netname != "")
172 if(autocvar_g_domination_point_amt)
173 fragamt = autocvar_g_domination_point_amt;
175 fragamt = this.frags;
176 TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
177 TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
179 // give credit to the individual player, if he is still there
180 if (this.enemy.playerid == this.enemy_playerid)
182 GameRules_scoring_add(this.enemy, SCORE, fragamt);
183 GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
190 void dompointtouch(entity this, entity toucher)
192 if(!IS_PLAYER(toucher))
194 if(GetResource(toucher, RES_HEALTH) < 1)
197 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
200 if(time < this.captime + 0.3)
203 // only valid teams can claim it
204 entity head = find(NULL, classname, "dom_team");
205 while (head && head.team != toucher.team)
206 head = find(head, classname, "dom_team");
207 if (!head || head.netname == "" || head == this.goalentity)
212 this.team = this.goalentity.team; // this stores the PREVIOUS team!
214 this.cnt = toucher.team;
215 this.owner = head; // team to switch to after the delay
216 this.dmg_inflictor = toucher;
219 // this.delay = time + cvar("g_domination_point_capturetime");
220 //this.nextthink = time + cvar("g_domination_point_capturetime");
221 //this.think = dompoint_captured;
223 // go to neutral team in the mean time
224 head = find(NULL, classname, "dom_team");
225 while (head && head.netname != "")
226 head = find(head, classname, "dom_team");
230 WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
231 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
232 WaypointSprite_Ping(this.sprite);
234 this.goalentity = head;
235 this.model = head.mdl;
236 this.modelindex = head.dmg;
237 this.skin = head.skin;
239 this.enemy = toucher; // individual player scoring
240 this.enemy_playerid = toucher.playerid;
241 dompoint_captured(this);
244 void dom_controlpoint_setup(entity this)
247 // find the spawnfunc_dom_team representing unclaimed points
248 head = find(NULL, classname, "dom_team");
249 while(head && head.netname != "")
250 head = find(head, classname, "dom_team");
252 objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
254 // copy important properties from spawnfunc_dom_team entity
255 this.goalentity = head;
256 _setmodel(this, head.mdl); // precision already set
257 this.skin = head.skin;
261 if(this.message == "")
262 this.message = " has captured a control point";
269 float points, waittime;
270 if (autocvar_g_domination_point_amt)
271 points = autocvar_g_domination_point_amt;
274 if (autocvar_g_domination_point_rate)
275 waittime = autocvar_g_domination_point_rate;
277 waittime = this.wait;
279 total_pps += points/waittime;
282 this.t_width = 0.02; // frame animation rate
284 this.t_length = 239; // maximum frame
286 setthink(this, dompointthink);
287 this.nextthink = time;
288 settouch(this, dompointtouch);
289 this.solid = SOLID_TRIGGER;
290 if(!this.flags & FL_ITEM)
291 IL_PUSH(g_items, this);
292 this.flags = FL_ITEM;
293 setsize(this, '-32 -32 -32', '32 32 32');
294 setorigin(this, this.origin + '0 0 20');
297 waypoint_spawnforitem(this);
298 WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
301 int total_control_points;
302 void Domination_count_controlpoints()
304 total_control_points = 0;
305 for (int i = 1; i <= NUM_TEAMS; ++i)
307 Team_SetNumberOfControlPoints(Team_GetTeamFromIndex(i), 0);
309 IL_EACH(g_dompoints, true,
311 ++total_control_points;
312 if (!Entity_HasValidTeam(it.goalentity))
316 entity team_ = Entity_GetTeam(it.goalentity);
317 int num_control_points = Team_GetNumberOfControlPoints(team_);
318 ++num_control_points;
319 Team_SetNumberOfControlPoints(team_, num_control_points);
323 int Domination_GetWinnerTeam()
326 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(1)) ==
327 total_control_points)
329 winner_team = NUM_TEAM_1;
331 for (int i = 2; i <= NUM_TEAMS; ++i)
333 if (Team_GetNumberOfControlPoints(Team_GetTeamFromIndex(i)) ==
334 total_control_points)
336 if (winner_team != 0)
340 winner_team = Team_IndexToTeam(i);
347 return -1; // no control points left?
350 bool Domination_CheckWinner()
352 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
354 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
355 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
358 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
362 Domination_count_controlpoints();
364 float winner_team = Domination_GetWinnerTeam();
366 if(winner_team == -1)
371 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
372 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
373 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
375 else if(winner_team == -1)
377 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
378 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
382 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
387 bool Domination_CheckPlayers()
392 void Domination_RoundStart()
394 FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
397 //go to best items, or control points you don't own
398 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
400 IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
402 if(it.cnt > -1) // this is just being fought
403 navigation_routerating(this, it, ratingscale, 5000);
404 else if(it.goalentity.cnt == 0) // unclaimed
405 navigation_routerating(this, it, ratingscale, 5000);
406 else if(it.goalentity.team != this.team) // other team's point
407 navigation_routerating(this, it, ratingscale, 5000);
411 void havocbot_role_dom(entity this)
416 if (navigation_goalrating_timeout(this))
418 navigation_goalrating_start(this);
419 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
420 havocbot_goalrating_items(this, 20000, this.origin, 8000);
421 //havocbot_goalrating_enemyplayers(this, 1500, this.origin, 2000);
422 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
423 navigation_goalrating_end(this);
425 navigation_goalrating_timeout_set(this);
429 MUTATOR_HOOKFUNCTION(dom, TeamBalance_CheckAllowedTeams)
432 M_ARGV(0, float) = domination_teams;
433 string ret_string = "dom_team";
435 entity head = find(NULL, classname, ret_string);
438 if(head.netname != "")
440 if (Team_IsValidTeam(head.team))
442 M_ARGV(0, float) |= Team_TeamToBit(head.team);
446 head = find(head, classname, ret_string);
449 M_ARGV(1, string) = string_null;
454 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
456 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
457 FOREACH_CLIENT(IS_PLAYER(it), {
458 PutClientInServer(it);
459 if(domination_roundbased)
460 it.player_blocked = 1;
461 if(IS_REAL_CLIENT(it))
467 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
469 entity player = M_ARGV(0, entity);
471 if(domination_roundbased)
472 player.player_blocked = !round_handler_IsRoundStarted();
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);