1 #include "gamemode_domination.qh"
2 #ifndef GAMEMODE_DOMINATION_H
3 #define GAMEMODE_DOMINATION_H
5 #define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
6 bool autocvar_g_domination_roundbased;
7 int autocvar_g_domination_roundbased_point_limit;
8 int autocvar_g_domination_point_leadlimit;
10 void dom_Initialize();
12 REGISTER_MUTATOR(dom, false)
16 if (time > 1) // game loads at time 1
17 error("This is a game type and it cannot be added at runtime.");
20 int fraglimit_override = autocvar_g_domination_point_limit;
21 if (autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
22 fraglimit_override = autocvar_g_domination_roundbased_point_limit;
25 SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, autocvar_timelimit_override, -1);
26 have_team_spawns = -1; // request team spawns
31 LOG_INFO("This is a game type and it cannot be removed at runtime.");
38 // score rule declarations
39 const float ST_DOM_TICKS = 1;
40 const float ST_DOM_CAPS = 1;
42 // pps: points per second
43 .float dom_total_pps = _STAT(DOM_TOTAL_PPS);
44 .float dom_pps_red = _STAT(DOM_PPS_RED);
45 .float dom_pps_blue = _STAT(DOM_PPS_BLUE);
46 .float dom_pps_yellow = _STAT(DOM_PPS_YELLOW);
47 .float dom_pps_pink = _STAT(DOM_PPS_PINK);
54 // capture declarations
55 .float enemy_playerid;
60 float domination_roundbased;
61 float domination_teams;
66 #include <server/teamplay.qh>
70 int autocvar_g_domination_default_teams;
71 bool autocvar_g_domination_disable_frags;
72 int autocvar_g_domination_point_amt;
73 bool autocvar_g_domination_point_fullbright;
74 float autocvar_g_domination_round_timelimit;
75 float autocvar_g_domination_warmup;
76 float autocvar_g_domination_point_rate;
77 int autocvar_g_domination_teams_override;
79 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
81 if(autocvar_sv_eventlog)
82 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
85 void set_dom_state(entity e)
87 e.dom_total_pps = total_pps;
88 e.dom_pps_red = pps_red;
89 e.dom_pps_blue = pps_blue;
90 if(domination_teams >= 3)
91 e.dom_pps_yellow = pps_yellow;
92 if(domination_teams >= 4)
93 e.dom_pps_pink = pps_pink;
96 void dompoint_captured(entity this)
98 float old_delay, old_team, real_team;
100 // now that the delay has expired, switch to the latest team to lay claim to this point
101 entity head = this.owner;
103 real_team = this.cnt;
106 dom_EventLog("taken", this.team, this.dmg_inflictor);
107 this.dmg_inflictor = NULL;
109 this.goalentity = head;
110 this.model = head.mdl;
111 this.modelindex = head.dmg;
112 this.skin = head.skin;
114 float points, wait_time;
115 if (autocvar_g_domination_point_amt)
116 points = autocvar_g_domination_point_amt;
119 if (autocvar_g_domination_point_rate)
120 wait_time = autocvar_g_domination_point_rate;
122 wait_time = this.wait;
124 if(domination_roundbased)
125 bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
127 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
129 if(this.enemy.playerid == this.enemy_playerid)
130 PlayerScore_Add(this.enemy, SP_DOM_TAKES, 1);
134 if (head.noise != "")
136 _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
138 _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
139 if (head.noise1 != "")
140 play2all(head.noise1);
142 this.delay = time + wait_time;
145 old_delay = this.delay;
146 old_team = this.team;
147 this.team = real_team;
149 SUB_UseTargets (this, this, NULL);
150 this.delay = old_delay;
151 this.team = old_team;
153 entity msg = WP_DomNeut;
156 case NUM_TEAM_1: msg = WP_DomRed; break;
157 case NUM_TEAM_2: msg = WP_DomBlue; break;
158 case NUM_TEAM_3: msg = WP_DomYellow; break;
159 case NUM_TEAM_4: msg = WP_DomPink; break;
162 WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null);
164 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
165 FOREACH_ENTITY_CLASS("dom_controlpoint", true, LAMBDA(
166 if (autocvar_g_domination_point_amt)
167 points = autocvar_g_domination_point_amt;
170 if (autocvar_g_domination_point_rate)
171 wait_time = autocvar_g_domination_point_rate;
174 switch(it.goalentity.team)
176 case NUM_TEAM_1: pps_red += points/wait_time; break;
177 case NUM_TEAM_2: pps_blue += points/wait_time; break;
178 case NUM_TEAM_3: pps_yellow += points/wait_time; break;
179 case NUM_TEAM_4: pps_pink += points/wait_time; break;
181 total_pps += points/wait_time;
184 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
185 WaypointSprite_Ping(this.sprite);
189 FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(set_dom_state(it)));
192 void AnimateDomPoint(entity this)
194 if(this.pain_finished > time)
196 this.pain_finished = time + this.t_width;
197 if(this.nextthink > this.pain_finished)
198 this.nextthink = this.pain_finished;
200 this.frame = this.frame + 1;
201 if(this.frame > this.t_length)
205 void dompointthink(entity this)
209 this.nextthink = time + 0.1;
211 //this.frame = this.frame + 1;
212 //if(this.frame > 119)
214 AnimateDomPoint(this);
218 if (gameover || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
221 if(autocvar_g_domination_point_rate)
222 this.delay = time + autocvar_g_domination_point_rate;
224 this.delay = time + this.wait;
226 // give credit to the team
227 // NOTE: this defaults to 0
228 if (!domination_roundbased)
229 if (this.goalentity.netname != "")
231 if(autocvar_g_domination_point_amt)
232 fragamt = autocvar_g_domination_point_amt;
234 fragamt = this.frags;
235 TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
236 TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
238 // give credit to the individual player, if he is still there
239 if (this.enemy.playerid == this.enemy_playerid)
241 PlayerScore_Add(this.enemy, SP_SCORE, fragamt);
242 PlayerScore_Add(this.enemy, SP_DOM_TICKS, fragamt);
249 void dompointtouch(entity this, entity toucher)
251 if (!IS_PLAYER(toucher))
253 if (toucher.health < 1)
256 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
259 if(time < this.captime + 0.3)
262 // only valid teams can claim it
263 entity head = find(NULL, classname, "dom_team");
264 while (head && head.team != toucher.team)
265 head = find(head, classname, "dom_team");
266 if (!head || head.netname == "" || head == this.goalentity)
271 this.team = this.goalentity.team; // this stores the PREVIOUS team!
273 this.cnt = toucher.team;
274 this.owner = head; // team to switch to after the delay
275 this.dmg_inflictor = toucher;
278 // this.delay = time + cvar("g_domination_point_capturetime");
279 //this.nextthink = time + cvar("g_domination_point_capturetime");
280 //this.think = dompoint_captured;
282 // go to neutral team in the mean time
283 head = find(NULL, classname, "dom_team");
284 while (head && head.netname != "")
285 head = find(head, classname, "dom_team");
289 WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
290 WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
291 WaypointSprite_Ping(this.sprite);
293 this.goalentity = head;
294 this.model = head.mdl;
295 this.modelindex = head.dmg;
296 this.skin = head.skin;
298 this.enemy = toucher; // individual player scoring
299 this.enemy_playerid = toucher.playerid;
300 dompoint_captured(this);
303 void dom_controlpoint_setup(entity this)
306 // find the spawnfunc_dom_team representing unclaimed points
307 head = find(NULL, classname, "dom_team");
308 while(head && head.netname != "")
309 head = find(head, classname, "dom_team");
311 objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
313 // copy important properties from spawnfunc_dom_team entity
314 this.goalentity = head;
315 _setmodel(this, head.mdl); // precision already set
316 this.skin = head.skin;
320 if(this.message == "")
321 this.message = " has captured a control point";
328 float points, waittime;
329 if (autocvar_g_domination_point_amt)
330 points = autocvar_g_domination_point_amt;
333 if (autocvar_g_domination_point_rate)
334 waittime = autocvar_g_domination_point_rate;
336 waittime = this.wait;
338 total_pps += points/waittime;
341 this.t_width = 0.02; // frame animation rate
343 this.t_length = 239; // maximum frame
345 setthink(this, dompointthink);
346 this.nextthink = time;
347 settouch(this, dompointtouch);
348 this.solid = SOLID_TRIGGER;
349 this.flags = FL_ITEM;
350 setsize(this, '-32 -32 -32', '32 32 32');
351 setorigin(this, this.origin + '0 0 20');
354 waypoint_spawnforitem(this);
355 WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
358 float total_controlpoints;
359 void Domination_count_controlpoints()
361 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
362 FOREACH_ENTITY_CLASS("dom_controlpoint", true,
364 ++total_controlpoints;
365 redowned += (it.goalentity.team == NUM_TEAM_1);
366 blueowned += (it.goalentity.team == NUM_TEAM_2);
367 yellowowned += (it.goalentity.team == NUM_TEAM_3);
368 pinkowned += (it.goalentity.team == NUM_TEAM_4);
372 float Domination_GetWinnerTeam()
374 float winner_team = 0;
375 if(redowned == total_controlpoints)
376 winner_team = NUM_TEAM_1;
377 if(blueowned == total_controlpoints)
379 if(winner_team) return 0;
380 winner_team = NUM_TEAM_2;
382 if(yellowowned == total_controlpoints)
384 if(winner_team) return 0;
385 winner_team = NUM_TEAM_3;
387 if(pinkowned == total_controlpoints)
389 if(winner_team) return 0;
390 winner_team = NUM_TEAM_4;
394 return -1; // no control points left?
397 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
398 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
399 float Domination_CheckWinner()
401 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
403 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
404 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
405 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
409 Domination_count_controlpoints();
411 float winner_team = Domination_GetWinnerTeam();
413 if(winner_team == -1)
418 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
419 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
420 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
422 else if(winner_team == -1)
424 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
425 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
428 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
433 float Domination_CheckPlayers()
438 void Domination_RoundStart()
440 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(it.player_blocked = false));
443 //go to best items, or control points you don't own
444 void havocbot_role_dom(entity this)
449 if (this.bot_strategytime < time)
451 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
452 navigation_goalrating_start(this);
453 havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
454 havocbot_goalrating_items(this, 8000, this.origin, 8000);
455 //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
456 //havocbot_goalrating_waypoints(1, this.origin, 1000);
457 navigation_goalrating_end(this);
461 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
464 M_ARGV(0, float) = domination_teams;
465 string ret_string = "dom_team";
467 entity head = find(NULL, classname, ret_string);
470 if(head.netname != "")
474 case NUM_TEAM_1: c1 = 0; break;
475 case NUM_TEAM_2: c2 = 0; break;
476 case NUM_TEAM_3: c3 = 0; break;
477 case NUM_TEAM_4: c4 = 0; break;
481 head = find(head, classname, ret_string);
484 M_ARGV(1, string) = string_null;
489 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
491 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
492 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
493 PutClientInServer(it);
494 if(domination_roundbased)
495 it.player_blocked = 1;
496 if(IS_REAL_CLIENT(it))
502 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
504 entity player = M_ARGV(0, entity);
506 if(domination_roundbased)
507 if(!round_handler_IsRoundStarted())
508 player.player_blocked = 1;
510 player.player_blocked = 0;
513 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
515 entity player = M_ARGV(0, entity);
517 set_dom_state(player);
520 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
522 entity bot = M_ARGV(0, entity);
524 bot.havocbot_role = havocbot_role_dom;
528 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
529 Control point for Domination gameplay.
531 spawnfunc(dom_controlpoint)
538 setthink(this, dom_controlpoint_setup);
539 this.nextthink = time + 0.1;
540 this.reset = dom_controlpoint_setup;
545 this.effects = this.effects | EF_LOWPRECISION;
546 if (autocvar_g_domination_point_fullbright)
547 this.effects |= EF_FULLBRIGHT;
550 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
551 Team declaration for Domination gameplay, this allows you to decide what team
552 names and control point models are used in your map.
554 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
555 can have netname set! The nameless team owns all control points at start.
559 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
561 Scoreboard color of the team (for example 4 is red and 13 is blue)
563 Model to use for control points owned by this team (for example
564 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
567 Skin of the model to use (for team skins on a single model)
569 Sound to play when this team captures a point.
570 (this is a localized sound, like a small alarm or other effect)
572 Narrator speech to play when this team captures a point.
573 (this is a global sound, like "Red team has captured a control point")
578 if(!g_domination || autocvar_g_domination_teams_override >= 2)
583 precache_model(this.model);
584 if (this.noise != "")
585 precache_sound(this.noise);
586 if (this.noise1 != "")
587 precache_sound(this.noise1);
588 this.classname = "dom_team";
589 _setmodel(this, this.model); // precision not needed
590 this.mdl = this.model;
591 this.dmg = this.modelindex;
594 // this would have to be changed if used in quakeworld
596 this.team = this.cnt + 1; // WHY are these different anyway?
600 void ScoreRules_dom(int teams)
602 if(domination_roundbased)
604 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
605 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
606 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
607 ScoreRules_basics_end();
611 float sp_domticks, sp_score;
612 sp_score = sp_domticks = 0;
613 if(autocvar_g_domination_disable_frags)
614 sp_domticks = SFL_SORT_PRIO_PRIMARY;
616 sp_score = SFL_SORT_PRIO_PRIMARY;
617 ScoreRules_basics(teams, sp_score, sp_score, true);
618 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
619 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
620 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
621 ScoreRules_basics_end();
625 // code from here on is just to support maps that don't have control point and team entities
626 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage)
629 entity e = new_pure(dom_team);
630 e.netname = strzone(teamname);
632 e.model = pointmodel;
634 e.noise = strzone(Sound_fixpath(capsound));
635 e.noise1 = strzone(capnarration);
636 e.message = strzone(capmessage);
638 // this code is identical to spawnfunc_dom_team
639 _setmodel(e, e.model); // precision not needed
641 e.dmg = e.modelindex;
644 // this would have to be changed if used in quakeworld
650 void dom_spawnpoint(vector org)
653 e.classname = "dom_controlpoint";
654 setthink(e, spawnfunc_dom_controlpoint);
657 spawnfunc_dom_controlpoint(e);
660 // spawn some default teams if the map is not set up for domination
661 void dom_spawnteams(int teams)
664 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");
665 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");
667 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");
669 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");
670 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", "");
673 void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
675 // if no teams are found, spawn defaults
676 if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
678 LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.\n");
679 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
680 dom_spawnteams(domination_teams);
683 CheckAllowedTeams(NULL);
684 //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
687 if(c1 >= 0) teams |= BIT(0);
688 if(c2 >= 0) teams |= BIT(1);
689 if(c3 >= 0) teams |= BIT(2);
690 if(c4 >= 0) teams |= BIT(3);
691 domination_teams = teams;
693 domination_roundbased = autocvar_g_domination_roundbased;
695 ScoreRules_dom(domination_teams);
697 if(domination_roundbased)
699 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
700 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
704 void dom_Initialize()
707 InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);