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 setsize(this, '-32 -32 -32', '32 32 32');
288 setorigin(this, this.origin + '0 0 20');
291 waypoint_spawnforitem(this);
292 WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
295 float total_controlpoints;
296 void Domination_count_controlpoints()
298 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
299 FOREACH_ENTITY_CLASS("dom_controlpoint", true,
301 ++total_controlpoints;
302 redowned += (it.goalentity.team == NUM_TEAM_1);
303 blueowned += (it.goalentity.team == NUM_TEAM_2);
304 yellowowned += (it.goalentity.team == NUM_TEAM_3);
305 pinkowned += (it.goalentity.team == NUM_TEAM_4);
309 float Domination_GetWinnerTeam()
311 float winner_team = 0;
312 if(redowned == total_controlpoints)
313 winner_team = NUM_TEAM_1;
314 if(blueowned == total_controlpoints)
316 if(winner_team) return 0;
317 winner_team = NUM_TEAM_2;
319 if(yellowowned == total_controlpoints)
321 if(winner_team) return 0;
322 winner_team = NUM_TEAM_3;
324 if(pinkowned == total_controlpoints)
326 if(winner_team) return 0;
327 winner_team = NUM_TEAM_4;
331 return -1; // no control points left?
334 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
335 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
336 float Domination_CheckWinner()
338 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
340 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
341 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
342 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
346 Domination_count_controlpoints();
348 float winner_team = Domination_GetWinnerTeam();
350 if(winner_team == -1)
355 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
356 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
357 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
359 else if(winner_team == -1)
361 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
362 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
365 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
370 float Domination_CheckPlayers()
375 void Domination_RoundStart()
377 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(it.player_blocked = false));
380 //go to best items, or control points you don't own
381 void havocbot_role_dom(entity this)
386 if (this.bot_strategytime < time)
388 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
389 navigation_goalrating_start(this);
390 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
391 havocbot_goalrating_items(this, 8000, this.origin, 8000);
392 //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
393 //havocbot_goalrating_waypoints(1, this.origin, 1000);
394 navigation_goalrating_end(this);
398 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
401 M_ARGV(0, float) = domination_teams;
402 string ret_string = "dom_team";
404 entity head = find(NULL, classname, ret_string);
407 if(head.netname != "")
411 case NUM_TEAM_1: c1 = 0; break;
412 case NUM_TEAM_2: c2 = 0; break;
413 case NUM_TEAM_3: c3 = 0; break;
414 case NUM_TEAM_4: c4 = 0; break;
418 head = find(head, classname, ret_string);
421 M_ARGV(1, string) = string_null;
426 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
428 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
429 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
430 PutClientInServer(it);
431 if(domination_roundbased)
432 it.player_blocked = 1;
433 if(IS_REAL_CLIENT(it))
439 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
441 entity player = M_ARGV(0, entity);
443 if(domination_roundbased)
444 if(!round_handler_IsRoundStarted())
445 player.player_blocked = 1;
447 player.player_blocked = 0;
450 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
452 entity player = M_ARGV(0, entity);
454 set_dom_state(player);
457 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
459 entity bot = M_ARGV(0, entity);
461 bot.havocbot_role = havocbot_role_dom;
465 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
466 Control point for Domination gameplay.
468 spawnfunc(dom_controlpoint)
475 setthink(this, dom_controlpoint_setup);
476 this.nextthink = time + 0.1;
477 this.reset = dom_controlpoint_setup;
482 this.effects = this.effects | EF_LOWPRECISION;
483 if (autocvar_g_domination_point_fullbright)
484 this.effects |= EF_FULLBRIGHT;
487 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
488 Team declaration for Domination gameplay, this allows you to decide what team
489 names and control point models are used in your map.
491 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
492 can have netname set! The nameless team owns all control points at start.
496 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
498 Scoreboard color of the team (for example 4 is red and 13 is blue)
500 Model to use for control points owned by this team (for example
501 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
504 Skin of the model to use (for team skins on a single model)
506 Sound to play when this team captures a point.
507 (this is a localized sound, like a small alarm or other effect)
509 Narrator speech to play when this team captures a point.
510 (this is a global sound, like "Red team has captured a control point")
515 if(!g_domination || autocvar_g_domination_teams_override >= 2)
520 precache_model(this.model);
521 if (this.noise != "")
522 precache_sound(this.noise);
523 if (this.noise1 != "")
524 precache_sound(this.noise1);
525 this.classname = "dom_team";
526 _setmodel(this, this.model); // precision not needed
527 this.mdl = this.model;
528 this.dmg = this.modelindex;
531 // this would have to be changed if used in quakeworld
533 this.team = this.cnt + 1; // WHY are these different anyway?
537 void ScoreRules_dom(int teams)
539 if(domination_roundbased)
541 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
542 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
543 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
544 ScoreRules_basics_end();
548 float sp_domticks, sp_score;
549 sp_score = sp_domticks = 0;
550 if(autocvar_g_domination_disable_frags)
551 sp_domticks = SFL_SORT_PRIO_PRIMARY;
553 sp_score = SFL_SORT_PRIO_PRIMARY;
554 ScoreRules_basics(teams, sp_score, sp_score, true);
555 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
556 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
557 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
558 ScoreRules_basics_end();
562 // code from here on is just to support maps that don't have control point and team entities
563 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
566 entity e = new_pure(dom_team);
567 e.netname = strzone(teamname);
569 e.model = pointmodel;
571 e.noise = strzone(Sound_fixpath(capsound));
572 e.noise1 = strzone(capnarration);
573 e.message = strzone(capmessage);
575 // this code is identical to spawnfunc_dom_team
576 _setmodel(e, e.model); // precision not needed
578 e.dmg = e.modelindex;
581 // this would have to be changed if used in quakeworld
587 void dom_spawnpoint(vector org)
590 e.classname = "dom_controlpoint";
591 setthink(e, spawnfunc_dom_controlpoint);
594 spawnfunc_dom_controlpoint(e);
597 // spawn some default teams if the map is not set up for domination
598 void dom_spawnteams(int teams)
601 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");
602 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");
604 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");
606 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");
607 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
610 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
612 // if no teams are found, spawn defaults
613 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
615 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
616 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
617 dom_spawnteams(domination_teams);
620 CheckAllowedTeams(NULL);
621 //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
624 if(c1 >= 0) teams |= BIT(0);
625 if(c2 >= 0) teams |= BIT(1);
626 if(c3 >= 0) teams |= BIT(2);
627 if(c4 >= 0) teams |= BIT(3);
628 domination_teams = teams;
630 domination_roundbased = autocvar_g_domination_roundbased;
632 ScoreRules_dom(domination_teams);
634 if(domination_roundbased)
636 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
637 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
641 void dom_Initialize()
644 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);