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 e.dom_total_pps = total_pps;
25 e.dom_pps_red = pps_red;
26 e.dom_pps_blue = pps_blue;
27 if(domination_teams >= 3)
28 e.dom_pps_yellow = pps_yellow;
29 if(domination_teams >= 4)
30 e.dom_pps_pink = 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, 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 != "")
431 case NUM_TEAM_1: c1 = 0; break;
432 case NUM_TEAM_2: c2 = 0; break;
433 case NUM_TEAM_3: c3 = 0; break;
434 case NUM_TEAM_4: c4 = 0; break;
438 head = find(head, classname, ret_string);
441 M_ARGV(1, string) = string_null;
446 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
448 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
449 FOREACH_CLIENT(IS_PLAYER(it), {
450 PutClientInServer(it);
451 if(domination_roundbased)
452 it.player_blocked = 1;
453 if(IS_REAL_CLIENT(it))
459 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
461 entity player = M_ARGV(0, entity);
463 if(domination_roundbased)
464 if(!round_handler_IsRoundStarted())
465 player.player_blocked = 1;
467 player.player_blocked = 0;
470 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
472 entity player = M_ARGV(0, entity);
474 set_dom_state(player);
477 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
479 entity bot = M_ARGV(0, entity);
481 bot.havocbot_role = havocbot_role_dom;
485 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
486 Control point for Domination gameplay.
488 spawnfunc(dom_controlpoint)
495 setthink(this, dom_controlpoint_setup);
496 this.nextthink = time + 0.1;
497 this.reset = dom_controlpoint_setup;
502 this.effects = this.effects | EF_LOWPRECISION;
503 if (autocvar_g_domination_point_fullbright)
504 this.effects |= EF_FULLBRIGHT;
506 IL_PUSH(g_dompoints, this);
509 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
510 Team declaration for Domination gameplay, this allows you to decide what team
511 names and control point models are used in your map.
513 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
514 can have netname set! The nameless team owns all control points at start.
518 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
520 Scoreboard color of the team (for example 4 is red and 13 is blue)
522 Model to use for control points owned by this team (for example
523 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
526 Skin of the model to use (for team skins on a single model)
528 Sound to play when this team captures a point.
529 (this is a localized sound, like a small alarm or other effect)
531 Narrator speech to play when this team captures a point.
532 (this is a global sound, like "Red team has captured a control point")
537 if(!g_domination || autocvar_g_domination_teams_override >= 2)
542 precache_model(this.model);
543 if (this.noise != "")
544 precache_sound(this.noise);
545 if (this.noise1 != "")
546 precache_sound(this.noise1);
547 this.classname = "dom_team";
548 _setmodel(this, this.model); // precision not needed
549 this.mdl = this.model;
550 this.dmg = this.modelindex;
553 // this would have to be changed if used in quakeworld
555 this.team = this.cnt + 1; // WHY are these different anyway?
559 void ScoreRules_dom(int teams)
561 if(domination_roundbased)
563 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
564 field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
565 field(SP_DOM_TAKES, "takes", 0);
570 float sp_domticks, sp_score;
571 sp_score = sp_domticks = 0;
572 if(autocvar_g_domination_disable_frags)
573 sp_domticks = SFL_SORT_PRIO_PRIMARY;
575 sp_score = SFL_SORT_PRIO_PRIMARY;
576 GameRules_scoring(teams, sp_score, sp_score, {
577 field_team(ST_DOM_TICKS, "ticks", sp_domticks);
578 field(SP_DOM_TICKS, "ticks", sp_domticks);
579 field(SP_DOM_TAKES, "takes", 0);
584 // code from here on is just to support maps that don't have control point and team entities
585 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
588 entity e = new_pure(dom_team);
589 e.netname = strzone(teamname);
591 e.model = pointmodel;
593 e.noise = strzone(Sound_fixpath(capsound));
594 e.noise1 = strzone(capnarration);
595 e.message = strzone(capmessage);
597 // this code is identical to spawnfunc_dom_team
598 _setmodel(e, e.model); // precision not needed
600 e.dmg = e.modelindex;
603 // this would have to be changed if used in quakeworld
609 void dom_spawnpoint(vector org)
612 e.classname = "dom_controlpoint";
613 setthink(e, spawnfunc_dom_controlpoint);
616 spawnfunc_dom_controlpoint(e);
619 // spawn some default teams if the map is not set up for domination
620 void dom_spawnteams(int teams)
623 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");
624 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");
626 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");
628 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");
629 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
632 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
634 // if no teams are found, spawn defaults
635 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
637 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
638 domination_teams = autocvar_g_domination_teams_override;
639 if (domination_teams < 2)
640 domination_teams = autocvar_g_domination_default_teams;
641 domination_teams = bound(2, domination_teams, 4);
642 dom_spawnteams(domination_teams);
645 CheckAllowedTeams(NULL);
646 //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
649 if(c1 >= 0) teams |= BIT(0);
650 if(c2 >= 0) teams |= BIT(1);
651 if(c3 >= 0) teams |= BIT(2);
652 if(c4 >= 0) teams |= BIT(3);
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);