made cvars like g_ctf only be queried once on startup
[divverent/nexuiz.git] / data / qcsrc / server / mode_onslaught.qc
1
2 .string target2;
3 .float iscaptured;
4 .float islinked;
5 .float isshielded;
6 void() onslaught_updatelinks =
7 {
8         local entity l, links;
9         local float stop, t1, t2, t3, t4;
10         // first check if the game has ended
11         t1 = t2 = t3 = t4 = 0;
12         l = findchain(classname, "onslaught_generator");
13         while (l)
14         {
15                 if (l.iscaptured)
16                 {
17                         if (l.team == COLOR_TEAM1) t1 = 1;
18                         if (l.team == COLOR_TEAM2) t2 = 1;
19                         if (l.team == COLOR_TEAM3) t3 = 1;
20                         if (l.team == COLOR_TEAM4) t4 = 1;
21                 }
22                 l = l.chain;
23         }
24         if (t1 + t2 + t3 + t4 < 2)
25         {
26                 // game over, only one team remains (or none)
27                 return;
28         }
29         // mark generators as being shielded and networked
30         l = findchain(classname, "onslaught_generator");
31         while (l)
32         {
33                 l.islinked = l.iscaptured;
34                 l.isshielded = l.iscaptured;
35                 l = l.chain;
36         }
37         // mark points as shielded and not networked
38         l = findchain(classname, "onslaught_controlpoint");
39         while (l)
40         {
41                 l.islinked = FALSE;
42                 l.isshielded = TRUE;
43                 l = l.chain;
44         }
45         // flow power outward from the generators through the network
46         links = findchain(classname, "onslaught_link");
47         stop = TRUE;
48         while (stop)
49         {
50                 stop = FALSE;
51                 l = links;
52                 while (l)
53                 {
54                         // if both points are captured by the same team, and only one of
55                         // them is powered, mark the other one as powered as well
56                         if (l.enemy.team == l.goalentity.team)
57                         {
58                                 if (l.goalentity.islinked)
59                                 {
60                                         if (l.enemy.iscaptured)
61                                         if (!l.enemy.islinked)
62                                         {
63                                                 stop = FALSE;
64                                                 l.enemy.islinked = TRUE;
65                                         }
66                                 }
67                                 else
68                                 {
69                                         if (l.goalentity.iscaptured)
70                                         if (!l.goalentity.islinked)
71                                         {
72                                                 stop = FALSE;
73                                                 l.goalentity.islinked = TRUE;
74                                         }
75                                 }
76                         }
77                         l = l.chain;
78                 }
79         }
80         // now that we know which points are powered we can mark their neighbors
81         // as unshielded if team differs
82         l = links;
83         while (l)
84         {
85                 if (l.goalentity.team != l.enemy.team)
86                 {
87                         if (l.goalentity.islinked)
88                                 l.enemy.isshielded = FALSE;
89                         if (l.enemy.islinked)
90                                 l.goalentity.isshielded = FALSE;
91                 }
92                 l = l.chain;
93         }
94         // now update the takedamage and alpha variables on generator shields
95         l = findchain(classname, "onslaught_generator");
96         while (l)
97         {
98                 if (l.isshielded)
99                 {
100                         l.enemy.alpha = -1;
101                         l.takedamage = DAMAGE_AIM;
102                 }
103                 else
104                 {
105                         l.enemy.alpha = -1;
106                         l.takedamage = DAMAGE_AIM;
107                 }
108                 l = l.chain;
109         }
110         // now update the takedamage and alpha variables on control point icons
111         l = findchain(classname, "onslaught_controlpoint");
112         while (l)
113         {
114                 if (l.isshielded)
115                 {
116                         l.enemy.alpha = -1;
117                         if (l.goalentity)
118                                 l.goalentity.takedamage = DAMAGE_AIM;
119                 }
120                 else
121                 {
122                         l.enemy.alpha = -1;
123                         if (l.goalentity)
124                                 l.goalentity.takedamage = DAMAGE_AIM;
125                 }
126                 l = l.chain;
127         }
128 };
129
130 void() onslaught_generator_think =
131 {
132         self.nextthink = ceil(time + 1);
133         if (cvar("timelimit"))
134         if (time > cvar("timelimit") * 60 - 60)
135         {
136                 // self.max_health / 120 gives 2 minutes of overtime
137                 sound(self, CHAN_AUTO, "sound/onslaught/generator_decay.wav", 1, ATTN_NORM);
138                 Damage(self, self, self, self.max_health / 60, DEATH_HURTTRIGGER, self.origin, '0 0 0');
139         }
140 };
141
142 void() onslaught_generator_deaththink =
143 {
144         local vector org;
145         if (self.count > 0)
146         {
147                 self.nextthink = time + 0.1;
148                 self.count = self.count - 1;
149                 org = randompos(self.origin + self.mins + '8 8 8', self.origin + self.maxs + '-8 -8 -8');
150                 pointparticles(particleeffectnum("onslaught_generator_smallexplosion"), org, '0 0 0', 1);
151                 sound(self, CHAN_AUTO, "sound/weapons/grenade_impact.wav", 1, ATTN_NORM);
152         }
153         else
154         {
155                 org = self.origin;
156                 pointparticles(particleeffectnum("onslaught_generator_finalexplosion"), org, '0 0 0', 1);
157                 sound(self, CHAN_AUTO, "sound/weapons/rocket_impact.wav", 1, ATTN_NORM);
158         }
159 };
160
161 void(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) onslaught_generator_damage =
162 {
163         if (damage <= 0)
164                 return;
165         if (attacker != self)
166         {
167                 if (self.isshielded)
168                 {
169                         // this is protected by a shield, so ignore the damage
170                         if (time > self.pain_finished)
171                         if (attacker.classname == "player")
172                         {
173                                 play2(attacker, "sound/onslaught/damageblockedbyshield.wav");
174                                 self.pain_finished = time + 1;
175                         }
176                         return;
177                 }
178                 if (time > self.pain_finished)
179                 {
180                         self.pain_finished = time + 5;
181                         bprint(ColoredTeamName(self.team), " generator under attack!\n");
182                         play2team(self.team, "sound/onslaught/generator_underattack.wav");
183                 }
184         }
185         self.health = self.health - damage;
186         // choose an animation frame based on health
187         self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1);
188         // see if the generator is still functional, or dying
189         if (self.health > 0)
190                 bprint(ColoredTeamName(self.team), " generator has ", ftos(floor(self.health)), " health remaining\n");
191         else
192         {
193                 if (attacker == self)
194                         bprint(ColoredTeamName(self.team), " generator spontaneously exploded due to overtime!\n");
195                 else
196                         bprint(ColoredTeamName(self.team), " generator destroyed by ", ColoredTeamName(attacker.team), "!\n");
197                 self.iscaptured = FALSE;
198                 self.islinked = FALSE;
199                 self.isshielded = FALSE;
200                 self.takedamage = DAMAGE_NO; // can't be hurt anymore
201                 self.event_damage = SUB_Null; // won't do anything if hurt
202                 self.count = 30; // 30 explosions
203                 self.think = onslaught_generator_deaththink; // explosion sequence
204                 self.nextthink = time; // start exploding immediately
205                 self.think(); // do the first explosion now
206                 onslaught_updatelinks();
207         }
208 };
209
210 // update links after a delay
211 void() onslaught_generator_delayed =
212 {
213         onslaught_updatelinks();
214         // now begin normal thinking
215         self.think = onslaught_generator_think;
216         self.nextthink = time;
217 };
218
219 /*QUAKED onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64)
220 Base generator.
221
222 onslaught_link entities can target this.
223
224 keys:
225 "team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET.
226 "targetname" - name that onslaught_link entities will use to target this.
227 */
228 void() onslaught_generator =
229 {
230         if (!g_onslaught)
231         {
232                 remove(self);
233                 return;
234         }
235         local entity e;
236         precache_model("models/onslaught/generator.md3");
237         precache_model("models/onslaught/generator_shield.md3");
238         precache_sound("sound/onslaught/generator_decay.wav");
239         precache_sound("sound/weapons/grenade_impact.wav");
240         precache_sound("sound/weapons/rocket_impact.wav");
241         precache_sound("sound/onslaught/generator_underattack.wav");
242         if (!self.team)
243                 objerror("team must be set");
244         self.solid = SOLID_BSP;
245         self.movetype = MOVETYPE_NONE;
246         self.max_health = self.health = 1000;
247         setmodel(self, "models/onslaught/generator.md3");
248         //setsize(self, '-32 -32 -24', '32 32 64');
249         setorigin(self, self.origin);
250         self.takedamage = DAMAGE_AIM;
251         self.event_damage = onslaught_generator_damage;
252         self.iscaptured = TRUE;
253         self.islinked = TRUE;
254         self.isshielded = TRUE;
255         // spawn shield model which indicates whether this can be damaged
256         self.enemy = e = spawn();
257         e.solid = SOLID_NOT;
258         e.movetype = MOVETYPE_NONE;
259         e.effects = EF_ADDITIVE;
260         setmodel(e, "models/onslaught/generator_shield.md3");
261         //setsize(e, '-32 -32 0', '32 32 128');
262         setorigin(e, self.origin);
263         e.colormap = self.colormap;
264         self.think = onslaught_generator_delayed;
265         self.nextthink = time + 0.2;
266 };
267
268 void(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) onslaught_controlpoint_icon_damage =
269 {
270         if (damage <= 0)
271                 return;
272         if (self.owner.isshielded)
273         {
274                 // this is protected by a shield, so ignore the damage
275                 if (time > self.pain_finished)
276                 if (attacker.classname == "player")
277                 {
278                         play2(attacker, "sound/onslaught/damageblockedbyshield.wav");
279                         self.pain_finished = time + 1;
280                 }
281                 return;
282         }
283         if (time > self.pain_finished)
284         if (attacker.classname == "player")
285         {
286                 play2team(self.team, "sound/onslaught/controlpoint_underattack.wav");
287                 self.pain_finished = time + 5;
288         }
289         self.health = self.health - damage;
290         self.alpha = self.health / self.max_health;
291         self.pain_finished = time + 1;
292         // colormod flash when shot
293         self.colormod = '2 2 2';
294         if (self.health < 0)
295         {
296                 sound(self, CHAN_AUTO, "sound/weapons/grenade_impact.wav", 1, ATTN_NORM);
297                 pointparticles(particleeffectnum("onslaught_controlpoint_explosion"), self.origin, '0 0 0', 1);
298                 bprint(ColoredTeamName(self.team), " ", self.message, " control point destroyed by ", ColoredTeamName(attacker.team), "\n");
299                 self.owner.goalentity = world;
300                 self.owner.islinked = FALSE;
301                 self.owner.iscaptured = FALSE;
302                 self.owner.team = 0;
303                 self.owner.colormap = 1024;
304                 onslaught_updatelinks();
305                 remove(self);
306         }
307 };
308
309 void() onslaught_controlpoint_icon_think =
310 {
311         self.nextthink = time + 0.1;
312         if (time > self.pain_finished + 1)
313         {
314                 self.health = self.health + self.count;
315                 if (self.health >= self.max_health)
316                         self.health = self.max_health;
317         }
318         self.alpha = self.health / self.max_health;
319         // colormod flash when shot
320         self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1));
321 };
322
323 void() onslaught_controlpoint_icon_buildthink =
324 {
325         self.nextthink = time + 0.1;
326         self.health = self.health + self.count;
327         if (self.health >= self.max_health)
328         {
329                 self.health = self.max_health;
330                 self.count = self.count * 0.2; // slow repair rate from now on
331                 self.think = onslaught_controlpoint_icon_think;
332                 sound(self, CHAN_BODY, "sound/onslaught/controlpoint_built.wav", 1, ATTN_NORM);
333                 bprint(ColoredTeamName(self.team), " captured ", self.owner.message, " control point\n");
334                 self.owner.iscaptured = TRUE;
335                 onslaught_updatelinks();
336         }
337         self.alpha = self.health / self.max_health;
338         // colormod flash when shot
339         self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1));
340 };
341
342 void() onslaught_controlpoint_touch =
343 {
344         local entity e;
345         if (other.classname != "player")
346                 return;
347         // if there's already an icon built, nothing happens
348         if (self.goalentity)
349                 return;
350         // shielded points are definitely off-limits
351         if (self.isshielded)
352                 return;
353         // check to see if this player has a legitimate claim to capture this
354         // control point - more specifically that there is a captured path of
355         // points leading back to the team generator
356         e = findchain(classname, "onslaught_link");
357         while (e)
358         {
359                 if (e.goalentity == self)
360                 {
361                         if (e.enemy.islinked)
362                         if (e.enemy.team == other.team)
363                                 break;
364                 }
365                 else if (e.enemy == self)
366                 {
367                         if (e.goalentity.islinked)
368                         if (e.goalentity.team == other.team)
369                                 break;
370                 }
371                 e = e.chain;
372         }
373         if (!e)
374                 return;
375         // we've verified that this player has a legitimate claim to this point,
376         // so start building the captured point icon (which only captures this
377         // point if it successfully builds without being destroyed first)
378         self.goalentity = e = spawn();
379         e.owner = self;
380         e.max_health = 300;
381         e.health = e.max_health * 0.1;
382         e.alpha = e.health / e.max_health;
383         e.solid = SOLID_BBOX;
384         e.movetype = MOVETYPE_NONE;
385         setmodel(e, "models/onslaught/controlpoint_icon.md3");
386         setsize(e, '-32 -32 -32', '32 32 32');
387         setorigin(e, self.origin + '0 0 96');
388         e.takedamage = DAMAGE_AIM;
389         e.event_damage = onslaught_controlpoint_icon_damage;
390         e.team = other.team;
391         e.think = onslaught_controlpoint_icon_buildthink;
392         e.nextthink = time + 0.1;
393         e.count = e.max_health / 50; // how long it takes to build
394         sound(e, CHAN_BODY, "sound/onslaught/controlpoint_build.wav", 1, ATTN_NORM);
395         self.team = e.team;
396         self.colormap = 1024 + (self.team - 1) * 17;
397 };
398
399 /*QUAKED onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128)
400 Control point.  Be sure to give this enough clearance so that the shootable part has room to exist
401
402 This should link to an onslaught_controlpoint entity or onslaught_generator entity.
403
404 keys:
405 "targetname" - name that onslaught_link entities will use to target this.
406 "target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities.
407 "message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc)
408 */
409 void() onslaught_controlpoint =
410 {
411         local entity e;
412         if (!g_onslaught)
413         {
414                 remove(self);
415                 return;
416         }
417         precache_model("models/onslaught/controlpoint_pad.md3");
418         precache_model("models/onslaught/controlpoint_shield.md3");
419         precache_model("models/onslaught/controlpoint_icon.md3");
420         precache_sound("sound/onslaught/controlpoint_build.wav");
421         precache_sound("sound/onslaught/controlpoint_built.wav");
422         precache_sound("sound/weapons/grenade_impact.wav");
423         precache_sound("sound/onslaught/damageblockedbyshield.wav");
424         precache_sound("sound/onslaught/controlpoint_underattack.wav");
425         self.solid = SOLID_BSP;
426         self.movetype = MOVETYPE_NONE;
427         setmodel(self, "models/onslaught/controlpoint_pad.md3");
428         //setsize(self, '-32 -32 0', '32 32 8');
429         setorigin(self, self.origin);
430         self.touch = onslaught_controlpoint_touch;
431         self.colormap = 1024;
432         self.iscaptured = FALSE;
433         self.islinked = FALSE;
434         self.isshielded = TRUE;
435         // spawn shield model which indicates whether this can be damaged
436         self.enemy = e = spawn();
437         e.solid = SOLID_NOT;
438         e.movetype = MOVETYPE_NONE;
439         e.effects = EF_ADDITIVE;
440         setmodel(e, "models/onslaught/controlpoint_shield.md3");
441         //setsize(e, '-32 -32 0', '32 32 128');
442         setorigin(e, self.origin);
443         e.colormap = self.colormap;
444         onslaught_updatelinks();
445 };
446
447 void() onslaught_link_delayed =
448 {
449         self.goalentity = find(world, targetname, self.target);
450         self.enemy = find(world, targetname, self.target2);
451         if (!self.goalentity)
452                 objerror("can not find target\n");
453         if (!self.enemy)
454                 objerror("can not find target2\n");
455 }
456
457 /*QUAKED onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16)
458 Link between control points.
459
460 This entity targets two different onslaught_controlpoint or onslaught_generator entities, and suppresses shielding on both if they are owned by different teams.
461
462 keys:
463 "target" - first control point.
464 "target2" - second control point.
465 */
466 void() onslaught_link =
467 {
468         if (!g_onslaught)
469         {
470                 remove(self);
471                 return;
472         }
473         if (self.target == "" || self.target2 == "")
474                 objerror("target and target2 must be set\n");
475         self.think = onslaught_link_delayed;
476         self.nextthink = time + 0.1;
477 };