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