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 float total_controlpoints;
301 void Domination_count_controlpoints()
303 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
304 IL_EACH(g_dompoints, true,
306 ++total_controlpoints;
307 redowned += (it.goalentity.team == NUM_TEAM_1);
308 blueowned += (it.goalentity.team == NUM_TEAM_2);
309 yellowowned += (it.goalentity.team == NUM_TEAM_3);
310 pinkowned += (it.goalentity.team == NUM_TEAM_4);
314 float Domination_GetWinnerTeam()
316 float winner_team = 0;
317 if(redowned == total_controlpoints)
318 winner_team = NUM_TEAM_1;
319 if(blueowned == total_controlpoints)
321 if(winner_team) return 0;
322 winner_team = NUM_TEAM_2;
324 if(yellowowned == total_controlpoints)
326 if(winner_team) return 0;
327 winner_team = NUM_TEAM_3;
329 if(pinkowned == total_controlpoints)
331 if(winner_team) return 0;
332 winner_team = NUM_TEAM_4;
336 return -1; // no control points left?
339 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
340 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
341 float Domination_CheckWinner()
343 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
345 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
346 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
349 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
353 Domination_count_controlpoints();
355 float winner_team = Domination_GetWinnerTeam();
357 if(winner_team == -1)
362 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
363 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
364 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
366 else if(winner_team == -1)
368 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
369 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
373 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
378 float Domination_CheckPlayers()
383 void Domination_RoundStart()
385 FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
388 //go to best items, or control points you don't own
389 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
391 IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
393 if(it.cnt > -1) // this is just being fought
394 navigation_routerating(this, it, ratingscale, 5000);
395 else if(it.goalentity.cnt == 0) // unclaimed
396 navigation_routerating(this, it, ratingscale * 0.5, 5000);
397 else if(it.goalentity.team != this.team) // other team's point
398 navigation_routerating(this, it, ratingscale * 0.2, 5000);
402 void havocbot_role_dom(entity this)
407 if (navigation_goalrating_timeout(this))
409 navigation_goalrating_start(this);
410 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
411 havocbot_goalrating_items(this, 8000, this.origin, 8000);
412 //havocbot_goalrating_enemyplayers(this, 1500, this.origin, 2000);
413 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
414 navigation_goalrating_end(this);
416 navigation_goalrating_timeout_set(this);
420 MUTATOR_HOOKFUNCTION(dom, CheckAllowedTeams)
423 M_ARGV(0, float) = domination_teams;
424 string ret_string = "dom_team";
426 entity head = find(NULL, classname, ret_string);
429 if(head.netname != "")
433 case NUM_TEAM_1: c1 = 0; break;
434 case NUM_TEAM_2: c2 = 0; break;
435 case NUM_TEAM_3: c3 = 0; break;
436 case NUM_TEAM_4: c4 = 0; break;
440 head = find(head, classname, ret_string);
443 M_ARGV(1, string) = string_null;
448 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
450 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
451 FOREACH_CLIENT(IS_PLAYER(it), {
452 PutClientInServer(it);
453 if(domination_roundbased)
454 it.player_blocked = 1;
455 if(IS_REAL_CLIENT(it))
461 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
463 entity player = M_ARGV(0, entity);
465 if(domination_roundbased)
466 if(!round_handler_IsRoundStarted())
467 player.player_blocked = 1;
469 player.player_blocked = 0;
472 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
474 entity player = M_ARGV(0, entity);
476 set_dom_state(player);
479 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
481 entity bot = M_ARGV(0, entity);
483 bot.havocbot_role = havocbot_role_dom;
487 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
488 Control point for Domination gameplay.
490 spawnfunc(dom_controlpoint)
497 setthink(this, dom_controlpoint_setup);
498 this.nextthink = time + 0.1;
499 this.reset = dom_controlpoint_setup;
504 this.effects = this.effects | EF_LOWPRECISION;
505 if (autocvar_g_domination_point_fullbright)
506 this.effects |= EF_FULLBRIGHT;
508 IL_PUSH(g_dompoints, this);
511 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
512 Team declaration for Domination gameplay, this allows you to decide what team
513 names and control point models are used in your map.
515 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
516 can have netname set! The nameless team owns all control points at start.
520 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
522 Scoreboard color of the team (for example 4 is red and 13 is blue)
524 Model to use for control points owned by this team (for example
525 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
528 Skin of the model to use (for team skins on a single model)
530 Sound to play when this team captures a point.
531 (this is a localized sound, like a small alarm or other effect)
533 Narrator speech to play when this team captures a point.
534 (this is a global sound, like "Red team has captured a control point")
539 if(!g_domination || autocvar_g_domination_teams_override >= 2)
544 precache_model(this.model);
545 if (this.noise != "")
546 precache_sound(this.noise);
547 if (this.noise1 != "")
548 precache_sound(this.noise1);
549 this.classname = "dom_team";
550 _setmodel(this, this.model); // precision not needed
551 this.mdl = this.model;
552 this.dmg = this.modelindex;
555 // this would have to be changed if used in quakeworld
557 this.team = this.cnt + 1; // WHY are these different anyway?
561 void ScoreRules_dom(int teams)
563 if(domination_roundbased)
565 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
566 field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
567 field(SP_DOM_TAKES, "takes", 0);
572 float sp_domticks, sp_score;
573 sp_score = sp_domticks = 0;
574 if(autocvar_g_domination_disable_frags)
575 sp_domticks = SFL_SORT_PRIO_PRIMARY;
577 sp_score = SFL_SORT_PRIO_PRIMARY;
578 GameRules_scoring(teams, sp_score, sp_score, {
579 field_team(ST_DOM_TICKS, "ticks", sp_domticks);
580 field(SP_DOM_TICKS, "ticks", sp_domticks);
581 field(SP_DOM_TAKES, "takes", 0);
586 // code from here on is just to support maps that don't have control point and team entities
587 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
590 entity e = new_pure(dom_team);
591 e.netname = strzone(teamname);
593 e.model = pointmodel;
595 e.noise = strzone(Sound_fixpath(capsound));
596 e.noise1 = strzone(capnarration);
597 e.message = strzone(capmessage);
599 // this code is identical to spawnfunc_dom_team
600 _setmodel(e, e.model); // precision not needed
602 e.dmg = e.modelindex;
605 // this would have to be changed if used in quakeworld
611 void dom_spawnpoint(vector org)
614 e.classname = "dom_controlpoint";
615 setthink(e, spawnfunc_dom_controlpoint);
618 spawnfunc_dom_controlpoint(e);
621 // spawn some default teams if the map is not set up for domination
622 void dom_spawnteams(int teams)
625 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");
626 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");
628 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");
630 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");
631 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
634 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
636 // if no teams are found, spawn defaults
637 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
639 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
640 domination_teams = autocvar_g_domination_teams_override;
641 if (domination_teams < 2)
642 domination_teams = autocvar_g_domination_default_teams;
643 domination_teams = bound(2, domination_teams, 4);
644 dom_spawnteams(domination_teams);
647 CheckAllowedTeams(NULL);
648 //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
651 if(c1 >= 0) teams |= BIT(0);
652 if(c2 >= 0) teams |= BIT(1);
653 if(c3 >= 0) teams |= BIT(2);
654 if(c4 >= 0) teams |= BIT(3);
655 domination_teams = teams;
657 domination_roundbased = autocvar_g_domination_roundbased;
659 ScoreRules_dom(domination_teams);
661 if(domination_roundbased)
663 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
664 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
668 void dom_Initialize()
671 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);