1 #include "gamemode_domination.qh"
6 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
8 if(autocvar_sv_eventlog)
9 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
12 void set_dom_state(entity e)
14 e.dom_total_pps = total_pps;
15 e.dom_pps_red = pps_red;
16 e.dom_pps_blue = pps_blue;
17 if(domination_teams >= 3)
18 e.dom_pps_yellow = pps_yellow;
19 if(domination_teams >= 4)
20 e.dom_pps_pink = pps_pink;
23 void dompoint_captured ()
26 float old_delay, old_team, real_team;
28 // now that the delay has expired, switch to the latest team to lay claim to this point
34 dom_EventLog("taken", self.team, self.dmg_inflictor);
35 self.dmg_inflictor = world;
37 self.goalentity = head;
38 self.model = head.mdl;
39 self.modelindex = head.dmg;
40 self.skin = head.skin;
42 float points, wait_time;
43 if (autocvar_g_domination_point_amt)
44 points = autocvar_g_domination_point_amt;
47 if (autocvar_g_domination_point_rate)
48 wait_time = autocvar_g_domination_point_rate;
50 wait_time = self.wait;
52 if(domination_roundbased)
53 bprint(sprintf("^3%s^3%s\n", head.netname, self.message));
55 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time);
57 if(self.enemy.playerid == self.enemy_playerid)
58 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
64 sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
66 sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
67 if (head.noise1 != "")
68 play2all(head.noise1);
70 self.delay = time + wait_time;
73 old_delay = self.delay;
75 self.team = real_team;
79 self.delay = old_delay;
82 entity msg = WP_DomNeut;
85 case NUM_TEAM_1: msg = WP_DomRed; break;
86 case NUM_TEAM_2: msg = WP_DomBlue; break;
87 case NUM_TEAM_3: msg = WP_DomYellow; break;
88 case NUM_TEAM_4: msg = WP_DomPink; break;
91 WaypointSprite_UpdateSprites(self.sprite, msg, WP_Null, WP_Null);
93 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
94 for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
96 if (autocvar_g_domination_point_amt)
97 points = autocvar_g_domination_point_amt;
100 if (autocvar_g_domination_point_rate)
101 wait_time = autocvar_g_domination_point_rate;
103 wait_time = head.wait;
104 switch(head.goalentity.team)
107 pps_red += points/wait_time;
110 pps_blue += points/wait_time;
113 pps_yellow += points/wait_time;
116 pps_pink += points/wait_time;
119 total_pps += points/wait_time;
122 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
123 WaypointSprite_Ping(self.sprite);
127 FOR_EACH_REALCLIENT(head)
131 void AnimateDomPoint()
133 if(self.pain_finished > time)
135 self.pain_finished = time + self.t_width;
136 if(self.nextthink > self.pain_finished)
137 self.nextthink = self.pain_finished;
139 self.frame = self.frame + 1;
140 if(self.frame > self.t_length)
148 self.nextthink = time + 0.1;
150 //self.frame = self.frame + 1;
151 //if(self.frame > 119)
157 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
160 if(autocvar_g_domination_point_rate)
161 self.delay = time + autocvar_g_domination_point_rate;
163 self.delay = time + self.wait;
165 // give credit to the team
166 // NOTE: this defaults to 0
167 if (!domination_roundbased)
168 if (self.goalentity.netname != "")
170 if(autocvar_g_domination_point_amt)
171 fragamt = autocvar_g_domination_point_amt;
173 fragamt = self.frags;
174 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
175 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
177 // give credit to the individual player, if he is still there
178 if (self.enemy.playerid == self.enemy_playerid)
180 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
181 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
191 if (!IS_PLAYER(other))
193 if (other.health < 1)
196 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
199 if(time < self.captime + 0.3)
202 // only valid teams can claim it
203 head = find(world, classname, "dom_team");
204 while (head && head.team != other.team)
205 head = find(head, classname, "dom_team");
206 if (!head || head.netname == "" || head == self.goalentity)
211 self.team = self.goalentity.team; // this stores the PREVIOUS team!
213 self.cnt = other.team;
214 self.owner = head; // team to switch to after the delay
215 self.dmg_inflictor = other;
218 // self.delay = time + cvar("g_domination_point_capturetime");
219 //self.nextthink = time + cvar("g_domination_point_capturetime");
220 //self.think = dompoint_captured;
222 // go to neutral team in the mean time
223 head = find(world, classname, "dom_team");
224 while (head && head.netname != "")
225 head = find(head, classname, "dom_team");
229 WaypointSprite_UpdateSprites(self.sprite, WP_DomNeut, WP_Null, WP_Null);
230 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
231 WaypointSprite_Ping(self.sprite);
233 self.goalentity = head;
234 self.model = head.mdl;
235 self.modelindex = head.dmg;
236 self.skin = head.skin;
238 self.enemy = other; // individual player scoring
239 self.enemy_playerid = other.playerid;
243 void dom_controlpoint_setup()
246 // find the spawnfunc_dom_team representing unclaimed points
247 head = find(world, classname, "dom_team");
248 while(head && head.netname != "")
249 head = find(head, classname, "dom_team");
251 objerror("no spawnfunc_dom_team with netname \"\" found\n");
253 // copy important properties from spawnfunc_dom_team entity
254 self.goalentity = head;
255 setmodel(self, head.mdl); // precision already set
256 self.skin = head.skin;
260 if(self.message == "")
261 self.message = " has captured a control point";
268 float points, waittime;
269 if (autocvar_g_domination_point_amt)
270 points = autocvar_g_domination_point_amt;
273 if (autocvar_g_domination_point_rate)
274 waittime = autocvar_g_domination_point_rate;
276 waittime = self.wait;
278 total_pps += points/waittime;
281 self.t_width = 0.02; // frame animation rate
283 self.t_length = 239; // maximum frame
285 self.think = dompointthink;
286 self.nextthink = time;
287 self.touch = dompointtouch;
288 self.solid = SOLID_TRIGGER;
289 self.flags = FL_ITEM;
290 setsize(self, '-32 -32 -32', '32 32 32');
291 setorigin(self, self.origin + '0 0 20');
294 waypoint_spawnforitem(self);
295 WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT);
298 float total_controlpoints, redowned, blueowned, yellowowned, pinkowned;
299 void Domination_count_controlpoints()
302 total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
303 for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
305 ++total_controlpoints;
306 redowned += (e.goalentity.team == NUM_TEAM_1);
307 blueowned += (e.goalentity.team == NUM_TEAM_2);
308 yellowowned += (e.goalentity.team == NUM_TEAM_3);
309 pinkowned += (e.goalentity.team == NUM_TEAM_4);
313 float Domination_GetWinnerTeam()
315 float winner_team = 0;
316 if(redowned == total_controlpoints)
317 winner_team = NUM_TEAM_1;
318 if(blueowned == total_controlpoints)
320 if(winner_team) return 0;
321 winner_team = NUM_TEAM_2;
323 if(yellowowned == total_controlpoints)
325 if(winner_team) return 0;
326 winner_team = NUM_TEAM_3;
328 if(pinkowned == total_controlpoints)
330 if(winner_team) return 0;
331 winner_team = NUM_TEAM_4;
335 return -1; // no control points left?
338 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
339 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
340 float Domination_CheckWinner()
342 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
344 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
345 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
346 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
350 Domination_count_controlpoints();
352 float winner_team = Domination_GetWinnerTeam();
354 if(winner_team == -1)
359 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
360 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
361 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
363 else if(winner_team == -1)
365 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
366 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
369 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
374 float Domination_CheckPlayers()
379 void Domination_RoundStart()
383 e.player_blocked = 0;
386 //go to best items, or control points you don't own
387 void havocbot_role_dom()
389 if(self.deadflag != DEAD_NO)
392 if (self.bot_strategytime < time)
394 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
395 navigation_goalrating_start();
396 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
397 havocbot_goalrating_items(8000, self.origin, 8000);
398 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
399 //havocbot_goalrating_waypoints(1, self.origin, 1000);
400 navigation_goalrating_end();
404 MUTATOR_HOOKFUNCTION(dom_GetTeamCount)
406 ret_float = domination_teams;
410 MUTATOR_HOOKFUNCTION(dom_ResetMap)
412 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
413 FOR_EACH_PLAYER(self)
416 self.player_blocked = 1;
417 if(IS_REAL_CLIENT(self))
423 MUTATOR_HOOKFUNCTION(dom_PlayerSpawn)
425 if(domination_roundbased)
426 if(!round_handler_IsRoundStarted())
427 self.player_blocked = 1;
429 self.player_blocked = 0;
433 MUTATOR_HOOKFUNCTION(dom_ClientConnect)
439 MUTATOR_HOOKFUNCTION(dom_BotRoles)
441 self.havocbot_role = havocbot_role_dom;
445 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
446 Control point for Domination gameplay.
448 void spawnfunc_dom_controlpoint()
455 self.think = dom_controlpoint_setup;
456 self.nextthink = time + 0.1;
457 self.reset = dom_controlpoint_setup;
462 self.effects = self.effects | EF_LOWPRECISION;
463 if (autocvar_g_domination_point_fullbright)
464 self.effects |= EF_FULLBRIGHT;
467 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
468 Team declaration for Domination gameplay, this allows you to decide what team
469 names and control point models are used in your map.
471 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
472 can have netname set! The nameless team owns all control points at start.
476 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
478 Scoreboard color of the team (for example 4 is red and 13 is blue)
480 Model to use for control points owned by this team (for example
481 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
484 Skin of the model to use (for team skins on a single model)
486 Sound to play when this team captures a point.
487 (this is a localized sound, like a small alarm or other effect)
489 Narrator speech to play when this team captures a point.
490 (this is a global sound, like "Red team has captured a control point")
493 void spawnfunc_dom_team()
495 if(!g_domination || autocvar_g_domination_teams_override >= 2)
500 precache_model(self.model);
501 if (self.noise != "")
502 precache_sound(self.noise);
503 if (self.noise1 != "")
504 precache_sound(self.noise1);
505 self.classname = "dom_team";
506 setmodel(self, self.model); // precision not needed
507 self.mdl = self.model;
508 self.dmg = self.modelindex;
511 // this would have to be changed if used in quakeworld
513 self.team = self.cnt + 1; // WHY are these different anyway?
517 void ScoreRules_dom(float teams)
519 if(domination_roundbased)
521 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
522 ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
523 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
524 ScoreRules_basics_end();
528 float sp_domticks, sp_score;
529 sp_score = sp_domticks = 0;
530 if(autocvar_g_domination_disable_frags)
531 sp_domticks = SFL_SORT_PRIO_PRIMARY;
533 sp_score = SFL_SORT_PRIO_PRIMARY;
534 ScoreRules_basics(teams, sp_score, sp_score, true);
535 ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks);
536 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks);
537 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
538 ScoreRules_basics_end();
542 // code from here on is just to support maps that don't have control point and team entities
543 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
548 self.classname = "dom_team";
549 self.netname = teamname;
550 self.cnt = teamcolor;
551 self.model = pointmodel;
552 self.skin = pointskin;
553 self.noise = capsound;
554 self.noise1 = capnarration;
555 self.message = capmessage;
557 // this code is identical to spawnfunc_dom_team
558 setmodel(self, self.model); // precision not needed
559 self.mdl = self.model;
560 self.dmg = self.modelindex;
563 // this would have to be changed if used in quakeworld
564 self.team = self.cnt + 1;
570 void dom_spawnpoint(vector org)
575 self.classname = "dom_controlpoint";
576 self.think = spawnfunc_dom_controlpoint;
577 self.nextthink = time;
578 setorigin(self, org);
579 spawnfunc_dom_controlpoint();
583 // spawn some default teams if the map is not set up for domination
584 void dom_spawnteams(float teams)
586 dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
587 dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
589 dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
591 dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
592 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
595 void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
597 // if no teams are found, spawn defaults
598 if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
600 print("No ""dom_team"" entities found on this map, creating them anyway.\n");
601 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
602 dom_spawnteams(domination_teams);
605 CheckAllowedTeams(world);
606 domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
608 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
609 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
610 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
611 if(domination_teams >= 3) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
612 if(domination_teams >= 4) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
614 domination_roundbased = autocvar_g_domination_roundbased;
616 ScoreRules_dom(domination_teams);
618 if(domination_roundbased)
620 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
621 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
625 void dom_Initialize()
627 precache_model("models/domination/dom_red.md3");
628 precache_model("models/domination/dom_blue.md3");
629 precache_model("models/domination/dom_yellow.md3");
630 precache_model("models/domination/dom_pink.md3");
631 precache_model("models/domination/dom_unclaimed.md3");
632 precache_sound("domination/claim.wav");
634 InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);
638 MUTATOR_DEFINITION(gamemode_domination)
640 MUTATOR_HOOK(GetTeamCount, dom_GetTeamCount, CBC_ORDER_ANY);
641 MUTATOR_HOOK(reset_map_players, dom_ResetMap, CBC_ORDER_ANY);
642 MUTATOR_HOOK(PlayerSpawn, dom_PlayerSpawn, CBC_ORDER_ANY);
643 MUTATOR_HOOK(ClientConnect, dom_ClientConnect, CBC_ORDER_ANY);
644 MUTATOR_HOOK(HavocBot_ChooseRole, dom_BotRoles, CBC_ORDER_ANY);
648 if(time > 1) // game loads at time 1
649 error("This is a game type and it cannot be added at runtime.");
655 print("This is a game type and it cannot be removed at runtime.");