]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_onslaught.qc
Multiple improvements to onslaught code, including CSQC generators/control points...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_onslaught.qc
1 // =======================
2 // CaptureShield Functions
3 // =======================
4
5 float ons_CaptureShield_Customize()
6 {
7         if(!self.enemy.isshielded && (onslaught_controlpoint_attackable(self.enemy, other.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return FALSE; }
8         if(SAME_TEAM(self, other)) { return FALSE; }
9
10         return TRUE;
11 }
12
13 void ons_CaptureShield_Touch()
14 {
15         if(!self.enemy.isshielded && (onslaught_controlpoint_attackable(self.enemy, other.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return; }
16         if(!IS_PLAYER(other)) { return; }
17         if(SAME_TEAM(other, self)) { return; }
18
19         vector mymid = (self.absmin + self.absmax) * 0.5;
20         vector othermid = (other.absmin + other.absmax) * 0.5;
21
22         Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ons_captureshield_force);
23
24         play2(other, "onslaught/damageblockedbyshield.wav");
25         
26         if(self.enemy.classname == "onslaught_generator")
27                 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_GENERATOR_SHIELDED);
28         else
29                 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_CONTROLPOINT_SHIELDED);
30 }
31
32 void ons_CaptureShield_Reset()
33 {
34         self.colormap = self.enemy.colormap;
35         self.team = self.enemy.team;
36 }
37
38 void ons_CaptureShield_Spawn(entity generator, string themodel)
39 {
40         entity shield = spawn();
41
42         shield.enemy = generator;
43         shield.team = generator.team;
44         shield.colormap = generator.colormap;
45         shield.reset = ons_CaptureShield_Reset;
46         shield.touch = ons_CaptureShield_Touch;
47         shield.customizeentityforclient = ons_CaptureShield_Customize;
48         shield.classname = "ons_captureshield";
49         shield.effects = EF_ADDITIVE;
50         shield.movetype = MOVETYPE_NOCLIP;
51         shield.solid = SOLID_TRIGGER;
52         shield.avelocity = '7 0 11';
53         shield.scale = 1;
54
55         setorigin(shield, generator.origin);
56         setmodel(shield, themodel);
57         setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
58 }
59
60 void ons_gib_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce)
61 {
62         self.velocity = self.velocity + vforce;
63 }
64
65 .float giblifetime;
66 void ons_throwgib_think()
67 {
68         float d;
69
70         self.nextthink = time + 0.05;
71
72         d = self.giblifetime - time;
73
74         if(d<0)
75         {
76                 self.think = SUB_Remove;
77                 return;
78         }
79         if(d<1)
80                 self.alpha = d;
81
82         if(d>2)
83         if(random()<0.6)
84                 pointparticles(particleeffectnum("onslaught_generator_gib_flame"), self.origin, '0 0 0', 1);
85 }
86
87 void ons_throwgib(vector v_from, vector v_to, string smodel, float f_lifetime, float b_burn)
88 {
89         entity gib;
90
91         gib = spawn();
92
93         setmodel(gib, smodel);
94         setorigin(gib, v_from);
95         gib.solid = SOLID_CORPSE;
96         gib.movetype = MOVETYPE_BOUNCE;
97         gib.takedamage = DAMAGE_YES;
98         gib.event_damage = ons_gib_damage;
99         gib.health = -1;
100         gib.effects = EF_LOWPRECISION;
101         gib.flags = FL_NOTARGET;
102         gib.velocity = v_to;
103         gib.giblifetime = time + f_lifetime;
104
105         if (b_burn)
106         {
107                 gib.think = ons_throwgib_think;
108                 gib.nextthink = time + 0.05;
109         }
110         else
111                 SUB_SetFade(gib, gib.giblifetime, 2);
112 }
113
114 void FixSize(entity e)
115 {
116         e.mins_x = rint(e.mins_x);
117         e.mins_y = rint(e.mins_y);
118         e.mins_z = rint(e.mins_z);
119         
120         e.maxs_x = rint(e.maxs_x);
121         e.maxs_y = rint(e.maxs_y);
122         e.maxs_z = rint(e.maxs_z);
123 }
124
125 void setmodel_fixsize(entity e, string m)
126 {
127         setmodel(e, m);
128         FixSize(e);
129 }
130
131 void onslaught_updatelinks()
132 {
133         entity l, links;
134         // first check if the game has ended
135         dprint("--- updatelinks ---\n");
136         links = findchain(classname, "onslaught_link");
137         // mark generators as being shielded and networked
138         l = findchain(classname, "onslaught_generator");
139         while (l)
140         {
141                 if (l.iscaptured)
142                         dprint(etos(l), " (generator) belongs to team ", ftos(l.team), "\n");
143                 else
144                         dprint(etos(l), " (generator) is destroyed\n");
145                 l.islinked = l.iscaptured;
146                 l.isshielded = l.iscaptured;
147                 l = l.chain;
148         }
149         // mark points as shielded and not networked
150         l = findchain(classname, "onslaught_controlpoint");
151         while (l)
152         {
153                 l.islinked = FALSE;
154                 l.isshielded = TRUE;
155                 float i;
156                 for(i = 0; i < 17; ++i) { l.isgenneighbor[i] = FALSE; l.iscpneighbor[i] = FALSE; }
157                 dprint(etos(l), " (point) belongs to team ", ftos(l.team), "\n");
158                 l = l.chain;
159         }
160         // flow power outward from the generators through the network
161         l = links;
162         while (l)
163         {
164                 dprint(etos(l), " (link) connects ", etos(l.goalentity), " with ", etos(l.enemy), "\n");
165                 l = l.chain;
166         }
167         float stop = FALSE;
168         while (!stop)
169         {
170                 stop = TRUE;
171                 l = links;
172                 while (l)
173                 {
174                         // if both points are captured by the same team, and only one of
175                         // them is powered, mark the other one as powered as well
176                         if (l.enemy.iscaptured && l.goalentity.iscaptured)
177                                 if (l.enemy.islinked != l.goalentity.islinked)
178                                         if (l.enemy.team == l.goalentity.team)
179                                         {
180                                                 if (!l.goalentity.islinked)
181                                                 {
182                                                         stop = FALSE;
183                                                         l.goalentity.islinked = TRUE;
184                                                         dprint(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)\n");
185                                                 }
186                                                 else if (!l.enemy.islinked)
187                                                 {
188                                                         stop = FALSE;
189                                                         l.enemy.islinked = TRUE;
190                                                         dprint(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)\n");
191                                                 }
192                                         }
193                         l = l.chain;
194                 }
195         }
196         // now that we know which points are powered we can mark their neighbors
197         // as unshielded if team differs
198         l = links;
199         while (l)
200         {
201                 if (l.goalentity.islinked)
202                 {
203                         if(DIFF_TEAM(l.goalentity, l.enemy))
204                         {
205                                 dprint(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)\n");
206                                 l.enemy.isshielded = FALSE;
207                         }
208                         if(l.goalentity.classname == "onslaught_generator")
209                                 l.enemy.isgenneighbor[l.goalentity.team] = TRUE;
210                         else
211                                 l.enemy.iscpneighbor[l.goalentity.team] = TRUE;
212                 }
213                 if (l.enemy.islinked)
214                 {
215                         if(DIFF_TEAM(l.goalentity, l.enemy))
216                         {
217                                 dprint(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)\n");
218                                 l.goalentity.isshielded = FALSE;
219                         }
220                         if(l.enemy.classname == "onslaught_generator")
221                                 l.goalentity.isgenneighbor[l.enemy.team] = TRUE;
222                         else
223                                 l.goalentity.iscpneighbor[l.enemy.team] = TRUE;
224                 }
225                 l = l.chain;
226         }
227         // now update the takedamage and alpha variables on generator shields
228         l = findchain(classname, "onslaught_generator");
229         while (l)
230         {
231                 if (l.isshielded)
232                 {
233                         dprint(etos(l), " (generator) is shielded\n");
234                         l.takedamage = DAMAGE_NO;
235                         l.bot_attack = FALSE;
236                 }
237                 else
238                 {
239                         dprint(etos(l), " (generator) is not shielded\n");
240                         l.takedamage = DAMAGE_AIM;
241                         l.bot_attack = TRUE;
242                 }
243                 l = l.chain;
244         }
245         // now update the takedamage and alpha variables on control point icons
246         l = findchain(classname, "onslaught_controlpoint");
247         while (l)
248         {
249                 if (l.isshielded)
250                 {
251                         dprint(etos(l), " (point) is shielded\n");
252                         if (l.goalentity)
253                         {
254                                 l.goalentity.takedamage = DAMAGE_NO;
255                                 l.goalentity.bot_attack = FALSE;
256                         }
257                 }
258                 else
259                 {
260                         dprint(etos(l), " (point) is not shielded\n");
261                         if (l.goalentity)
262                         {
263                                 l.goalentity.takedamage = DAMAGE_AIM;
264                                 l.goalentity.bot_attack = TRUE;
265                         }
266                 }
267                 onslaught_controlpoint_updatesprite(l);
268                 l = l.chain;
269         }
270         // count generators owned by each team
271         float t1 = 0, t2 = 0, t3 = 0, t4 = 0;
272         l = findchain(classname, "onslaught_generator");
273         while (l)
274         {
275                 if (l.iscaptured)
276                 {
277                         switch(l.team)
278                         {
279                                 case NUM_TEAM_1: t1 = 1; break;
280                                 case NUM_TEAM_2: t2 = 1; break;
281                                 case NUM_TEAM_3: t3 = 1; break;
282                                 case NUM_TEAM_4: t4 = 1; break;
283                         }
284                 }
285                 onslaught_generator_updatesprite(l);
286                 l = l.chain;
287         }
288         l = findchain(classname, "ons_captureshield");
289         while(l)
290         {
291                 l.team = l.enemy.team;
292                 l.colormap = l.enemy.colormap;
293                 l = l.chain;
294         }
295         // see if multiple teams remain (if not, it's game over)
296         if (t1 + t2 + t3 + t4 < 2)
297                 dprint("--- game over ---\n");
298         else
299                 dprint("--- done updating links ---\n");
300 }
301
302 float onslaught_controlpoint_can_be_linked(entity cp, float t)
303 {
304         if(cp.isgenneighbor[t]) { return 2; }
305         if(cp.iscpneighbor[t]) { return 1; }
306
307         return 0;
308 }
309
310 float onslaught_controlpoint_attackable(entity cp, float t)
311         // -2: SAME TEAM, attackable by enemy!
312         // -1: SAME TEAM!
313         // 0: off limits
314         // 1: attack it
315         // 2: touch it
316         // 3: attack it (HIGH PRIO)
317         // 4: touch it (HIGH PRIO)
318 {
319         float a;
320
321         if(cp.isshielded)
322         {
323                 return 0;
324         }
325         else if(cp.goalentity)
326         {
327                 // if there's already an icon built, nothing happens
328                 if(cp.team == t)
329                 {
330                         a = onslaught_controlpoint_can_be_linked(cp, t);
331                         if(a) // attackable by enemy?
332                                 return -2; // EMERGENCY!
333                         return -1;
334                 }
335                 // we know it can be linked, so no need to check
336                 // but...
337                 a = onslaught_controlpoint_can_be_linked(cp, t);
338                 if(a == 2) // near our generator?
339                         return 3; // EMERGENCY!
340                 return 1;
341         }
342         else
343         {
344                 // free point
345                 if(onslaught_controlpoint_can_be_linked(cp, t))
346                 {
347                         a = onslaught_controlpoint_can_be_linked(cp, t); // why was this here NUM_TEAM_1 + NUM_TEAM_2 - t
348                         if(a == 2)
349                                 return 4; // GET THIS ONE NOW!
350                         else
351                                 return 2; // TOUCH ME
352                 }
353         }
354         return 0;
355 }
356
357 float overtime_msg_time;
358 void onslaught_generator_think()
359 {
360         float d;
361         entity e;
362         self.nextthink = ceil(time + 1);
363         if (!gameover)
364         {
365                 if (autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60)
366                 {
367                         if (!overtime_msg_time)
368                         {
369                                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT);
370                                 overtime_msg_time = time;
371                         }
372                         // self.max_health / 300 gives 5 minutes of overtime.
373                         // control points reduce the overtime duration.
374                         sound(self, CH_TRIGGER, "onslaught/generator_decay.wav", VOL_BASE, ATTEN_NORM);
375                         d = 1;
376                         e = findchain(classname, "onslaught_controlpoint");
377                         while (e)
378                         {
379                                 if (e.team != self.team)
380                                         if (e.islinked)
381                                                 d = d + 1;
382                                 e = e.chain;
383                         }
384
385                         if(autocvar_g_campaign && autocvar__campaign_testrun)
386                                 d = d * self.max_health;
387                         else
388                                 d = d * self.max_health / max(30, 60 * autocvar_timelimit_suddendeath);
389
390                         Damage(self, self, self, d, DEATH_HURTTRIGGER, self.origin, '0 0 0');
391                 }
392                 else if (overtime_msg_time)
393                         overtime_msg_time = 0;
394
395         if(!self.isshielded && self.wait < time)
396         {
397             self.wait = time + 5;
398             FOR_EACH_REALPLAYER(e)
399             {
400                                 if(SAME_TEAM(e, self))
401                                 {
402                                         Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM);
403                     soundto(MSG_ONE, e, CHAN_AUTO, "kh/alarm.wav", VOL_BASE, ATTEN_NONE);    // FIXME: unique sound?
404                 }
405                                 else
406                                         Send_Notification(NOTIF_ONE, e, MSG_CENTER, APP_TEAM_NUM_4(self.team, CENTER_ONS_NOTSHIELDED_));
407             }
408         }
409         }
410 }
411
412 void onslaught_generator_ring_spawn(vector org)
413 {
414         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);
415 }
416
417 void onslaught_generator_ray_think()
418 {
419         self.nextthink = time + 0.05;
420         if(self.count > 10)
421         {
422                 self.think = SUB_Remove;
423                 return;
424         }
425
426         if(self.count > 5)
427                 self.alpha -= 0.1;
428         else
429                 self.alpha += 0.1;
430
431         self.scale += 0.2;
432         self.count +=1;
433 }
434
435 void onslaught_generator_ray_spawn(vector org)
436 {
437         entity e;
438         e = spawn();
439         setmodel(e, "models/onslaught/ons_ray.md3");
440         setorigin(e, org);
441         e.angles = randomvec() * 360;
442         e.alpha = 0;
443         e.scale = random() * 5 + 8;
444         e.think = onslaught_generator_ray_think;
445         e.nextthink = time + 0.05;
446 }
447
448 void onslaught_generator_shockwave_spawn(vector org)
449 {
450         shockwave_spawn("models/onslaught/shockwave.md3", org, -64, 0.75, 0.5);
451 }
452
453 void onslaught_generator_damage_think()
454 {
455         if(self.owner.health < 0)
456         {
457                 self.think = SUB_Remove;
458                 return;
459         }
460         self.nextthink = time+0.1;
461
462         // damaged fx (less probable the more damaged is the generator)
463         if(random() < 0.9 - self.owner.health / self.owner.max_health)
464                 if(random() < 0.01)
465                 {
466                         pointparticles(particleeffectnum("electro_ballexplode"), self.origin + randompos('-50 -50 -20', '50 50 50'), '0 0 0', 1);
467                         sound(self, CH_TRIGGER, "onslaught/electricity_explode.wav", VOL_BASE, ATTEN_NORM);
468                 }
469                 else
470                         pointparticles(particleeffectnum("torch_small"), self.origin + randompos('-60 -60 -20', '60 60 60'), '0 0 0', 1);
471 }
472
473 void onslaught_generator_damage_spawn(entity gd_owner)
474 {
475         entity e;
476         e = spawn();
477         e.owner = gd_owner;
478         e.health = self.owner.health;
479         setorigin(e, gd_owner.origin);
480         e.think = onslaught_generator_damage_think;
481         e.nextthink = time+1;
482 }
483
484 void onslaught_generator_deaththink()
485 {
486         vector org;
487         float i;
488
489         if (!self.count)
490                 self.count = 40;
491
492         // White shockwave
493         if(self.count==40||self.count==20)
494         {
495                 onslaught_generator_ring_spawn(self.origin);
496                 sound(self, CH_TRIGGER, "onslaught/shockwave.wav", VOL_BASE, ATTEN_NORM);
497         }
498
499         // Throw some gibs
500         if(random() < 0.3)
501         {
502                 i = random();
503                 if(i < 0.3)
504                         ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 11 + '0 0 20', "models/onslaught/gen_gib1.md3", 6, TRUE);
505                 else if(i > 0.7)
506                         ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 12 + '0 0 20', "models/onslaught/gen_gib2.md3", 6, TRUE);
507                 else
508                         ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 13 + '0 0 20', "models/onslaught/gen_gib3.md3", 6, TRUE);
509         }
510
511         // Spawn fire balls
512         for(i=0;i < 10;++i)
513         {
514                 org = self.origin + randompos('-30 -30 -30' * i + '0 0 -20', '30 30 30' * i + '0 0 20');
515                 pointparticles(particleeffectnum("onslaught_generator_gib_explode"), org, '0 0 0', 1);
516         }
517
518         // Short explosion sound + small explosion
519         if(random() < 0.25)
520         {
521                 te_explosion(self.origin);
522                 sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
523         }
524
525         // Particles
526         org = self.origin + randompos(self.mins + '8 8 8', self.maxs + '-8 -8 -8');
527         pointparticles(particleeffectnum("onslaught_generator_smallexplosion"), org, '0 0 0', 1);
528
529         // rays
530         if(random() > 0.25 )
531         {
532                 onslaught_generator_ray_spawn(self.origin);
533         }
534
535         // Final explosion
536         if(self.count==1)
537         {
538                 org = self.origin;
539                 te_explosion(org);
540                 onslaught_generator_shockwave_spawn(org);
541                 pointparticles(particleeffectnum("onslaught_generator_finalexplosion"), org, '0 0 0', 1);
542                 sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
543         }
544         else
545                 self.nextthink = time + 0.05;
546
547         self.count = self.count - 1;
548 }
549
550 void onslaught_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
551 {
552         float i;
553         if (damage <= 0)
554                 return;
555         if(warmup_stage)
556                 return;
557         if (attacker != self)
558         {
559                 if (self.isshielded)
560                 {
561                         // this is protected by a shield, so ignore the damage
562                         if (time > self.pain_finished)
563                                 if (IS_PLAYER(attacker))
564                                 {
565                                         play2(attacker, "onslaught/damageblockedbyshield.wav");
566                                         self.pain_finished = time + 1;
567                                 }
568                         return;
569                 }
570                 if (time > self.pain_finished)
571                 {
572                         self.pain_finished = time + 10;
573                         entity head;
574                         FOR_EACH_REALPLAYER(head) if(SAME_TEAM(head, self)) { Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_GENERATOR_UNDERATTACK); }
575                         play2team(self.team, "onslaught/generator_underattack.wav");
576                 }
577         }
578         self.health = self.health - damage;
579         WaypointSprite_UpdateHealth(self.sprite, self.health);
580         // choose an animation frame based on health
581         self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1);
582         // see if the generator is still functional, or dying
583         if (self.health > 0)
584         {
585                 self.lasthealth = self.health;
586         }
587         else if (!warmup_stage)
588         {
589                 if (attacker == self)
590                         Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_OVERTIME_));
591                 else
592                 {
593                         Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_));
594                         PlayerScore_Add(attacker, SP_SCORE, 100);
595                 }
596                 self.iscaptured = FALSE;
597                 self.islinked = FALSE;
598                 self.isshielded = FALSE;
599                 self.takedamage = DAMAGE_NO; // can't be hurt anymore
600                 self.event_damage = func_null; // won't do anything if hurt
601                 self.count = 0; // reset counter
602                 self.think = onslaught_generator_deaththink; // explosion sequence
603                 self.nextthink = time; // start exploding immediately
604                 self.think(); // do the first explosion now
605
606                 WaypointSprite_UpdateMaxHealth(self.sprite, 0);
607
608                 onslaught_updatelinks();
609         }
610
611         // Throw some flaming gibs on damage, more damage = more chance for gib
612         if(random() < damage/220)
613         {
614                 sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
615                 i = random();
616                 if(i < 0.3)
617                         ons_throwgib(hitloc + '0 0 20', force * -1, "models/onslaught/gen_gib1.md3", 5, TRUE);
618                 else if(i > 0.7)
619                         ons_throwgib(hitloc + '0 0 20', force * -1, "models/onslaught/gen_gib2.md3", 5, TRUE);
620                 else
621                         ons_throwgib(hitloc + '0 0 20', force * -1, "models/onslaught/gen_gib3.md3", 5, TRUE);
622         }
623         else
624         {
625                 // particles on every hit
626                 pointparticles(particleeffectnum("sparks"), hitloc, force * -1, 1);
627
628                 //sound on every hit
629                 if (random() < 0.5)
630                         sound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE, ATTEN_NORM);
631                 else
632                         sound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM);
633         }
634
635         //throw some gibs on damage
636         if(random() < damage/200+0.2)
637                 if(random() < 0.5)
638                         ons_throwgib(hitloc + '0 0 20', randomvec()*360, "models/onslaught/gen_gib1.md3", 5, FALSE);
639
640         self.SendFlags |= GSF_STATUS;
641 }
642
643 // update links after a delay
644 void onslaught_generator_delayed()
645 {
646         onslaught_updatelinks();
647         // now begin normal thinking
648         generator_link(onslaught_generator_think);
649
650         self.SendFlags = GSF_SETUP;
651 }
652
653 string onslaught_generator_waypointsprite_for_team(entity e, float t)
654 {
655         if(t != e.team)
656         if(e.isshielded)
657                 return "ons-gen-shielded";
658         switch(e.team)
659         {
660                 case NUM_TEAM_1: return "ons-gen-red";
661                 case NUM_TEAM_2: return "ons-gen-blue";
662                 case NUM_TEAM_3: return "ons-gen-yellow";
663                 case NUM_TEAM_4: return "ons-gen-pink";
664         }
665         return "";
666 }
667
668 void onslaught_generator_updatesprite(entity e)
669 {
670         string s1, s2, s3;
671         s1 = onslaught_generator_waypointsprite_for_team(e, NUM_TEAM_1);
672         s2 = onslaught_generator_waypointsprite_for_team(e, NUM_TEAM_2);
673         s3 = onslaught_generator_waypointsprite_for_team(e, -1);
674         WaypointSprite_UpdateSprites(e.sprite, s1, s2, s3);
675
676         if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded)
677         {
678                 e.lastteam = e.team + 2;
679                 e.lastshielded = e.isshielded;
680                 if(e.lastshielded)
681                 {
682                         if(e.team == NUM_TEAM_1 || e.team == NUM_TEAM_2 || e.team == NUM_TEAM_3 || e.team == NUM_TEAM_4)
683                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, FALSE));
684                         else
685                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5');
686                 }
687                 else
688                 {
689                         if(e.team == NUM_TEAM_1 || e.team == NUM_TEAM_2 || e.team == NUM_TEAM_3 || e.team == NUM_TEAM_4)
690                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, FALSE));
691                         else
692                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75');
693                 }
694                 WaypointSprite_Ping(e.sprite);
695         }
696 }
697
698 string onslaught_controlpoint_waypointsprite_for_team(entity e, float t)
699 {
700         float a;
701         if(t != -1)
702         {
703                 a = onslaught_controlpoint_attackable(e, t);
704                 if(a == 3 || a == 4) // ATTACK/TOUCH THIS ONE NOW
705                 {
706                         switch(e.team)
707                         {
708                                 case NUM_TEAM_1: return "ons-cp-atck-red";
709                                 case NUM_TEAM_2: return "ons-cp-atck-blue";
710                                 case NUM_TEAM_3: return "ons-cp-atck-yellow";
711                                 case NUM_TEAM_4: return "ons-cp-atck-pink";
712                                 default: return "ons-cp-atck-neut";
713                         }
714                 }
715                 else if(a == -2) // DEFEND THIS ONE NOW
716                 {
717                         switch(e.team)
718                         {
719                                 case NUM_TEAM_1: return "ons-cp-dfnd-red";
720                                 case NUM_TEAM_2: return "ons-cp-dfnd-blue";
721                                 case NUM_TEAM_3: return "ons-cp-dfnd-yellow";
722                                 case NUM_TEAM_4: return "ons-cp-dfnd-pink";
723                         }
724                 }
725                 else if(e.team == t || a == -1 || a == 1) // own point, or fire at it
726                 {
727                         switch(e.team)
728                         {
729                                 case NUM_TEAM_1: return "ons-cp-red";
730                                 case NUM_TEAM_2: return "ons-cp-blue";
731                                 case NUM_TEAM_3: return "ons-cp-yellow";
732                                 case NUM_TEAM_4: return "ons-cp-pink";
733                         }
734                 }
735                 else if(a == 2) // touch it
736                         return "ons-cp-neut";
737         }
738         else
739         {
740                 switch(e.team)
741                 {
742                         case NUM_TEAM_1: return "ons-cp-red";
743                         case NUM_TEAM_2: return "ons-cp-blue";
744                         case NUM_TEAM_3: return "ons-cp-yellow";
745                         case NUM_TEAM_4: return "ons-cp-pink";
746                         default: return "ons-cp-neut";
747                 }
748         }
749         return "";
750 }
751
752 void onslaught_controlpoint_updatesprite(entity e)
753 {
754         string s1, s2, s3;
755         s1 = onslaught_controlpoint_waypointsprite_for_team(e, NUM_TEAM_1);
756         s2 = onslaught_controlpoint_waypointsprite_for_team(e, NUM_TEAM_2);
757         s3 = onslaught_controlpoint_waypointsprite_for_team(e, -1);
758         WaypointSprite_UpdateSprites(e.sprite, s1, s2, s3);
759
760         float sh;
761         sh = !(onslaught_controlpoint_can_be_linked(e, NUM_TEAM_1) || onslaught_controlpoint_can_be_linked(e, NUM_TEAM_2));
762
763         if(e.lastteam != e.team + 2 || e.lastshielded != sh || e.iscaptured != e.lastcaptured)
764         {
765                 if(e.iscaptured) // don't mess up build bars!
766                 {
767                         if(sh)
768                         {
769                                 WaypointSprite_UpdateMaxHealth(e.sprite, 0);
770                         }
771                         else
772                         {
773                                 WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health);
774                                 WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health);
775                         }
776                 }
777                 if(e.lastshielded)
778                 {
779                         if(e.team == NUM_TEAM_1 || e.team == NUM_TEAM_2 || e.team == NUM_TEAM_3 || e.team == NUM_TEAM_4)
780                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, FALSE));
781                         else
782                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5');
783                 }
784                 else
785                 {
786                         if(e.team == NUM_TEAM_1 || e.team == NUM_TEAM_2 || e.team == NUM_TEAM_3 || e.team == NUM_TEAM_4)
787                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, FALSE));
788                         else
789                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75');
790                 }
791                 WaypointSprite_Ping(e.sprite);
792
793                 e.lastteam = e.team + 2;
794                 e.lastshielded = sh;
795                 e.lastcaptured = e.iscaptured;
796         }
797 }
798
799 void onslaught_generator_reset()
800 {
801         self.team = self.team_saved;
802         self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health;
803         self.takedamage = DAMAGE_AIM;
804         self.bot_attack = TRUE;
805         self.iscaptured = TRUE;
806         self.islinked = TRUE;
807         self.isshielded = TRUE;
808         self.think = onslaught_generator_delayed;
809         self.nextthink = time + 0.2;
810         setmodel(self, "models/onslaught/generator.md3");
811         setsize(self, GENERATOR_MIN, GENERATOR_MAX);
812
813         self.SendFlags |= GSF_STATUS;
814
815         if(!self.noalign)
816         {
817                 setorigin(self, self.origin + '0 0 20');
818                 droptofloor();
819         }
820
821         WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
822         WaypointSprite_UpdateHealth(self.sprite, self.health);
823 }
824
825 /*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64)
826   Base generator.
827
828   spawnfunc_onslaught_link entities can target this.
829
830 keys:
831 "team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET.
832 "targetname" - name that spawnfunc_onslaught_link entities will use to target this.
833  */
834 void spawnfunc_onslaught_generator()
835 {
836         if(!g_onslaught) { remove(self); return; }
837
838         //entity e;
839         precache_model("models/onslaught/generator.md3");
840         precache_model("models/onslaught/generator_shield.md3");
841         precache_model("models/onslaught/shockwave.md3");
842         precache_model("models/onslaught/shockwavetransring.md3");
843         precache_model("models/onslaught/gen_gib1.md3");
844         precache_model("models/onslaught/gen_gib2.md3");
845         precache_model("models/onslaught/gen_gib3.md3");
846         precache_model("models/onslaught/ons_ray.md3");
847         precache_sound("onslaught/generator_decay.wav");
848         precache_sound("weapons/grenade_impact.wav");
849         precache_sound("weapons/rocket_impact.wav");
850         precache_sound("onslaught/generator_underattack.wav");
851         precache_sound("onslaught/shockwave.wav");
852         precache_sound("onslaught/ons_hit1.wav");
853         precache_sound("onslaught/ons_hit2.wav");
854         precache_sound("onslaught/electricity_explode.wav");
855         precache_sound("onslaught/generator_underattack.wav");
856         if (!self.team)
857                 objerror("team must be set");
858
859         ons_generator[self.team] = self;
860         self.team_saved = self.team;
861         self.colormap = 1024 + (self.team - 1) * 17;
862         self.solid = SOLID_BBOX;
863         self.movetype = MOVETYPE_NONE;
864         self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health;
865         setmodel(self, "models/onslaught/generator.md3");
866         setsize(self, GENERATOR_MIN, GENERATOR_MAX);
867         setorigin(self, self.origin);
868         self.takedamage = DAMAGE_AIM;
869         self.bot_attack = TRUE;
870         self.event_damage = onslaught_generator_damage;
871         self.iscaptured = TRUE;
872         self.islinked = TRUE;
873         self.isshielded = TRUE;
874         // helper entity that create fx when generator is damaged
875         onslaught_generator_damage_spawn(self);
876
877         ons_CaptureShield_Spawn(self, "models/onslaught/generator_shield.md3");
878         
879         InitializeEntity(self, onslaught_generator_delayed, INITPRIO_LAST);
880
881         WaypointSprite_SpawnFixed(string_null, self.origin + '0 0 128', self, sprite, RADARICON_NONE, '0 0 0');
882         WaypointSprite_UpdateRule(self.sprite, NUM_TEAM_2, SPRITERULE_TEAMPLAY);
883         WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
884         WaypointSprite_UpdateHealth(self.sprite, self.health);
885
886         waypoint_spawnforitem(self);
887
888         onslaught_updatelinks();
889
890         self.reset = onslaught_generator_reset;
891 }
892
893 .float waslinked;
894
895 void onslaught_controlpoint_icon_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
896 {
897         entity oself;
898
899         if (damage <= 0)
900                 return;
901         if (self.owner.isshielded)
902         {
903                 // this is protected by a shield, so ignore the damage
904                 if (time > self.pain_finished)
905                         if (IS_PLAYER(attacker))
906                         {
907                                 play2(attacker, "onslaught/damageblockedbyshield.wav");
908                                 self.pain_finished = time + 1;
909                         }
910                 return;
911         }
912
913         if(IS_PLAYER(attacker))
914         if(time - ons_notification_time[self.team] > 10)
915         {
916                 play2team(self.team, "onslaught/controlpoint_underattack.wav");
917                 ons_notification_time[self.team] = time;
918         }
919
920         self.health = self.health - damage;
921         if(self.owner.iscaptured)
922                 WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
923         else
924                 WaypointSprite_UpdateBuildFinished(self.owner.sprite, time + (self.max_health - self.health) / (self.count / CP_THINKRATE));
925         self.pain_finished = time + 1;
926         // particles on every hit
927         pointparticles(particleeffectnum("sparks"), hitloc, force*-1, 1);
928         //sound on every hit
929         if (random() < 0.5)
930                 sound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE+0.3, ATTEN_NORM);
931         else
932                 sound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE+0.3, ATTEN_NORM);
933
934         if (self.health < 0)
935         {
936                 sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
937                 pointparticles(particleeffectnum("rocket_explode"), self.origin, '0 0 0', 1);
938                 {
939                         Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_CPDESTROYED_), self.owner.message, attacker.netname);
940                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 25, "models/onslaught/controlpoint_icon_gib1.md3", 3, FALSE);
941                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 45, "models/onslaught/controlpoint_icon_gib2.md3", 3, FALSE);
942                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 45, "models/onslaught/controlpoint_icon_gib2.md3", 3, FALSE);
943                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, FALSE);
944                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, FALSE);
945                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, FALSE);
946                         ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, FALSE);
947                 }
948
949                 PlayerScore_Add(attacker, SP_ONS_TAKES, 1);
950                 PlayerScore_Add(attacker, SP_SCORE, 10);
951
952                 self.owner.goalentity = world;
953                 self.owner.islinked = FALSE;
954                 self.owner.iscaptured = FALSE;
955                 self.owner.team = 0;
956                 self.owner.colormap = 1024;
957
958                 WaypointSprite_UpdateMaxHealth(self.owner.sprite, 0);
959
960                 onslaught_updatelinks();
961
962                 // Use targets now (somebody make sure this is in the right place..)
963                 oself = self;
964                 self = self.owner;
965                 activator = self;
966                 SUB_UseTargets ();
967                 self = oself;
968
969                 self.owner.waslinked = self.owner.islinked;
970                 if(self.owner.model != "models/onslaught/controlpoint_pad.md3")
971                         setmodel_fixsize(self.owner, "models/onslaught/controlpoint_pad.md3");
972
973                 remove(self);
974         }
975
976         self.SendFlags |= CPSF_STATUS;
977 }
978
979 void onslaught_controlpoint_icon_think()
980 {
981         entity oself;
982         self.nextthink = time + CP_THINKRATE;
983
984         if(autocvar_g_onslaught_cp_proxydecap)
985         {
986         float _enemy_count = 0;
987         float _friendly_count = 0;
988         float _dist;
989         entity _player;
990
991         FOR_EACH_PLAYER(_player)
992         {
993             if(!_player.deadflag)
994             {
995                 _dist = vlen(_player.origin - self.origin);
996                 if(_dist < autocvar_g_onslaught_cp_proxydecap_distance)
997                 {
998                                         if(SAME_TEAM(_player, self))
999                         ++_friendly_count;
1000                     else
1001                         ++_enemy_count;
1002                 }
1003             }
1004         }
1005
1006         _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * CP_THINKRATE);
1007         _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * CP_THINKRATE);
1008
1009         self.health = bound(0, self.health + (_friendly_count - _enemy_count), self.max_health);
1010                 self.SendFlags |= CPSF_STATUS;
1011         if(self.health <= 0)
1012         {
1013             onslaught_controlpoint_icon_damage(self, self, 1, 0, self.origin, '0 0 0');
1014             return;
1015         }
1016     }
1017
1018         if (time > self.pain_finished + 5)
1019         {
1020                 if(self.health < self.max_health)
1021                 {
1022                         self.health = self.health + self.count;
1023                         if (self.health >= self.max_health)
1024                                 self.health = self.max_health;
1025                         WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
1026                 }
1027         }
1028
1029         if(self.owner.islinked != self.owner.waslinked)
1030         {
1031                 // unteam the spawnpoint if needed
1032                 float t;
1033                 t = self.owner.team;
1034                 if(!self.owner.islinked)
1035                         self.owner.team = 0;
1036
1037                 oself = self;
1038                 self = self.owner;
1039                 activator = self;
1040                 SUB_UseTargets ();
1041                 self = oself;
1042
1043                 self.owner.team = t;
1044
1045                 self.owner.waslinked = self.owner.islinked;
1046         }
1047
1048         // damaged fx
1049         if(random() < 0.6 - self.health / self.max_health)
1050         {
1051                 pointparticles(particleeffectnum("electricity_sparks"), self.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1);
1052
1053                 if(random() > 0.8)
1054                         sound(self, CH_PAIN, "onslaught/ons_spark1.wav", VOL_BASE, ATTEN_NORM);
1055                 else if (random() > 0.5)
1056                         sound(self, CH_PAIN, "onslaught/ons_spark2.wav", VOL_BASE, ATTEN_NORM);
1057         }
1058 }
1059
1060 void onslaught_controlpoint_icon_buildthink()
1061 {
1062         entity oself;
1063         float a;
1064
1065         self.nextthink = time + CP_THINKRATE;
1066
1067         // only do this if there is power
1068         a = onslaught_controlpoint_can_be_linked(self.owner, self.owner.team);
1069         if(!a)
1070                 return;
1071
1072         self.health = self.health + self.count;
1073
1074         self.SendFlags |= CPSF_STATUS;
1075
1076         if (self.health >= self.max_health)
1077         {
1078                 self.health = self.max_health;
1079                 self.count = autocvar_g_onslaught_cp_regen * CP_THINKRATE; // slow repair rate from now on
1080                 self.think = onslaught_controlpoint_icon_think;
1081                 sound(self, CH_TRIGGER, "onslaught/controlpoint_built.wav", VOL_BASE, ATTEN_NORM);
1082                 self.owner.iscaptured = TRUE;
1083                 self.solid = SOLID_BBOX;
1084
1085                 WaypointSprite_UpdateMaxHealth(self.owner.sprite, self.max_health);
1086                 WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
1087
1088                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ONSLAUGHT_CAPTURE, self.owner.ons_toucher.netname, self.owner.message);
1089                 Send_Notification(NOTIF_ALL_EXCEPT, self.owner.ons_toucher, MSG_CENTER, APP_TEAM_ENT_4(self.owner.ons_toucher, CENTER_ONS_CAPTURE_), self.owner.message);
1090                 Send_Notification(NOTIF_ONE, self.owner.ons_toucher, MSG_CENTER, CENTER_ONS_CAPTURE, self.owner.message);
1091                 PlayerTeamScore_Add(self.owner.ons_toucher, SP_ONS_CAPS, ST_ONS_CAPS, 1);
1092                 PlayerTeamScore_AddScore(self.owner.ons_toucher, 10);
1093                 
1094                 self.owner.ons_toucher = world;
1095
1096                 onslaught_updatelinks();
1097
1098                 // Use targets now (somebody make sure this is in the right place..)
1099                 oself = self;
1100                 self = self.owner;
1101                 activator = self;
1102                 SUB_UseTargets ();
1103                 self = oself;
1104
1105                 self.SendFlags |= CPSF_SETUP;
1106         }
1107         if(self.owner.model != "models/onslaught/controlpoint_pad2.md3")
1108                 setmodel_fixsize(self.owner, "models/onslaught/controlpoint_pad2.md3");
1109                 
1110         if(random() < 0.9 - self.health / self.max_health)
1111                 pointparticles(particleeffectnum("rage"), self.origin + 10 * randomvec(), '0 0 -1', 1);
1112 }
1113
1114 void onslaught_controlpoint_touch()
1115 {
1116         entity e, toucher = other;
1117         float a;
1118
1119         if((toucher.vehicle_flags & VHF_ISVEHICLE) && toucher.owner)
1120         if(autocvar_g_onslaught_allow_vehicle_touch)
1121                 toucher = toucher.owner;
1122         else
1123                 return;
1124
1125         if(!IS_PLAYER(toucher)) { return; }
1126
1127         a = onslaught_controlpoint_attackable(self, other.team);
1128         if(a != 2 && a != 4)
1129                 return;
1130         // we've verified that this player has a legitimate claim to this point,
1131         // so start building the captured point icon (which only captures this
1132         // point if it successfully builds without being destroyed first)
1133         self.goalentity = e = spawn();
1134         e.classname = "onslaught_controlpoint_icon";
1135         e.owner = self;
1136         e.max_health = autocvar_g_onslaught_cp_health;
1137         e.health = autocvar_g_onslaught_cp_buildhealth;
1138         e.solid = SOLID_NOT;
1139         e.movetype = MOVETYPE_NONE;
1140         //setmodel(e, "models/onslaught/controlpoint_icon.md3");
1141         setsize(e, CPICON_MIN, CPICON_MAX);
1142         setorigin(e, self.origin + '0 0 96');
1143         e.takedamage = DAMAGE_AIM;
1144         e.bot_attack = TRUE;
1145         e.event_damage = onslaught_controlpoint_icon_damage;
1146         e.team = other.team;
1147         e.colormap = 1024 + (e.team - 1) * 17;
1148         e.count = (e.max_health - e.health) * CP_THINKRATE / autocvar_g_onslaught_cp_buildtime; // how long it takes to build
1149         sound(e, CH_TRIGGER, "onslaught/controlpoint_build.wav", VOL_BASE, ATTEN_NORM);
1150         self.team = e.team;
1151         self.colormap = e.colormap;
1152         WaypointSprite_UpdateBuildFinished(self.sprite, time + (e.max_health - e.health) / (e.count / CP_THINKRATE));
1153         self.ons_toucher = other;
1154         onslaught_updatelinks();
1155
1156         onslaught_controlpoint_icon_link(e, onslaught_controlpoint_icon_buildthink);
1157 }
1158
1159 void onslaught_controlpoint_think()
1160 {
1161         self.nextthink = time + CP_THINKRATE;
1162         CSQCMODEL_AUTOUPDATE();
1163 }
1164
1165 void onslaught_controlpoint_reset()
1166 {
1167         if(self.goalentity && self.goalentity != world)
1168                 remove(self.goalentity);
1169         self.goalentity = world;
1170         self.team = 0;
1171         self.colormap = 1024;
1172         self.iscaptured = FALSE;
1173         self.islinked = FALSE;
1174         self.isshielded = TRUE;
1175         self.think = onslaught_controlpoint_think;
1176         self.ons_toucher = world;
1177         self.nextthink = time + CP_THINKRATE; // don't like func_null :P
1178         setmodel_fixsize(self, "models/onslaught/controlpoint_pad.md3");
1179         //setsize(self, '-32 -32 0', '32 32 8');
1180
1181         WaypointSprite_UpdateMaxHealth(self.sprite, 0);
1182
1183         onslaught_updatelinks();
1184
1185         activator = self;
1186         SUB_UseTargets(); // to reset the structures, playerspawns etc.
1187
1188         CSQCMODEL_AUTOUPDATE();
1189 }
1190
1191 /*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128)
1192   Control point. Be sure to give this enough clearance so that the shootable part has room to exist
1193
1194   This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity.
1195
1196 keys:
1197 "targetname" - name that spawnfunc_onslaught_link entities will use to target this.
1198 "target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities.
1199 "message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc)
1200  */
1201
1202 void spawnfunc_onslaught_controlpoint()
1203 {
1204         if(!g_onslaught) { remove(self); return; }
1205
1206         precache_model("models/onslaught/controlpoint_pad.md3");
1207         precache_model("models/onslaught/controlpoint_pad2.md3");
1208         precache_model("models/onslaught/controlpoint_shield.md3");
1209         precache_model("models/onslaught/controlpoint_icon.md3");
1210         precache_model("models/onslaught/controlpoint_icon_dmg1.md3");
1211         precache_model("models/onslaught/controlpoint_icon_dmg2.md3");
1212         precache_model("models/onslaught/controlpoint_icon_dmg3.md3");
1213         precache_model("models/onslaught/controlpoint_icon_gib1.md3");
1214         precache_model("models/onslaught/controlpoint_icon_gib2.md3");
1215         precache_model("models/onslaught/controlpoint_icon_gib4.md3");
1216         precache_sound("onslaught/controlpoint_build.wav");
1217         precache_sound("onslaught/controlpoint_built.wav");
1218         precache_sound("weapons/grenade_impact.wav");
1219         precache_sound("onslaught/damageblockedbyshield.wav");
1220         precache_sound("onslaught/controlpoint_underattack.wav");
1221         precache_sound("onslaught/ons_spark1.wav");
1222         precache_sound("onslaught/ons_spark2.wav");
1223
1224         self.solid = SOLID_BBOX;
1225         self.movetype = MOVETYPE_NONE;
1226         setmodel_fixsize(self, "models/onslaught/controlpoint_pad.md3");
1227         if(!self.noalign)
1228         {
1229                 setorigin(self, self.origin + '0 0 20');
1230                 droptofloor();
1231         }
1232         self.touch = onslaught_controlpoint_touch;
1233         self.team = 0;
1234         self.colormap = 1024;
1235         self.iscaptured = FALSE;
1236         self.islinked = FALSE;
1237         self.isshielded = TRUE;
1238
1239         if(self.message == "") { self.message = "a"; }
1240
1241         waypoint_spawnforitem(self);
1242
1243         self.think = onslaught_controlpoint_think;
1244         self.nextthink = time + CP_THINKRATE;
1245
1246         self.reset = onslaught_controlpoint_reset;
1247
1248         WaypointSprite_SpawnFixed(string_null, self.origin + '0 0 128', self, sprite, RADARICON_NONE, '0 0 0');
1249         WaypointSprite_UpdateRule(self.sprite, NUM_TEAM_2, SPRITERULE_TEAMPLAY);
1250
1251         onslaught_updatelinks();
1252         
1253         ons_CaptureShield_Spawn(self, "models/onslaught/controlpoint_shield.md3");
1254
1255         CSQCMODEL_AUTOINIT();
1256 }
1257
1258 float onslaught_link_send(entity to, float sendflags)
1259 {
1260         WriteByte(MSG_ENTITY, ENT_CLIENT_RADARLINK);
1261         WriteByte(MSG_ENTITY, sendflags);
1262         if(sendflags & 1)
1263         {
1264                 WriteCoord(MSG_ENTITY, self.goalentity.origin_x);
1265                 WriteCoord(MSG_ENTITY, self.goalentity.origin_y);
1266                 WriteCoord(MSG_ENTITY, self.goalentity.origin_z);
1267         }
1268         if(sendflags & 2)
1269         {
1270                 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1271                 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1272                 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1273         }
1274         if(sendflags & 4)
1275         {
1276                 WriteByte(MSG_ENTITY, self.clientcolors); // which is goalentity's color + enemy's color * 16
1277         }
1278         return TRUE;
1279 }
1280
1281 void onslaught_link_checkupdate()
1282 {
1283         // TODO check if the two sides have moved (currently they won't move anyway)
1284         float redpower, bluepower;
1285
1286         redpower = bluepower = 0;
1287         if(self.goalentity.islinked)
1288         {
1289                 switch(self.goalentity.team)
1290                 {
1291                         case NUM_TEAM_1: redpower = 1; break;
1292                         case NUM_TEAM_2: bluepower = 1; break;
1293                 }
1294         }
1295         if(self.enemy.islinked)
1296         {
1297                 switch(self.enemy.team)
1298                 {
1299                         case NUM_TEAM_1: redpower = 2; break;
1300                         case NUM_TEAM_2: bluepower = 2; break;
1301                 }
1302         }
1303
1304         float cc;
1305         if(redpower == 1 && bluepower == 2)
1306                 cc = (NUM_TEAM_1 - 1) * 0x01 + (NUM_TEAM_2 - 1) * 0x10;
1307         else if(redpower == 2 && bluepower == 1)
1308                 cc = (NUM_TEAM_1 - 1) * 0x10 + (NUM_TEAM_2 - 1) * 0x01;
1309         else if(redpower)
1310                 cc = (NUM_TEAM_1 - 1) * 0x11;
1311         else if(bluepower)
1312                 cc = (NUM_TEAM_2 - 1) * 0x11;
1313         else
1314                 cc = 0;
1315
1316         //print(etos(self), " rp=", ftos(redpower), " bp=", ftos(bluepower), " ");
1317         //print("cc=", ftos(cc), "\n");
1318
1319         if(cc != self.clientcolors)
1320         {
1321                 self.clientcolors = cc;
1322                 self.SendFlags |= 4;
1323         }
1324
1325         self.nextthink = time;
1326 }
1327
1328 void onslaught_link_delayed()
1329 {
1330         self.goalentity = find(world, targetname, self.target);
1331         self.enemy = find(world, targetname, self.target2);
1332         if (!self.goalentity)
1333                 objerror("can not find target\n");
1334         if (!self.enemy)
1335                 objerror("can not find target2\n");
1336         dprint(etos(self.goalentity), " linked with ", etos(self.enemy), "\n");
1337         self.SendFlags |= 3;
1338         self.think = onslaught_link_checkupdate;
1339         self.nextthink = time;
1340 }
1341
1342 /*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16)
1343   Link between control points.
1344
1345   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.
1346
1347 keys:
1348 "target" - first control point.
1349 "target2" - second control point.
1350  */
1351 void spawnfunc_onslaught_link()
1352 {
1353         if (!g_onslaught)
1354         {
1355                 remove(self);
1356                 return;
1357         }
1358         if (self.target == "" || self.target2 == "")
1359                 objerror("target and target2 must be set\n");
1360         InitializeEntity(self, onslaught_link_delayed, INITPRIO_FINDTARGET);
1361         Net_LinkEntity(self, FALSE, 0, onslaught_link_send);
1362 }
1363
1364 // bot junk
1365 #define HAVOCBOT_ONS_ROLE_NONE          0
1366 #define HAVOCBOT_ONS_ROLE_DEFENSE       2
1367 #define HAVOCBOT_ONS_ROLE_ASSISTANT     4
1368 #define HAVOCBOT_ONS_ROLE_OFFENSE       8
1369
1370 .float havocbot_role_flags;
1371 .float havocbot_attack_time;
1372
1373 .void() havocbot_role;
1374 .void() havocbot_previous_role;
1375
1376 void() havocbot_role_ons_defense;
1377 void() havocbot_role_ons_offense;
1378 void() havocbot_role_ons_assistant;
1379
1380 void(entity bot) havocbot_ons_reset_role;
1381 void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
1382 void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
1383
1384 .entity havocbot_ons_target;
1385
1386 void havocbot_goalrating_ons_offenseitems(float ratingscale, vector org, float sradius)
1387 {
1388         entity head;
1389         float t, i, c, needarmor = FALSE, needweapons = FALSE;
1390
1391         // Needs armor/health?
1392         if(self.health<100)
1393                 needarmor = TRUE;
1394
1395         // Needs weapons?
1396         c = 0;
1397         for(i = WEP_FIRST; i <= WEP_LAST ; ++i)
1398         {
1399                 // Find weapon
1400                 if(self.weapons & WepSet_FromWeapon(i))
1401                 if(++c>=4)
1402                         break;
1403         }
1404
1405         if(c<4)
1406                 needweapons = TRUE;
1407
1408         if(!needweapons && !needarmor)
1409                 return;
1410
1411 //      dprint(self.netname, " needs weapons ", ftos(needweapons) , "\n");
1412 //      dprint(self.netname, " needs armor ", ftos(needarmor) , "\n");
1413
1414         // See what is around
1415         head = findchainfloat(bot_pickup, TRUE);
1416         while (head)
1417         {
1418                 // gather health and armor only
1419                 if (head.solid)
1420                 if ( ((head.health || head.armorvalue) && needarmor) || (head.weapons && needweapons ) )
1421                 if (vlen(head.origin - org) < sradius)
1422                 {
1423                         t = head.bot_pickupevalfunc(self, head);
1424                         if (t > 0)
1425                                 navigation_routerating(head, t * ratingscale, 500);
1426                 }
1427                 head = head.chain;
1428         }
1429 }
1430
1431 void havocbot_role_ons_setrole(entity bot, float role)
1432 {
1433         dprint(strcat(bot.netname," switched to "));
1434         switch(role)
1435         {
1436                 case HAVOCBOT_ONS_ROLE_DEFENSE:
1437                         dprint("defense");
1438                         bot.havocbot_role = havocbot_role_ons_defense;
1439                         bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE;
1440                         bot.havocbot_role_timeout = 0;
1441                         break;
1442                 case HAVOCBOT_ONS_ROLE_ASSISTANT:
1443                         dprint("assistant");
1444                         bot.havocbot_role = havocbot_role_ons_assistant;
1445                         bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT;
1446                         bot.havocbot_role_timeout = 0;
1447                         break;
1448                 case HAVOCBOT_ONS_ROLE_OFFENSE:
1449                         dprint("offense");
1450                         bot.havocbot_role = havocbot_role_ons_offense;
1451                         bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE;
1452                         bot.havocbot_role_timeout = 0;
1453                         break;
1454         }
1455         dprint("\n");
1456 }
1457
1458 float havocbot_ons_teamcount(entity bot, float role)
1459 {
1460         float c = 0;
1461         entity head;
1462
1463         FOR_EACH_PLAYER(head)
1464         if(head.team==self.team)
1465         if(head.havocbot_role_flags & role)
1466                 ++c;
1467
1468         return c;
1469 }
1470
1471 void havocbot_goalrating_ons_controlpoints_attack(float ratingscale)
1472 {
1473         entity cp, cp1, cp2, best, pl, wp;
1474         float radius, found, bestvalue, c;
1475
1476         cp1 = cp2 = findchain(classname, "onslaught_controlpoint");
1477
1478         // Filter control points
1479         for (; cp2; cp2 = cp2.chain)
1480         {
1481                 cp2.wpcost = c = 0;
1482                 cp2.wpconsidered = FALSE;
1483
1484                 if(cp2.isshielded)
1485                         continue;
1486
1487                 // Ignore owned controlpoints
1488                 if(self.team == NUM_TEAM_1)
1489                 {
1490                         if( (cp2.isgenneighbor[NUM_TEAM_2] || cp2.iscpneighbor[NUM_TEAM_2]) && !(cp2.isgenneighbor[NUM_TEAM_1] || cp2.iscpneighbor[NUM_TEAM_1]) )
1491                                 continue;
1492                 }
1493                 else if(self.team == NUM_TEAM_2)
1494                 {
1495                         if( (cp2.isgenneighbor[NUM_TEAM_1] || cp2.iscpneighbor[NUM_TEAM_1]) && !(cp2.isgenneighbor[NUM_TEAM_2] || cp2.iscpneighbor[NUM_TEAM_2]) )
1496                                 continue;
1497                 }
1498
1499                 // Count team mates interested in this control point
1500                 // (easier and cleaner than keeping counters per cp and teams)
1501                 FOR_EACH_PLAYER(pl)
1502                 if(pl.team==self.team)
1503                 if(pl.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE)
1504                 if(pl.havocbot_ons_target==cp2)
1505                         ++c;
1506
1507                 // NOTE: probably decrease the cost of attackable control points
1508                 cp2.wpcost = c;
1509                 cp2.wpconsidered = TRUE;
1510         }
1511
1512         // We'll consider only the best case
1513         bestvalue = 99999999999;
1514         cp = world;
1515         for (; cp1; cp1 = cp1.chain)
1516         {
1517                 if (!cp1.wpconsidered)
1518                         continue;
1519
1520                 if(cp1.wpcost<bestvalue)
1521                 {
1522                         bestvalue = cp1.wpcost;
1523                         cp = cp1;
1524                         self.havocbot_ons_target = cp1;
1525                 }
1526         }
1527
1528         if (!cp)
1529                 return;
1530
1531 //      dprint(self.netname, " chose cp ranked ", ftos(bestvalue), "\n");
1532
1533         if(cp.goalentity)
1534         {
1535                 // Should be attacked
1536                 // Rate waypoints near it
1537                 found = FALSE;
1538                 best = world;
1539                 bestvalue = 99999999999;
1540                 for(radius=0; radius<1000 && !found; radius+=500)
1541                 {
1542                         for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
1543                         {
1544                                 if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
1545                                 if(wp.classname=="waypoint")
1546                                 if(checkpvs(wp.origin,cp))
1547                                 {
1548                                         found = TRUE;
1549                                         if(wp.cnt<bestvalue)
1550                                         {
1551                                                 best = wp;
1552                                                 bestvalue = wp.cnt;
1553                                         }
1554                                 }
1555                         }
1556                 }
1557
1558                 if(best)
1559                 {
1560                         navigation_routerating(best, ratingscale, 10000);
1561                         best.cnt += 1;
1562
1563                         self.havocbot_attack_time = 0;
1564                         if(checkpvs(self.view_ofs,cp))
1565                         if(checkpvs(self.view_ofs,best))
1566                                 self.havocbot_attack_time = time + 2;
1567                 }
1568                 else
1569                 {
1570                         navigation_routerating(cp, ratingscale, 10000);
1571                 }
1572         //      dprint(self.netname, " found an attackable controlpoint at ", vtos(cp.origin) ,"\n");
1573         }
1574         else
1575         {
1576                 // Should be touched
1577                 // dprint(self.netname, " found a touchable controlpoint at ", vtos(cp.origin) ,"\n");
1578                 found = FALSE;
1579
1580                 // Look for auto generated waypoint
1581                 if (!bot_waypoints_for_items)
1582                 for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
1583                 {
1584                         if(wp.classname=="waypoint")
1585                         {
1586                                 navigation_routerating(wp, ratingscale, 10000);
1587                                 found = TRUE;
1588                         }
1589                 }
1590
1591                 // Nothing found, rate the controlpoint itself
1592                 if (!found)
1593                         navigation_routerating(cp, ratingscale, 10000);
1594         }
1595 }
1596
1597 float havocbot_goalrating_ons_generator_attack(float ratingscale)
1598 {
1599         entity g, wp, bestwp;
1600         float found, best;
1601
1602         for (g = findchain(classname, "onslaught_generator"); g; g = g.chain)
1603         {
1604                 if(g.team == self.team || g.isshielded)
1605                         continue;
1606
1607                 // Should be attacked
1608                 // Rate waypoints near it
1609                 found = FALSE;
1610                 bestwp = world;
1611                 best = 99999999999;
1612
1613                 for(wp=findradius(g.origin,400); wp; wp=wp.chain)
1614                 {
1615                         if(wp.classname=="waypoint")
1616                         if(checkpvs(wp.origin,g))
1617                         {
1618                                 found = TRUE;
1619                                 if(wp.cnt<best)
1620                                 {
1621                                         bestwp = wp;
1622                                         best = wp.cnt;
1623                                 }
1624                         }
1625                 }
1626
1627                 if(bestwp)
1628                 {
1629                 //      dprint("waypoints found around generator\n");
1630                         navigation_routerating(bestwp, ratingscale, 10000);
1631                         bestwp.cnt += 1;
1632
1633                         self.havocbot_attack_time = 0;
1634                         if(checkpvs(self.view_ofs,g))
1635                         if(checkpvs(self.view_ofs,bestwp))
1636                                 self.havocbot_attack_time = time + 5;
1637
1638                         return TRUE;
1639                 }
1640                 else
1641                 {
1642                 //      dprint("generator found without waypoints around\n");
1643                         // if there aren't waypoints near the generator go straight to it
1644                         navigation_routerating(g, ratingscale, 10000);
1645                         self.havocbot_attack_time = 0;
1646                         return TRUE;
1647                 }
1648         }
1649         return FALSE;
1650 }
1651
1652 void havocbot_role_ons_offense()
1653 {
1654         if(self.deadflag != DEAD_NO)
1655         {
1656                 self.havocbot_attack_time = 0;
1657                 havocbot_ons_reset_role(self);
1658                 return;
1659         }
1660
1661         // Set the role timeout if necessary
1662         if (!self.havocbot_role_timeout)
1663                 self.havocbot_role_timeout = time + 120;
1664
1665         if (time > self.havocbot_role_timeout)
1666         {
1667                 havocbot_ons_reset_role(self);
1668                 return;
1669         }
1670
1671         if(self.havocbot_attack_time>time)
1672                 return;
1673
1674         if (self.bot_strategytime < time)
1675         {
1676                 navigation_goalrating_start();
1677                 havocbot_goalrating_enemyplayers(20000, self.origin, 650);
1678                 if(!havocbot_goalrating_ons_generator_attack(20000))
1679                         havocbot_goalrating_ons_controlpoints_attack(20000);
1680                 havocbot_goalrating_ons_offenseitems(10000, self.origin, 10000);
1681                 navigation_goalrating_end();
1682
1683                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1684         }
1685 }
1686
1687 void havocbot_role_ons_assistant()
1688 {
1689         havocbot_ons_reset_role(self);
1690 }
1691
1692 void havocbot_role_ons_defense()
1693 {
1694         havocbot_ons_reset_role(self);
1695 }
1696
1697 void havocbot_ons_reset_role(entity bot)
1698 {
1699         entity head;
1700         float c;
1701
1702         if(self.deadflag != DEAD_NO)
1703                 return;
1704
1705         bot.havocbot_ons_target = world;
1706
1707         // TODO: Defend control points or generator if necessary
1708
1709         // if there is only me on the team switch to offense
1710         c = 0;
1711         FOR_EACH_PLAYER(head)
1712         if(head.team==self.team)
1713                 ++c;
1714
1715         if(c==1)
1716         {
1717                 havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
1718                 return;
1719         }
1720
1721         havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
1722 }
1723
1724
1725 // ==============
1726 // Hook Functions
1727 // ==============
1728
1729 MUTATOR_HOOKFUNCTION(ons_ResetMap)
1730 {
1731         FOR_EACH_PLAYER(self) { self.ons_deathloc = '0 0 0'; }
1732         return FALSE;
1733 }
1734
1735 MUTATOR_HOOKFUNCTION(ons_RemovePlayer)
1736 {
1737         self.ons_deathloc = '0 0 0';
1738         return FALSE;
1739 }
1740
1741 MUTATOR_HOOKFUNCTION(ons_PlayerSpawn)
1742 {
1743         if(autocvar_g_onslaught_spawn_at_controlpoints)
1744         if(random() <= autocvar_g_onslaught_spawn_at_controlpoints_chance)
1745         {
1746                 float random_target = autocvar_g_onslaught_spawn_at_controlpoints_random;
1747                 entity tmp_entity, closest_target = world;
1748                 vector spawn_loc = self.ons_deathloc;
1749                 
1750                 // new joining player or round reset, don't bother checking
1751                 if(spawn_loc == '0 0 0') { return FALSE; }
1752                 
1753                 tmp_entity = findchain(classname, "onslaught_controlpoint");
1754                 
1755                 if(random_target) { RandomSelection_Init(); }
1756
1757                 while(tmp_entity)
1758                 {
1759                         if(SAME_TEAM(tmp_entity, self))
1760                         if(random_target)
1761                                 RandomSelection_Add(tmp_entity, 0, string_null, 1, 1);
1762                         else if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world)
1763                                 closest_target = tmp_entity;
1764                         
1765                         tmp_entity = tmp_entity.chain;
1766                 }
1767                 
1768                 if(random_target) { closest_target = RandomSelection_chosen_ent; }
1769                 
1770                 if(closest_target)
1771                 {
1772                         float i;
1773                         vector loc;
1774                         for(i = 0; i < 10; ++i)
1775                         {
1776                                 loc = closest_target.origin + '0 0 96';
1777                                 loc += ('0 1 0' * random()) * 128;
1778                                 tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self);
1779                                 if(trace_fraction == 1.0 && !trace_startsolid)
1780                                 {
1781                                         traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world
1782                                         if(trace_fraction == 1.0 && !trace_startsolid)
1783                                         {
1784                                                 setorigin(self, loc);
1785                                                 self.angles = normalize(loc - closest_target.origin) * RAD2DEG;
1786                                                 return FALSE;
1787                                         }
1788                                 }
1789                         }
1790                 }
1791         }
1792         
1793         if(autocvar_g_onslaught_spawn_at_generator)
1794         if(random() <= autocvar_g_onslaught_spawn_at_generator_chance)
1795         {
1796                 float random_target = autocvar_g_onslaught_spawn_at_generator_random;
1797                 entity tmp_entity, closest_target = world;
1798                 vector spawn_loc = self.ons_deathloc;
1799                 
1800                 // new joining player or round reset, don't bother checking
1801                 if(spawn_loc == '0 0 0') { return FALSE; }
1802                 
1803                 tmp_entity = findchain(classname, "onslaught_generator");
1804                 
1805                 if(random_target) { RandomSelection_Init(); }
1806
1807                 while(tmp_entity)
1808                 {
1809                         if(random_target)
1810                                 RandomSelection_Add(tmp_entity, 0, string_null, 1, 1);
1811                         else
1812                         {
1813                                 if(SAME_TEAM(tmp_entity, self))
1814                                 if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world)
1815                                         closest_target = tmp_entity;
1816                         }
1817                         
1818                         tmp_entity = tmp_entity.chain;
1819                 }
1820                 
1821                 if(random_target) { closest_target = RandomSelection_chosen_ent; }
1822                 
1823                 if(closest_target)
1824                 {
1825                         float i;
1826                         vector loc;
1827                         for(i = 0; i < 10; ++i)
1828                         {
1829                                 loc = closest_target.origin + '0 0 128';
1830                                 loc += ('0 1 0' * random()) * 256;
1831                                 tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self);
1832                                 if(trace_fraction == 1.0 && !trace_startsolid)
1833                                 {
1834                                         traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world
1835                                         if(trace_fraction == 1.0 && !trace_startsolid)
1836                                         {
1837                                                 setorigin(self, loc);
1838                                                 self.angles = normalize(loc - closest_target.origin) * RAD2DEG;
1839                                                 return FALSE;
1840                                         }
1841                                 }
1842                         }
1843                 }
1844         }
1845
1846     return FALSE;
1847 }
1848
1849 MUTATOR_HOOKFUNCTION(ons_PlayerDies)
1850 {
1851         frag_target.ons_deathloc = frag_target.origin;
1852         return FALSE;
1853 }
1854
1855 MUTATOR_HOOKFUNCTION(ons_MonsterThink)
1856 {
1857         entity e = find(world, targetname, self.target);
1858         if (e != world)
1859                 self.team = e.team;
1860
1861         return FALSE;
1862 }
1863
1864 MUTATOR_HOOKFUNCTION(ons_MonsterSpawn)
1865 {
1866         entity e, ee = world;
1867         
1868         if(self.targetname)
1869         {
1870                 e = find(world,target,self.targetname);
1871                 if(e != world)
1872                 {
1873                         self.team = e.team;
1874                         ee = e;
1875                 }
1876         }
1877         
1878         if(ee)
1879         {
1880         activator = ee;
1881         self.use();
1882     }
1883
1884         return FALSE;
1885 }
1886
1887 MUTATOR_HOOKFUNCTION(ons_TurretSpawn)
1888 {
1889         entity e, ee = world;
1890         if(self.targetname)
1891         {
1892                 e = find(world, target, self.targetname);
1893                 if(e != world)
1894                 {
1895                         self.team = e.team;
1896                         ee = e;
1897                 }
1898         }
1899
1900         if(ee)
1901         {
1902                 activator = ee;
1903                 if(self.use)
1904                         self.use();
1905         }
1906
1907         return FALSE;
1908 }
1909
1910 MUTATOR_HOOKFUNCTION(ons_BotRoles)
1911 {
1912         havocbot_ons_reset_role(self);
1913         return TRUE;
1914 }
1915
1916 MUTATOR_HOOKFUNCTION(ons_GetTeamCount)
1917 {
1918         // onslaught is special
1919         entity head = findchain(classname, "onslaught_generator");
1920         while (head)
1921         {
1922                 switch(head.team)
1923                 {
1924                         case NUM_TEAM_1: c1 = 0; break;
1925                         case NUM_TEAM_2: c2 = 0; break;
1926                         case NUM_TEAM_3: c3 = 0; break;
1927                         case NUM_TEAM_4: c4 = 0; break;
1928                 }
1929                 head = head.chain;
1930         }
1931
1932         return TRUE;
1933 }
1934
1935 // scoreboard setup
1936 void ons_ScoreRules()
1937 {
1938         CheckAllowedTeams(world);
1939         ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), SFL_SORT_PRIO_PRIMARY, 0, TRUE);
1940         ScoreInfo_SetLabel_TeamScore  (ST_ONS_CAPS,     "caps",      SFL_SORT_PRIO_PRIMARY);
1941         ScoreInfo_SetLabel_PlayerScore(SP_ONS_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
1942         ScoreInfo_SetLabel_PlayerScore(SP_ONS_TAKES,    "takes",     0);
1943         ScoreRules_basics_end();
1944 }
1945
1946 void ons_DelayedInit() // Do this check with a delay so we can wait for teams to be set up
1947 {
1948         ons_ScoreRules();
1949 }
1950
1951 void ons_Initialize()
1952 {
1953         ons_captureshield_force = autocvar_g_onslaught_shield_force;
1954         InitializeEntity(world, ons_DelayedInit, INITPRIO_GAMETYPE);
1955 }
1956
1957 MUTATOR_DEFINITION(gamemode_onslaught)
1958 {
1959         MUTATOR_HOOK(reset_map_global, ons_ResetMap, CBC_ORDER_ANY);
1960         MUTATOR_HOOK(MakePlayerObserver, ons_RemovePlayer, CBC_ORDER_ANY);
1961         MUTATOR_HOOK(ClientDisconnect, ons_RemovePlayer, CBC_ORDER_ANY);
1962         MUTATOR_HOOK(PlayerSpawn, ons_PlayerSpawn, CBC_ORDER_ANY);
1963         MUTATOR_HOOK(PlayerDies, ons_PlayerDies, CBC_ORDER_ANY);
1964         MUTATOR_HOOK(MonsterMove, ons_MonsterThink, CBC_ORDER_ANY);
1965         MUTATOR_HOOK(MonsterSpawn, ons_MonsterSpawn, CBC_ORDER_ANY);
1966         MUTATOR_HOOK(TurretSpawn, ons_TurretSpawn, CBC_ORDER_ANY);
1967         MUTATOR_HOOK(HavocBot_ChooseRule, ons_BotRoles, CBC_ORDER_ANY);
1968
1969         MUTATOR_ONADD
1970         {
1971                 if(time > 1) // game loads at time 1
1972                         error("This is a game type and it cannot be added at runtime.");
1973                 ons_Initialize();
1974         }
1975
1976         MUTATOR_ONROLLBACK_OR_REMOVE
1977         {
1978                 // we actually cannot roll back ons_Initialize here
1979                 // BUT: we don't need to! If this gets called, adding always
1980                 // succeeds.
1981         }
1982
1983         MUTATOR_ONREMOVE
1984         {
1985                 print("This is a game type and it cannot be removed at runtime.");
1986                 return -1;
1987         }
1988
1989         return FALSE;
1990 }