]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_onslaught.qc
Fix duplicate weapons/all.qc inclusion
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_onslaught.qc
1 #include "../_all.qh"
2
3 #include "gamemode.qh"
4
5 float autocvar_g_onslaught_spawn_at_controlpoints;
6 float autocvar_g_onslaught_spawn_at_generator;
7 float autocvar_g_onslaught_cp_proxydecap;
8 float autocvar_g_onslaught_cp_proxydecap_distance = 512;
9 float autocvar_g_onslaught_cp_proxydecap_dps = 100;
10
11 void onslaught_generator_updatesprite(entity e);
12 void onslaught_controlpoint_updatesprite(entity e);
13 void onslaught_link_checkupdate();
14
15 .entity sprite;
16 .string target2;
17 .float iscaptured;
18 .float islinked;
19 .float isgenneighbor_red;
20 .float isgenneighbor_blue;
21 .float iscpneighbor_red;
22 .float iscpneighbor_blue;
23 .float isshielded;
24 .float lasthealth;
25 .float lastteam;
26 .float lastshielded;
27 .float lastcaptured;
28
29 entity ons_red_generator;
30 entity ons_blue_generator;
31
32 void ons_gib_damage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector vforce)
33 {
34         self.velocity = self.velocity + vforce;
35 }
36
37 .float giblifetime;
38 void ons_throwgib_think()
39 {
40         float d;
41
42         self.nextthink = time + 0.05;
43
44         d = self.giblifetime - time;
45
46         if(d<0)
47         {
48                 self.think = SUB_Remove;
49                 return;
50         }
51         if(d<1)
52                 self.alpha = d;
53
54         if(d>2)
55         if(random()<0.6)
56                 Send_Effect("onslaught_generator_gib_flame", self.origin, '0 0 0', 1);
57 }
58
59 void ons_throwgib(vector v_from, vector v_to, string smodel, float f_lifetime, float b_burn)
60 {
61         entity gib;
62
63         gib = spawn();
64
65         setmodel(gib, smodel);
66         setorigin(gib, v_from);
67         gib.solid = SOLID_BBOX;
68         gib.movetype = MOVETYPE_BOUNCE;
69         gib.takedamage = DAMAGE_YES;
70         gib.event_damage = ons_gib_damage;
71         gib.health = -1;
72         gib.effects = EF_LOWPRECISION;
73         gib.flags = FL_NOTARGET;
74         gib.velocity = v_to;
75         gib.giblifetime = time + f_lifetime;
76
77         if (b_burn)
78         {
79                 gib.think = ons_throwgib_think;
80                 gib.nextthink = time + 0.05;
81         }
82         else
83                 SUB_SetFade(gib, gib.giblifetime, 2);
84 }
85
86 void onslaught_updatelinks()
87 {
88         entity l, links;
89         float stop, t1, t2, t3, t4;
90         // first check if the game has ended
91         dprint("--- updatelinks ---\n");
92         links = findchain(classname, "onslaught_link");
93         // mark generators as being shielded and networked
94         l = findchain(classname, "onslaught_generator");
95         while (l)
96         {
97                 if (l.iscaptured)
98                         dprint(etos(l), " (generator) belongs to team ", ftos(l.team), "\n");
99                 else
100                         dprint(etos(l), " (generator) is destroyed\n");
101                 l.islinked = l.iscaptured;
102                 l.isshielded = l.iscaptured;
103                 l = l.chain;
104         }
105         // mark points as shielded and not networked
106         l = findchain(classname, "onslaught_controlpoint");
107         while (l)
108         {
109                 l.islinked = false;
110                 l.isshielded = true;
111                 l.isgenneighbor_red = false;
112                 l.isgenneighbor_blue = false;
113                 l.iscpneighbor_red = false;
114                 l.iscpneighbor_blue = false;
115                 dprint(etos(l), " (point) belongs to team ", ftos(l.team), "\n");
116                 l = l.chain;
117         }
118         // flow power outward from the generators through the network
119         l = links;
120         while (l)
121         {
122                 dprint(etos(l), " (link) connects ", etos(l.goalentity), " with ", etos(l.enemy), "\n");
123                 l = l.chain;
124         }
125         stop = false;
126         while (!stop)
127         {
128                 stop = true;
129                 l = links;
130                 while (l)
131                 {
132                         // if both points are captured by the same team, and only one of
133                         // them is powered, mark the other one as powered as well
134                         if (l.enemy.iscaptured && l.goalentity.iscaptured)
135                                 if (l.enemy.islinked != l.goalentity.islinked)
136                                         if (l.enemy.team == l.goalentity.team)
137                                         {
138                                                 if (!l.goalentity.islinked)
139                                                 {
140                                                         stop = false;
141                                                         l.goalentity.islinked = true;
142                                                         dprint(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)\n");
143                                                 }
144                                                 else if (!l.enemy.islinked)
145                                                 {
146                                                         stop = false;
147                                                         l.enemy.islinked = true;
148                                                         dprint(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)\n");
149                                                 }
150                                         }
151                         l = l.chain;
152                 }
153         }
154         // now that we know which points are powered we can mark their neighbors
155         // as unshielded if team differs
156         l = links;
157         while (l)
158         {
159                 if (l.goalentity.islinked)
160                 {
161                         if (l.goalentity.team != l.enemy.team)
162                         {
163                                 dprint(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)\n");
164                                 l.enemy.isshielded = false;
165                         }
166                         if(l.goalentity.classname == "onslaught_generator")
167                         {
168                                 if(l.goalentity.team == NUM_TEAM_1)
169                                         l.enemy.isgenneighbor_red = true;
170                                 else if(l.goalentity.team == NUM_TEAM_2)
171                                         l.enemy.isgenneighbor_blue = true;
172                         }
173                         else
174                         {
175                                 if(l.goalentity.team == NUM_TEAM_1)
176                                         l.enemy.iscpneighbor_red = true;
177                                 else if(l.goalentity.team == NUM_TEAM_2)
178                                         l.enemy.iscpneighbor_blue = true;
179                         }
180                 }
181                 if (l.enemy.islinked)
182                 {
183                         if (l.goalentity.team != l.enemy.team)
184                         {
185                                 dprint(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)\n");
186                                 l.goalentity.isshielded = false;
187                         }
188                         if(l.enemy.classname == "onslaught_generator")
189                         {
190                                 if(l.enemy.team == NUM_TEAM_1)
191                                         l.goalentity.isgenneighbor_red = true;
192                                 else if(l.enemy.team == NUM_TEAM_2)
193                                         l.goalentity.isgenneighbor_blue = true;
194                         }
195                         else
196                         {
197                                 if(l.enemy.team == NUM_TEAM_1)
198                                         l.goalentity.iscpneighbor_red = true;
199                                 else if(l.enemy.team == NUM_TEAM_2)
200                                         l.goalentity.iscpneighbor_blue = true;
201                         }
202                 }
203                 l = l.chain;
204         }
205         // now update the takedamage and alpha variables on generator shields
206         l = findchain(classname, "onslaught_generator");
207         while (l)
208         {
209                 if (l.isshielded)
210                 {
211                         dprint(etos(l), " (generator) is shielded\n");
212                         l.enemy.alpha = 1;
213                         l.takedamage = DAMAGE_NO;
214                         l.bot_attack = false;
215                 }
216                 else
217                 {
218                         dprint(etos(l), " (generator) is not shielded\n");
219                         l.enemy.alpha = -1;
220                         l.takedamage = DAMAGE_AIM;
221                         l.bot_attack = true;
222                 }
223                 l = l.chain;
224         }
225         // now update the takedamage and alpha variables on control point icons
226         l = findchain(classname, "onslaught_controlpoint");
227         while (l)
228         {
229                 if (l.isshielded)
230                 {
231                         dprint(etos(l), " (point) is shielded\n");
232                         l.enemy.alpha = 1;
233                         if (l.goalentity)
234                         {
235                                 l.goalentity.takedamage = DAMAGE_NO;
236                                 l.goalentity.bot_attack = false;
237                         }
238                 }
239                 else
240                 {
241                         dprint(etos(l), " (point) is not shielded\n");
242                         l.enemy.alpha = -1;
243                         if (l.goalentity)
244                         {
245                                 l.goalentity.takedamage = DAMAGE_AIM;
246                                 l.goalentity.bot_attack = true;
247                         }
248                 }
249                 onslaught_controlpoint_updatesprite(l);
250                 l = l.chain;
251         }
252         // count generators owned by each team
253         t1 = t2 = t3 = t4 = 0;
254         l = findchain(classname, "onslaught_generator");
255         while (l)
256         {
257                 if (l.iscaptured)
258                 {
259                         if (l.team == NUM_TEAM_1) t1 = 1;
260                         if (l.team == NUM_TEAM_2) t2 = 1;
261                         if (l.team == NUM_TEAM_3) t3 = 1;
262                         if (l.team == NUM_TEAM_4) t4 = 1;
263                 }
264                 onslaught_generator_updatesprite(l);
265                 l = l.chain;
266         }
267         // see if multiple teams remain (if not, it's game over)
268         if (t1 + t2 + t3 + t4 < 2)
269                 dprint("--- game over ---\n");
270         else
271                 dprint("--- done updating links ---\n");
272 }
273
274 float onslaught_controlpoint_can_be_linked(entity cp, float t)
275 {
276         if(t == NUM_TEAM_1)
277         {
278                 if(cp.isgenneighbor_red)
279                         return 2;
280                 if(cp.iscpneighbor_red)
281                         return 1;
282         }
283         else if(t == NUM_TEAM_2)
284         {
285                 if(cp.isgenneighbor_blue)
286                         return 2;
287                 if(cp.iscpneighbor_blue)
288                         return 1;
289         }
290         return 0;
291         /*
292            entity e;
293         // check to see if this player has a legitimate claim to capture this
294         // control point - more specifically that there is a captured path of
295         // points leading back to the team generator
296         e = findchain(classname, "onslaught_link");
297         while (e)
298         {
299         if (e.goalentity == cp)
300         {
301         dprint(etos(e), " (link) connects to ", etos(e.enemy), " (point)");
302         if (e.enemy.islinked)
303         {
304         dprint(" which is linked");
305         if (e.enemy.team == t)
306         {
307         dprint(" and has the correct team!\n");
308         return 1;
309         }
310         else
311         dprint(" but has the wrong team\n");
312         }
313         else
314         dprint("\n");
315         }
316         else if (e.enemy == cp)
317         {
318         dprint(etos(e), " (link) connects to ", etos(e.goalentity), " (point)");
319         if (e.goalentity.islinked)
320         {
321         dprint(" which is linked");
322         if (e.goalentity.team == t)
323         {
324         dprint(" and has a team!\n");
325         return 1;
326         }
327         else
328         dprint(" but has the wrong team\n");
329         }
330         else
331         dprint("\n");
332         }
333         e = e.chain;
334         }
335         return 0;
336          */
337 }
338
339 float onslaught_controlpoint_attackable(entity cp, float t)
340         // -2: SAME TEAM, attackable by enemy!
341         // -1: SAME TEAM!
342         // 0: off limits
343         // 1: attack it
344         // 2: touch it
345         // 3: attack it (HIGH PRIO)
346         // 4: touch it (HIGH PRIO)
347 {
348         float a;
349
350         if(cp.isshielded)
351         {
352                 return 0;
353         }
354         else if(cp.goalentity)
355         {
356                 // if there's already an icon built, nothing happens
357                 if(cp.team == t)
358                 {
359                         a = onslaught_controlpoint_can_be_linked(cp, NUM_TEAM_1 + NUM_TEAM_2 - t);
360                         if(a) // attackable by enemy?
361                                 return -2; // EMERGENCY!
362                         return -1;
363                 }
364                 // we know it can be linked, so no need to check
365                 // but...
366                 a = onslaught_controlpoint_can_be_linked(cp, t);
367                 if(a == 2) // near our generator?
368                         return 3; // EMERGENCY!
369                 return 1;
370         }
371         else
372         {
373                 // free point
374                 if(onslaught_controlpoint_can_be_linked(cp, t))
375                 {
376                         a = onslaught_controlpoint_can_be_linked(cp, NUM_TEAM_1 + NUM_TEAM_2 - t);
377                         if(a == 2)
378                                 return 4; // GET THIS ONE NOW!
379                         else
380                                 return 2; // TOUCH ME
381                 }
382         }
383         return 0;
384 }
385
386 float overtime_msg_time;
387 void onslaught_generator_think()
388 {
389         float d;
390         entity e;
391         self.nextthink = ceil(time + 1);
392         if (!gameover)
393         {
394                 if (autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60)
395                 {
396                         if (!overtime_msg_time)
397                         {
398                                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT);
399                                 overtime_msg_time = time;
400                         }
401                         // self.max_health / 300 gives 5 minutes of overtime.
402                         // control points reduce the overtime duration.
403                         sound(self, CH_TRIGGER, "onslaught/generator_decay.wav", VOL_BASE, ATTEN_NORM);
404                         d = 1;
405                         e = findchain(classname, "onslaught_controlpoint");
406                         while (e)
407                         {
408                                 if (e.team != self.team)
409                                         if (e.islinked)
410                                                 d = d + 1;
411                                 e = e.chain;
412                         }
413
414                         if(autocvar_g_campaign && autocvar__campaign_testrun)
415                                 d = d * self.max_health;
416                         else
417                                 d = d * self.max_health / max(30, 60 * autocvar_timelimit_suddendeath);
418
419                         Damage(self, self, self, d, DEATH_HURTTRIGGER, self.origin, '0 0 0');
420                 }
421                 else if (overtime_msg_time)
422                         overtime_msg_time = 0;
423
424         if(!self.isshielded && self.wait < time)
425         {
426             self.wait = time + 5;
427             FOR_EACH_REALPLAYER(e)
428             {
429                                 if(SAME_TEAM(e, self))
430                 {
431                                         Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_ONS_NOTSHIELDED);
432                     soundto(MSG_ONE, e, CHAN_AUTO, "kh/alarm.wav", VOL_BASE, ATTEN_NONE);    // FIXME: Uniqe sound?
433                 }
434             }
435         }
436         }
437 }
438
439 void onslaught_generator_ring_spawn(vector org)
440 {
441         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);
442 }
443
444 void onslaught_generator_ray_think()
445 {
446         self.nextthink = time + 0.05;
447         if(self.count > 10)
448         {
449                 self.think = SUB_Remove;
450                 return;
451         }
452
453         if(self.count > 5)
454                 self.alpha -= 0.1;
455         else
456                 self.alpha += 0.1;
457
458         self.scale += 0.2;
459         self.count +=1;
460 }
461
462 void onslaught_generator_ray_spawn(vector org)
463 {
464         entity e;
465         e = spawn();
466         setmodel(e, "models/onslaught/ons_ray.md3");
467         setorigin(e, org);
468         e.angles = randomvec() * 360;
469         e.alpha = 0;
470         e.scale = random() * 5 + 8;
471         e.think = onslaught_generator_ray_think;
472         e.nextthink = time + 0.05;
473 }
474
475 void onslaught_generator_shockwave_spawn(vector org)
476 {
477         shockwave_spawn("models/onslaught/shockwave.md3", org, -64, 0.75, 0.5);
478 }
479
480 void onslaught_generator_damage_think()
481 {
482         if(self.owner.health < 0)
483         {
484                 self.think = SUB_Remove;
485                 return;
486         }
487         self.nextthink = time+0.1;
488
489         // damaged fx (less probable the more damaged is the generator)
490         if(random() < 0.9 - self.owner.health / self.owner.max_health)
491                 if(random() < 0.01)
492                 {
493                         Send_Effect("electro_ballexplode", self.origin + randompos('-50 -50 -20', '50 50 50'), '0 0 0', 1);
494                         sound(self, CH_TRIGGER, "onslaught/electricity_explode.wav", VOL_BASE, ATTEN_NORM);
495                 }
496                 else
497                         Send_Effect("torch_small", self.origin + randompos('-60 -60 -20', '60 60 60'), '0 0 0', 1);
498 }
499
500 void onslaught_generator_damage_spawn(entity gd_owner)
501 {
502         entity e;
503         e = spawn();
504         e.owner = gd_owner;
505         e.health = self.owner.health;
506         setorigin(e, gd_owner.origin);
507         e.think = onslaught_generator_damage_think;
508         e.nextthink = time+1;
509 }
510
511 void onslaught_generator_deaththink()
512 {
513         vector org;
514         float i;
515
516         if (!self.count)
517                 self.count = 40;
518
519         // White shockwave
520         if(self.count==40||self.count==20)
521         {
522                 onslaught_generator_ring_spawn(self.origin);
523                 sound(self, CH_TRIGGER, "onslaught/shockwave.wav", VOL_BASE, ATTEN_NORM);
524         }
525
526         // Throw some gibs
527         if(random() < 0.3)
528         {
529                 i = random();
530                 if(i < 0.3)
531                         ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 11 + '0 0 20', "models/onslaught/gen_gib1.md3", 6, true);
532                 else if(i > 0.7)
533                         ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 12 + '0 0 20', "models/onslaught/gen_gib2.md3", 6, true);
534                 else
535                         ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 13 + '0 0 20', "models/onslaught/gen_gib3.md3", 6, true);
536         }
537
538         // Spawn fire balls
539         for(i=0;i < 10;++i)
540         {
541                 org = self.origin + randompos('-30 -30 -30' * i + '0 0 -20', '30 30 30' * i + '0 0 20');
542                 Send_Effect("onslaught_generator_gib_explode", org, '0 0 0', 1);
543         }
544
545         // Short explosion sound + small explosion
546         if(random() < 0.25)
547         {
548                 te_explosion(self.origin);
549                 sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
550         }
551
552         // Particles
553         org = self.origin + randompos(self.mins + '8 8 8', self.maxs + '-8 -8 -8');
554         Send_Effect("onslaught_generator_smallexplosion", org, '0 0 0', 1);
555
556         // rays
557         if(random() > 0.25 )
558         {
559                 onslaught_generator_ray_spawn(self.origin);
560         }
561
562         // Final explosion
563         if(self.count==1)
564         {
565                 org = self.origin;
566                 te_explosion(org);
567                 onslaught_generator_shockwave_spawn(org);
568                 Send_Effect("onslaught_generator_finalexplosion", org, '0 0 0', 1);
569                 sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
570         }
571         else
572                 self.nextthink = time + 0.05;
573
574         self.count = self.count - 1;
575 }
576
577 void onslaught_generator_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
578 {
579         float i;
580         if (damage <= 0)
581                 return;
582         if(warmup_stage)
583                 return;
584         if (attacker != self)
585         {
586                 if (self.isshielded)
587                 {
588                         // this is protected by a shield, so ignore the damage
589                         if (time > self.pain_finished)
590                                 if (IS_PLAYER(attacker))
591                                 {
592                                         play2(attacker, "onslaught/damageblockedbyshield.wav");
593                                         self.pain_finished = time + 1;
594                                 }
595                         return;
596                 }
597                 if (time > self.pain_finished)
598                 {
599                         self.pain_finished = time + 10;
600                         bprint(Team_ColoredFullName(self.team), " generator under attack!\n");
601                         play2team(self.team, "onslaught/generator_underattack.wav");
602                 }
603         }
604         self.health = self.health - damage;
605         WaypointSprite_UpdateHealth(self.sprite, self.health);
606         // choose an animation frame based on health
607         self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1);
608         // see if the generator is still functional, or dying
609         if (self.health > 0)
610         {
611 #ifdef ONSLAUGHT_SPAM
612                 float h, lh;
613                 lh = ceil(self.lasthealth / 100) * 100;
614                 h = ceil(self.health / 100) * 100;
615                 if(lh != h)
616                         bprint(Team_ColoredFullName(self.team), " generator has less than ", ftos(h), " health remaining\n");
617 #endif
618                 self.lasthealth = self.health;
619         }
620         else if (!warmup_stage)
621         {
622                 if (attacker == self)
623                         bprint(Team_ColoredFullName(self.team), " generator spontaneously exploded due to overtime!\n");
624                 else
625                 {
626                         string t;
627                         t = Team_ColoredFullName(attacker.team);
628                         bprint(Team_ColoredFullName(self.team), " generator destroyed by ", t, "!\n");
629                 }
630                 self.iscaptured = false;
631                 self.islinked = false;
632                 self.isshielded = false;
633                 self.takedamage = DAMAGE_NO; // can't be hurt anymore
634                 self.event_damage = func_null; // won't do anything if hurt
635                 self.count = 0; // reset counter
636                 self.think = onslaught_generator_deaththink; // explosion sequence
637                 self.nextthink = time; // start exploding immediately
638                 self.think(); // do the first explosion now
639
640                 WaypointSprite_UpdateMaxHealth(self.sprite, 0);
641
642                 onslaught_updatelinks();
643         }
644
645         if(self.health <= 0)
646                 setmodel(self, "models/onslaught/generator_dead.md3");
647         else if(self.health < self.max_health * 0.10)
648                 setmodel(self, "models/onslaught/generator_dmg9.md3");
649         else if(self.health < self.max_health * 0.20)
650                 setmodel(self, "models/onslaught/generator_dmg8.md3");
651         else if(self.health < self.max_health * 0.30)
652                 setmodel(self, "models/onslaught/generator_dmg7.md3");
653         else if(self.health < self.max_health * 0.40)
654                 setmodel(self, "models/onslaught/generator_dmg6.md3");
655         else if(self.health < self.max_health * 0.50)
656                 setmodel(self, "models/onslaught/generator_dmg5.md3");
657         else if(self.health < self.max_health * 0.60)
658                 setmodel(self, "models/onslaught/generator_dmg4.md3");
659         else if(self.health < self.max_health * 0.70)
660                 setmodel(self, "models/onslaught/generator_dmg3.md3");
661         else if(self.health < self.max_health * 0.80)
662                 setmodel(self, "models/onslaught/generator_dmg2.md3");
663         else if(self.health < self.max_health * 0.90)
664                 setmodel(self, "models/onslaught/generator_dmg1.md3");
665         setsize(self, '-52 -52 -14', '52 52 75');
666
667         // Throw some flaming gibs on damage, more damage = more chance for gib
668         if(random() < damage/220)
669         {
670                 sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
671                 i = random();
672                 if(i < 0.3)
673                         ons_throwgib(hitloc + '0 0 20', force * -1, "models/onslaught/gen_gib1.md3", 5, true);
674                 else if(i > 0.7)
675                         ons_throwgib(hitloc + '0 0 20', force * -1, "models/onslaught/gen_gib2.md3", 5, true);
676                 else
677                         ons_throwgib(hitloc + '0 0 20', force * -1, "models/onslaught/gen_gib3.md3", 5, true);
678         }
679         else
680         {
681                 // particles on every hit
682                 Send_Effect("sparks", hitloc, force * -1, 1);
683
684                 //sound on every hit
685                 if (random() < 0.5)
686                         sound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE, ATTEN_NORM);
687                 else
688                         sound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM);
689         }
690
691         //throw some gibs on damage
692         if(random() < damage/200+0.2)
693                 if(random() < 0.5)
694                         ons_throwgib(hitloc + '0 0 20', randomvec()*360, "models/onslaught/gen_gib1.md3", 5, false);
695 }
696
697 // update links after a delay
698 void onslaught_generator_delayed()
699 {
700         onslaught_updatelinks();
701         // now begin normal thinking
702         self.think = onslaught_generator_think;
703         self.nextthink = time;
704 }
705
706 string onslaught_generator_waypointsprite_for_team(entity e, float t)
707 {
708         if(t == e.team)
709         {
710                 if(e.team == NUM_TEAM_1)
711                         return "ons-gen-red";
712                 else if(e.team == NUM_TEAM_2)
713                         return "ons-gen-blue";
714         }
715         if(e.isshielded)
716                 return "ons-gen-shielded";
717         if(e.team == NUM_TEAM_1)
718                 return "ons-gen-red";
719         else if(e.team == NUM_TEAM_2)
720                 return "ons-gen-blue";
721         return "";
722 }
723
724 void onslaught_generator_updatesprite(entity e)
725 {
726         string s1, s2, s3;
727         s1 = onslaught_generator_waypointsprite_for_team(e, NUM_TEAM_1);
728         s2 = onslaught_generator_waypointsprite_for_team(e, NUM_TEAM_2);
729         s3 = onslaught_generator_waypointsprite_for_team(e, -1);
730         WaypointSprite_UpdateSprites(e.sprite, s1, s2, s3);
731
732         if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded)
733         {
734                 e.lastteam = e.team + 2;
735                 e.lastshielded = e.isshielded;
736                 if(e.lastshielded)
737                 {
738                         if(e.team == NUM_TEAM_1 || e.team == NUM_TEAM_2)
739                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, false));
740                         else
741                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5');
742                 }
743                 else
744                 {
745                         if(e.team == NUM_TEAM_1 || e.team == NUM_TEAM_2)
746                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, false));
747                         else
748                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75');
749                 }
750                 WaypointSprite_Ping(e.sprite);
751         }
752 }
753
754 string onslaught_controlpoint_waypointsprite_for_team(entity e, float t)
755 {
756         float a;
757         if(t != -1)
758         {
759                 a = onslaught_controlpoint_attackable(e, t);
760                 if(a == 3 || a == 4) // ATTACK/TOUCH THIS ONE NOW
761                 {
762                         if(e.team == NUM_TEAM_1)
763                                 return "ons-cp-atck-red";
764                         else if(e.team == NUM_TEAM_2)
765                                 return "ons-cp-atck-blue";
766                         else
767                                 return "ons-cp-atck-neut";
768                 }
769                 else if(a == -2) // DEFEND THIS ONE NOW
770                 {
771                         if(e.team == NUM_TEAM_1)
772                                 return "ons-cp-dfnd-red";
773                         else if(e.team == NUM_TEAM_2)
774                                 return "ons-cp-dfnd-blue";
775                 }
776                 else if(e.team == t || a == -1 || a == 1) // own point, or fire at it
777                 {
778                         if(e.team == NUM_TEAM_1)
779                                 return "ons-cp-red";
780                         else if(e.team == NUM_TEAM_2)
781                                 return "ons-cp-blue";
782                 }
783                 else if(a == 2) // touch it
784                         return "ons-cp-neut";
785         }
786         else
787         {
788                 if(e.team == NUM_TEAM_1)
789                         return "ons-cp-red";
790                 else if(e.team == NUM_TEAM_2)
791                         return "ons-cp-blue";
792                 else
793                         return "ons-cp-neut";
794         }
795         return "";
796 }
797
798 void onslaught_controlpoint_updatesprite(entity e)
799 {
800         string s1, s2, s3;
801         s1 = onslaught_controlpoint_waypointsprite_for_team(e, NUM_TEAM_1);
802         s2 = onslaught_controlpoint_waypointsprite_for_team(e, NUM_TEAM_2);
803         s3 = onslaught_controlpoint_waypointsprite_for_team(e, -1);
804         WaypointSprite_UpdateSprites(e.sprite, s1, s2, s3);
805
806         float sh;
807         sh = !(onslaught_controlpoint_can_be_linked(e, NUM_TEAM_1) || onslaught_controlpoint_can_be_linked(e, NUM_TEAM_2));
808
809         if(e.lastteam != e.team + 2 || e.lastshielded != sh || e.iscaptured != e.lastcaptured)
810         {
811                 if(e.iscaptured) // don't mess up build bars!
812                 {
813                         if(sh)
814                         {
815                                 WaypointSprite_UpdateMaxHealth(e.sprite, 0);
816                         }
817                         else
818                         {
819                                 WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health);
820                                 WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health);
821                         }
822                 }
823                 if(e.lastshielded)
824                 {
825                         if(e.team == NUM_TEAM_1 || e.team == NUM_TEAM_2)
826                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, false));
827                         else
828                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5');
829                 }
830                 else
831                 {
832                         if(e.team == NUM_TEAM_1 || e.team == NUM_TEAM_2)
833                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, false));
834                         else
835                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75');
836                 }
837                 WaypointSprite_Ping(e.sprite);
838
839                 e.lastteam = e.team + 2;
840                 e.lastshielded = sh;
841                 e.lastcaptured = e.iscaptured;
842         }
843 }
844
845 void onslaught_generator_reset()
846 {
847         self.team = self.team_saved;
848         self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health;
849         self.takedamage = DAMAGE_AIM;
850         self.bot_attack = true;
851         self.iscaptured = true;
852         self.islinked = true;
853         self.isshielded = true;
854         self.enemy.solid = SOLID_NOT;
855         self.think = onslaught_generator_delayed;
856         self.nextthink = time + 0.2;
857         setmodel(self, "models/onslaught/generator.md3");
858         setsize(self, '-52 -52 -14', '52 52 75');
859
860         if(!self.noalign)
861         {
862                 setorigin(self, self.origin + '0 0 20');
863                 droptofloor();
864         }
865
866         WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
867         WaypointSprite_UpdateHealth(self.sprite, self.health);
868 }
869
870 /*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64)
871   Base generator.
872
873   spawnfunc_onslaught_link entities can target this.
874
875 keys:
876 "team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET.
877 "targetname" - name that spawnfunc_onslaught_link entities will use to target this.
878  */
879 void spawnfunc_onslaught_generator()
880 {
881         if (!g_onslaught)
882         {
883                 remove(self);
884                 return;
885         }
886
887         //entity e;
888         precache_model("models/onslaught/generator.md3");
889         precache_model("models/onslaught/generator_shield.md3");
890         precache_model("models/onslaught/generator_dmg1.md3");
891         precache_model("models/onslaught/generator_dmg2.md3");
892         precache_model("models/onslaught/generator_dmg3.md3");
893         precache_model("models/onslaught/generator_dmg4.md3");
894         precache_model("models/onslaught/generator_dmg5.md3");
895         precache_model("models/onslaught/generator_dmg6.md3");
896         precache_model("models/onslaught/generator_dmg7.md3");
897         precache_model("models/onslaught/generator_dmg8.md3");
898         precache_model("models/onslaught/generator_dmg9.md3");
899         precache_model("models/onslaught/generator_dead.md3");
900         precache_model("models/onslaught/shockwave.md3");
901         precache_model("models/onslaught/shockwavetransring.md3");
902         precache_model("models/onslaught/gen_gib1.md3");
903         precache_model("models/onslaught/gen_gib2.md3");
904         precache_model("models/onslaught/gen_gib3.md3");
905         precache_model("models/onslaught/ons_ray.md3");
906         precache_sound("onslaught/generator_decay.wav");
907         precache_sound("weapons/grenade_impact.wav");
908         precache_sound("weapons/rocket_impact.wav");
909         precache_sound("onslaught/generator_underattack.wav");
910         precache_sound("onslaught/shockwave.wav");
911         precache_sound("onslaught/ons_hit1.wav");
912         precache_sound("onslaught/ons_hit2.wav");
913         precache_sound("onslaught/electricity_explode.wav");
914         if (!self.team)
915                 objerror("team must be set");
916
917         if(self.team == NUM_TEAM_1)
918         ons_red_generator = self;
919
920         if(self.team == NUM_TEAM_2)
921         ons_blue_generator = self;
922
923         self.team_saved = self.team;
924         self.colormap = 1024 + (self.team - 1) * 17;
925         self.solid = SOLID_BBOX;
926         self.movetype = MOVETYPE_NONE;
927         self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health;
928         setmodel(self, "models/onslaught/generator.md3");
929         setsize(self, '-52 -52 -14', '52 52 75');
930         setorigin(self, self.origin);
931         self.takedamage = DAMAGE_AIM;
932         self.bot_attack = true;
933         self.event_damage = onslaught_generator_damage;
934         self.iscaptured = true;
935         self.islinked = true;
936         self.isshielded = true;
937         // helper entity that create fx when generator is damaged
938         onslaught_generator_damage_spawn(self);
939         // spawn shield model which indicates whether this can be damaged
940         self.enemy = spawn();
941         setattachment(self.enemy , self, "");
942         self.enemy.classname = "onslaught_generator_shield";
943         self.enemy.solid = SOLID_NOT;
944         self.enemy.movetype = MOVETYPE_NONE;
945         self.enemy.effects = EF_ADDITIVE;
946         setmodel(self.enemy, "models/onslaught/generator_shield.md3");
947         //setorigin(e, self.origin);
948         self.enemy.colormap = self.colormap;
949         self.enemy.team = self.team;
950         //self.think = onslaught_generator_delayed;
951         //self.nextthink = time + 0.2;
952         InitializeEntity(self, onslaught_generator_delayed, INITPRIO_LAST);
953
954         WaypointSprite_SpawnFixed(string_null, self.origin + '0 0 128', self, sprite, RADARICON_NONE, '0 0 0');
955         WaypointSprite_UpdateRule(self.sprite, NUM_TEAM_2, SPRITERULE_TEAMPLAY);
956         WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
957         WaypointSprite_UpdateHealth(self.sprite, self.health);
958
959         waypoint_spawnforitem(self);
960
961         onslaught_updatelinks();
962
963         self.reset = onslaught_generator_reset;
964 }
965
966 .float waslinked;
967 .float cp_bob_spd;
968 .vector cp_origin, cp_bob_origin, cp_bob_dmg;
969
970 float ons_notification_time_team1;
971 float ons_notification_time_team2;
972
973 void onslaught_controlpoint_icon_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
974 {
975         entity oself;
976         float nag;
977
978         if (damage <= 0)
979                 return;
980         if (self.owner.isshielded)
981         {
982                 // this is protected by a shield, so ignore the damage
983                 if (time > self.pain_finished)
984                         if (IS_PLAYER(attacker))
985                         {
986                                 play2(attacker, "onslaught/damageblockedbyshield.wav");
987                                 self.pain_finished = time + 1;
988                         }
989                 return;
990         }
991
992         if (IS_PLAYER(attacker))
993         {
994                 nag = false;
995                 if(self.team == NUM_TEAM_1)
996                 {
997                         if(time - ons_notification_time_team1 > 10)
998                         {
999                                 nag = true;
1000                                 ons_notification_time_team1 = time;
1001                         }
1002                 }
1003                 else if(self.team == NUM_TEAM_2)
1004                 {
1005                         if(time - ons_notification_time_team2 > 10)
1006                         {
1007                                 nag = true;
1008                                 ons_notification_time_team2 = time;
1009                         }
1010                 }
1011                 else
1012                         nag = true;
1013
1014                 if(nag)
1015                         play2team(self.team, "onslaught/controlpoint_underattack.wav");
1016         }
1017
1018         self.health = self.health - damage;
1019         if(self.owner.iscaptured)
1020                 WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
1021         else
1022                 WaypointSprite_UpdateBuildFinished(self.owner.sprite, time + (self.max_health - self.health) / (self.count / sys_frametime));
1023         self.pain_finished = time + 1;
1024         self.punchangle = (2 * randomvec() - '1 1 1') * 45;
1025         self.cp_bob_dmg_z = (2 * random() - 1) * 15;
1026         // colormod flash when shot
1027         self.colormod = '2 2 2';
1028         // particles on every hit
1029         Send_Effect("sparks", hitloc, force*-1, 1);
1030         //sound on every hit
1031         if (random() < 0.5)
1032                 sound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE+0.3, ATTEN_NORM);
1033         else
1034                 sound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE+0.3, ATTEN_NORM);
1035
1036         if (self.health < 0)
1037         {
1038                 sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
1039                 Send_Effect("rocket_explode", self.origin, '0 0 0', 1);
1040                 {
1041                         string t;
1042                         t = Team_ColoredFullName(attacker.team);
1043                         bprint(Team_ColoredFullName(self.team), " ", self.message, " control point destroyed by ", t, "\n");
1044                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 25, "models/onslaught/controlpoint_icon_gib1.md3", 3, false);
1045                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 45, "models/onslaught/controlpoint_icon_gib2.md3", 3, false);
1046                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 45, "models/onslaught/controlpoint_icon_gib2.md3", 3, false);
1047                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, false);
1048                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, false);
1049                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, false);
1050                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, false);
1051                 }
1052                 self.owner.goalentity = world;
1053                 self.owner.islinked = false;
1054                 self.owner.iscaptured = false;
1055                 self.owner.team = 0;
1056                 self.owner.colormap = 1024;
1057
1058                 WaypointSprite_UpdateMaxHealth(self.owner.sprite, 0);
1059
1060                 onslaught_updatelinks();
1061
1062                 // Use targets now (somebody make sure this is in the right place..)
1063                 oself = self;
1064                 self = self.owner;
1065                 activator = self;
1066                 SUB_UseTargets ();
1067                 self = oself;
1068
1069
1070                 self.owner.waslinked = self.owner.islinked;
1071                 if(self.owner.model != "models/onslaught/controlpoint_pad.md3")
1072                         setmodel(self.owner, "models/onslaught/controlpoint_pad.md3");
1073                 //setsize(self, '-32 -32 0', '32 32 8');
1074
1075                 remove(self);
1076         }
1077 }
1078
1079 void onslaught_controlpoint_icon_think()
1080 {
1081         entity oself;
1082         self.nextthink = time + sys_frametime;
1083
1084         if(autocvar_g_onslaught_cp_proxydecap)
1085         {
1086         float _enemy_count = 0;
1087         float _friendly_count = 0;
1088         float _dist;
1089         entity _player;
1090
1091         FOR_EACH_PLAYER(_player)
1092         {
1093             if(!_player.deadflag)
1094             {
1095                 _dist = vlen(_player.origin - self.origin);
1096                 if(_dist < autocvar_g_onslaught_cp_proxydecap_distance)
1097                 {
1098                     if(_player.team == self.team)
1099                         ++_friendly_count;
1100                     else
1101                         ++_enemy_count;
1102                 }
1103             }
1104         }
1105
1106         _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * sys_frametime);
1107         _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * sys_frametime);
1108
1109         self.health = bound(0, self.health + (_friendly_count - _enemy_count), self.max_health);
1110         if(self.health <= 0)
1111         {
1112             onslaught_controlpoint_icon_damage(self, self, 1, 0, self.origin, '0 0 0');
1113             return;
1114         }
1115     }
1116
1117         if (time > self.pain_finished + 5)
1118         {
1119                 if(self.health < self.max_health)
1120                 {
1121                         self.health = self.health + self.count;
1122                         if (self.health >= self.max_health)
1123                                 self.health = self.max_health;
1124                         WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
1125                 }
1126         }
1127         if (self.health < self.max_health * 0.25)
1128                 setmodel(self, "models/onslaught/controlpoint_icon_dmg3.md3");
1129         else if (self.health < self.max_health * 0.50)
1130                 setmodel(self, "models/onslaught/controlpoint_icon_dmg2.md3");
1131         else if (self.health < self.max_health * 0.75)
1132                 setmodel(self, "models/onslaught/controlpoint_icon_dmg1.md3");
1133         else if (self.health < self.max_health * 0.90)
1134                 setmodel(self, "models/onslaught/controlpoint_icon.md3");
1135         // colormod flash when shot
1136         self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1));
1137
1138         if(self.owner.islinked != self.owner.waslinked)
1139         {
1140                 // unteam the spawnpoint if needed
1141                 float t;
1142                 t = self.owner.team;
1143                 if(!self.owner.islinked)
1144                         self.owner.team = 0;
1145
1146                 oself = self;
1147                 self = self.owner;
1148                 activator = self;
1149                 SUB_UseTargets ();
1150                 self = oself;
1151
1152                 self.owner.team = t;
1153
1154                 self.owner.waslinked = self.owner.islinked;
1155         }
1156
1157         if (self.punchangle.x > 0)
1158         {
1159                 self.punchangle_x = self.punchangle.x - 60 * sys_frametime;
1160                 if (self.punchangle.x < 0)
1161                         self.punchangle_x = 0;
1162         }
1163         else if (self.punchangle.x < 0)
1164         {
1165                 self.punchangle_x = self.punchangle.x + 60 * sys_frametime;
1166                 if (self.punchangle.x > 0)
1167                         self.punchangle_x = 0;
1168         }
1169
1170         if (self.punchangle.y > 0)
1171         {
1172                 self.punchangle_y = self.punchangle.y - 60 * sys_frametime;
1173                 if (self.punchangle.y < 0)
1174                         self.punchangle_y = 0;
1175         }
1176         else if (self.punchangle.y < 0)
1177         {
1178                 self.punchangle_y = self.punchangle.y + 60 * sys_frametime;
1179                 if (self.punchangle.y > 0)
1180                         self.punchangle_y = 0;
1181         }
1182
1183         if (self.punchangle.z > 0)
1184         {
1185                 self.punchangle_z = self.punchangle.z - 60 * sys_frametime;
1186                 if (self.punchangle.z < 0)
1187                         self.punchangle_z = 0;
1188         }
1189         else if (self.punchangle.z < 0)
1190         {
1191                 self.punchangle_z = self.punchangle.z + 60 * sys_frametime;
1192                 if (self.punchangle.z > 0)
1193                         self.punchangle_z = 0;
1194         }
1195
1196         self.angles_x = self.punchangle.x;
1197         self.angles_y = self.punchangle.y + self.mangle.y;
1198         self.angles_z = self.punchangle.z;
1199         self.mangle_y = self.mangle.y + 45 * sys_frametime;
1200
1201         self.cp_bob_origin_z = 4 * PI * (1 - cos(self.cp_bob_spd));
1202         self.cp_bob_spd = self.cp_bob_spd + 1.875 * sys_frametime;
1203         if(self.cp_bob_dmg.z > 0)
1204                 self.cp_bob_dmg_z = self.cp_bob_dmg.z - 3 * sys_frametime;
1205         else
1206                 self.cp_bob_dmg_z = 0;
1207         setorigin(self,self.cp_origin + self.cp_bob_origin + self.cp_bob_dmg);
1208
1209         // damaged fx
1210         if(random() < 0.6 - self.health / self.max_health)
1211         {
1212                 Send_Effect("electricity_sparks", self.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1);
1213
1214                 if(random() > 0.8)
1215                         sound(self, CH_PAIN, "onslaught/ons_spark1.wav", VOL_BASE, ATTEN_NORM);
1216                 else if (random() > 0.5)
1217                         sound(self, CH_PAIN, "onslaught/ons_spark2.wav", VOL_BASE, ATTEN_NORM);
1218         }
1219 }
1220
1221 void onslaught_controlpoint_icon_buildthink()
1222 {
1223         entity oself;
1224         float a;
1225
1226         self.nextthink = time + sys_frametime;
1227
1228         // only do this if there is power
1229         a = onslaught_controlpoint_can_be_linked(self.owner, self.owner.team);
1230         if(!a)
1231                 return;
1232
1233         self.health = self.health + self.count;
1234
1235         if (self.health >= self.max_health)
1236         {
1237                 self.health = self.max_health;
1238                 self.count = autocvar_g_onslaught_cp_regen * sys_frametime; // slow repair rate from now on
1239                 self.think = onslaught_controlpoint_icon_think;
1240                 sound(self, CH_TRIGGER, "onslaught/controlpoint_built.wav", VOL_BASE, ATTEN_NORM);
1241                 bprint(Team_ColoredFullName(self.team), " captured ", self.owner.message, " control point\n");
1242                 self.owner.iscaptured = true;
1243
1244                 WaypointSprite_UpdateMaxHealth(self.owner.sprite, self.max_health);
1245                 WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
1246
1247                 onslaught_updatelinks();
1248
1249                 // Use targets now (somebody make sure this is in the right place..)
1250                 oself = self;
1251                 self = self.owner;
1252                 activator = self;
1253                 SUB_UseTargets ();
1254                 self = oself;
1255                 self.cp_origin = self.origin;
1256                 self.cp_bob_origin = '0 0 0.1';
1257                 self.cp_bob_spd = 0;
1258         }
1259         self.alpha = self.health / self.max_health;
1260         // colormod flash when shot
1261         self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1));
1262         if(self.owner.model != "models/onslaught/controlpoint_pad2.md3")
1263                 setmodel(self.owner, "models/onslaught/controlpoint_pad2.md3");
1264         //setsize(self, '-32 -32 0', '32 32 8');
1265
1266         if(random() < 0.9 - self.health / self.max_health)
1267                 Send_Effect("rage", self.origin + 10 * randomvec(), '0 0 -1', 1);
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_TurretSpawn)
1662 {
1663         entity e, ee = world;
1664         if(self.targetname)
1665         {
1666                 e = find(world, target, self.targetname);
1667
1668                 if(e != world)
1669                 {
1670                         self.team = e.team;
1671                         ee = e;
1672                 }
1673         }
1674         
1675         if(ee)
1676         {
1677                 activator = ee;
1678                 self.use();
1679         }
1680
1681         return FALSE;
1682 }
1683
1684 MUTATOR_HOOKFUNCTION(ons_MonsterThink)
1685 {
1686         entity e = find(world, targetname, self.target);
1687         if (e != world)
1688                 self.team = e.team;
1689
1690         return false;
1691 }
1692
1693 MUTATOR_HOOKFUNCTION(ons_MonsterSpawn)
1694 {
1695         entity e, ee = world;
1696
1697         if(self.targetname)
1698         {
1699                 e = find(world,target,self.targetname);
1700         activator = ee;
1701         self.use();
1702     }
1703
1704         return false;
1705 }
1706
1707 MUTATOR_DEFINITION(gamemode_onslaught)
1708 {
1709         MUTATOR_HOOK(BuildMutatorsPrettyString, ons_BuildMutatorsPrettyString, CBC_ORDER_ANY);
1710         MUTATOR_HOOK(BuildMutatorsString, ons_BuildMutatorsString, CBC_ORDER_ANY);
1711         MUTATOR_HOOK(PlayerSpawn, ons_PlayerSpawn, CBC_ORDER_ANY);
1712         MUTATOR_HOOK(TurretSpawn, ons_TurretSpawn, CBC_ORDER_ANY);
1713         MUTATOR_HOOK(MonsterMove, ons_MonsterThink, CBC_ORDER_ANY);
1714         MUTATOR_HOOK(MonsterSpawn, ons_MonsterSpawn, CBC_ORDER_ANY);
1715         //MUTATOR_HOOK(Spawn_Score, ons_Spawn_Score, CBC_ORDER_ANY);
1716
1717         MUTATOR_ONADD
1718         {
1719                 if(time > 1) // game loads at time 1
1720                         error("This is a game type and it cannot be added at runtime.");
1721         }
1722
1723         MUTATOR_ONREMOVE
1724         {
1725                 print("This is a game type and it cannot be removed at runtime.");
1726                 return -1;
1727         }
1728
1729         return 0;
1730 }