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