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 (gameover || 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);
345 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
349 Domination_count_controlpoints();
351 float winner_team = Domination_GetWinnerTeam();
353 if(winner_team == -1)
358 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
359 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
360 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
362 else if(winner_team == -1)
364 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
365 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
368 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
373 float Domination_CheckPlayers()
378 void Domination_RoundStart()
380 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(it.player_blocked = false));
383 //go to best items, or control points you don't own
384 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
386 IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
388 if(it.cnt > -1) // this is just being fought
389 navigation_routerating(this, it, ratingscale, 5000);
390 else if(it.goalentity.cnt == 0) // unclaimed
391 navigation_routerating(this, it, ratingscale * 0.5, 5000);
392 else if(it.goalentity.team != this.team) // other team's point
393 navigation_routerating(this, it, ratingscale * 0.2, 5000);
397 void havocbot_role_dom(entity this)
402 if (this.bot_strategytime < time)
404 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
405 navigation_goalrating_start(this);
406 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
407 havocbot_goalrating_items(this, 8000, this.origin, 8000);
408 //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
409 //havocbot_goalrating_waypoints(1, this.origin, 1000);
410 navigation_goalrating_end(this);
414 MUTATOR_HOOKFUNCTION(dom, CheckAllowedTeams)
417 M_ARGV(0, float) = domination_teams;
418 string ret_string = "dom_team";
420 entity head = find(NULL, classname, ret_string);
423 if(head.netname != "")
427 case NUM_TEAM_1: c1 = 0; break;
428 case NUM_TEAM_2: c2 = 0; break;
429 case NUM_TEAM_3: c3 = 0; break;
430 case NUM_TEAM_4: c4 = 0; break;
434 head = find(head, classname, ret_string);
437 M_ARGV(1, string) = string_null;
442 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
444 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
445 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
446 PutClientInServer(it);
447 if(domination_roundbased)
448 it.player_blocked = 1;
449 if(IS_REAL_CLIENT(it))
455 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
457 entity player = M_ARGV(0, entity);
459 if(domination_roundbased)
460 if(!round_handler_IsRoundStarted())
461 player.player_blocked = 1;
463 player.player_blocked = 0;
466 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
468 entity player = M_ARGV(0, entity);
470 set_dom_state(player);
473 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
475 entity bot = M_ARGV(0, entity);
477 bot.havocbot_role = havocbot_role_dom;
481 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
482 Control point for Domination gameplay.
484 spawnfunc(dom_controlpoint)
491 setthink(this, dom_controlpoint_setup);
492 this.nextthink = time + 0.1;
493 this.reset = dom_controlpoint_setup;
498 this.effects = this.effects | EF_LOWPRECISION;
499 if (autocvar_g_domination_point_fullbright)
500 this.effects |= EF_FULLBRIGHT;
502 IL_PUSH(g_dompoints, this);
505 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
506 Team declaration for Domination gameplay, this allows you to decide what team
507 names and control point models are used in your map.
509 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
510 can have netname set! The nameless team owns all control points at start.
514 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
516 Scoreboard color of the team (for example 4 is red and 13 is blue)
518 Model to use for control points owned by this team (for example
519 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
522 Skin of the model to use (for team skins on a single model)
524 Sound to play when this team captures a point.
525 (this is a localized sound, like a small alarm or other effect)
527 Narrator speech to play when this team captures a point.
528 (this is a global sound, like "Red team has captured a control point")
533 if(!g_domination || autocvar_g_domination_teams_override >= 2)
538 precache_model(this.model);
539 if (this.noise != "")
540 precache_sound(this.noise);
541 if (this.noise1 != "")
542 precache_sound(this.noise1);
543 this.classname = "dom_team";
544 _setmodel(this, this.model); // precision not needed
545 this.mdl = this.model;
546 this.dmg = this.modelindex;
549 // this would have to be changed if used in quakeworld
551 this.team = this.cnt + 1; // WHY are these different anyway?
555 void ScoreRules_dom(int teams)
557 if(domination_roundbased)
559 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
560 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
561 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
562 ScoreRules_basics_end();
566 float sp_domticks, sp_score;
567 sp_score = sp_domticks = 0;
568 if(autocvar_g_domination_disable_frags)
569 sp_domticks = SFL_SORT_PRIO_PRIMARY;
571 sp_score = SFL_SORT_PRIO_PRIMARY;
572 ScoreRules_basics(teams, sp_score, sp_score, true);
573 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
574 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
575 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
576 ScoreRules_basics_end();
580 // code from here on is just to support maps that don't have control point and team entities
581 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
584 entity e = new_pure(dom_team);
585 e.netname = strzone(teamname);
587 e.model = pointmodel;
589 e.noise = strzone(Sound_fixpath(capsound));
590 e.noise1 = strzone(capnarration);
591 e.message = strzone(capmessage);
593 // this code is identical to spawnfunc_dom_team
594 _setmodel(e, e.model); // precision not needed
596 e.dmg = e.modelindex;
599 // this would have to be changed if used in quakeworld
605 void dom_spawnpoint(vector org)
608 e.classname = "dom_controlpoint";
609 setthink(e, spawnfunc_dom_controlpoint);
612 spawnfunc_dom_controlpoint(e);
615 // spawn some default teams if the map is not set up for domination
616 void dom_spawnteams(int teams)
619 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");
620 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");
622 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");
624 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");
625 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
628 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
630 // if no teams are found, spawn defaults
631 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
633 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
634 domination_teams = autocvar_g_domination_teams_override;
635 if (domination_teams < 2)
636 domination_teams = autocvar_g_domination_default_teams;
637 domination_teams = bound(2, domination_teams, 4);
638 dom_spawnteams(domination_teams);
641 CheckAllowedTeams(NULL);
642 //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
645 if(c1 >= 0) teams |= BIT(0);
646 if(c2 >= 0) teams |= BIT(1);
647 if(c3 >= 0) teams |= BIT(2);
648 if(c4 >= 0) teams |= BIT(3);
649 domination_teams = teams;
651 domination_roundbased = autocvar_g_domination_roundbased;
653 ScoreRules_dom(domination_teams);
655 if(domination_roundbased)
657 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
658 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
662 void dom_Initialize()
665 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);