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 PlayerScore_Add(this.enemy, SP_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), LAMBDA(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 PlayerScore_Add(this.enemy, SP_SCORE, fragamt);
180 PlayerScore_Add(this.enemy, SP_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), LAMBDA(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 (this.bot_strategytime < time)
407 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
408 navigation_goalrating_start(this);
409 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
410 havocbot_goalrating_items(this, 8000, this.origin, 8000);
411 //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
412 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
413 navigation_goalrating_end(this);
417 MUTATOR_HOOKFUNCTION(dom, CheckAllowedTeams)
420 M_ARGV(0, float) = domination_teams;
421 string ret_string = "dom_team";
423 entity head = find(NULL, classname, ret_string);
426 if(head.netname != "")
430 case NUM_TEAM_1: c1 = 0; break;
431 case NUM_TEAM_2: c2 = 0; break;
432 case NUM_TEAM_3: c3 = 0; break;
433 case NUM_TEAM_4: c4 = 0; break;
437 head = find(head, classname, ret_string);
440 M_ARGV(1, string) = string_null;
445 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
447 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
448 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
449 PutClientInServer(it);
450 if(domination_roundbased)
451 it.player_blocked = 1;
452 if(IS_REAL_CLIENT(it))
458 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
460 entity player = M_ARGV(0, entity);
462 if(domination_roundbased)
463 if(!round_handler_IsRoundStarted())
464 player.player_blocked = 1;
466 player.player_blocked = 0;
469 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
471 entity player = M_ARGV(0, entity);
473 set_dom_state(player);
476 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
478 entity bot = M_ARGV(0, entity);
480 bot.havocbot_role = havocbot_role_dom;
484 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
485 Control point for Domination gameplay.
487 spawnfunc(dom_controlpoint)
494 setthink(this, dom_controlpoint_setup);
495 this.nextthink = time + 0.1;
496 this.reset = dom_controlpoint_setup;
501 this.effects = this.effects | EF_LOWPRECISION;
502 if (autocvar_g_domination_point_fullbright)
503 this.effects |= EF_FULLBRIGHT;
505 IL_PUSH(g_dompoints, this);
508 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
509 Team declaration for Domination gameplay, this allows you to decide what team
510 names and control point models are used in your map.
512 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
513 can have netname set! The nameless team owns all control points at start.
517 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
519 Scoreboard color of the team (for example 4 is red and 13 is blue)
521 Model to use for control points owned by this team (for example
522 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
525 Skin of the model to use (for team skins on a single model)
527 Sound to play when this team captures a point.
528 (this is a localized sound, like a small alarm or other effect)
530 Narrator speech to play when this team captures a point.
531 (this is a global sound, like "Red team has captured a control point")
536 if(!g_domination || autocvar_g_domination_teams_override >= 2)
541 precache_model(this.model);
542 if (this.noise != "")
543 precache_sound(this.noise);
544 if (this.noise1 != "")
545 precache_sound(this.noise1);
546 this.classname = "dom_team";
547 _setmodel(this, this.model); // precision not needed
548 this.mdl = this.model;
549 this.dmg = this.modelindex;
552 // this would have to be changed if used in quakeworld
554 this.team = this.cnt + 1; // WHY are these different anyway?
558 void ScoreRules_dom(int teams)
560 if(domination_roundbased)
562 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
563 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
564 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
565 ScoreRules_basics_end();
569 float sp_domticks, sp_score;
570 sp_score = sp_domticks = 0;
571 if(autocvar_g_domination_disable_frags)
572 sp_domticks = SFL_SORT_PRIO_PRIMARY;
574 sp_score = SFL_SORT_PRIO_PRIMARY;
575 ScoreRules_basics(teams, sp_score, sp_score, true);
576 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
577 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
578 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
579 ScoreRules_basics_end();
583 // code from here on is just to support maps that don't have control point and team entities
584 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
587 entity e = new_pure(dom_team);
588 e.netname = strzone(teamname);
590 e.model = pointmodel;
592 e.noise = strzone(Sound_fixpath(capsound));
593 e.noise1 = strzone(capnarration);
594 e.message = strzone(capmessage);
596 // this code is identical to spawnfunc_dom_team
597 _setmodel(e, e.model); // precision not needed
599 e.dmg = e.modelindex;
602 // this would have to be changed if used in quakeworld
608 void dom_spawnpoint(vector org)
611 e.classname = "dom_controlpoint";
612 setthink(e, spawnfunc_dom_controlpoint);
615 spawnfunc_dom_controlpoint(e);
618 // spawn some default teams if the map is not set up for domination
619 void dom_spawnteams(int teams)
622 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");
623 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");
625 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");
627 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");
628 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
631 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
633 // if no teams are found, spawn defaults
634 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
636 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
637 domination_teams = autocvar_g_domination_teams_override;
638 if (domination_teams < 2)
639 domination_teams = autocvar_g_domination_default_teams;
640 domination_teams = bound(2, domination_teams, 4);
641 dom_spawnteams(domination_teams);
644 CheckAllowedTeams(NULL);
645 //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
648 if(c1 >= 0) teams |= BIT(0);
649 if(c2 >= 0) teams |= BIT(1);
650 if(c3 >= 0) teams |= BIT(2);
651 if(c4 >= 0) teams |= BIT(3);
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);