]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_domination.qc
Merge remote-tracking branch 'origin/Mario/domination_mutator'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_domination.qc
1 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
2 {
3         if(autocvar_sv_eventlog)
4                 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
5 }
6
7 void set_dom_state(entity e)
8 {
9         e.dom_total_pps = total_pps;
10         e.dom_pps_red = pps_red;
11         e.dom_pps_blue = pps_blue;
12         if(c3 >= 0)
13                 e.dom_pps_yellow = pps_yellow;
14         if(c4 >= 0)
15                 e.dom_pps_pink = pps_pink;
16 }
17
18 void dompoint_captured ()
19 {
20         entity head;
21         float old_delay, old_team, real_team;
22
23         // now that the delay has expired, switch to the latest team to lay claim to this point
24         head = self.owner;
25
26         real_team = self.cnt;
27         self.cnt = -1;
28
29         dom_EventLog("taken", self.team, self.dmg_inflictor);
30         self.dmg_inflictor = world;
31
32         self.goalentity = head;
33         self.model = head.mdl;
34         self.modelindex = head.dmg;
35         self.skin = head.skin;
36         
37         float points, wait_time;
38         if (autocvar_g_domination_point_amt)
39                 points = autocvar_g_domination_point_amt;
40         else
41                 points = self.frags;
42         if (autocvar_g_domination_point_rate)
43                 wait_time = autocvar_g_domination_point_rate;
44         else
45                 wait_time = self.wait;
46
47         bprint("^3", head.netname, "^3", self.message);
48         if (points != 1)
49                 bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
50         else
51                 bprint(" ^7(", ftos(points), " point every ", ftos(wait_time), " seconds)\n");
52
53         if(self.enemy.playerid == self.enemy_playerid)
54                 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
55         else
56                 self.enemy = world;
57
58         if (head.noise != "")
59                 if(self.enemy)
60                         sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
61                 else
62                         sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
63         if (head.noise1 != "")
64                 play2all(head.noise1);
65
66         self.delay = time + wait_time;
67
68         // do trigger work
69         old_delay = self.delay;
70         old_team = self.team;
71         self.team = real_team;
72         self.delay = 0;
73         activator = self;
74         SUB_UseTargets ();
75         self.delay = old_delay;
76         self.team = old_team;
77
78         switch(self.goalentity.team)
79         {
80                 case COLOR_TEAM1:
81                         WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
82                         break;
83                 case COLOR_TEAM2:
84                         WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
85                         break;
86                 case COLOR_TEAM3:
87                         WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
88                         break;
89                 case COLOR_TEAM4:
90                         WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
91         }
92
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; )
95         {
96                 if (autocvar_g_domination_point_amt)
97                         points = autocvar_g_domination_point_amt;
98                 else
99                         points = head.frags;
100                 if (autocvar_g_domination_point_rate)
101                         wait_time = autocvar_g_domination_point_rate;
102                 else
103                         wait_time = head.wait;
104                 switch(head.goalentity.team)
105                 {
106                         case COLOR_TEAM1:
107                                 pps_red += points/wait_time;
108                                 break;
109                         case COLOR_TEAM2:
110                                 pps_blue += points/wait_time;
111                                 break;
112                         case COLOR_TEAM3:
113                                 pps_yellow += points/wait_time;
114                                 break;
115                         case COLOR_TEAM4:
116                                 pps_pink += points/wait_time;
117                 }
118                 total_pps += points/wait_time;
119         }
120
121         WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
122         WaypointSprite_Ping(self.sprite);
123
124         self.captime = time;
125
126         FOR_EACH_REALCLIENT(head)
127                 set_dom_state(head);
128 }
129
130 void AnimateDomPoint()
131 {
132         if(self.pain_finished > time)
133                 return;
134         self.pain_finished = time + self.t_width;
135         if(self.nextthink > self.pain_finished)
136                 self.nextthink = self.pain_finished;
137
138         self.frame = self.frame + 1;
139         if(self.frame > self.t_length)
140                 self.frame = 0;
141 }
142
143 void dompointthink()
144 {
145         float fragamt;
146
147         self.nextthink = time + 0.1;
148
149         //self.frame = self.frame + 1;
150         //if(self.frame > 119)
151         //      self.frame = 0;
152         AnimateDomPoint();
153
154         // give points
155
156         if (gameover || self.delay > time || time < game_starttime)     // game has ended, don't keep giving points
157                 return;
158
159         if(autocvar_g_domination_point_rate)
160                 self.delay = time + autocvar_g_domination_point_rate;
161         else
162                 self.delay = time + self.wait;
163
164         // give credit to the team
165         // NOTE: this defaults to 0
166         if (self.goalentity.netname != "")
167         {
168                 if(autocvar_g_domination_point_amt)
169                         fragamt = autocvar_g_domination_point_amt;
170                 else
171                         fragamt = self.frags;
172                 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
173                 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
174
175                 // give credit to the individual player, if he is still there
176                 if (self.enemy.playerid == self.enemy_playerid)
177                 {
178                         PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
179                         PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
180                 }
181                 else
182                         self.enemy = world;
183         }
184 }
185
186 void dompointtouch()
187 {
188         entity head;
189         if (other.classname != "player")
190                 return;
191         if (other.health < 1)
192                 return;
193
194         if(time < self.captime + 0.3)
195                 return;
196
197         // only valid teams can claim it
198         head = find(world, classname, "dom_team");
199         while (head && head.team != other.team)
200                 head = find(head, classname, "dom_team");
201         if (!head || head.netname == "" || head == self.goalentity)
202                 return;
203
204         // delay capture
205
206         self.team = self.goalentity.team; // this stores the PREVIOUS team!
207
208         self.cnt = other.team;
209         self.owner = head; // team to switch to after the delay
210         self.dmg_inflictor = other;
211
212         // self.state = 1;
213         // self.delay = time + cvar("g_domination_point_capturetime");
214         //self.nextthink = time + cvar("g_domination_point_capturetime");
215         //self.think = dompoint_captured;
216
217         // go to neutral team in the mean time
218         head = find(world, classname, "dom_team");
219         while (head && head.netname != "")
220                 head = find(head, classname, "dom_team");
221         if(head == world)
222                 return;
223
224         WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
225         WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
226         WaypointSprite_Ping(self.sprite);
227
228         self.goalentity = head;
229         self.model = head.mdl;
230         self.modelindex = head.dmg;
231         self.skin = head.skin;
232
233         self.enemy = other; // individual player scoring
234         self.enemy_playerid = other.playerid;
235         dompoint_captured();
236 }
237
238 void dom_controlpoint_setup()
239 {
240         entity head;
241         // find the spawnfunc_dom_team representing unclaimed points
242         head = find(world, classname, "dom_team");
243         while(head && head.netname != "")
244                 head = find(head, classname, "dom_team");
245         if (!head)
246                 objerror("no spawnfunc_dom_team with netname \"\" found\n");
247
248         // copy important properties from spawnfunc_dom_team entity
249         self.goalentity = head;
250         setmodel(self, head.mdl); // precision already set
251         self.skin = head.skin;
252
253         self.cnt = -1;
254
255         if(self.message == "")
256                 self.message = " has captured a control point";
257
258         if(self.frags <= 0)
259                 self.frags = 1;
260         if(self.wait <= 0)
261                 self.wait = 5;
262
263         float points, waittime;
264         if (autocvar_g_domination_point_amt)
265                 points = autocvar_g_domination_point_amt;
266         else
267                 points = self.frags;
268         if (autocvar_g_domination_point_rate)
269                 waittime = autocvar_g_domination_point_rate;
270         else
271                 waittime = self.wait;
272
273         total_pps += points/waittime;
274
275         if(!self.t_width)
276                 self.t_width = 0.02; // frame animation rate
277         if(!self.t_length)
278                 self.t_length = 239; // maximum frame
279
280         self.think = dompointthink;
281         self.nextthink = time;
282         self.touch = dompointtouch;
283         self.solid = SOLID_TRIGGER;
284         self.flags = FL_ITEM;
285         setsize(self, '-32 -32 -32', '32 32 32');
286         setorigin(self, self.origin + '0 0 20');
287         droptofloor();
288
289         waypoint_spawnforitem(self);
290         WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT, '0 1 1');
291 }
292
293 //go to best items, or control points you don't own
294 void havocbot_role_dom()
295 {
296         if(self.deadflag != DEAD_NO)
297                 return;
298
299         if (self.bot_strategytime < time)
300         {
301                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
302                 navigation_goalrating_start();
303                 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
304                 havocbot_goalrating_items(8000, self.origin, 8000);
305                 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
306                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
307                 navigation_goalrating_end();
308         }
309 }
310
311 MUTATOR_HOOKFUNCTION(dom_ClientConnect)
312 {
313         set_dom_state(self);
314         return FALSE;
315 }
316
317 MUTATOR_HOOKFUNCTION(dom_BotRoles)
318 {
319         self.havocbot_role = havocbot_role_dom;
320         return TRUE;
321 }
322
323 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
324 Control point for Domination gameplay.
325 */
326 void spawnfunc_dom_controlpoint()
327 {
328         if(!g_domination)
329         {
330                 remove(self);
331                 return;
332         }
333         self.think = dom_controlpoint_setup;
334         self.nextthink = time + 0.1;
335         self.reset = dom_controlpoint_setup;
336
337         if(!self.scale)
338                 self.scale = 0.6;
339
340         self.effects = self.effects | EF_LOWPRECISION;
341         if (autocvar_g_domination_point_fullbright)
342                 self.effects |= EF_FULLBRIGHT;
343 }
344
345 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
346 Team declaration for Domination gameplay, this allows you to decide what team
347 names and control point models are used in your map.
348
349 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
350 can have netname set!  The nameless team owns all control points at start.
351
352 Keys:
353 "netname"
354  Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
355 "cnt"
356  Scoreboard color of the team (for example 4 is red and 13 is blue)
357 "model"
358  Model to use for control points owned by this team (for example
359  "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
360  keycard)
361 "skin"
362  Skin of the model to use (for team skins on a single model)
363 "noise"
364  Sound to play when this team captures a point.
365  (this is a localized sound, like a small alarm or other effect)
366 "noise1"
367  Narrator speech to play when this team captures a point.
368  (this is a global sound, like "Red team has captured a control point")
369 */
370
371 void spawnfunc_dom_team()
372 {
373         if(!g_domination || autocvar_g_domination_teams_override >= 2)
374         {
375                 remove(self);
376                 return;
377         }
378         precache_model(self.model);
379         if (self.noise != "")
380                 precache_sound(self.noise);
381         if (self.noise1 != "")
382                 precache_sound(self.noise1);
383         self.classname = "dom_team";
384         setmodel(self, self.model); // precision not needed
385         self.mdl = self.model;
386         self.dmg = self.modelindex;
387         self.model = "";
388         self.modelindex = 0;
389         // this would have to be changed if used in quakeworld
390         if(self.cnt)
391                 self.team = self.cnt + 1; // WHY are these different anyway?
392 }
393
394 // scoreboard setup
395 void ScoreRules_dom()
396 {
397         float sp_domticks, sp_score;
398         sp_score = sp_domticks = 0;
399         if(autocvar_g_domination_disable_frags)
400                 sp_domticks = SFL_SORT_PRIO_PRIMARY;
401         else
402                 sp_score = SFL_SORT_PRIO_PRIMARY;
403         CheckAllowedTeams(world);
404         ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), sp_score, sp_score, TRUE);
405         ScoreInfo_SetLabel_TeamScore  (ST_DOM_TICKS,    "ticks",     sp_domticks);
406         ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS,    "ticks",     sp_domticks);
407         ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES,    "takes",     0);
408         ScoreRules_basics_end();
409 }
410
411 // code from here on is just to support maps that don't have control point and team entities
412 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
413 {
414         entity oldself;
415         oldself = self;
416         self = spawn();
417         self.classname = "dom_team";
418         self.netname = teamname;
419         self.cnt = teamcolor;
420         self.model = pointmodel;
421         self.skin = pointskin;
422         self.noise = capsound;
423         self.noise1 = capnarration;
424         self.message = capmessage;
425
426         // this code is identical to spawnfunc_dom_team
427         setmodel(self, self.model); // precision not needed
428         self.mdl = self.model;
429         self.dmg = self.modelindex;
430         self.model = "";
431         self.modelindex = 0;
432         // this would have to be changed if used in quakeworld
433         self.team = self.cnt + 1;
434
435         //eprint(self);
436         self = oldself;
437 }
438
439 void dom_spawnpoint(vector org)
440 {
441         entity oldself;
442         oldself = self;
443         self = spawn();
444         self.classname = "dom_controlpoint";
445         self.think = spawnfunc_dom_controlpoint;
446         self.nextthink = time;
447         setorigin(self, org);
448         spawnfunc_dom_controlpoint();
449         self = oldself;
450 }
451
452 // spawn some default teams if the map is not set up for domination
453 void dom_spawnteams()
454 {
455         float numteams = ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override);
456
457         dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
458         dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
459         if(numteams > 2)
460                 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
461         if(numteams > 3)
462                 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
463         dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
464 }
465
466 void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
467 {
468         // if no teams are found, spawn defaults
469         if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
470         {
471                 print("No ""dom_team"" entities found on this map, creating them anyway.\n");
472                 dom_spawnteams();
473         }
474         
475         ScoreRules_dom();
476 }
477
478 void dom_Initialize()
479 {
480         precache_model("models/domination/dom_red.md3");
481         precache_model("models/domination/dom_blue.md3");
482         precache_model("models/domination/dom_yellow.md3");
483         precache_model("models/domination/dom_pink.md3");
484         precache_model("models/domination/dom_unclaimed.md3");
485         precache_sound("domination/claim.wav");
486         
487         addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
488         addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
489         addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
490         if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
491         if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
492         
493         InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);
494 }
495
496
497 MUTATOR_DEFINITION(gamemode_domination)
498 {
499         MUTATOR_HOOK(ClientConnect, dom_ClientConnect, CBC_ORDER_ANY);
500         MUTATOR_HOOK(HavocBot_ChooseRule, dom_BotRoles, CBC_ORDER_ANY);
501         
502         MUTATOR_ONADD
503         {
504                 if(time > 1) // game loads at time 1
505                         error("This is a game type and it cannot be added at runtime.");
506                 dom_Initialize();
507         }
508
509         MUTATOR_ONREMOVE
510         {
511                 error("This is a game type and it cannot be removed at runtime.");
512         }
513
514         return 0;
515 }