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 FOREACH_ENTITY_CLASS("dom_controlpoint", true, LAMBDA(
103 if (autocvar_g_domination_point_amt)
104 points = autocvar_g_domination_point_amt;
107 if (autocvar_g_domination_point_rate)
108 wait_time = autocvar_g_domination_point_rate;
111 switch(it.goalentity.team)
113 case NUM_TEAM_1: pps_red += points/wait_time; break;
114 case NUM_TEAM_2: pps_blue += points/wait_time; break;
115 case NUM_TEAM_3: pps_yellow += points/wait_time; break;
116 case NUM_TEAM_4: pps_pink += points/wait_time; break;
118 total_pps += points/wait_time;
121 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
122 WaypointSprite_Ping(this.sprite);
126 FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(set_dom_state(it)));
129 void AnimateDomPoint(entity this)
131 if(this.pain_finished > time)
133 this.pain_finished = time + this.t_width;
134 if(this.nextthink > this.pain_finished)
135 this.nextthink = this.pain_finished;
137 this.frame = this.frame + 1;
138 if(this.frame > this.t_length)
142 void dompointthink(entity this)
146 this.nextthink = time + 0.1;
148 //this.frame = this.frame + 1;
149 //if(this.frame > 119)
151 AnimateDomPoint(this);
155 if (gameover || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
158 if(autocvar_g_domination_point_rate)
159 this.delay = time + autocvar_g_domination_point_rate;
161 this.delay = time + this.wait;
163 // give credit to the team
164 // NOTE: this defaults to 0
165 if (!domination_roundbased)
166 if (this.goalentity.netname != "")
168 if(autocvar_g_domination_point_amt)
169 fragamt = autocvar_g_domination_point_amt;
171 fragamt = this.frags;
172 TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
173 TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
175 // give credit to the individual player, if he is still there
176 if (this.enemy.playerid == this.enemy_playerid)
178 PlayerScore_Add(this.enemy, SP_SCORE, fragamt);
179 PlayerScore_Add(this.enemy, SP_DOM_TICKS, fragamt);
186 void dompointtouch(entity this, entity toucher)
188 if (!IS_PLAYER(toucher))
190 if (toucher.health < 1)
193 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
196 if(time < this.captime + 0.3)
199 // only valid teams can claim it
200 entity head = find(NULL, classname, "dom_team");
201 while (head && head.team != toucher.team)
202 head = find(head, classname, "dom_team");
203 if (!head || head.netname == "" || head == this.goalentity)
208 this.team = this.goalentity.team; // this stores the PREVIOUS team!
210 this.cnt = toucher.team;
211 this.owner = head; // team to switch to after the delay
212 this.dmg_inflictor = toucher;
215 // this.delay = time + cvar("g_domination_point_capturetime");
216 //this.nextthink = time + cvar("g_domination_point_capturetime");
217 //this.think = dompoint_captured;
219 // go to neutral team in the mean time
220 head = find(NULL, classname, "dom_team");
221 while (head && head.netname != "")
222 head = find(head, classname, "dom_team");
226 WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
227 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
228 WaypointSprite_Ping(this.sprite);
230 this.goalentity = head;
231 this.model = head.mdl;
232 this.modelindex = head.dmg;
233 this.skin = head.skin;
235 this.enemy = toucher; // individual player scoring
236 this.enemy_playerid = toucher.playerid;
237 dompoint_captured(this);
240 void dom_controlpoint_setup(entity this)
243 // find the spawnfunc_dom_team representing unclaimed points
244 head = find(NULL, classname, "dom_team");
245 while(head && head.netname != "")
246 head = find(head, classname, "dom_team");
248 objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
250 // copy important properties from spawnfunc_dom_team entity
251 this.goalentity = head;
252 _setmodel(this, head.mdl); // precision already set
253 this.skin = head.skin;
257 if(this.message == "")
258 this.message = " has captured a control point";
265 float points, waittime;
266 if (autocvar_g_domination_point_amt)
267 points = autocvar_g_domination_point_amt;
270 if (autocvar_g_domination_point_rate)
271 waittime = autocvar_g_domination_point_rate;
273 waittime = this.wait;
275 total_pps += points/waittime;
278 this.t_width = 0.02; // frame animation rate
280 this.t_length = 239; // maximum frame
282 setthink(this, dompointthink);
283 this.nextthink = time;
284 settouch(this, dompointtouch);
285 this.solid = SOLID_TRIGGER;
286 this.flags = FL_ITEM;
287 IL_PUSH(g_items, this);
288 setsize(this, '-32 -32 -32', '32 32 32');
289 setorigin(this, this.origin + '0 0 20');
292 waypoint_spawnforitem(this);
293 WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
296 float total_controlpoints;
297 void Domination_count_controlpoints()
299 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
300 FOREACH_ENTITY_CLASS("dom_controlpoint", true,
302 ++total_controlpoints;
303 redowned += (it.goalentity.team == NUM_TEAM_1);
304 blueowned += (it.goalentity.team == NUM_TEAM_2);
305 yellowowned += (it.goalentity.team == NUM_TEAM_3);
306 pinkowned += (it.goalentity.team == NUM_TEAM_4);
310 float Domination_GetWinnerTeam()
312 float winner_team = 0;
313 if(redowned == total_controlpoints)
314 winner_team = NUM_TEAM_1;
315 if(blueowned == total_controlpoints)
317 if(winner_team) return 0;
318 winner_team = NUM_TEAM_2;
320 if(yellowowned == total_controlpoints)
322 if(winner_team) return 0;
323 winner_team = NUM_TEAM_3;
325 if(pinkowned == total_controlpoints)
327 if(winner_team) return 0;
328 winner_team = NUM_TEAM_4;
332 return -1; // no control points left?
335 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
336 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
337 float Domination_CheckWinner()
339 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
341 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
342 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
343 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
347 Domination_count_controlpoints();
349 float winner_team = Domination_GetWinnerTeam();
351 if(winner_team == -1)
356 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
357 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
358 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
360 else if(winner_team == -1)
362 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
363 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
366 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
371 float Domination_CheckPlayers()
376 void Domination_RoundStart()
378 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(it.player_blocked = false));
381 //go to best items, or control points you don't own
382 void havocbot_role_dom(entity this)
387 if (this.bot_strategytime < time)
389 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
390 navigation_goalrating_start(this);
391 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
392 havocbot_goalrating_items(this, 8000, this.origin, 8000);
393 //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
394 //havocbot_goalrating_waypoints(1, this.origin, 1000);
395 navigation_goalrating_end(this);
399 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
402 M_ARGV(0, float) = domination_teams;
403 string ret_string = "dom_team";
405 entity head = find(NULL, classname, ret_string);
408 if(head.netname != "")
412 case NUM_TEAM_1: c1 = 0; break;
413 case NUM_TEAM_2: c2 = 0; break;
414 case NUM_TEAM_3: c3 = 0; break;
415 case NUM_TEAM_4: c4 = 0; break;
419 head = find(head, classname, ret_string);
422 M_ARGV(1, string) = string_null;
427 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
429 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
430 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
431 PutClientInServer(it);
432 if(domination_roundbased)
433 it.player_blocked = 1;
434 if(IS_REAL_CLIENT(it))
440 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
442 entity player = M_ARGV(0, entity);
444 if(domination_roundbased)
445 if(!round_handler_IsRoundStarted())
446 player.player_blocked = 1;
448 player.player_blocked = 0;
451 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
453 entity player = M_ARGV(0, entity);
455 set_dom_state(player);
458 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
460 entity bot = M_ARGV(0, entity);
462 bot.havocbot_role = havocbot_role_dom;
466 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
467 Control point for Domination gameplay.
469 spawnfunc(dom_controlpoint)
476 setthink(this, dom_controlpoint_setup);
477 this.nextthink = time + 0.1;
478 this.reset = dom_controlpoint_setup;
483 this.effects = this.effects | EF_LOWPRECISION;
484 if (autocvar_g_domination_point_fullbright)
485 this.effects |= EF_FULLBRIGHT;
488 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
489 Team declaration for Domination gameplay, this allows you to decide what team
490 names and control point models are used in your map.
492 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
493 can have netname set! The nameless team owns all control points at start.
497 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
499 Scoreboard color of the team (for example 4 is red and 13 is blue)
501 Model to use for control points owned by this team (for example
502 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
505 Skin of the model to use (for team skins on a single model)
507 Sound to play when this team captures a point.
508 (this is a localized sound, like a small alarm or other effect)
510 Narrator speech to play when this team captures a point.
511 (this is a global sound, like "Red team has captured a control point")
516 if(!g_domination || autocvar_g_domination_teams_override >= 2)
521 precache_model(this.model);
522 if (this.noise != "")
523 precache_sound(this.noise);
524 if (this.noise1 != "")
525 precache_sound(this.noise1);
526 this.classname = "dom_team";
527 _setmodel(this, this.model); // precision not needed
528 this.mdl = this.model;
529 this.dmg = this.modelindex;
532 // this would have to be changed if used in quakeworld
534 this.team = this.cnt + 1; // WHY are these different anyway?
538 void ScoreRules_dom(int teams)
540 if(domination_roundbased)
542 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
543 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
544 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
545 ScoreRules_basics_end();
549 float sp_domticks, sp_score;
550 sp_score = sp_domticks = 0;
551 if(autocvar_g_domination_disable_frags)
552 sp_domticks = SFL_SORT_PRIO_PRIMARY;
554 sp_score = SFL_SORT_PRIO_PRIMARY;
555 ScoreRules_basics(teams, sp_score, sp_score, true);
556 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
557 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
558 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
559 ScoreRules_basics_end();
563 // code from here on is just to support maps that don't have control point and team entities
564 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
567 entity e = new_pure(dom_team);
568 e.netname = strzone(teamname);
570 e.model = pointmodel;
572 e.noise = strzone(Sound_fixpath(capsound));
573 e.noise1 = strzone(capnarration);
574 e.message = strzone(capmessage);
576 // this code is identical to spawnfunc_dom_team
577 _setmodel(e, e.model); // precision not needed
579 e.dmg = e.modelindex;
582 // this would have to be changed if used in quakeworld
588 void dom_spawnpoint(vector org)
591 e.classname = "dom_controlpoint";
592 setthink(e, spawnfunc_dom_controlpoint);
595 spawnfunc_dom_controlpoint(e);
598 // spawn some default teams if the map is not set up for domination
599 void dom_spawnteams(int teams)
602 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");
603 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");
605 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");
607 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");
608 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
611 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
613 // if no teams are found, spawn defaults
614 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
616 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
617 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
618 dom_spawnteams(domination_teams);
621 CheckAllowedTeams(NULL);
622 //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
625 if(c1 >= 0) teams |= BIT(0);
626 if(c2 >= 0) teams |= BIT(1);
627 if(c3 >= 0) teams |= BIT(2);
628 if(c4 >= 0) teams |= BIT(3);
629 domination_teams = teams;
631 domination_roundbased = autocvar_g_domination_roundbased;
633 ScoreRules_dom(domination_teams);
635 if(domination_roundbased)
637 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
638 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
642 void dom_Initialize()
645 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);