]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/mode_onslaught.qc
tetris: no longer switch fonts
[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 .float lastcaptured;
18
19 .string model1, model2, model3;
20
21
22 void onslaught_generator_boom_think()
23 {
24         self.nextthink = time + 0.05;
25         if(self.frame > 14)
26         {
27                 self.think = SUB_Remove;
28                 return;
29         }
30         self.frame +=1;
31 };
32
33 void onslaught_generator_boom_spawn(vector org, float fscale)
34 {
35         entity e;
36         e = spawn();
37         setmodel(e, "models/onslaught/boom.md3");
38         setorigin(e, org);
39
40         e.scale = fscale;
41         setsize(e, e.mins * e.scale, e.maxs * e.scale);
42         e.angles = randomvec() * 360;
43
44         e.effects = EF_NOSHADOW;
45
46         e.think = onslaught_generator_boom_think;
47         e.nextthink = time + 0.05;
48 };
49
50 void ons_gib_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce)
51 {
52         self.velocity = self.velocity + vforce;
53 }
54
55 .float giblifetime;
56 void ons_throwgib_think()
57 {
58         local vector org;
59
60         self.nextthink = time + 0.05;
61         if(self.count > self.giblifetime)
62         {
63                 self.think = SUB_Remove;
64                 return;
65         }
66         if(self.count > self.giblifetime-10)
67                 self.alpha -= 0.1;
68         org = self.origin + 20 * randomvec();
69         onslaught_generator_boom_spawn(org, random()*0.5+0.3);
70         self.count +=1;
71 };
72
73 void ons_throwgib(vector v_from, vector v_to, string smodel, float f_lifetime, float f_fadetime, float b_burn)
74 {
75         local entity gib;
76
77         gib = spawn();
78
79         setmodel(gib, smodel);
80         setorigin(gib, v_from);
81         gib.solid = SOLID_BBOX;
82         gib.movetype = MOVETYPE_BOUNCE;
83         gib.takedamage = DAMAGE_YES;
84         gib.event_damage = ons_gib_damage;
85         gib.health = -1;
86         gib.effects = EF_LOWPRECISION;
87         gib.flags = FL_NOTARGET;
88         gib.velocity = v_to;
89         gib.giblifetime = f_lifetime;
90
91         if (b_burn)
92         {
93                 gib.think = ons_throwgib_think;
94                 gib.nextthink = time + 0.05;
95         }
96         else
97                 SUB_SetFade(gib, time + f_lifetime, 2);
98 };
99
100 void onslaught_updatelinks()
101 {
102         local entity l, links;
103         local float stop, t1, t2, t3, t4;
104         // first check if the game has ended
105         dprint("--- updatelinks ---\n");
106         links = findchain(classname, "onslaught_link");
107         // mark generators as being shielded and networked
108         l = findchain(classname, "onslaught_generator");
109         while (l)
110         {
111                 if (l.iscaptured)
112                         dprint(etos(l), " (generator) belongs to team ", ftos(l.team), "\n");
113                 else
114                         dprint(etos(l), " (generator) is destroyed\n");
115                 l.islinked = l.iscaptured;
116                 l.isshielded = l.iscaptured;
117                 l = l.chain;
118         }
119         // mark points as shielded and not networked
120         l = findchain(classname, "onslaught_controlpoint");
121         while (l)
122         {
123                 l.islinked = FALSE;
124                 l.isshielded = TRUE;
125                 l.isgenneighbor_red = FALSE;
126                 l.isgenneighbor_blue = FALSE;
127                 l.iscpneighbor_red = FALSE;
128                 l.iscpneighbor_blue = FALSE;
129                 dprint(etos(l), " (point) belongs to team ", ftos(l.team), "\n");
130                 l = l.chain;
131         }
132         // flow power outward from the generators through the network
133         l = links;
134         while (l)
135         {
136                 dprint(etos(l), " (link) connects ", etos(l.goalentity), " with ", etos(l.enemy), "\n");
137                 l = l.chain;
138         }
139         stop = FALSE;
140         while (!stop)
141         {
142                 stop = TRUE;
143                 l = links;
144                 while (l)
145                 {
146                         // if both points are captured by the same team, and only one of
147                         // them is powered, mark the other one as powered as well
148                         if (l.enemy.iscaptured && l.goalentity.iscaptured)
149                                 if (l.enemy.islinked != l.goalentity.islinked)
150                                         if (l.enemy.team == l.goalentity.team)
151                                         {
152                                                 if (!l.goalentity.islinked)
153                                                 {
154                                                         stop = FALSE;
155                                                         l.goalentity.islinked = TRUE;
156                                                         dprint(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)\n");
157                                                 }
158                                                 else if (!l.enemy.islinked)
159                                                 {
160                                                         stop = FALSE;
161                                                         l.enemy.islinked = TRUE;
162                                                         dprint(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)\n");
163                                                 }
164                                         }
165                         l = l.chain;
166                 }
167         }
168         // now that we know which points are powered we can mark their neighbors
169         // as unshielded if team differs
170         l = links;
171         while (l)
172         {
173                 if (l.goalentity.islinked)
174                 {
175                         if (l.goalentity.team != l.enemy.team)
176                         {
177                                 dprint(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)\n");
178                                 l.enemy.isshielded = FALSE;
179                         }
180                         if(l.goalentity.classname == "onslaught_generator")
181                         {
182                                 if(l.goalentity.team == COLOR_TEAM1)
183                                         l.enemy.isgenneighbor_red = TRUE;
184                                 else if(l.goalentity.team == COLOR_TEAM2)
185                                         l.enemy.isgenneighbor_blue = TRUE;
186                         }
187                         else
188                         {
189                                 if(l.goalentity.team == COLOR_TEAM1)
190                                         l.enemy.iscpneighbor_red = TRUE;
191                                 else if(l.goalentity.team == COLOR_TEAM2)
192                                         l.enemy.iscpneighbor_blue = TRUE;
193                         }
194                 }
195                 if (l.enemy.islinked)
196                 {
197                         if (l.goalentity.team != l.enemy.team)
198                         {
199                                 dprint(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)\n");
200                                 l.goalentity.isshielded = FALSE;
201                         }
202                         if(l.enemy.classname == "onslaught_generator")
203                         {
204                                 if(l.enemy.team == COLOR_TEAM1)
205                                         l.goalentity.isgenneighbor_red = TRUE;
206                                 else if(l.enemy.team == COLOR_TEAM2)
207                                         l.goalentity.isgenneighbor_blue = TRUE;
208                         }
209                         else
210                         {
211                                 if(l.enemy.team == COLOR_TEAM1)
212                                         l.goalentity.iscpneighbor_red = TRUE;
213                                 else if(l.enemy.team == COLOR_TEAM2)
214                                         l.goalentity.iscpneighbor_blue = TRUE;
215                         }
216                 }
217                 l = l.chain;
218         }
219         // now update the takedamage and alpha variables on generator shields
220         l = findchain(classname, "onslaught_generator");
221         while (l)
222         {
223                 if (l.isshielded)
224                 {
225                         dprint(etos(l), " (generator) is shielded\n");
226                         l.enemy.alpha = 1;
227                         l.takedamage = DAMAGE_NO;
228                         l.bot_attack = FALSE;
229                 }
230                 else
231                 {
232                         dprint(etos(l), " (generator) is not shielded\n");
233                         l.enemy.alpha = -1;
234                         l.takedamage = DAMAGE_AIM;
235                         l.bot_attack = TRUE;
236                 }
237                 l = l.chain;
238         }
239         // now update the takedamage and alpha variables on control point icons
240         l = findchain(classname, "onslaught_controlpoint");
241         while (l)
242         {
243                 if (l.isshielded)
244                 {
245                         dprint(etos(l), " (point) is shielded\n");
246                         l.enemy.alpha = 1;
247                         if (l.goalentity)
248                         {
249                                 l.goalentity.takedamage = DAMAGE_NO;
250                                 l.goalentity.bot_attack = FALSE;
251                         }
252                 }
253                 else
254                 {
255                         dprint(etos(l), " (point) is not shielded\n");
256                         l.enemy.alpha = -1;
257                         if (l.goalentity)
258                         {
259                                 l.goalentity.takedamage = DAMAGE_AIM;
260                                 l.goalentity.bot_attack = TRUE;
261                         }
262                 }
263                 onslaught_controlpoint_updatesprite(l);
264                 l = l.chain;
265         }
266         // count generators owned by each team
267         t1 = t2 = t3 = t4 = 0;
268         l = findchain(classname, "onslaught_generator");
269         while (l)
270         {
271                 if (l.iscaptured)
272                 {
273                         if (l.team == COLOR_TEAM1) t1 = 1;
274                         if (l.team == COLOR_TEAM2) t2 = 1;
275                         if (l.team == COLOR_TEAM3) t3 = 1;
276                         if (l.team == COLOR_TEAM4) t4 = 1;
277                 }
278                 onslaught_generator_updatesprite(l);
279                 l = l.chain;
280         }
281         // see if multiple teams remain (if not, it's game over)
282         if (t1 + t2 + t3 + t4 < 2)
283                 dprint("--- game over ---\n");
284         else
285                 dprint("--- done updating links ---\n");
286 };
287
288 float onslaught_controlpoint_can_be_linked(entity cp, float t)
289 {
290         if(t == COLOR_TEAM1)
291         {
292                 if(cp.isgenneighbor_red)
293                         return 2;
294                 if(cp.iscpneighbor_red)
295                         return 1;
296         }
297         else if(t == COLOR_TEAM2)
298         {
299                 if(cp.isgenneighbor_blue)
300                         return 2;
301                 if(cp.iscpneighbor_blue)
302                         return 1;
303         }
304         return 0;
305         /*
306            entity e;
307         // check to see if this player has a legitimate claim to capture this
308         // control point - more specifically that there is a captured path of
309         // points leading back to the team generator
310         e = findchain(classname, "onslaught_link");
311         while (e)
312         {
313         if (e.goalentity == cp)
314         {
315         dprint(etos(e), " (link) connects to ", etos(e.enemy), " (point)");
316         if (e.enemy.islinked)
317         {
318         dprint(" which is linked");
319         if (e.enemy.team == t)
320         {
321         dprint(" and has the correct team!\n");
322         return 1;
323         }
324         else
325         dprint(" but has the wrong team\n");
326         }
327         else
328         dprint("\n");
329         }
330         else if (e.enemy == cp)
331         {
332         dprint(etos(e), " (link) connects to ", etos(e.goalentity), " (point)");
333         if (e.goalentity.islinked)
334         {
335         dprint(" which is linked");
336         if (e.goalentity.team == t)
337         {
338         dprint(" and has a team!\n");
339         return 1;
340         }
341         else
342         dprint(" but has the wrong team\n");
343         }
344         else
345         dprint("\n");
346         }
347         e = e.chain;
348         }
349         return 0;
350          */
351 }
352
353 float onslaught_controlpoint_attackable(entity cp, float t)
354         // -2: SAME TEAM, attackable by enemy!
355         // -1: SAME TEAM!
356         // 0: off limits
357         // 1: attack it
358         // 2: touch it
359         // 3: attack it (HIGH PRIO)
360         // 4: touch it (HIGH PRIO)
361 {
362         float a;
363
364         if(cp.isshielded)
365         {
366                 return 0;
367         }
368         else if(cp.goalentity)
369         {
370                 // if there's already an icon built, nothing happens
371                 if(cp.team == t)
372                 {
373                         a = onslaught_controlpoint_can_be_linked(cp, COLOR_TEAM1 + COLOR_TEAM2 - t);
374                         if(a) // attackable by enemy?
375                                 return -2; // EMERGENCY!
376                         return -1;
377                 }
378                 // we know it can be linked, so no need to check
379                 // but...
380                 a = onslaught_controlpoint_can_be_linked(cp, t);
381                 if(a == 2) // near our generator?
382                         return 3; // EMERGENCY!
383                 return 1;
384         }
385         else
386         {
387                 // free point
388                 if(onslaught_controlpoint_can_be_linked(cp, t))
389                 {
390                         a = onslaught_controlpoint_can_be_linked(cp, COLOR_TEAM1 + COLOR_TEAM2 - t);
391                         if(a == 2)
392                                 return 4; // GET THIS ONE NOW!
393                         else
394                                 return 2; // TOUCH ME
395                 }
396         }
397         return 0;
398 }
399
400 void onslaught_generator_think()
401 {
402         local float d;
403         local entity e;
404         self.nextthink = ceil(time + 1);
405         if (cvar("timelimit"))
406                 if (time > cvar("timelimit") * 60 - 60)
407                 {
408                         // self.max_health / 300 gives 5 minutes of overtime.
409                         // control points reduce the overtime duration.
410                         sound(self, CHAN_TRIGGER, "onslaught/generator_decay.wav", VOL_BASE, ATTN_NORM);
411                         d = 1;
412                         e = findchain(classname, "onslaught_controlpoint");
413                         while (e)
414                         {
415                                 if (e.team != self.team)
416                                         if (e.islinked)
417                                                 d = d + 1;
418                                 e = e.chain;
419                         }
420                         d = d * self.max_health / 300;
421                         Damage(self, self, self, d, DEATH_HURTTRIGGER, self.origin, '0 0 0');
422                 }
423
424 };
425
426 void onslaught_generator_ring_think()
427 {
428         self.nextthink = time + 0.05;
429         if(self.count > 24)
430         {
431                 self.think = SUB_Remove;
432                 return;
433         }
434
435         self.scale = self.count * 4;
436
437         self.frame = self.count;
438
439         self.count += 1;
440         self.alpha = 0.1;
441 };
442
443 void onslaught_generator_ring_spawn(vector org)
444 {
445         entity e;
446         e = spawn();
447         setmodel(e, "models/onslaught/shockwavetransring.md3");
448         setorigin(e, org);
449
450         e.count = 1;
451         e.alpha = 0;
452
453         e.think = onslaught_generator_ring_think;
454         e.nextthink = time + 0.05;
455 };
456 void onslaught_generator_ray_think()
457 {
458         self.nextthink = time + 0.05;
459         if(self.count > 10)
460         {
461                 self.think = SUB_Remove;
462                 return;
463         }
464
465         if(self.count > 5)
466                 self.alpha -= 0.1;
467         else
468                 self.alpha += 0.1;
469
470         self.scale += 0.2;
471         self.count +=1;
472 };
473
474 void onslaught_generator_ray_spawn(vector org)
475 {
476         entity e;
477         e = spawn();
478         setmodel(e, "models/onslaught/ons_ray.md3");
479         setorigin(e, org);
480         e.angles = randomvec() * 360;
481         e.alpha = 0;
482         e.scale = random() * 5 + 8;
483         e.think = onslaught_generator_ray_think;
484         e.nextthink = time + 0.05;
485 };
486
487 void onslaught_generator_shockwave_think()
488 {
489         self.nextthink = time + 0.05;
490         if(self.count > 25)
491         {
492                 self.think = SUB_Remove;
493                 return;
494         }
495
496         if(self.count > 15)
497                 self.alpha -= 0.1;
498         else
499                 self.alpha = 1;
500
501         self.scale = self.count * 4;
502         setsize(self, self.mins * self.scale, self.maxs * self.scale);
503         self.frame = self.count;
504
505         self.count +=1;
506 };
507
508 void onslaught_generator_shockwave_spawn(vector org)
509 {
510         entity e;
511         e = spawn();
512         setmodel(e, "models/onslaught/shockwave.md3");
513         setorigin(e, org);
514
515         e.alpha = 0;
516         e.frame = 0;
517         e.count = 0;
518
519
520         e.think = onslaught_generator_shockwave_think;
521         e.nextthink = time + 0.05;
522 };
523
524 void onslaught_generator_damage_think()
525 {
526         if(self.owner.health < 0)
527         {
528                 self.think = SUB_Remove;
529                 return;
530         }
531         self.nextthink = time+0.1;
532
533         // damaged fx (less probable the more damaged is the generator)
534         if(random() < 0.9 - self.owner.health / self.owner.max_health)
535                 if(random() < 0.01)
536                 {
537                         pointparticles(particleeffectnum("electro_ballexplode"), self.origin + randompos('-50 -50 -20', '50 50 50'), '0 0 0', 1);
538                         sound(self, CHAN_TRIGGER, "onslaught/electricity_explode.wav", VOL_BASE, ATTN_NORM);
539                 }
540                 else
541                         pointparticles(particleeffectnum("torch_small"), self.origin + randompos('-60 -60 -20', '60 60 60'), '0 0 0', 1);
542 };
543
544 void onslaught_generator_damage_spawn(entity gd_owner)
545 {
546         entity e;
547         e = spawn();
548         e.owner = gd_owner;
549         e.health = self.owner.health;
550         setorigin(e, gd_owner.origin);
551         e.think = onslaught_generator_damage_think;
552         e.nextthink = time+1;
553 };
554
555 void onslaught_generator_deaththink()
556 {
557         local vector org;
558         local float i;
559
560         if not (self.count)
561                 self.count = 40;
562
563         // White shockwave
564         if(self.count==40||self.count==20)
565         {
566                 onslaught_generator_ring_spawn(self.origin);
567                 sound(self, CHAN_TRIGGER, "onslaught/shockwave.wav", VOL_BASE, ATTN_NORM);
568         }
569
570         // Throw some gibs
571         if(random() < 0.3)
572         {
573                 i = random();
574                 if(i < 0.3)
575                         ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 11 + '0 0 20', "models/onslaught/gen_gib1.md3", 25, 1, 1);
576                 else if(i > 0.7)
577                         ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 12 + '0 0 20', "models/onslaught/gen_gib2.md3", 20, 1, 1);
578                 else
579                         ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 13 + '0 0 20', "models/onslaught/gen_gib3.md3", 15, 1, 1);
580         }
581
582         // Spawn fire balls
583         for(i=0;i < 6;++i)
584         {
585                 org = self.origin + randompos('-30 -30 -30' * i + '0 0 -20', '30 30 30' * i + '0 0 20');
586                 onslaught_generator_boom_spawn(org, (6-i)/5+0.3);
587         }
588
589         // Short explosion sound + small explosion
590         if(random() < 0.25)
591         {
592                 te_explosion(self.origin);
593                 sound(self, CHAN_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
594         }
595
596         // Particles
597         org = self.origin + randompos(self.mins + '8 8 8', self.maxs + '-8 -8 -8');
598         pointparticles(particleeffectnum("onslaught_generator_smallexplosion"), org, '0 0 0', 1);
599
600         // rays
601         if(random() > 0.25 )
602         {
603                 onslaught_generator_ray_spawn(self.origin);
604         }
605
606         // Final explosion
607         if(self.count==1)
608         {
609                 org = self.origin;
610                 te_explosion(org);
611                 onslaught_generator_shockwave_spawn(org);
612                 pointparticles(particleeffectnum("onslaught_generator_finalexplosion"), org, '0 0 0', 1);
613                 sound(self, CHAN_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
614         }
615         else
616                 self.nextthink = time + 0.05;
617
618         self.count = self.count - 1;
619 };
620
621 void onslaught_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
622 {
623         local float i;
624         if (damage <= 0)
625                 return;
626         if (attacker != self)
627         {
628                 if (self.isshielded)
629                 {
630                         // this is protected by a shield, so ignore the damage
631                         if (time > self.pain_finished)
632                                 if (attacker.classname == "player")
633                                 {
634                                         play2(attacker, "onslaught/damageblockedbyshield.wav");
635                                         self.pain_finished = time + 1;
636                                 }
637                         return;
638                 }
639                 if (time > self.pain_finished)
640                 {
641                         self.pain_finished = time + 10;
642                         bprint(ColoredTeamName(self.team), " generator under attack!\n");
643                         play2team(self.team, "onslaught/generator_underattack.wav");
644                 }
645         }
646         self.health = self.health - damage;
647         WaypointSprite_UpdateHealth(self.sprite, self.health);
648         // choose an animation frame based on health
649         self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1);
650         // see if the generator is still functional, or dying
651         if (self.health > 0)
652         {
653 #ifdef ONSLAUGHT_SPAM
654                 float h, lh;
655                 lh = ceil(self.lasthealth / 100) * 100;
656                 h = ceil(self.health / 100) * 100;
657                 if(lh != h)
658                         bprint(ColoredTeamName(self.team), " generator has less than ", ftos(h), " health remaining\n");
659 #endif
660                 self.lasthealth = self.health;
661         }
662         else
663         {
664                 if (attacker == self)
665                         bprint(ColoredTeamName(self.team), " generator spontaneously exploded due to overtime!\n");
666                 else
667                 {
668                         string t;
669                         t = ColoredTeamName(attacker.team);
670                         bprint(ColoredTeamName(self.team), " generator destroyed by ", t, "!\n");
671                 }
672                 self.iscaptured = FALSE;
673                 self.islinked = FALSE;
674                 self.isshielded = FALSE;
675                 self.takedamage = DAMAGE_NO; // can't be hurt anymore
676                 self.event_damage = SUB_Null; // won't do anything if hurt
677                 self.count = 0; // reset counter
678                 self.think = onslaught_generator_deaththink; // explosion sequence
679                 self.nextthink = time; // start exploding immediately
680                 self.think(); // do the first explosion now
681
682                 WaypointSprite_UpdateMaxHealth(self.sprite, 0);
683
684                 onslaught_updatelinks();
685         }
686         if(self.health <= 0)
687                 setmodel(self, "models/onslaught/generator_dead.md3");
688         else if(self.health < self.max_health * 0.10)
689                 setmodel(self, "models/onslaught/generator_dmg9.md3");
690         else if(self.health < self.max_health * 0.20)
691                 setmodel(self, "models/onslaught/generator_dmg8.md3");
692         else if(self.health < self.max_health * 0.30)
693                 setmodel(self, "models/onslaught/generator_dmg7.md3");
694         else if(self.health < self.max_health * 0.40)
695                 setmodel(self, "models/onslaught/generator_dmg6.md3");
696         else if(self.health < self.max_health * 0.50)
697                 setmodel(self, "models/onslaught/generator_dmg5.md3");
698         else if(self.health < self.max_health * 0.60)
699                 setmodel(self, "models/onslaught/generator_dmg4.md3");
700         else if(self.health < self.max_health * 0.70)
701                 setmodel(self, "models/onslaught/generator_dmg3.md3");
702         else if(self.health < self.max_health * 0.80)
703                 setmodel(self, "models/onslaught/generator_dmg2.md3");
704         else if(self.health < self.max_health * 0.90)
705                 setmodel(self, "models/onslaught/generator_dmg1.md3");
706         setsize(self, '-52 -52 -14', '52 52 75');
707
708         // Throw some flaming gibs on damage, more damage = more chance for gib
709         if(random() < damage/220)
710         {
711                 sound(self, CHAN_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
712                 i = random();
713                 if(i < 0.3)
714                         ons_throwgib(hitloc, (force * -1) + '0 0 40', "models/onslaught/gen_gib1.md3", 7, 1, 1);
715                 else if(i > 0.7)
716                         ons_throwgib(hitloc, (force * -1)+ '0 0 40', "models/onslaught/gen_gib2.md3", 6, 1, 1);
717                 else
718                         ons_throwgib(hitloc, (force * -1)+ '0 0 40', "models/onslaught/gen_gib3.md3", 5, 1, 1);
719         }
720         else
721         {
722                 // particles on every hit
723                 pointparticles(particleeffectnum("sparks"), hitloc, force * -1, 1);
724
725                 //sound on every hit
726                 if (random() < 0.5)
727                         sound(self, CHAN_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE, ATTN_NORM);
728                 else
729                         sound(self, CHAN_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE, ATTN_NORM);
730         }
731
732         //throw some gibs on damage
733         if(random() < damage/200+0.2)
734                 if(random() < 0.5)
735                         ons_throwgib(hitloc, randomvec()*360, "models/onslaught/gen_gib1.md3", 1, 1, 0);
736 };
737
738 // update links after a delay
739 void onslaught_generator_delayed()
740 {
741         onslaught_updatelinks();
742         // now begin normal thinking
743         self.think = onslaught_generator_think;
744         self.nextthink = time;
745 };
746
747 string onslaught_generator_waypointsprite_for_team(entity e, float t)
748 {
749         if(t == e.team)
750         {
751                 if(e.team == COLOR_TEAM1)
752                         return "ons-gen-red";
753                 else if(e.team == COLOR_TEAM2)
754                         return "ons-gen-blue";
755         }
756         if(e.isshielded)
757                 return "ons-gen-shielded";
758         if(e.team == COLOR_TEAM1)
759                 return "ons-gen-red";
760         else if(e.team == COLOR_TEAM2)
761                 return "ons-gen-blue";
762         return "";
763 }
764
765 void onslaught_generator_updatesprite(entity e)
766 {
767         string s1, s2, s3;
768         s1 = onslaught_generator_waypointsprite_for_team(e, COLOR_TEAM1);
769         s2 = onslaught_generator_waypointsprite_for_team(e, COLOR_TEAM2);
770         s3 = onslaught_generator_waypointsprite_for_team(e, -1);
771         WaypointSprite_UpdateSprites(e.sprite, s1, s2, s3);
772
773         if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded)
774         {
775                 e.lastteam = e.team + 2;
776                 e.lastshielded = e.isshielded;
777                 if(e.lastshielded)
778                 {
779                         if(e.team == COLOR_TEAM1 || e.team == COLOR_TEAM2)
780                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, FALSE));
781                         else
782                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5');
783                 }
784                 else
785                 {
786                         if(e.team == COLOR_TEAM1 || e.team == COLOR_TEAM2)
787                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, FALSE));
788                         else
789                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75');
790                 }
791                 WaypointSprite_Ping(e.sprite);
792         }
793 }
794
795 string onslaught_controlpoint_waypointsprite_for_team(entity e, float t)
796 {
797         float a;
798         if(t != -1)
799         {
800                 a = onslaught_controlpoint_attackable(e, t);
801                 if(a == 3 || a == 4) // ATTACK/TOUCH THIS ONE NOW
802                 {
803                         if(e.team == COLOR_TEAM1)
804                                 return "ons-cp-atck-red";
805                         else if(e.team == COLOR_TEAM2)
806                                 return "ons-cp-atck-blue";
807                         else
808                                 return "ons-cp-atck-neut";
809                 }
810                 else if(a == -2) // DEFEND THIS ONE NOW
811                 {
812                         if(e.team == COLOR_TEAM1)
813                                 return "ons-cp-dfnd-red";
814                         else if(e.team == COLOR_TEAM2)
815                                 return "ons-cp-dfnd-blue";
816                 }
817                 else if(e.team == t || a == -1 || a == 1) // own point, or fire at it
818                 {
819                         if(e.team == COLOR_TEAM1)
820                                 return "ons-cp-red";
821                         else if(e.team == COLOR_TEAM2)
822                                 return "ons-cp-blue";
823                 }
824                 else if(a == 2) // touch it
825                         return "ons-cp-neut";
826         }
827         else
828         {
829                 if(e.team == COLOR_TEAM1)
830                         return "ons-cp-red";
831                 else if(e.team == COLOR_TEAM2)
832                         return "ons-cp-blue";
833                 else
834                         return "ons-cp-neut";
835         }
836         return "";
837 }
838
839 void onslaught_controlpoint_updatesprite(entity e)
840 {
841         string s1, s2, s3;
842         s1 = onslaught_controlpoint_waypointsprite_for_team(e, COLOR_TEAM1);
843         s2 = onslaught_controlpoint_waypointsprite_for_team(e, COLOR_TEAM2);
844         s3 = onslaught_controlpoint_waypointsprite_for_team(e, -1);
845         WaypointSprite_UpdateSprites(e.sprite, s1, s2, s3);
846
847         float sh;
848         sh = !(onslaught_controlpoint_can_be_linked(e, COLOR_TEAM1) || onslaught_controlpoint_can_be_linked(e, COLOR_TEAM2));
849
850         if(e.lastteam != e.team + 2 || e.lastshielded != sh || e.iscaptured != e.lastcaptured)
851         {
852                 if(e.iscaptured) // don't mess up build bars!
853                 {
854                         if(sh)
855                         {
856                                 WaypointSprite_UpdateMaxHealth(e.sprite, 0);
857                         }
858                         else
859                         {
860                                 WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health);
861                                 WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health);
862                         }
863                 }
864                 if(e.lastshielded)
865                 {
866                         if(e.team == COLOR_TEAM1 || e.team == COLOR_TEAM2)
867                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, FALSE));
868                         else
869                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5');
870                 }
871                 else
872                 {
873                         if(e.team == COLOR_TEAM1 || e.team == COLOR_TEAM2)
874                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, FALSE));
875                         else
876                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75');
877                 }
878                 WaypointSprite_Ping(e.sprite);
879
880                 e.lastteam = e.team + 2;
881                 e.lastshielded = sh;
882                 e.lastcaptured = e.iscaptured;
883         }
884 }
885
886 void onslaught_generator_reset()
887 {
888         self.team = self.team_saved;
889         self.lasthealth = self.max_health = self.health = cvar("g_onslaught_gen_health");
890         self.takedamage = DAMAGE_AIM;
891         self.bot_attack = TRUE;
892         self.iscaptured = TRUE;
893         self.islinked = TRUE;
894         self.isshielded = TRUE;
895         self.enemy.solid = SOLID_NOT;
896         self.think = onslaught_generator_delayed;
897         self.nextthink = time + 0.2;
898         setmodel(self, "models/onslaught/generator.md3");
899
900         WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
901         WaypointSprite_UpdateHealth(self.sprite, self.health);
902 }
903
904 /*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64)
905   Base generator.
906
907   spawnfunc_onslaught_link entities can target this.
908
909 keys:
910 "team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET.
911 "targetname" - name that spawnfunc_onslaught_link entities will use to target this.
912  */
913 void spawnfunc_onslaught_generator()
914 {
915         if (!g_onslaught)
916         {
917                 remove(self);
918                 return;
919         }
920
921         local entity e;
922         precache_model("models/onslaught/generator.md3");
923         precache_model("models/onslaught/generator_shield.md3");
924         precache_model("models/onslaught/generator_dmg1.md3");
925         precache_model("models/onslaught/generator_dmg2.md3");
926         precache_model("models/onslaught/generator_dmg3.md3");
927         precache_model("models/onslaught/generator_dmg4.md3");
928         precache_model("models/onslaught/generator_dmg5.md3");
929         precache_model("models/onslaught/generator_dmg6.md3");
930         precache_model("models/onslaught/generator_dmg7.md3");
931         precache_model("models/onslaught/generator_dmg8.md3");
932         precache_model("models/onslaught/generator_dmg9.md3");
933         precache_model("models/onslaught/generator_dead.md3");
934         precache_model("models/onslaught/boom.md3");
935         precache_model("models/onslaught/shockwave.md3");
936         precache_model("models/onslaught/shockwavetransring.md3");
937         precache_model("models/onslaught/gen_gib1.md3");
938         precache_model("models/onslaught/gen_gib2.md3");
939         precache_model("models/onslaught/gen_gib3.md3");
940         precache_model("models/onslaught/ons_ray.md3");
941         precache_sound("onslaught/generator_decay.wav");
942         precache_sound("weapons/grenade_impact.wav");
943         precache_sound("weapons/rocket_impact.wav");
944         precache_sound("onslaught/generator_underattack.wav");
945         precache_sound("onslaught/shockwave.wav");
946         precache_sound("onslaught/ons_hit1.wav");
947         precache_sound("onslaught/ons_hit2.wav");
948         precache_sound("onslaught/electricity_explode.wav");
949         if (!self.team)
950                 objerror("team must be set");
951         self.team_saved = self.team;
952         self.colormap = 1024 + (self.team - 1) * 17;
953         self.solid = SOLID_BBOX;
954         self.movetype = MOVETYPE_NONE;
955         self.lasthealth = self.max_health = self.health = cvar("g_onslaught_gen_health");
956         setmodel(self, "models/onslaught/generator.md3");
957         setsize(self, '-52 -52 -14', '52 52 75');
958         setorigin(self, self.origin);
959         self.takedamage = DAMAGE_AIM;
960         self.bot_attack = TRUE;
961         self.event_damage = onslaught_generator_damage;
962         self.iscaptured = TRUE;
963         self.islinked = TRUE;
964         self.isshielded = TRUE;
965         // helper entity that create fx when generator is damaged
966         onslaught_generator_damage_spawn(self);
967         // spawn shield model which indicates whether this can be damaged
968         self.enemy = e = spawn();
969         e.classname = "onslaught_generator_shield";
970         e.solid = SOLID_NOT;
971         e.movetype = MOVETYPE_NONE;
972         e.effects = EF_ADDITIVE;
973         setmodel(e, "models/onslaught/generator_shield.md3");
974         setorigin(e, self.origin);
975         e.colormap = self.colormap;
976         e.team = self.team;
977         self.think = onslaught_generator_delayed;
978         self.nextthink = time + 0.2;
979         InitializeEntity(self, onslaught_generator_delayed, INITPRIO_LAST);
980
981         WaypointSprite_SpawnFixed(string_null, e.origin + '0 0 1' * e.maxs_z, self, sprite);
982         WaypointSprite_UpdateRule(self.sprite, COLOR_TEAM2, SPRITERULE_TEAMPLAY);
983         WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
984         WaypointSprite_UpdateHealth(self.sprite, self.health);
985
986         waypoint_spawnforitem(self);
987
988         onslaught_updatelinks();
989
990         self.reset = onslaught_generator_reset;
991 };
992
993 .float waslinked;
994 .float cp_bob_spd;
995 .vector cp_origin, cp_bob_origin, cp_bob_dmg;
996
997 float ons_notification_time_team1;
998 float ons_notification_time_team2;
999
1000 void onslaught_controlpoint_icon_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1001 {
1002         entity oself;
1003         float nag;
1004
1005         if (damage <= 0)
1006                 return;
1007         if (self.owner.isshielded)
1008         {
1009                 // this is protected by a shield, so ignore the damage
1010                 if (time > self.pain_finished)
1011                         if (attacker.classname == "player")
1012                         {
1013                                 play2(attacker, "onslaught/damageblockedbyshield.wav");
1014                                 self.pain_finished = time + 1;
1015                         }
1016                 return;
1017         }
1018
1019         if (attacker.classname == "player")
1020         {
1021                 if(self.team == COLOR_TEAM1)
1022                 {
1023                         if(time - ons_notification_time_team1 > 10)
1024                         {
1025                                 nag = TRUE;
1026                                 ons_notification_time_team1 = time;
1027                         }
1028                 }
1029                 else if(self.team == COLOR_TEAM2)
1030                 {
1031                         if(time - ons_notification_time_team2 > 10)
1032                         {
1033                                 nag = TRUE;
1034                                 ons_notification_time_team2 = time;
1035                         }
1036                 }
1037                 else
1038                         nag = TRUE;
1039
1040                 if(nag)
1041                         play2team(self.team, "onslaught/controlpoint_underattack.wav");
1042         }
1043
1044         self.health = self.health - damage;
1045         if(self.owner.iscaptured)
1046                 WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
1047         else
1048                 WaypointSprite_UpdateBuildFinished(self.owner.sprite, time + (self.max_health - self.health) / (self.count / sys_ticrate));
1049         self.pain_finished = time + 1;
1050         self.punchangle = (2 * randomvec() - '1 1 1') * 45;
1051         self.cp_bob_dmg_z = (2 * random() - 1) * 15;
1052         // colormod flash when shot
1053         self.colormod = '2 2 2';
1054         // particles on every hit
1055         pointparticles(particleeffectnum("sparks"), hitloc, force*-1, 1);
1056         //sound on every hit
1057         if (random() < 0.5)
1058                 sound(self, CHAN_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE+0.3, ATTN_NORM);
1059         else
1060                 sound(self, CHAN_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE+0.3, ATTN_NORM);
1061
1062         if (self.health < 0)
1063         {
1064                 sound(self, CHAN_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
1065                 pointparticles(particleeffectnum("rocket_explode"), self.origin, '0 0 0', 1);
1066                 {
1067                         string t;
1068                         t = ColoredTeamName(attacker.team);
1069                         bprint(ColoredTeamName(self.team), " ", self.message, " control point destroyed by ", t, "\n");
1070                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 25, "models/onslaught/controlpoint_icon_gib1.md3", 5, 1, 0);
1071                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 45, "models/onslaught/controlpoint_icon_gib2.md3", 5, 1, 0);
1072                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 45, "models/onslaught/controlpoint_icon_gib2.md3", 5, 1, 0);
1073                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 5, 1, 0);
1074                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 5, 1, 0);
1075                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 5, 1, 0);
1076                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 5, 1, 0);
1077                 }
1078                 self.owner.goalentity = world;
1079                 self.owner.islinked = FALSE;
1080                 self.owner.iscaptured = FALSE;
1081                 self.owner.team = 0;
1082                 self.owner.colormap = 1024;
1083
1084                 WaypointSprite_UpdateMaxHealth(self.owner.sprite, 0);
1085
1086                 onslaught_updatelinks();
1087
1088                 // Use targets now (somebody make sure this is in the right place..)
1089                 oself = self;
1090                 self = self.owner;
1091                 activator = self;
1092                 SUB_UseTargets ();
1093                 self = oself;
1094
1095
1096                 self.owner.waslinked = self.owner.islinked;
1097                 if(self.owner.model != "models/onslaught/controlpoint_pad.md3")
1098                         setmodel(self.owner, "models/onslaught/controlpoint_pad.md3");
1099                 //setsize(self, '-32 -32 0', '32 32 8');
1100
1101                 remove(self);
1102         }
1103 };
1104
1105 void onslaught_controlpoint_icon_think()
1106 {
1107         entity oself;
1108         self.nextthink = time + sys_ticrate;
1109         if (time > self.pain_finished + 5)
1110         {
1111                 if(self.health < self.max_health)
1112                 {
1113                         self.health = self.health + self.count;
1114                         if (self.health >= self.max_health)
1115                                 self.health = self.max_health;
1116                         WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
1117                 }
1118         }
1119         if (self.health < self.max_health * 0.25)
1120                 setmodel(self, "models/onslaught/controlpoint_icon_dmg3.md3");
1121         else if (self.health < self.max_health * 0.50)
1122                 setmodel(self, "models/onslaught/controlpoint_icon_dmg2.md3");
1123         else if (self.health < self.max_health * 0.75)
1124                 setmodel(self, "models/onslaught/controlpoint_icon_dmg1.md3");
1125         else if (self.health < self.max_health * 0.90)
1126                 setmodel(self, "models/onslaught/controlpoint_icon.md3");
1127         // colormod flash when shot
1128         self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1));
1129
1130         if(self.owner.islinked != self.owner.waslinked)
1131         {
1132                 // unteam the spawnpoint if needed
1133                 float t;
1134                 t = self.owner.team;
1135                 if(!self.owner.islinked)
1136                         self.owner.team = 0;
1137
1138                 oself = self;
1139                 self = self.owner;
1140                 activator = self;
1141                 SUB_UseTargets ();
1142                 self = oself;
1143
1144                 self.owner.team = t;
1145
1146                 self.owner.waslinked = self.owner.islinked;
1147         }
1148         if (self.punchangle_x > 2)
1149                 self.punchangle_x = self.punchangle_x - 2;
1150         else if (self.punchangle_x < -2)
1151                 self.punchangle_x = self.punchangle_x + 2;
1152         else
1153                 self.punchangle_x = 0;
1154         if (self.punchangle_y > 2)
1155                 self.punchangle_y = self.punchangle_y - 2;
1156         else if (self.punchangle_y < -2)
1157                 self.punchangle_y = self.punchangle_y + 2;
1158         else
1159                 self.punchangle_y = 0;
1160         if (self.punchangle_z > 2)
1161                 self.punchangle_z = self.punchangle_z - 2;
1162         else if (self.punchangle_z < -2)
1163                 self.punchangle_z = self.punchangle_z + 2;
1164         else
1165                 self.punchangle_z = 0;
1166         self.angles_x = self.punchangle_x;
1167         self.angles_y = self.punchangle_y + self.mangle_y;
1168         self.angles_z = self.punchangle_z;
1169         self.mangle_y = self.mangle_y + 1.5;
1170
1171         self.cp_bob_origin_z = 4 * PI * (1 - cos(self.cp_bob_spd / 8));
1172         self.cp_bob_spd = self.cp_bob_spd + 0.5;
1173         if(self.cp_bob_dmg_z > 0)
1174                 self.cp_bob_dmg_z = self.cp_bob_dmg_z - 0.1;
1175         else
1176                 self.cp_bob_dmg_z = 0;
1177         self.origin = self.cp_origin + self.cp_bob_origin + self.cp_bob_dmg;
1178
1179         // damaged fx
1180         if(random() < 0.6 - self.health / self.max_health)
1181         {
1182                 pointparticles(particleeffectnum("electricity_sparks"), self.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1);
1183
1184                 if(random() > 0.8)
1185                         sound(self, CHAN_PAIN, "onslaught/ons_spark1.wav", VOL_BASE, ATTN_NORM);
1186                 else if (random() > 0.5)
1187                         sound(self, CHAN_PAIN, "onslaught/ons_spark2.wav", VOL_BASE, ATTN_NORM);
1188         }
1189 };
1190
1191 void onslaught_controlpoint_icon_buildthink()
1192 {
1193         local entity oself;
1194         float a;
1195
1196         self.nextthink = time + sys_ticrate;
1197
1198         // only do this if there is power
1199         a = onslaught_controlpoint_can_be_linked(self.owner, self.owner.team);
1200         if(!a)
1201                 return;
1202
1203         self.health = self.health + self.count;
1204
1205         if (self.health >= self.max_health)
1206         {
1207                 self.health = self.max_health;
1208                 self.count = cvar("g_onslaught_cp_regen") * sys_ticrate; // slow repair rate from now on
1209                 self.think = onslaught_controlpoint_icon_think;
1210                 sound(self, CHAN_TRIGGER, "onslaught/controlpoint_built.wav", VOL_BASE, ATTN_NORM);
1211                 bprint(ColoredTeamName(self.team), " captured ", self.owner.message, " control point\n");
1212                 self.owner.iscaptured = TRUE;
1213
1214                 WaypointSprite_UpdateMaxHealth(self.owner.sprite, self.max_health);
1215                 WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
1216
1217                 onslaught_updatelinks();
1218
1219                 // Use targets now (somebody make sure this is in the right place..)
1220                 oself = self;
1221                 self = self.owner;
1222                 activator = self;
1223                 SUB_UseTargets ();
1224                 self = oself;
1225                 self.cp_origin = self.origin;
1226                 self.cp_bob_origin = '0 0 0.1';
1227                 self.cp_bob_spd = 0;
1228         }
1229         self.alpha = self.health / self.max_health;
1230         // colormod flash when shot
1231         self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1));
1232         if(self.owner.model != "models/onslaught/controlpoint_pad2.md3")
1233                 setmodel(self.owner, "models/onslaught/controlpoint_pad2.md3");
1234         //setsize(self, '-32 -32 0', '32 32 8');
1235
1236         if(random() < 0.9 - self.health / self.max_health)
1237                 pointparticles(particleeffectnum("rage"), self.origin + 10 * randomvec(), '0 0 -1', 1);
1238 };
1239
1240
1241
1242
1243 void onslaught_controlpoint_touch()
1244 {
1245         local entity e;
1246         float a;
1247         if (other.classname != "player")
1248                 return;
1249         a = onslaught_controlpoint_attackable(self, other.team);
1250         if(a != 2 && a != 4)
1251                 return;
1252         // we've verified that this player has a legitimate claim to this point,
1253         // so start building the captured point icon (which only captures this
1254         // point if it successfully builds without being destroyed first)
1255         self.goalentity = e = spawn();
1256         e.classname = "onslaught_controlpoint_icon";
1257         e.owner = self;
1258         e.max_health = cvar("g_onslaught_cp_health");
1259         e.health = cvar("g_onslaught_cp_buildhealth");
1260         e.solid = SOLID_BBOX;
1261         e.movetype = MOVETYPE_NONE;
1262         setmodel(e, "models/onslaught/controlpoint_icon.md3");
1263         setsize(e, '-32 -32 -32', '32 32 32');
1264         setorigin(e, self.origin + '0 0 96');
1265         e.takedamage = DAMAGE_AIM;
1266         e.bot_attack = TRUE;
1267         e.event_damage = onslaught_controlpoint_icon_damage;
1268         e.team = other.team;
1269         e.colormap = 1024 + (e.team - 1) * 17;
1270         e.think = onslaught_controlpoint_icon_buildthink;
1271         e.nextthink = time + sys_ticrate;
1272         e.count = (e.max_health - e.health) * sys_ticrate / cvar("g_onslaught_cp_buildtime"); // how long it takes to build
1273         sound(e, CHAN_TRIGGER, "onslaught/controlpoint_build.wav", VOL_BASE, ATTN_NORM);
1274         self.team = e.team;
1275         self.colormap = e.colormap;
1276         WaypointSprite_UpdateBuildFinished(self.sprite, time + (e.max_health - e.health) / (e.count / sys_ticrate));
1277         onslaught_updatelinks();
1278 };
1279
1280 void onslaught_controlpoint_reset()
1281 {
1282         if(self.goalentity && self.goalentity != world)
1283                 remove(self.goalentity);
1284         self.goalentity = world;
1285         self.team = 0;
1286         self.colormap = 1024;
1287         self.iscaptured = FALSE;
1288         self.islinked = FALSE;
1289         self.isshielded = TRUE;
1290         self.enemy.solid = SOLID_NOT;
1291         self.enemy.colormap = self.colormap;
1292         self.think = self.enemy.think = SUB_Null;
1293         self.nextthink = 0; // don't like SUB_Null :P
1294         setmodel(self, "models/onslaught/controlpoint_pad.md3");
1295         //setsize(self, '-32 -32 0', '32 32 8');
1296
1297         WaypointSprite_UpdateMaxHealth(self.sprite, 0);
1298
1299         onslaught_updatelinks();
1300
1301         activator = self;
1302         SUB_UseTargets(); // to reset the structures, playerspawns etc.
1303 }
1304
1305 /*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128)
1306   Control point. Be sure to give this enough clearance so that the shootable part has room to exist
1307
1308   This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity.
1309
1310 keys:
1311 "targetname" - name that spawnfunc_onslaught_link entities will use to target this.
1312 "target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities.
1313 "message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc)
1314  */
1315 void spawnfunc_onslaught_controlpoint()
1316 {
1317         local entity e;
1318         if (!g_onslaught)
1319         {
1320                 remove(self);
1321                 return;
1322         }
1323         precache_model("models/onslaught/controlpoint_pad.md3");
1324         precache_model("models/onslaught/controlpoint_pad2.md3");
1325         precache_model("models/onslaught/controlpoint_shield.md3");
1326         precache_model("models/onslaught/controlpoint_icon.md3");
1327         precache_model("models/onslaught/controlpoint_icon_dmg1.md3");
1328         precache_model("models/onslaught/controlpoint_icon_dmg2.md3");
1329         precache_model("models/onslaught/controlpoint_icon_dmg3.md3");
1330         precache_model("models/onslaught/controlpoint_icon_gib1.md3");
1331         precache_model("models/onslaught/controlpoint_icon_gib2.md3");
1332         precache_model("models/onslaught/controlpoint_icon_gib4.md3");
1333         precache_sound("onslaught/controlpoint_build.wav");
1334         precache_sound("onslaught/controlpoint_built.wav");
1335         precache_sound("weapons/grenade_impact.wav");
1336         precache_sound("onslaught/damageblockedbyshield.wav");
1337         precache_sound("onslaught/controlpoint_underattack.wav");
1338         precache_sound("onslaught/ons_spark1.wav");
1339         precache_sound("onslaught/ons_spark2.wav");
1340         self.solid = SOLID_BBOX;
1341         self.movetype = MOVETYPE_NONE;
1342         setmodel(self, "models/onslaught/controlpoint_pad.md3");
1343         //setsize(self, '-32 -32 0', '32 32 8');
1344         setorigin(self, self.origin);
1345         self.touch = onslaught_controlpoint_touch;
1346         self.team = 0;
1347         self.colormap = 1024;
1348         self.iscaptured = FALSE;
1349         self.islinked = FALSE;
1350         self.isshielded = TRUE;
1351         // spawn shield model which indicates whether this can be damaged
1352         self.enemy = e = spawn();
1353         e.classname = "onslaught_controlpoint_shield";
1354         e.solid = SOLID_NOT;
1355         e.movetype = MOVETYPE_NONE;
1356         e.effects = EF_ADDITIVE;
1357         setmodel(e, "models/onslaught/controlpoint_shield.md3");
1358         //setsize(e, '-32 -32 0', '32 32 128');
1359         setorigin(e, self.origin);
1360         e.colormap = self.colormap;
1361
1362         waypoint_spawnforitem(self);
1363
1364         WaypointSprite_SpawnFixed(string_null, e.origin + '0 0 1' * e.maxs_z, self, sprite);
1365         WaypointSprite_UpdateRule(self.sprite, COLOR_TEAM2, SPRITERULE_TEAMPLAY);
1366
1367         onslaught_updatelinks();
1368
1369         self.reset = onslaught_controlpoint_reset;
1370 };
1371
1372 float onslaught_link_send(entity to, float sendflags)
1373 {
1374         WriteByte(MSG_ENTITY, ENT_CLIENT_RADARLINK);
1375         WriteByte(MSG_ENTITY, sendflags);
1376         if(sendflags & 1)
1377         {
1378                 WriteCoord(MSG_ENTITY, self.goalentity.origin_x);
1379                 WriteCoord(MSG_ENTITY, self.goalentity.origin_y);
1380                 WriteCoord(MSG_ENTITY, self.goalentity.origin_z);
1381         }
1382         if(sendflags & 2)
1383         {
1384                 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1385                 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1386                 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1387         }
1388         if(sendflags & 4)
1389         {
1390                 WriteByte(MSG_ENTITY, self.clientcolors); // which is goalentity's color + enemy's color * 16
1391         }
1392         return TRUE;
1393 }
1394
1395 void onslaught_link_checkupdate()
1396 {
1397         // TODO check if the two sides have moved (currently they won't move anyway)
1398         float redpower, bluepower;
1399
1400         redpower = bluepower = 0;
1401         if(self.goalentity.islinked)
1402         {
1403                 if(self.goalentity.team == COLOR_TEAM1)
1404                         redpower = 1;
1405                 else if(self.goalentity.team == COLOR_TEAM2)
1406                         bluepower = 1;
1407         }
1408         if(self.enemy.islinked)
1409         {
1410                 if(self.enemy.team == COLOR_TEAM1)
1411                         redpower = 2;
1412                 else if(self.enemy.team == COLOR_TEAM2)
1413                         bluepower = 2;
1414         }
1415
1416         float cc;
1417         if(redpower == 1 && bluepower == 2)
1418                 cc = (COLOR_TEAM1 - 1) * 0x01 + (COLOR_TEAM2 - 1) * 0x10;
1419         else if(redpower == 2 && bluepower == 1)
1420                 cc = (COLOR_TEAM1 - 1) * 0x10 + (COLOR_TEAM2 - 1) * 0x01;
1421         else if(redpower)
1422                 cc = (COLOR_TEAM1 - 1) * 0x11;
1423         else if(bluepower)
1424                 cc = (COLOR_TEAM2 - 1) * 0x11;
1425         else
1426                 cc = 0;
1427
1428         //print(etos(self), " rp=", ftos(redpower), " bp=", ftos(bluepower), " ");
1429         //print("cc=", ftos(cc), "\n");
1430
1431         if(cc != self.clientcolors)
1432         {
1433                 self.clientcolors = cc;
1434                 self.SendFlags |= 4;
1435         }
1436
1437         self.nextthink = time;
1438 }
1439
1440 void onslaught_link_delayed()
1441 {
1442         self.goalentity = find(world, targetname, self.target);
1443         self.enemy = find(world, targetname, self.target2);
1444         if (!self.goalentity)
1445                 objerror("can not find target\n");
1446         if (!self.enemy)
1447                 objerror("can not find target2\n");
1448         dprint(etos(self.goalentity), " linked with ", etos(self.enemy), "\n");
1449         self.SendFlags |= 3;
1450         self.think = onslaught_link_checkupdate;
1451         self.nextthink = time;
1452 }
1453
1454 /*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16)
1455   Link between control points.
1456
1457   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.
1458
1459 keys:
1460 "target" - first control point.
1461 "target2" - second control point.
1462  */
1463 void spawnfunc_onslaught_link()
1464 {
1465         if (!g_onslaught)
1466         {
1467                 remove(self);
1468                 return;
1469         }
1470         if (self.target == "" || self.target2 == "")
1471                 objerror("target and target2 must be set\n");
1472         InitializeEntity(self, onslaught_link_delayed, INITPRIO_FINDTARGET);
1473         Net_LinkEntity(self, FALSE, 0, onslaught_link_send);
1474 };