]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/mode_onslaught.qc
onslaught: more consistent generator model names (+ small code changes for that)...
[divverent/nexuiz.git] / data / qcsrc / server / mode_onslaught.qc
1 void onslaught_generator_updatesprite(entity e);
2 void onslaught_controlpoint_updatesprite(entity e);
3 void onslaught_link_checkupdate();
4
5 .entity sprite;
6 .string target2;
7 .float iscaptured;
8 .float islinked;
9 .float isgenneighbor_red;
10 .float isgenneighbor_blue;
11 .float iscpneighbor_red;
12 .float iscpneighbor_blue;
13 .float isshielded;
14 .float lasthealth;
15 .float lastteam;
16 .float lastshielded;
17
18 .string model1, model2, model3;
19
20 void onslaught_updatelinks()
21 {
22         local entity l, links;
23         local float stop, t1, t2, t3, t4;
24         // first check if the game has ended
25         dprint("--- updatelinks ---\n");
26         links = findchain(classname, "onslaught_link");
27         // mark generators as being shielded and networked
28         l = findchain(classname, "onslaught_generator");
29         while (l)
30         {
31                 if (l.iscaptured)
32                         dprint(etos(l), " (generator) belongs to team ", ftos(l.team), "\n");
33                 else
34                         dprint(etos(l), " (generator) is destroyed\n");
35                 l.islinked = l.iscaptured;
36                 l.isshielded = l.iscaptured;
37                 l = l.chain;
38         }
39         // mark points as shielded and not networked
40         l = findchain(classname, "onslaught_controlpoint");
41         while (l)
42         {
43                 l.islinked = FALSE;
44                 l.isshielded = TRUE;
45                 l.isgenneighbor_red = FALSE;
46                 l.isgenneighbor_blue = FALSE;
47                 l.iscpneighbor_red = FALSE;
48                 l.iscpneighbor_blue = FALSE;
49                 dprint(etos(l), " (point) belongs to team ", ftos(l.team), "\n");
50                 l = l.chain;
51         }
52         // flow power outward from the generators through the network
53         l = links;
54         while (l)
55         {
56                 dprint(etos(l), " (link) connects ", etos(l.goalentity), " with ", etos(l.enemy), "\n");
57                 l = l.chain;
58         }
59         stop = FALSE;
60         while (!stop)
61         {
62                 stop = TRUE;
63                 l = links;
64                 while (l)
65                 {
66                         // if both points are captured by the same team, and only one of
67                         // them is powered, mark the other one as powered as well
68                         if (l.enemy.iscaptured && l.goalentity.iscaptured)
69                         if (l.enemy.islinked != l.goalentity.islinked)
70                         if (l.enemy.team == l.goalentity.team)
71                         {
72                                 if (!l.goalentity.islinked)
73                                 {
74                                         stop = FALSE;
75                                         l.goalentity.islinked = TRUE;
76                                         dprint(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)\n");
77                                 }
78                                 else if (!l.enemy.islinked)
79                                 {
80                                         stop = FALSE;
81                                         l.enemy.islinked = TRUE;
82                                         dprint(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)\n");
83                                 }
84                         }
85                         l = l.chain;
86                 }
87         }
88         // now that we know which points are powered we can mark their neighbors
89         // as unshielded if team differs
90         l = links;
91         while (l)
92         {
93                 if (l.goalentity.team != l.enemy.team)
94                 {
95                         if (l.goalentity.islinked)
96                         {
97                                 dprint(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)\n");
98                                 l.enemy.isshielded = FALSE;
99                                 if(l.goalentity.classname == "onslaught_generator")
100                                 {
101                                         if(l.goalentity.team == COLOR_TEAM1)
102                                                 l.enemy.isgenneighbor_red = TRUE;
103                                         else if(l.goalentity.team == COLOR_TEAM2)
104                                                 l.enemy.isgenneighbor_blue = TRUE;
105                                 }
106                                 else
107                                 {
108                                         if(l.goalentity.team == COLOR_TEAM1)
109                                                 l.enemy.iscpneighbor_red = TRUE;
110                                         else if(l.goalentity.team == COLOR_TEAM2)
111                                                 l.enemy.iscpneighbor_blue = TRUE;
112                                 }
113                         }
114                         if (l.enemy.islinked)
115                         {
116                                 dprint(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)\n");
117                                 l.goalentity.isshielded = FALSE;
118                                 if(l.enemy.classname == "onslaught_generator")
119                                 {
120                                         if(l.enemy.team == COLOR_TEAM1)
121                                                 l.goalentity.isgenneighbor_red = TRUE;
122                                         else if(l.enemy.team == COLOR_TEAM2)
123                                                 l.goalentity.isgenneighbor_blue = TRUE;
124                                 }
125                                 else
126                                 {
127                                         if(l.enemy.team == COLOR_TEAM1)
128                                                 l.goalentity.iscpneighbor_red = TRUE;
129                                         else if(l.enemy.team == COLOR_TEAM2)
130                                                 l.goalentity.iscpneighbor_blue = TRUE;
131                                 }
132                         }
133                 }
134                 l = l.chain;
135         }
136         // now update the takedamage and alpha variables on generator shields
137         l = findchain(classname, "onslaught_generator");
138         while (l)
139         {
140                 if (l.isshielded)
141                 {
142                         dprint(etos(l), " (generator) is shielded\n");
143                         l.enemy.alpha = 1;
144                         l.takedamage = DAMAGE_NO;
145                         l.bot_attack = FALSE;
146                 }
147                 else
148                 {
149                         dprint(etos(l), " (generator) is not shielded\n");
150                         l.enemy.alpha = -1;
151                         l.takedamage = DAMAGE_AIM;
152                         l.bot_attack = TRUE;
153                 }
154                 l = l.chain;
155         }
156         // now update the takedamage and alpha variables on control point icons
157         l = findchain(classname, "onslaught_controlpoint");
158         while (l)
159         {
160                 if (l.isshielded)
161                 {
162                         dprint(etos(l), " (point) is shielded\n");
163                         l.enemy.alpha = 1;
164                         if (l.goalentity)
165                         {
166                                 l.goalentity.takedamage = DAMAGE_NO;
167                                 l.goalentity.bot_attack = FALSE;
168                         }
169                 }
170                 else
171                 {
172                         dprint(etos(l), " (point) is not shielded\n");
173                         l.enemy.alpha = -1;
174                         if (l.goalentity)
175                         {
176                                 l.goalentity.takedamage = DAMAGE_AIM;
177                                 l.goalentity.bot_attack = TRUE;
178                         }
179                 }
180                 onslaught_controlpoint_updatesprite(l);
181                 l = l.chain;
182         }
183         // count generators owned by each team
184         t1 = t2 = t3 = t4 = 0;
185         l = findchain(classname, "onslaught_generator");
186         while (l)
187         {
188                 if (l.iscaptured)
189                 {
190                         if (l.team == COLOR_TEAM1) t1 = 1;
191                         if (l.team == COLOR_TEAM2) t2 = 1;
192                         if (l.team == COLOR_TEAM3) t3 = 1;
193                         if (l.team == COLOR_TEAM4) t4 = 1;
194                 }
195                 onslaught_generator_updatesprite(l);
196                 l = l.chain;
197         }
198         // see if multiple teams remain (if not, it's game over)
199         if (t1 + t2 + t3 + t4 < 2)
200                 dprint("--- game over ---\n");
201         else
202                 dprint("--- done updating links ---\n");
203 };
204
205 float onslaught_controlpoint_can_be_linked(entity cp, float t)
206 {
207         if(t == COLOR_TEAM1)
208         {
209                 if(cp.isgenneighbor_red)
210                         return 2;
211                 if(cp.iscpneighbor_red)
212                         return 1;
213         }
214         else if(t == COLOR_TEAM2)
215         {
216                 if(cp.isgenneighbor_blue)
217                         return 2;
218                 if(cp.iscpneighbor_blue)
219                         return 1;
220         }
221         return 0;
222 /*
223         entity e;
224         // check to see if this player has a legitimate claim to capture this
225         // control point - more specifically that there is a captured path of
226         // points leading back to the team generator
227         e = findchain(classname, "onslaught_link");
228         while (e)
229         {
230                 if (e.goalentity == cp)
231                 {
232                         dprint(etos(e), " (link) connects to ", etos(e.enemy), " (point)");
233                         if (e.enemy.islinked)
234                         {
235                                 dprint(" which is linked");
236                                 if (e.enemy.team == t)
237                                 {
238                                         dprint(" and has the correct team!\n");
239                                         return 1;
240                                 }
241                                 else
242                                         dprint(" but has the wrong team\n");
243                         }
244                         else
245                                 dprint("\n");
246                 }
247                 else if (e.enemy == cp)
248                 {
249                         dprint(etos(e), " (link) connects to ", etos(e.goalentity), " (point)");
250                         if (e.goalentity.islinked)
251                         {
252                                 dprint(" which is linked");
253                                 if (e.goalentity.team == t)
254                                 {
255                                         dprint(" and has a team!\n");
256                                         return 1;
257                                 }
258                                 else
259                                         dprint(" but has the wrong team\n");
260                         }
261                         else
262                                 dprint("\n");
263                 }
264                 e = e.chain;
265         }
266         return 0;
267 */
268 }
269
270 float onslaught_controlpoint_attackable(entity cp, float t)
271 // -2: SAME TEAM, attackable by enemy!
272 // -1: SAME TEAM!
273 // 0:  off limits
274 // 1:  attack it
275 // 2:  touch it
276 // 3:  attack it (HIGH PRIO)
277 // 4:  touch it (HIGH PRIO)
278 {
279         float a;
280
281         if(cp.isshielded)
282         {
283                 return 0;
284         }
285         else if(cp.goalentity)
286         {
287                 // if there's already an icon built, nothing happens
288                 if(cp.team == t)
289                 {
290                         a = onslaught_controlpoint_can_be_linked(cp, COLOR_TEAM1 + COLOR_TEAM2 - t);
291                         if(a) // attackable by enemy?
292                                 return -2; // EMERGENCY!
293                         return -1;
294                 }
295                 // we know it can be linked, so no need to check
296                 // but...
297                 a = onslaught_controlpoint_can_be_linked(cp, t);
298                 if(a == 2) // near our generator?
299                         return 3; // EMERGENCY!
300                 return 1;
301         }
302         else
303         {
304                 // free point
305                 if(onslaught_controlpoint_can_be_linked(cp, t))
306                 {
307                         a = onslaught_controlpoint_can_be_linked(cp, COLOR_TEAM1 + COLOR_TEAM2 - t);
308                         if(a == 2)
309                                 return 4; // GET THIS ONE NOW!
310                         else
311                                 return 2; // TOUCH ME
312                 }
313         }
314         return 0;
315 }
316
317 void onslaught_generator_think()
318 {
319         local float d;
320         local entity e;
321         self.nextthink = ceil(time + 1);
322         if (cvar("timelimit"))
323         if (time > cvar("timelimit") * 60 - 60)
324         {
325                 // self.max_health / 300 gives 5 minutes of overtime.
326                 // control points reduce the overtime duration.
327                 sound(self, CHAN_TRIGGER, "onslaught/generator_decay.wav", VOL_BASE, ATTN_NORM);
328                 d = 1;
329                 e = findchain(classname, "onslaught_controlpoint");
330                 while (e)
331                 {
332                         if (e.team != self.team)
333                         if (e.islinked)
334                                 d = d + 1;
335                         e = e.chain;
336                 }
337                 d = d * self.max_health / 300;
338                 Damage(self, self, self, d, DEATH_HURTTRIGGER, self.origin, '0 0 0');
339         }
340 };
341
342 void onslaught_generator_deaththink()
343 {
344         local vector org;
345         if (self.count > 0)
346         {
347                 self.nextthink = time + 0.1;
348                 self.count = self.count - 1;
349                 org = randompos(self.origin + self.mins + '8 8 8', self.origin + self.maxs + '-8 -8 -8');
350                 pointparticles(particleeffectnum("onslaught_generator_smallexplosion"), org, '0 0 0', 1);
351                 sound(self, CHAN_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
352         }
353         else
354         {
355                 org = self.origin;
356                 pointparticles(particleeffectnum("onslaught_generator_finalexplosion"), org, '0 0 0', 1);
357                 sound(self, CHAN_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
358         }
359 };
360
361 void onslaught_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
362 {
363         if (damage <= 0)
364                 return;
365         if (attacker != self)
366         {
367                 if (self.isshielded)
368                 {
369                         // this is protected by a shield, so ignore the damage
370                         if (time > self.pain_finished)
371                         if (attacker.classname == "player")
372                         {
373                                 play2(attacker, "onslaught/damageblockedbyshield.wav");
374                                 self.pain_finished = time + 1;
375                         }
376                         return;
377                 }
378                 if (time > self.pain_finished)
379                 {
380                         self.pain_finished = time + 5;
381                         bprint(ColoredTeamName(self.team), " generator under attack!\n");
382                         play2team(self.team, "onslaught/generator_underattack.wav");
383                 }
384         }
385         self.health = self.health - damage;
386         // choose an animation frame based on health
387         self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1);
388         // see if the generator is still functional, or dying
389         if (self.health > 0)
390         {
391                 float h, lh;
392                 lh = ceil(self.lasthealth / 100) * 100;
393                 h = ceil(self.health / 100) * 100;
394                 if(lh != h)
395                         bprint(ColoredTeamName(self.team), " generator has less than ", ftos(h), " health remaining\n");
396                 self.lasthealth = self.health;
397         }
398         else
399         {
400                 if (attacker == self)
401                         bprint(ColoredTeamName(self.team), " generator spontaneously exploded due to overtime!\n");
402                 else
403                 {
404                         string t;
405                         t = ColoredTeamName(attacker.team);
406                         bprint(ColoredTeamName(self.team), " generator destroyed by ", t, "!\n");
407                 }
408                 self.iscaptured = FALSE;
409                 self.islinked = FALSE;
410                 self.isshielded = FALSE;
411                 self.takedamage = DAMAGE_NO; // can't be hurt anymore
412                 self.event_damage = SUB_Null; // won't do anything if hurt
413                 self.count = 30; // 30 explosions
414                 self.think = onslaught_generator_deaththink; // explosion sequence
415                 self.nextthink = time; // start exploding immediately
416                 self.think(); // do the first explosion now
417                 onslaught_updatelinks();
418         }
419         if(self.health < cvar("g_onslaught_gen_health") * 0.75)
420                 setmodel(self, "models/onslaught/generator_dmg1.md3");
421         if(self.health < cvar("g_onslaught_gen_health") * 0.35)
422                 setmodel(self, "models/onslaught/generator_dmg2.md3");
423         if(self.health <= 0)
424                 setmodel(self, "models/onslaught/generator_dead.md3");
425 };
426
427 // update links after a delay
428 void onslaught_generator_delayed()
429 {
430         onslaught_updatelinks();
431         // now begin normal thinking
432         self.think = onslaught_generator_think;
433         self.nextthink = time;
434 };
435
436 string onslaught_generator_waypointsprite_for_team(entity e, float t)
437 {
438         if(t == e.team)
439         {
440                 if(e.team == COLOR_TEAM1)
441                         return "ons-gen-red";
442                 else if(e.team == COLOR_TEAM2)
443                         return "ons-gen-blue";
444         }
445         if(e.isshielded)
446                 return "ons-gen-shielded";
447         if(e.team == COLOR_TEAM1)
448                 return "ons-gen-red";
449         else if(e.team == COLOR_TEAM2)
450                 return "ons-gen-blue";
451         return "";
452 }
453
454 void onslaught_generator_updatesprite(entity e)
455 {
456         string s1, s2, s3;
457         s1 = onslaught_generator_waypointsprite_for_team(e, COLOR_TEAM1);
458         s2 = onslaught_generator_waypointsprite_for_team(e, COLOR_TEAM2);
459         s3 = onslaught_generator_waypointsprite_for_team(e, -1);
460         WaypointSprite_UpdateSprites(e.sprite, s1, s2, s3);
461
462         if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded)
463         {
464                 e.lastteam = e.team + 2;
465                 e.lastshielded = e.isshielded;
466                 if(e.lastshielded)
467                 {
468                         if(e.team == COLOR_TEAM1)
469                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0 0');
470                         else if(e.team == COLOR_TEAM2)
471                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0 0 0.5');
472                         else
473                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5');
474                 }
475                 else
476                 {
477                         if(e.team == COLOR_TEAM1)
478                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '1 0 0');
479                         else if(e.team == COLOR_TEAM2)
480                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0 0 1');
481                         else
482                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75');
483                 }
484                 WaypointSprite_Ping(e.sprite);
485         }
486 }
487
488 string onslaught_controlpoint_waypointsprite_for_team(entity e, float t)
489 {
490         float a;
491         if(t != -1)
492         {
493                 a = onslaught_controlpoint_attackable(e, t);
494                 if(a == 3 || a == 4) // ATTACK/TOUCH THIS ONE NOW
495                 {
496                         if(e.team == COLOR_TEAM1)
497                                 return "ons-cp-atck-red";
498                         else if(e.team == COLOR_TEAM2)
499                                 return "ons-cp-atck-blue";
500                         else
501                                 return "ons-cp-atck-neut";
502                 }
503                 else if(a == -2) // DEFEND THIS ONE NOW
504                 {
505                         if(e.team == COLOR_TEAM1)
506                                 return "ons-cp-dfnd-red";
507                         else if(e.team == COLOR_TEAM2)
508                                 return "ons-cp-dfnd-blue";
509                 }
510                 else if(e.team == t || a == -1 || a == 1) // own point, or fire at it
511                 {
512                         if(e.team == COLOR_TEAM1)
513                                 return "ons-cp-red";
514                         else if(e.team == COLOR_TEAM2)
515                                 return "ons-cp-blue";
516                 }
517                 else if(a == 2) // touch it
518                         return "ons-cp-neut";
519         }
520         else
521         {
522                 if(e.team == COLOR_TEAM1)
523                         return "ons-cp-red";
524                 else if(e.team == COLOR_TEAM2)
525                         return "ons-cp-blue";
526                 else
527                         return "ons-cp-neut";
528         }
529         return "";
530 }
531
532 void onslaught_controlpoint_updatesprite(entity e)
533 {
534         string s1, s2, s3;
535         s1 = onslaught_controlpoint_waypointsprite_for_team(e, COLOR_TEAM1);
536         s2 = onslaught_controlpoint_waypointsprite_for_team(e, COLOR_TEAM2);
537         s3 = onslaught_controlpoint_waypointsprite_for_team(e, -1);
538         WaypointSprite_UpdateSprites(e.sprite, s1, s2, s3);
539
540         float sh;
541         sh = !(onslaught_controlpoint_can_be_linked(e, COLOR_TEAM1) || onslaught_controlpoint_can_be_linked(e, COLOR_TEAM2));
542
543         if(e.lastteam != e.team + 2 || e.lastshielded != sh)
544         {
545                 e.lastteam = e.team + 2;
546                 e.lastshielded = sh;
547                 if(e.lastshielded)
548                 {
549                         if(e.team == COLOR_TEAM1)
550                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0 0');
551                         else if(e.team == COLOR_TEAM2)
552                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0 0 0.5');
553                         else
554                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5');
555                 }
556                 else
557                 {
558                         if(e.team == COLOR_TEAM1)
559                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '1 0 0');
560                         else if(e.team == COLOR_TEAM2)
561                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0 0 1');
562                         else
563                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75');
564                 }
565                 WaypointSprite_Ping(e.sprite);
566         }
567 }
568
569 void onslaught_generator_reset()
570 {
571         self.team = self.team_saved;
572         self.lasthealth = self.max_health = self.health = cvar("g_onslaught_gen_health");
573         self.takedamage = DAMAGE_AIM;
574         self.bot_attack = TRUE;
575         self.iscaptured = TRUE;
576         self.islinked = TRUE;
577         self.isshielded = TRUE;
578         self.enemy.solid = SOLID_NOT;
579         self.think = onslaught_generator_delayed;
580         self.nextthink = time + 0.2;
581 }
582
583 /*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64)
584 Base generator.
585
586 spawnfunc_onslaught_link entities can target this.
587
588 keys:
589 "team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET.
590 "targetname" - name that spawnfunc_onslaught_link entities will use to target this.
591 */
592 void spawnfunc_onslaught_generator()
593 {
594         if (!g_onslaught)
595         {
596                 remove(self);
597                 return;
598         }
599
600         local entity e;
601         precache_model("models/onslaught/generator.md3");
602         precache_model("models/onslaught/generator_shield.md3");
603         precache_model("models/onslaught/generator_dmg1.md3");
604         precache_model("models/onslaught/generator_dmg2.md3");
605         precache_model("models/onslaught/generator_dead.md3");
606         precache_sound("onslaught/generator_decay.wav");
607         precache_sound("weapons/grenade_impact.wav");
608         precache_sound("weapons/rocket_impact.wav");
609         precache_sound("onslaught/generator_underattack.wav");
610         if (!self.team)
611                 objerror("team must be set");
612         self.team_saved = self.team;
613         self.colormap = 1024 + (self.team - 1) * 17;
614         self.solid = SOLID_BSP;
615         self.movetype = MOVETYPE_NONE;
616         self.lasthealth = self.max_health = self.health = cvar("g_onslaught_gen_health");
617         setmodel(self, "models/onslaught/generator.md3");
618         //setsize(self, '-32 -32 -24', '32 32 64');
619         setorigin(self, self.origin);
620         self.takedamage = DAMAGE_AIM;
621         self.bot_attack = TRUE;
622         self.event_damage = onslaught_generator_damage;
623         self.iscaptured = TRUE;
624         self.islinked = TRUE;
625         self.isshielded = TRUE;
626         // spawn shield model which indicates whether this can be damaged
627         self.enemy = e = spawn();
628         e.classname = "onslaught_generator_shield";
629         e.solid = SOLID_NOT;
630         e.movetype = MOVETYPE_NONE;
631         e.effects = EF_ADDITIVE;
632         setmodel(e, "models/onslaught/generator_shield.md3");
633         //setsize(e, '-32 -32 0', '32 32 128');
634         setorigin(e, self.origin);
635         e.colormap = self.colormap;
636         e.team = self.team;
637         self.think = onslaught_generator_delayed;
638         self.nextthink = time + 0.2;
639         InitializeEntity(self, onslaught_generator_delayed, INITPRIO_LAST);
640
641         WaypointSprite_SpawnFixed(string_null, e.origin + '0 0 1' * e.maxs_z, self, sprite);
642         WaypointSprite_UpdateRule(self.sprite, COLOR_TEAM2, SPRITERULE_TEAMPLAY);
643
644         onslaught_updatelinks();
645
646         self.reset = onslaught_generator_reset;
647 };
648
649 .float waslinked;
650 void onslaught_controlpoint_icon_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
651 {
652         entity oself;
653         if (damage <= 0)
654                 return;
655         if (self.owner.isshielded)
656         {
657                 // this is protected by a shield, so ignore the damage
658                 if (time > self.pain_finished)
659                 if (attacker.classname == "player")
660                 {
661                         play2(attacker, "onslaught/damageblockedbyshield.wav");
662                         self.pain_finished = time + 1;
663                 }
664                 return;
665         }
666         if (time > self.pain_finished)
667         if (attacker.classname == "player")
668         {
669                 play2team(self.team, "onslaught/controlpoint_underattack.wav");
670                 self.pain_finished = time + 5;
671         }
672         self.health = self.health - damage;
673         self.alpha = self.health / self.max_health;
674         self.pain_finished = time + 1;
675         // colormod flash when shot
676         self.colormod = '2 2 2';
677         if (self.health < 0)
678         {
679                 sound(self, CHAN_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
680                 pointparticles(particleeffectnum("onslaught_controlpoint_explosion"), self.origin, '0 0 0', 1);
681                 {
682                         string t;
683                         t = ColoredTeamName(attacker.team);
684                         bprint(ColoredTeamName(self.team), " ", self.message, " control point destroyed by ", t, "\n");
685                 }
686                 self.owner.goalentity = world;
687                 self.owner.islinked = FALSE;
688                 self.owner.iscaptured = FALSE;
689                 self.owner.team = 0;
690                 self.owner.colormap = 1024;
691                 onslaught_updatelinks();
692
693                 // Use targets now (somebody make sure this is in the right place..)
694                 oself = self;
695                 self = self.owner;
696                 activator = self;
697                 SUB_UseTargets ();
698                 self = oself;
699
700                 self.owner.waslinked = self.owner.islinked;
701
702                 remove(self);
703         }
704 };
705
706 void onslaught_controlpoint_icon_think()
707 {
708         entity oself;
709         self.nextthink = time + 0.1;
710         if (time > self.pain_finished + 1)
711         {
712                 self.health = self.health + self.count;
713                 if (self.health >= self.max_health)
714                         self.health = self.max_health;
715         }
716         self.alpha = self.health / self.max_health;
717         // colormod flash when shot
718         self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1));
719
720         if(self.owner.islinked != self.owner.waslinked)
721         {
722                 // unteam the spawnpoint if needed
723                 float t;
724                 t = self.owner.team;
725                 if(!self.owner.islinked)
726                         self.owner.team = 0;
727
728                 oself = self;
729                 self = self.owner;
730                 activator = self;
731                 SUB_UseTargets ();
732                 self = oself;
733
734                 self.owner.team = t;
735
736                 self.owner.waslinked = self.owner.islinked;
737         }
738 };
739
740 void onslaught_controlpoint_icon_buildthink()
741 {
742         local entity oself;
743         float a;
744
745         self.nextthink = time + 0.1;
746
747         // only do this if there is power
748         a = onslaught_controlpoint_can_be_linked(self.owner, self.owner.team);
749         if(!a)
750                 return;
751
752         self.health = self.health + self.count;
753
754         if (self.health >= self.max_health)
755         {
756                 self.health = self.max_health;
757                 self.count = self.count * 0.2; // slow repair rate from now on
758                 self.think = onslaught_controlpoint_icon_think;
759                 sound(self, CHAN_TRIGGER, "onslaught/controlpoint_built.wav", VOL_BASE, ATTN_NORM);
760                 bprint(ColoredTeamName(self.team), " captured ", self.owner.message, " control point\n");
761                 self.owner.iscaptured = TRUE;
762                 onslaught_updatelinks();
763
764                 // Use targets now (somebody make sure this is in the right place..)
765                 oself = self;
766                 self = self.owner;
767                 activator = self;
768                 SUB_UseTargets ();
769                 self = oself;
770         }
771         self.alpha = self.health / self.max_health;
772         // colormod flash when shot
773         self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1));
774 };
775
776 void onslaught_controlpoint_touch()
777 {
778         local entity e;
779         float a;
780         if (other.classname != "player")
781                 return;
782         a = onslaught_controlpoint_attackable(self, other.team);
783         if(a != 2 && a != 4)
784                 return;
785         // we've verified that this player has a legitimate claim to this point,
786         // so start building the captured point icon (which only captures this
787         // point if it successfully builds without being destroyed first)
788         self.goalentity = e = spawn();
789         e.classname = "onslaught_controlpoint_icon";
790         e.owner = self;
791         e.max_health = cvar("g_onslaught_cp_health");
792         e.health = e.max_health * 0.1;
793         e.alpha = e.health / e.max_health;
794         e.solid = SOLID_BBOX;
795         e.movetype = MOVETYPE_NONE;
796         setmodel(e, "models/onslaught/controlpoint_icon.md3");
797         setsize(e, '-32 -32 -32', '32 32 32');
798         setorigin(e, self.origin + '0 0 96');
799         e.takedamage = DAMAGE_AIM;
800         e.bot_attack = TRUE;
801         e.event_damage = onslaught_controlpoint_icon_damage;
802         e.team = other.team;
803         e.colormap = 1024 + (e.team - 1) * 17;
804         e.think = onslaught_controlpoint_icon_buildthink;
805         e.nextthink = time + 0.1;
806         e.count = e.max_health / 50; // how long it takes to build
807         sound(e, CHAN_TRIGGER, "onslaught/controlpoint_build.wav", VOL_BASE, ATTN_NORM);
808         self.team = e.team;
809         self.colormap = e.colormap;
810 };
811
812 void onslaught_controlpoint_reset()
813 {
814         if(self.goalentity && self.goalentity != world)
815                 remove(self.goalentity);
816         self.goalentity = world;
817         self.team = 0;
818         self.colormap = 1024;
819         self.iscaptured = FALSE;
820         self.islinked = FALSE;
821         self.isshielded = TRUE;
822         self.enemy.solid = SOLID_NOT;
823         self.enemy.colormap = self.colormap;
824         self.think = self.enemy.think = SUB_Null;
825         self.nextthink = 0; // don't like SUB_Null :P
826         
827         onslaught_updatelinks();
828
829         activator = self;
830         SUB_UseTargets(); // to reset the structures, playerspawns etc.
831 }
832
833 /*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128)
834 Control point.  Be sure to give this enough clearance so that the shootable part has room to exist
835
836 This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity.
837
838 keys:
839 "targetname" - name that spawnfunc_onslaught_link entities will use to target this.
840 "target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities.
841 "message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc)
842 */
843 void spawnfunc_onslaught_controlpoint()
844 {
845         local entity e;
846         if (!g_onslaught)
847         {
848                 remove(self);
849                 return;
850         }
851         precache_model("models/onslaught/controlpoint_pad.md3");
852         precache_model("models/onslaught/controlpoint_shield.md3");
853         precache_model("models/onslaught/controlpoint_icon.md3");
854         precache_sound("onslaught/controlpoint_build.wav");
855         precache_sound("onslaught/controlpoint_built.wav");
856         precache_sound("weapons/grenade_impact.wav");
857         precache_sound("onslaught/damageblockedbyshield.wav");
858         precache_sound("onslaught/controlpoint_underattack.wav");
859         self.solid = SOLID_BSP;
860         self.movetype = MOVETYPE_NONE;
861         setmodel(self, "models/onslaught/controlpoint_pad.md3");
862         //setsize(self, '-32 -32 0', '32 32 8');
863         setorigin(self, self.origin);
864         self.touch = onslaught_controlpoint_touch;
865         self.team = 0;
866         self.colormap = 1024;
867         self.iscaptured = FALSE;
868         self.islinked = FALSE;
869         self.isshielded = TRUE;
870         // spawn shield model which indicates whether this can be damaged
871         self.enemy = e = spawn();
872         e.classname = "onslaught_controlpoint_shield";
873         e.solid = SOLID_NOT;
874         e.movetype = MOVETYPE_NONE;
875         e.effects = EF_ADDITIVE;
876         setmodel(e, "models/onslaught/controlpoint_shield.md3");
877         //setsize(e, '-32 -32 0', '32 32 128');
878         setorigin(e, self.origin);
879         e.colormap = self.colormap;
880
881         waypoint_spawnforitem(self);
882
883         WaypointSprite_SpawnFixed(string_null, e.origin + '0 0 1' * e.maxs_z, self, sprite);
884         WaypointSprite_UpdateRule(self.sprite, COLOR_TEAM2, SPRITERULE_TEAMPLAY);
885
886         onslaught_updatelinks();
887
888         self.reset = onslaught_controlpoint_reset;
889 };
890
891 float onslaught_link_send(entity to, float sendflags)
892 {
893         WriteByte(MSG_ENTITY, ENT_CLIENT_RADARLINK);
894         WriteByte(MSG_ENTITY, sendflags);
895         if(sendflags & 1)
896         {
897                 WriteCoord(MSG_ENTITY, self.goalentity.origin_x);
898                 WriteCoord(MSG_ENTITY, self.goalentity.origin_y);
899                 WriteCoord(MSG_ENTITY, self.goalentity.origin_z);
900         }
901         if(sendflags & 2)
902         {
903                 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
904                 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
905                 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
906         }
907         if(sendflags & 4)
908         {
909                 WriteByte(MSG_ENTITY, self.clientcolors); // which is goalentity's color + enemy's color * 16
910         }
911         return TRUE;
912 }
913
914 void onslaught_link_checkupdate()
915 {
916         // TODO check if the two sides have moved (currently they won't move anyway)
917         float redpower, bluepower;
918
919         redpower = bluepower = 0;
920         if(self.goalentity.islinked)
921         {
922                 if(self.goalentity.team == COLOR_TEAM1)
923                         redpower = 1;
924                 else if(self.goalentity.team == COLOR_TEAM2)
925                         bluepower = 1;
926         }
927         if(self.enemy.islinked)
928         {
929                 if(self.enemy.team == COLOR_TEAM1)
930                         redpower = 2;
931                 else if(self.enemy.team == COLOR_TEAM2)
932                         bluepower = 2;
933         }
934
935         float cc;
936         if(redpower == 1 && bluepower == 2)
937                 cc = (COLOR_TEAM1 - 1) * 0x01 + (COLOR_TEAM2 - 1) * 0x10;
938         else if(redpower == 2 && bluepower == 1)
939                 cc = (COLOR_TEAM1 - 1) * 0x10 + (COLOR_TEAM2 - 1) * 0x01;
940         else if(redpower)
941                 cc = (COLOR_TEAM1 - 1) * 0x11;
942         else if(bluepower)
943                 cc = (COLOR_TEAM2 - 1) * 0x11;
944         else
945                 cc = 0;
946
947         if(cc != self.clientcolors)
948         {
949                 self.clientcolors = cc;
950                 self.SendFlags |= 4;
951         }
952
953         self.nextthink = time;
954 }
955
956 void onslaught_link_delayed()
957 {
958         self.goalentity = find(world, targetname, self.target);
959         self.enemy = find(world, targetname, self.target2);
960         if (!self.goalentity)
961                 objerror("can not find target\n");
962         if (!self.enemy)
963                 objerror("can not find target2\n");
964         dprint(etos(self.goalentity), " linked with ", etos(self.enemy), "\n");
965
966         self.SendFlags |= 3;
967         self.think = onslaught_link_checkupdate;
968         self.nextthink = time;
969 }
970
971 /*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16)
972 Link between control points.
973
974 This entity targets two different spawnfunc_onslaught_controlpoint or spawnfunc_onslaught_generator entities, and suppresses shielding on both if they are owned by different teams.
975
976 keys:
977 "target" - first control point.
978 "target2" - second control point.
979 */
980 void spawnfunc_onslaught_link()
981 {
982         if (!g_onslaught)
983         {
984                 remove(self);
985                 return;
986         }
987         if (self.target == "" || self.target2 == "")
988                 objerror("target and target2 must be set\n");
989         InitializeEntity(self, onslaught_link_delayed, INITPRIO_FINDTARGET);
990         Net_LinkEntity(self, FALSE, 0, onslaught_link_send);
991 };