]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_onslaught.qc
mapinfo: remove some IS_GAMETYPE macros
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_onslaught.qc
1 #include "gamemode.qh"
2 #include "../controlpoint.qh"
3 #include "../generator.qh"
4
5 bool g_onslaught;
6
7 float autocvar_g_onslaught_debug;
8 float autocvar_g_onslaught_teleport_wait;
9 bool autocvar_g_onslaught_spawn_at_controlpoints;
10 bool autocvar_g_onslaught_spawn_at_generator;
11 float autocvar_g_onslaught_cp_proxydecap;
12 float autocvar_g_onslaught_cp_proxydecap_distance = 512;
13 float autocvar_g_onslaught_cp_proxydecap_dps = 100;
14 float autocvar_g_onslaught_spawn_at_controlpoints_chance = 0.5;
15 float autocvar_g_onslaught_spawn_at_controlpoints_random;
16 float autocvar_g_onslaught_spawn_at_generator_chance;
17 float autocvar_g_onslaught_spawn_at_generator_random;
18 float autocvar_g_onslaught_cp_buildhealth;
19 float autocvar_g_onslaught_cp_buildtime;
20 float autocvar_g_onslaught_cp_health;
21 float autocvar_g_onslaught_cp_regen;
22 float autocvar_g_onslaught_gen_health;
23 float autocvar_g_onslaught_shield_force = 100;
24 float autocvar_g_onslaught_allow_vehicle_touch;
25 float autocvar_g_onslaught_round_timelimit;
26 float autocvar_g_onslaught_point_limit;
27 float autocvar_g_onslaught_warmup;
28 float autocvar_g_onslaught_teleport_radius;
29 float autocvar_g_onslaught_spawn_choose;
30 float autocvar_g_onslaught_click_radius;
31
32 void FixSize(entity e);
33
34 // =======================
35 // CaptureShield Functions
36 // =======================
37
38 bool ons_CaptureShield_Customize()
39 {SELFPARAM();
40         entity e = WaypointSprite_getviewentity(other);
41
42         if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, e.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return false; }
43         if(SAME_TEAM(self, e)) { return false; }
44
45         return true;
46 }
47
48 void ons_CaptureShield_Touch()
49 {SELFPARAM();
50         if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, other.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return; }
51         if(!IS_PLAYER(other)) { return; }
52         if(SAME_TEAM(other, self)) { return; }
53
54         vector mymid = (self.absmin + self.absmax) * 0.5;
55         vector othermid = (other.absmin + other.absmax) * 0.5;
56
57         Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ons_captureshield_force);
58
59         if(IS_REAL_CLIENT(other))
60         {
61                 play2(other, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
62
63                 if(self.enemy.classname == "onslaught_generator")
64                         Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_GENERATOR_SHIELDED);
65                 else
66                         Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_CONTROLPOINT_SHIELDED);
67         }
68 }
69
70 void ons_CaptureShield_Reset()
71 {SELFPARAM();
72         self.colormap = self.enemy.colormap;
73         self.team = self.enemy.team;
74 }
75
76 void ons_CaptureShield_Spawn(entity generator, bool is_generator)
77 {
78         entity shield = spawn();
79
80         shield.enemy = generator;
81         shield.team = generator.team;
82         shield.colormap = generator.colormap;
83         shield.reset = ons_CaptureShield_Reset;
84         shield.touch = ons_CaptureShield_Touch;
85         shield.customizeentityforclient = ons_CaptureShield_Customize;
86         shield.classname = "ons_captureshield";
87         shield.effects = EF_ADDITIVE;
88         shield.movetype = MOVETYPE_NOCLIP;
89         shield.solid = SOLID_TRIGGER;
90         shield.avelocity = '7 0 11';
91         shield.scale = 1;
92         shield.model = ((is_generator) ? "models/onslaught/generator_shield.md3" : "models/onslaught/controlpoint_shield.md3");
93
94         precache_model(shield.model);
95         setorigin(shield, generator.origin);
96         _setmodel(shield, shield.model);
97         setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
98 }
99
100
101 // ==========
102 // Junk Pile
103 // ==========
104
105 void ons_debug(string input)
106 {
107         switch(autocvar_g_onslaught_debug)
108         {
109                 case 1: LOG_TRACE(input); break;
110                 case 2: LOG_INFO(input); break;
111         }
112 }
113
114 void setmodel_fixsize(entity e, Model m)
115 {
116         setmodel(e, m);
117         FixSize(e);
118 }
119
120 void onslaught_updatelinks()
121 {
122         entity l;
123         // first check if the game has ended
124         ons_debug("--- updatelinks ---\n");
125         // mark generators as being shielded and networked
126         for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
127         {
128                 if (l.iscaptured)
129                         ons_debug(strcat(etos(l), " (generator) belongs to team ", ftos(l.team), "\n"));
130                 else
131                         ons_debug(strcat(etos(l), " (generator) is destroyed\n"));
132                 l.islinked = l.iscaptured;
133                 l.isshielded = l.iscaptured;
134                 l.sprite.SendFlags |= 16;
135         }
136         // mark points as shielded and not networked
137         for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
138         {
139                 l.islinked = false;
140                 l.isshielded = true;
141                 int i;
142                 for(i = 0; i < 17; ++i) { l.isgenneighbor[i] = false; l.iscpneighbor[i] = false; }
143                 ons_debug(strcat(etos(l), " (point) belongs to team ", ftos(l.team), "\n"));
144                 l.sprite.SendFlags |= 16;
145         }
146         // flow power outward from the generators through the network
147         bool stop = false;
148         while (!stop)
149         {
150                 stop = true;
151                 for(l = ons_worldlinklist; l; l = l.ons_worldlinknext)
152                 {
153                         // if both points are captured by the same team, and only one of
154                         // them is powered, mark the other one as powered as well
155                         if (l.enemy.iscaptured && l.goalentity.iscaptured)
156                                 if (l.enemy.islinked != l.goalentity.islinked)
157                                         if(SAME_TEAM(l.enemy, l.goalentity))
158                                         {
159                                                 if (!l.goalentity.islinked)
160                                                 {
161                                                         stop = false;
162                                                         l.goalentity.islinked = true;
163                                                         ons_debug(strcat(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)\n"));
164                                                 }
165                                                 else if (!l.enemy.islinked)
166                                                 {
167                                                         stop = false;
168                                                         l.enemy.islinked = true;
169                                                         ons_debug(strcat(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)\n"));
170                                                 }
171                                         }
172                 }
173         }
174         // now that we know which points are powered we can mark their neighbors
175         // as unshielded if team differs
176         for(l = ons_worldlinklist; l; l = l.ons_worldlinknext)
177         {
178                 if (l.goalentity.islinked)
179                 {
180                         if(DIFF_TEAM(l.goalentity, l.enemy))
181                         {
182                                 ons_debug(strcat(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)\n"));
183                                 l.enemy.isshielded = false;
184                         }
185                         if(l.goalentity.classname == "onslaught_generator")
186                                 l.enemy.isgenneighbor[l.goalentity.team] = true;
187                         else
188                                 l.enemy.iscpneighbor[l.goalentity.team] = true;
189                 }
190                 if (l.enemy.islinked)
191                 {
192                         if(DIFF_TEAM(l.goalentity, l.enemy))
193                         {
194                                 ons_debug(strcat(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)\n"));
195                                 l.goalentity.isshielded = false;
196                         }
197                         if(l.enemy.classname == "onslaught_generator")
198                                 l.goalentity.isgenneighbor[l.enemy.team] = true;
199                         else
200                                 l.goalentity.iscpneighbor[l.enemy.team] = true;
201                 }
202         }
203         // now update the generators
204         for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
205         {
206                 if (l.isshielded)
207                 {
208                         ons_debug(strcat(etos(l), " (generator) is shielded\n"));
209                         l.takedamage = DAMAGE_NO;
210                         l.bot_attack = false;
211                 }
212                 else
213                 {
214                         ons_debug(strcat(etos(l), " (generator) is not shielded\n"));
215                         l.takedamage = DAMAGE_AIM;
216                         l.bot_attack = true;
217                 }
218
219                 ons_Generator_UpdateSprite(l);
220         }
221         // now update the takedamage and alpha variables on control point icons
222         for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
223         {
224                 if (l.isshielded)
225                 {
226                         ons_debug(strcat(etos(l), " (point) is shielded\n"));
227                         if (l.goalentity)
228                         {
229                                 l.goalentity.takedamage = DAMAGE_NO;
230                                 l.goalentity.bot_attack = false;
231                         }
232                 }
233                 else
234                 {
235                         ons_debug(strcat(etos(l), " (point) is not shielded\n"));
236                         if (l.goalentity)
237                         {
238                                 l.goalentity.takedamage = DAMAGE_AIM;
239                                 l.goalentity.bot_attack = true;
240                         }
241                 }
242                 ons_ControlPoint_UpdateSprite(l);
243         }
244         l = findchain(classname, "ons_captureshield");
245         while(l)
246         {
247                 l.team = l.enemy.team;
248                 l.colormap = l.enemy.colormap;
249                 l = l.chain;
250         }
251 }
252
253
254 // ===================
255 // Main Link Functions
256 // ===================
257
258 bool ons_Link_Send(entity this, entity to, int sendflags)
259 {
260         WriteByte(MSG_ENTITY, ENT_CLIENT_RADARLINK);
261         WriteByte(MSG_ENTITY, sendflags);
262         if(sendflags & 1)
263         {
264                 WriteCoord(MSG_ENTITY, self.goalentity.origin_x);
265                 WriteCoord(MSG_ENTITY, self.goalentity.origin_y);
266                 WriteCoord(MSG_ENTITY, self.goalentity.origin_z);
267         }
268         if(sendflags & 2)
269         {
270                 WriteCoord(MSG_ENTITY, self.enemy.origin_x);
271                 WriteCoord(MSG_ENTITY, self.enemy.origin_y);
272                 WriteCoord(MSG_ENTITY, self.enemy.origin_z);
273         }
274         if(sendflags & 4)
275         {
276                 WriteByte(MSG_ENTITY, self.clientcolors); // which is goalentity's color + enemy's color * 16
277         }
278         return true;
279 }
280
281 void ons_Link_CheckUpdate()
282 {SELFPARAM();
283         // TODO check if the two sides have moved (currently they won't move anyway)
284         float cc = 0, cc1 = 0, cc2 = 0;
285
286         if(self.goalentity.islinked || self.goalentity.iscaptured) { cc1 = (self.goalentity.team - 1) * 0x01; }
287         if(self.enemy.islinked || self.enemy.iscaptured) { cc2 = (self.enemy.team - 1) * 0x10; }
288
289         cc = cc1 + cc2;
290
291         if(cc != self.clientcolors)
292         {
293                 self.clientcolors = cc;
294                 self.SendFlags |= 4;
295         }
296
297         self.nextthink = time;
298 }
299
300 void ons_DelayedLinkSetup()
301 {SELFPARAM();
302         self.goalentity = find(world, targetname, self.target);
303         self.enemy = find(world, targetname, self.target2);
304         if(!self.goalentity) { objerror("can not find target\n"); }
305         if(!self.enemy) { objerror("can not find target2\n"); }
306
307         ons_debug(strcat(etos(self.goalentity), " linked with ", etos(self.enemy), "\n"));
308         self.SendFlags |= 3;
309         self.think = ons_Link_CheckUpdate;
310         self.nextthink = time;
311 }
312
313
314 // =============================
315 // Main Control Point Functions
316 // =============================
317
318 int ons_ControlPoint_CanBeLinked(entity cp, int teamnumber)
319 {
320         if(cp.isgenneighbor[teamnumber]) { return 2; }
321         if(cp.iscpneighbor[teamnumber]) { return 1; }
322
323         return 0;
324 }
325
326 int ons_ControlPoint_Attackable(entity cp, int teamnumber)
327         // -2: SAME TEAM, attackable by enemy!
328         // -1: SAME TEAM!
329         // 0: off limits
330         // 1: attack it
331         // 2: touch it
332         // 3: attack it (HIGH PRIO)
333         // 4: touch it (HIGH PRIO)
334 {
335         int a;
336
337         if(cp.isshielded)
338         {
339                 return 0;
340         }
341         else if(cp.goalentity)
342         {
343                 // if there's already an icon built, nothing happens
344                 if(cp.team == teamnumber)
345                 {
346                         a = ons_ControlPoint_CanBeLinked(cp, teamnumber);
347                         if(a) // attackable by enemy?
348                                 return -2; // EMERGENCY!
349                         return -1;
350                 }
351                 // we know it can be linked, so no need to check
352                 // but...
353                 a = ons_ControlPoint_CanBeLinked(cp, teamnumber);
354                 if(a == 2) // near our generator?
355                         return 3; // EMERGENCY!
356                 return 1;
357         }
358         else
359         {
360                 // free point
361                 if(ons_ControlPoint_CanBeLinked(cp, teamnumber))
362                 {
363                         a = ons_ControlPoint_CanBeLinked(cp, teamnumber); // why was this here NUM_TEAM_1 + NUM_TEAM_2 - t
364                         if(a == 2)
365                                 return 4; // GET THIS ONE NOW!
366                         else
367                                 return 2; // TOUCH ME
368                 }
369         }
370         return 0;
371 }
372
373 void ons_ControlPoint_Icon_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
374 {SELFPARAM();
375         if(damage <= 0) { return; }
376
377         if (self.owner.isshielded)
378         {
379                 // this is protected by a shield, so ignore the damage
380                 if (time > self.pain_finished)
381                         if (IS_PLAYER(attacker))
382                         {
383                                 play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
384                                 self.pain_finished = time + 1;
385                                 attacker.typehitsound += 1; // play both sounds (shield is way too quiet)
386                         }
387
388                 return;
389         }
390
391         if(IS_PLAYER(attacker))
392         if(time - ons_notification_time[self.team] > 10)
393         {
394                 play2team(self.team, SND(ONS_CONTROLPOINT_UNDERATTACK));
395                 ons_notification_time[self.team] = time;
396         }
397
398         self.health = self.health - damage;
399         if(self.owner.iscaptured)
400                 WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
401         else
402                 WaypointSprite_UpdateBuildFinished(self.owner.sprite, time + (self.max_health - self.health) / (self.count / ONS_CP_THINKRATE));
403         self.pain_finished = time + 1;
404         // particles on every hit
405         pointparticles(particleeffectnum(EFFECT_SPARKS), hitloc, force*-1, 1);
406         //sound on every hit
407         if (random() < 0.5)
408                 sound(self, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE+0.3, ATTEN_NORM);
409         else
410                 sound(self, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE+0.3, ATTEN_NORM);
411
412         if (self.health < 0)
413         {
414                 sound(self, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM);
415                 pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), self.origin, '0 0 0', 1);
416                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_CPDESTROYED_), self.owner.message, attacker.netname);
417
418                 PlayerScore_Add(attacker, SP_ONS_TAKES, 1);
419                 PlayerScore_Add(attacker, SP_SCORE, 10);
420
421                 self.owner.goalentity = world;
422                 self.owner.islinked = false;
423                 self.owner.iscaptured = false;
424                 self.owner.team = 0;
425                 self.owner.colormap = 1024;
426
427                 WaypointSprite_UpdateMaxHealth(self.owner.sprite, 0);
428
429                 onslaught_updatelinks();
430
431                 // Use targets now (somebody make sure this is in the right place..)
432                 setself(self.owner);
433                 activator = self;
434                 SUB_UseTargets ();
435                 setself(this);
436
437                 self.owner.waslinked = self.owner.islinked;
438                 if(self.owner.model != "models/onslaught/controlpoint_pad.md3")
439                         setmodel_fixsize(self.owner, MDL_ONS_CP_PAD1);
440                 //setsize(self, '-32 -32 0', '32 32 8');
441
442                 remove(self);
443         }
444
445         self.SendFlags |= CPSF_STATUS;
446 }
447
448 void ons_ControlPoint_Icon_Think()
449 {SELFPARAM();
450         self.nextthink = time + ONS_CP_THINKRATE;
451
452         if(autocvar_g_onslaught_cp_proxydecap)
453         {
454         int _enemy_count = 0;
455         int _friendly_count = 0;
456         float _dist;
457         entity _player;
458
459         FOR_EACH_PLAYER(_player)
460         {
461             if(!_player.deadflag)
462             {
463                 _dist = vlen(_player.origin - self.origin);
464                 if(_dist < autocvar_g_onslaught_cp_proxydecap_distance)
465                 {
466                                         if(SAME_TEAM(_player, self))
467                         ++_friendly_count;
468                     else
469                         ++_enemy_count;
470                 }
471             }
472         }
473
474         _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE);
475         _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE);
476
477         self.health = bound(0, self.health + (_friendly_count - _enemy_count), self.max_health);
478                 self.SendFlags |= CPSF_STATUS;
479         if(self.health <= 0)
480         {
481             ons_ControlPoint_Icon_Damage(self, self, 1, 0, self.origin, '0 0 0');
482             return;
483         }
484     }
485
486         if (time > self.pain_finished + 5)
487         {
488                 if(self.health < self.max_health)
489                 {
490                         self.health = self.health + self.count;
491                         if (self.health >= self.max_health)
492                                 self.health = self.max_health;
493                         WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
494                 }
495         }
496
497         if(self.owner.islinked != self.owner.waslinked)
498         {
499                 // unteam the spawnpoint if needed
500                 int t = self.owner.team;
501                 if(!self.owner.islinked)
502                         self.owner.team = 0;
503
504                 setself(self.owner);
505                 activator = self;
506                 SUB_UseTargets ();
507                 setself(this);
508
509                 self.owner.team = t;
510
511                 self.owner.waslinked = self.owner.islinked;
512         }
513
514         // damaged fx
515         if(random() < 0.6 - self.health / self.max_health)
516         {
517                 Send_Effect(EFFECT_ELECTRIC_SPARKS, self.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1);
518
519                 if(random() > 0.8)
520                         sound(self, CH_PAIN, SND_ONS_SPARK1, VOL_BASE, ATTEN_NORM);
521                 else if (random() > 0.5)
522                         sound(self, CH_PAIN, SND_ONS_SPARK2, VOL_BASE, ATTEN_NORM);
523         }
524 }
525
526 void ons_ControlPoint_Icon_BuildThink()
527 {SELFPARAM();
528         int a;
529
530         self.nextthink = time + ONS_CP_THINKRATE;
531
532         // only do this if there is power
533         a = ons_ControlPoint_CanBeLinked(self.owner, self.owner.team);
534         if(!a)
535                 return;
536
537         self.health = self.health + self.count;
538
539         self.SendFlags |= CPSF_STATUS;
540
541         if (self.health >= self.max_health)
542         {
543                 self.health = self.max_health;
544                 self.count = autocvar_g_onslaught_cp_regen * ONS_CP_THINKRATE; // slow repair rate from now on
545                 self.think = ons_ControlPoint_Icon_Think;
546                 sound(self, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILT, VOL_BASE, ATTEN_NORM);
547                 self.owner.iscaptured = true;
548                 self.solid = SOLID_BBOX;
549
550                 Send_Effect(EFFECT_CAP(self.owner.team), self.owner.origin, '0 0 0', 1);
551
552                 WaypointSprite_UpdateMaxHealth(self.owner.sprite, self.max_health);
553                 WaypointSprite_UpdateHealth(self.owner.sprite, self.health);
554
555                 if(IS_PLAYER(self.owner.ons_toucher))
556                 {
557                         Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ONSLAUGHT_CAPTURE, self.owner.ons_toucher.netname, self.owner.message);
558                         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);
559                         Send_Notification(NOTIF_ONE, self.owner.ons_toucher, MSG_CENTER, CENTER_ONS_CAPTURE, self.owner.message);
560                         PlayerScore_Add(self.owner.ons_toucher, SP_ONS_CAPS, 1);
561                         PlayerTeamScore_AddScore(self.owner.ons_toucher, 10);
562                 }
563
564                 self.owner.ons_toucher = world;
565
566                 onslaught_updatelinks();
567
568                 // Use targets now (somebody make sure this is in the right place..)
569                 setself(self.owner);
570                 activator = self;
571                 SUB_UseTargets ();
572                 setself(this);
573
574                 self.SendFlags |= CPSF_SETUP;
575         }
576         if(self.owner.model != MDL_ONS_CP_PAD2.model_str())
577                 setmodel_fixsize(self.owner, MDL_ONS_CP_PAD2);
578
579         if(random() < 0.9 - self.health / self.max_health)
580                 Send_Effect(EFFECT_RAGE, self.origin + 10 * randomvec(), '0 0 -1', 1);
581 }
582
583 void onslaught_controlpoint_icon_link(entity e, void() spawnproc);
584
585 void ons_ControlPoint_Icon_Spawn(entity cp, entity player)
586 {
587         entity e = spawn();
588
589         setsize(e, CPICON_MIN, CPICON_MAX);
590         setorigin(e, cp.origin + CPICON_OFFSET);
591
592         e.classname = "onslaught_controlpoint_icon";
593         e.owner = cp;
594         e.max_health = autocvar_g_onslaught_cp_health;
595         e.health = autocvar_g_onslaught_cp_buildhealth;
596         e.solid = SOLID_NOT;
597         e.takedamage = DAMAGE_AIM;
598         e.bot_attack = true;
599         e.event_damage = ons_ControlPoint_Icon_Damage;
600         e.team = player.team;
601         e.colormap = 1024 + (e.team - 1) * 17;
602         e.count = (e.max_health - e.health) * ONS_CP_THINKRATE / autocvar_g_onslaught_cp_buildtime; // how long it takes to build
603
604         sound(e, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILD, VOL_BASE, ATTEN_NORM);
605
606         cp.goalentity = e;
607         cp.team = e.team;
608         cp.colormap = e.colormap;
609
610         Send_Effect(EFFECT_FLAG_TOUCH(player.team), e.origin, '0 0 0', 1);
611
612         WaypointSprite_UpdateBuildFinished(cp.sprite, time + (e.max_health - e.health) / (e.count / ONS_CP_THINKRATE));
613         WaypointSprite_UpdateRule(cp.sprite,cp.team,SPRITERULE_TEAMPLAY);
614         cp.sprite.SendFlags |= 16;
615
616         onslaught_controlpoint_icon_link(e, ons_ControlPoint_Icon_BuildThink);
617 }
618
619 entity ons_ControlPoint_Waypoint(entity e)
620 {
621         if(e.team)
622         {
623                 int a = ons_ControlPoint_Attackable(e, e.team);
624
625                 if(a == -2) { return WP_OnsCPDefend; } // defend now
626                 if(a == -1 || a == 1 || a == 2) { return WP_OnsCP; } // touch
627                 if(a == 3 || a == 4) { return WP_OnsCPAttack; } // attack
628         }
629         else
630                 return WP_OnsCP;
631
632         return WP_Null;
633 }
634
635 void ons_ControlPoint_UpdateSprite(entity e)
636 {
637         entity s1 = ons_ControlPoint_Waypoint(e);
638         WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1);
639
640         bool sh;
641         sh = !(ons_ControlPoint_CanBeLinked(e, NUM_TEAM_1) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_2) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_3) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_4));
642
643         if(e.lastteam != e.team + 2 || e.lastshielded != sh || e.iscaptured != e.lastcaptured)
644         {
645                 if(e.iscaptured) // don't mess up build bars!
646                 {
647                         if(sh)
648                         {
649                                 WaypointSprite_UpdateMaxHealth(e.sprite, 0);
650                         }
651                         else
652                         {
653                                 WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health);
654                                 WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health);
655                         }
656                 }
657                 if(e.lastshielded)
658                 {
659                         if(e.team)
660                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, false));
661                         else
662                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5');
663                 }
664                 else
665                 {
666                         if(e.team)
667                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, false));
668                         else
669                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75');
670                 }
671                 WaypointSprite_Ping(e.sprite);
672
673                 e.lastteam = e.team + 2;
674                 e.lastshielded = sh;
675                 e.lastcaptured = e.iscaptured;
676         }
677 }
678
679 void ons_ControlPoint_Touch()
680 {SELFPARAM();
681         entity toucher = other;
682         int attackable;
683
684         if(IS_VEHICLE(toucher) && toucher.owner)
685         if(autocvar_g_onslaught_allow_vehicle_touch)
686                 toucher = toucher.owner;
687         else
688                 return;
689
690         if(!IS_PLAYER(toucher)) { return; }
691         if(toucher.frozen) { return; }
692         if(toucher.deadflag != DEAD_NO) { return; }
693
694         if ( SAME_TEAM(self,toucher) )
695         if ( self.iscaptured )
696         {
697                 if(time <= toucher.teleport_antispam)
698                         Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT_ANTISPAM, rint(toucher.teleport_antispam - time));
699                 else
700                         Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT);
701         }
702
703         attackable = ons_ControlPoint_Attackable(self, toucher.team);
704         if(attackable != 2 && attackable != 4)
705                 return;
706         // we've verified that this player has a legitimate claim to this point,
707         // so start building the captured point icon (which only captures this
708         // point if it successfully builds without being destroyed first)
709         ons_ControlPoint_Icon_Spawn(self, toucher);
710
711         self.ons_toucher = toucher;
712
713         onslaught_updatelinks();
714 }
715
716 void ons_ControlPoint_Think()
717 {SELFPARAM();
718         self.nextthink = time + ONS_CP_THINKRATE;
719         CSQCMODEL_AUTOUPDATE(self);
720 }
721
722 void ons_ControlPoint_Reset()
723 {SELFPARAM();
724         if(self.goalentity)
725                 remove(self.goalentity);
726
727         self.goalentity = world;
728         self.team = 0;
729         self.colormap = 1024;
730         self.iscaptured = false;
731         self.islinked = false;
732         self.isshielded = true;
733         self.think = ons_ControlPoint_Think;
734         self.ons_toucher = world;
735         self.nextthink = time + ONS_CP_THINKRATE;
736         setmodel_fixsize(self, MDL_ONS_CP_PAD1);
737
738         WaypointSprite_UpdateMaxHealth(self.sprite, 0);
739         WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY);
740
741         onslaught_updatelinks();
742
743         activator = self;
744         SUB_UseTargets(); // to reset the structures, playerspawns etc.
745
746         CSQCMODEL_AUTOUPDATE(self);
747 }
748
749 void ons_DelayedControlPoint_Setup(void)
750 {SELFPARAM();
751         onslaught_updatelinks();
752
753         // captureshield setup
754         ons_CaptureShield_Spawn(self, false);
755
756         CSQCMODEL_AUTOINIT(self);
757 }
758
759 void ons_ControlPoint_Setup(entity cp)
760 {SELFPARAM();
761         // declarations
762         setself(cp); // for later usage with droptofloor()
763
764         // main setup
765         cp.ons_worldcpnext = ons_worldcplist; // link control point into ons_worldcplist
766         ons_worldcplist = cp;
767
768         cp.netname = "Control point";
769         cp.team = 0;
770         cp.solid = SOLID_BBOX;
771         cp.movetype = MOVETYPE_NONE;
772         cp.touch = ons_ControlPoint_Touch;
773         cp.think = ons_ControlPoint_Think;
774         cp.nextthink = time + ONS_CP_THINKRATE;
775         cp.reset = ons_ControlPoint_Reset;
776         cp.colormap = 1024;
777         cp.iscaptured = false;
778         cp.islinked = false;
779         cp.isshielded = true;
780
781         if(cp.message == "") { cp.message = "a"; }
782
783         // appearence
784         setmodel_fixsize(cp, MDL_ONS_CP_PAD1);
785
786         // control point placement
787         if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location
788         {
789                 cp.noalign = true;
790                 cp.movetype = MOVETYPE_NONE;
791         }
792         else // drop to floor, automatically find a platform and set that as spawn origin
793         {
794                 setorigin(cp, cp.origin + '0 0 20');
795                 cp.noalign = false;
796                 setself(cp);
797                 droptofloor();
798                 cp.movetype = MOVETYPE_TOSS;
799         }
800
801         // waypointsprites
802         WaypointSprite_SpawnFixed(WP_Null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE);
803         WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY);
804
805         InitializeEntity(cp, ons_DelayedControlPoint_Setup, INITPRIO_SETLOCATION);
806 }
807
808
809 // =========================
810 // Main Generator Functions
811 // =========================
812
813 entity ons_Generator_Waypoint(entity e)
814 {
815         if (e.isshielded)
816                 return WP_OnsGenShielded;
817         return WP_OnsGen;
818 }
819
820 void ons_Generator_UpdateSprite(entity e)
821 {
822         entity s1 = ons_Generator_Waypoint(e);
823         WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1);
824
825         if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded)
826         {
827                 e.lastteam = e.team + 2;
828                 e.lastshielded = e.isshielded;
829                 if(e.lastshielded)
830                 {
831                         if(e.team)
832                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, false));
833                         else
834                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5');
835                 }
836                 else
837                 {
838                         if(e.team)
839                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, false));
840                         else
841                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75');
842                 }
843                 WaypointSprite_Ping(e.sprite);
844         }
845 }
846
847 void ons_GeneratorDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
848 {SELFPARAM();
849         if(damage <= 0) { return; }
850         if(warmup_stage || gameover) { return; }
851         if(!round_handler_IsRoundStarted()) { return; }
852
853         if (attacker != self)
854         {
855                 if (self.isshielded)
856                 {
857                         // this is protected by a shield, so ignore the damage
858                         if (time > self.pain_finished)
859                                 if (IS_PLAYER(attacker))
860                                 {
861                                         play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
862                                         attacker.typehitsound += 1;
863                                         self.pain_finished = time + 1;
864                                 }
865                         return;
866                 }
867                 if (time > self.pain_finished)
868                 {
869                         self.pain_finished = time + 10;
870                         entity head;
871                         FOR_EACH_REALPLAYER(head) if(SAME_TEAM(head, self)) { Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_GENERATOR_UNDERATTACK); }
872                         play2team(self.team, SND(ONS_GENERATOR_UNDERATTACK));
873                 }
874         }
875         self.health = self.health - damage;
876         WaypointSprite_UpdateHealth(self.sprite, self.health);
877         // choose an animation frame based on health
878         self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1);
879         // see if the generator is still functional, or dying
880         if (self.health > 0)
881         {
882                 self.lasthealth = self.health;
883         }
884         else
885         {
886                 if (attacker == self)
887                         Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_OVERTIME_));
888                 else
889                 {
890                         Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_));
891                         PlayerScore_Add(attacker, SP_SCORE, 100);
892                 }
893                 self.iscaptured = false;
894                 self.islinked = false;
895                 self.isshielded = false;
896                 self.takedamage = DAMAGE_NO; // can't be hurt anymore
897                 self.event_damage = func_null; // won't do anything if hurt
898                 self.count = 0; // reset counter
899                 self.think = func_null;
900                 self.nextthink = 0;
901                 //self.think(); // do the first explosion now
902
903                 WaypointSprite_UpdateMaxHealth(self.sprite, 0);
904                 WaypointSprite_Ping(self.sprite);
905                 //WaypointSprite_Kill(self.sprite); // can't do this yet, code too poor
906
907                 onslaught_updatelinks();
908         }
909
910         // Throw some flaming gibs on damage, more damage = more chance for gib
911         if(random() < damage/220)
912         {
913                 sound(self, CH_TRIGGER, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
914         }
915         else
916         {
917                 // particles on every hit
918                 Send_Effect(EFFECT_SPARKS, hitloc, force * -1, 1);
919
920                 //sound on every hit
921                 if (random() < 0.5)
922                         sound(self, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE, ATTEN_NORM);
923                 else
924                         sound(self, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE, ATTEN_NORM);
925         }
926
927         self.SendFlags |= GSF_STATUS;
928 }
929
930 void ons_GeneratorThink()
931 {SELFPARAM();
932         entity e;
933         self.nextthink = time + GEN_THINKRATE;
934         if (!gameover)
935         {
936         if(!self.isshielded && self.wait < time)
937         {
938             self.wait = time + 5;
939             FOR_EACH_REALPLAYER(e)
940             {
941                                 if(SAME_TEAM(e, self))
942                                 {
943                                         Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM);
944                     soundto(MSG_ONE, e, CHAN_AUTO, SND(KH_ALARM), VOL_BASE, ATTEN_NONE);    // FIXME: unique sound?
945                 }
946                                 else
947                                         Send_Notification(NOTIF_ONE, e, MSG_CENTER, APP_TEAM_NUM_4(self.team, CENTER_ONS_NOTSHIELDED_));
948             }
949         }
950         }
951 }
952
953 void ons_GeneratorReset()
954 {SELFPARAM();
955         self.team = self.team_saved;
956         self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health;
957         self.takedamage = DAMAGE_AIM;
958         self.bot_attack = true;
959         self.iscaptured = true;
960         self.islinked = true;
961         self.isshielded = true;
962         self.event_damage = ons_GeneratorDamage;
963         self.think = ons_GeneratorThink;
964         self.nextthink = time + GEN_THINKRATE;
965
966         Net_LinkEntity(self, false, 0, generator_send);
967
968         self.SendFlags = GSF_SETUP; // just incase
969         self.SendFlags |= GSF_STATUS;
970
971         WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
972         WaypointSprite_UpdateHealth(self.sprite, self.health);
973         WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY);
974
975         onslaught_updatelinks();
976 }
977
978 void ons_DelayedGeneratorSetup()
979 {SELFPARAM();
980         // bot waypoints
981         waypoint_spawnforitem_force(self, self.origin);
982         self.nearestwaypointtimeout = 0; // activate waypointing again
983         self.bot_basewaypoint = self.nearestwaypoint;
984
985         // captureshield setup
986         ons_CaptureShield_Spawn(self, true);
987
988         onslaught_updatelinks();
989
990         Net_LinkEntity(self, false, 0, generator_send);
991 }
992
993
994 void onslaught_generator_touch()
995 {SELFPARAM();
996         if ( IS_PLAYER(other) )
997         if ( SAME_TEAM(self,other) )
998         if ( self.iscaptured )
999         {
1000                 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_TELEPORT);
1001         }
1002 }
1003
1004 void ons_GeneratorSetup(entity gen) // called when spawning a generator entity on the map as a spawnfunc
1005 {SELFPARAM();
1006         // declarations
1007         int teamnumber = gen.team;
1008         setself(gen); // for later usage with droptofloor()
1009
1010         // main setup
1011         gen.ons_worldgeneratornext = ons_worldgeneratorlist; // link generator into ons_worldgeneratorlist
1012         ons_worldgeneratorlist = gen;
1013
1014         gen.netname = sprintf("%s generator", Team_ColoredFullName(teamnumber));
1015         gen.classname = "onslaught_generator";
1016         gen.solid = SOLID_BBOX;
1017         gen.team_saved = teamnumber;
1018         gen.movetype = MOVETYPE_NONE;
1019         gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health;
1020         gen.takedamage = DAMAGE_AIM;
1021         gen.bot_attack = true;
1022         gen.event_damage = ons_GeneratorDamage;
1023         gen.reset = ons_GeneratorReset;
1024         gen.think = ons_GeneratorThink;
1025         gen.nextthink = time + GEN_THINKRATE;
1026         gen.iscaptured = true;
1027         gen.islinked = true;
1028         gen.isshielded = true;
1029         gen.touch = onslaught_generator_touch;
1030
1031         // appearence
1032         // model handled by CSQC
1033         setsize(gen, GENERATOR_MIN, GENERATOR_MAX);
1034         setorigin(gen, (gen.origin + CPGEN_SPAWN_OFFSET));
1035         gen.colormap = 1024 + (teamnumber - 1) * 17;
1036
1037         // generator placement
1038         setself(gen);
1039         droptofloor();
1040
1041         // waypointsprites
1042         WaypointSprite_SpawnFixed(WP_Null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE);
1043         WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY);
1044         WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
1045         WaypointSprite_UpdateHealth(self.sprite, self.health);
1046
1047         InitializeEntity(gen, ons_DelayedGeneratorSetup, INITPRIO_SETLOCATION);
1048 }
1049
1050
1051 // ===============
1052 //  Round Handler
1053 // ===============
1054
1055 int total_generators;
1056 void Onslaught_count_generators()
1057 {
1058         entity e;
1059         total_generators = redowned = blueowned = yellowowned = pinkowned = 0;
1060         for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext)
1061         {
1062                 ++total_generators;
1063                 redowned += (e.team == NUM_TEAM_1 && e.health > 0);
1064                 blueowned += (e.team == NUM_TEAM_2 && e.health > 0);
1065                 yellowowned += (e.team == NUM_TEAM_3 && e.health > 0);
1066                 pinkowned += (e.team == NUM_TEAM_4 && e.health > 0);
1067         }
1068 }
1069
1070 int Onslaught_GetWinnerTeam()
1071 {
1072         int winner_team = 0;
1073         if(redowned > 0)
1074                 winner_team = NUM_TEAM_1;
1075         if(blueowned > 0)
1076         {
1077                 if(winner_team) return 0;
1078                 winner_team = NUM_TEAM_2;
1079         }
1080         if(yellowowned > 0)
1081         {
1082                 if(winner_team) return 0;
1083                 winner_team = NUM_TEAM_3;
1084         }
1085         if(pinkowned > 0)
1086         {
1087                 if(winner_team) return 0;
1088                 winner_team = NUM_TEAM_4;
1089         }
1090         if(winner_team)
1091                 return winner_team;
1092         return -1; // no generators left?
1093 }
1094
1095 #define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
1096 #define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1)
1097 bool Onslaught_CheckWinner()
1098 {
1099         entity e;
1100
1101         if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0))
1102         {
1103                 ons_stalemate = true;
1104
1105                 if (!wpforenemy_announced)
1106                 {
1107                         Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT);
1108                         sound(world, CH_INFO, SND_ONS_GENERATOR_DECAY, VOL_BASE, ATTEN_NONE);
1109
1110                         wpforenemy_announced = true;
1111                 }
1112
1113                 entity tmp_entity; // temporary entity
1114                 float d;
1115                 for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) if(time >= tmp_entity.ons_overtime_damagedelay)
1116                 {
1117                         // tmp_entity.max_health / 300 gives 5 minutes of overtime.
1118                         // control points reduce the overtime duration.
1119                         d = 1;
1120                         for(e = ons_worldcplist; e; e = e.ons_worldcpnext)
1121                         {
1122                                 if(DIFF_TEAM(e, tmp_entity))
1123                                 if(e.islinked)
1124                                         d = d + 1;
1125                         }
1126
1127                         if(autocvar_g_campaign && autocvar__campaign_testrun)
1128                                 d = d * tmp_entity.max_health;
1129                         else
1130                                 d = d * tmp_entity.max_health / max(30, 60 * autocvar_timelimit_suddendeath);
1131
1132                         Damage(tmp_entity, tmp_entity, tmp_entity, d, DEATH_HURTTRIGGER.m_id, tmp_entity.origin, '0 0 0');
1133
1134                         tmp_entity.sprite.SendFlags |= 16;
1135
1136                         tmp_entity.ons_overtime_damagedelay = time + 1;
1137                 }
1138         }
1139         else { wpforenemy_announced = false; ons_stalemate = false; }
1140
1141         Onslaught_count_generators();
1142
1143         if(ONS_OWNED_GENERATORS_OK())
1144                 return 0;
1145
1146         int winner_team = Onslaught_GetWinnerTeam();
1147
1148         if(winner_team > 0)
1149         {
1150                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
1151                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
1152                 TeamScore_AddToTeam(winner_team, ST_ONS_CAPS, +1);
1153         }
1154         else if(winner_team == -1)
1155         {
1156                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
1157                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
1158         }
1159
1160         ons_stalemate = false;
1161
1162         play2all(SND(CTF_CAPTURE(winner_team)));
1163
1164         round_handler_Init(7, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit);
1165
1166         FOR_EACH_PLAYER(e)
1167         {
1168                 e.ons_roundlost = true;
1169                 e.player_blocked = true;
1170
1171                 nades_Clear(e);
1172         }
1173
1174         return 1;
1175 }
1176
1177 bool Onslaught_CheckPlayers()
1178 {
1179         return 1;
1180 }
1181
1182 void Onslaught_RoundStart()
1183 {
1184         entity tmp_entity;
1185         FOR_EACH_PLAYER(tmp_entity) { tmp_entity.player_blocked = false; }
1186
1187         for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext)
1188                 tmp_entity.sprite.SendFlags |= 16;
1189
1190         for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
1191                 tmp_entity.sprite.SendFlags |= 16;
1192 }
1193
1194
1195 // ================
1196 // Bot player logic
1197 // ================
1198
1199 // NOTE: LEGACY CODE, needs to be re-written!
1200
1201 void havocbot_goalrating_ons_offenseitems(float ratingscale, vector org, float sradius)
1202 {SELFPARAM();
1203         entity head;
1204         float t, c;
1205         int i;
1206         bool needarmor = false, needweapons = false;
1207
1208         // Needs armor/health?
1209         if(self.health<100)
1210                 needarmor = true;
1211
1212         // Needs weapons?
1213         c = 0;
1214         for(i = WEP_FIRST; i <= WEP_LAST ; ++i)
1215         {
1216                 // Find weapon
1217                 if(self.weapons & WepSet_FromWeapon(i))
1218                 if(++c>=4)
1219                         break;
1220         }
1221
1222         if(c<4)
1223                 needweapons = true;
1224
1225         if(!needweapons && !needarmor)
1226                 return;
1227
1228         ons_debug(strcat(self.netname, " needs weapons ", ftos(needweapons) , "\n"));
1229         ons_debug(strcat(self.netname, " needs armor ", ftos(needarmor) , "\n"));
1230
1231         // See what is around
1232         head = findchainfloat(bot_pickup, true);
1233         while (head)
1234         {
1235                 // gather health and armor only
1236                 if (head.solid)
1237                 if ( ((head.health || head.armorvalue) && needarmor) || (head.weapons && needweapons ) )
1238                 if (vlen(head.origin - org) < sradius)
1239                 {
1240                         t = head.bot_pickupevalfunc(self, head);
1241                         if (t > 0)
1242                                 navigation_routerating(head, t * ratingscale, 500);
1243                 }
1244                 head = head.chain;
1245         }
1246 }
1247
1248 void havocbot_role_ons_setrole(entity bot, int role)
1249 {
1250         ons_debug(strcat(bot.netname," switched to "));
1251         switch(role)
1252         {
1253                 case HAVOCBOT_ONS_ROLE_DEFENSE:
1254                         ons_debug("defense");
1255                         bot.havocbot_role = havocbot_role_ons_defense;
1256                         bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE;
1257                         bot.havocbot_role_timeout = 0;
1258                         break;
1259                 case HAVOCBOT_ONS_ROLE_ASSISTANT:
1260                         ons_debug("assistant");
1261                         bot.havocbot_role = havocbot_role_ons_assistant;
1262                         bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT;
1263                         bot.havocbot_role_timeout = 0;
1264                         break;
1265                 case HAVOCBOT_ONS_ROLE_OFFENSE:
1266                         ons_debug("offense");
1267                         bot.havocbot_role = havocbot_role_ons_offense;
1268                         bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE;
1269                         bot.havocbot_role_timeout = 0;
1270                         break;
1271         }
1272         ons_debug("\n");
1273 }
1274
1275 int havocbot_ons_teamcount(entity bot, int role)
1276 {SELFPARAM();
1277         int c = 0;
1278         entity head;
1279
1280         FOR_EACH_PLAYER(head)
1281         if(SAME_TEAM(head, self))
1282         if(head.havocbot_role_flags & role)
1283                 ++c;
1284
1285         return c;
1286 }
1287
1288 void havocbot_goalrating_ons_controlpoints_attack(float ratingscale)
1289 {SELFPARAM();
1290         entity cp, cp1, cp2, best, pl, wp;
1291         float radius, bestvalue;
1292         int c;
1293         bool found;
1294
1295         // Filter control points
1296         for(cp2 = ons_worldcplist; cp2; cp2 = cp2.ons_worldcpnext)
1297         {
1298                 cp2.wpcost = c = 0;
1299                 cp2.wpconsidered = false;
1300
1301                 if(cp2.isshielded)
1302                         continue;
1303
1304                 // Ignore owned controlpoints
1305                 if(!(cp2.isgenneighbor[self.team] || cp2.iscpneighbor[self.team]))
1306                         continue;
1307
1308                 // Count team mates interested in this control point
1309                 // (easier and cleaner than keeping counters per cp and teams)
1310                 FOR_EACH_PLAYER(pl)
1311                 if(SAME_TEAM(pl, self))
1312                 if(pl.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE)
1313                 if(pl.havocbot_ons_target==cp2)
1314                         ++c;
1315
1316                 // NOTE: probably decrease the cost of attackable control points
1317                 cp2.wpcost = c;
1318                 cp2.wpconsidered = true;
1319         }
1320
1321         // We'll consider only the best case
1322         bestvalue = 99999999999;
1323         cp = world;
1324         for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext)
1325         {
1326                 if (!cp1.wpconsidered)
1327                         continue;
1328
1329                 if(cp1.wpcost<bestvalue)
1330                 {
1331                         bestvalue = cp1.wpcost;
1332                         cp = cp1;
1333                         self.havocbot_ons_target = cp1;
1334                 }
1335         }
1336
1337         if (!cp)
1338                 return;
1339
1340         ons_debug(strcat(self.netname, " chose cp ranked ", ftos(bestvalue), "\n"));
1341
1342         if(cp.goalentity)
1343         {
1344                 // Should be attacked
1345                 // Rate waypoints near it
1346                 found = false;
1347                 best = world;
1348                 bestvalue = 99999999999;
1349                 for(radius=0; radius<1000 && !found; radius+=500)
1350                 {
1351                         for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
1352                         {
1353                                 if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
1354                                 if(wp.classname=="waypoint")
1355                                 if(checkpvs(wp.origin,cp))
1356                                 {
1357                                         found = true;
1358                                         if(wp.cnt<bestvalue)
1359                                         {
1360                                                 best = wp;
1361                                                 bestvalue = wp.cnt;
1362                                         }
1363                                 }
1364                         }
1365                 }
1366
1367                 if(best)
1368                 {
1369                         navigation_routerating(best, ratingscale, 10000);
1370                         best.cnt += 1;
1371
1372                         self.havocbot_attack_time = 0;
1373                         if(checkpvs(self.view_ofs,cp))
1374                         if(checkpvs(self.view_ofs,best))
1375                                 self.havocbot_attack_time = time + 2;
1376                 }
1377                 else
1378                 {
1379                         navigation_routerating(cp, ratingscale, 10000);
1380                 }
1381                 ons_debug(strcat(self.netname, " found an attackable controlpoint at ", vtos(cp.origin) ,"\n"));
1382         }
1383         else
1384         {
1385                 // Should be touched
1386                 ons_debug(strcat(self.netname, " found a touchable controlpoint at ", vtos(cp.origin) ,"\n"));
1387                 found = false;
1388
1389                 // Look for auto generated waypoint
1390                 if (!bot_waypoints_for_items)
1391                 for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
1392                 {
1393                         if(wp.classname=="waypoint")
1394                         {
1395                                 navigation_routerating(wp, ratingscale, 10000);
1396                                 found = true;
1397                         }
1398                 }
1399
1400                 // Nothing found, rate the controlpoint itself
1401                 if (!found)
1402                         navigation_routerating(cp, ratingscale, 10000);
1403         }
1404 }
1405
1406 bool havocbot_goalrating_ons_generator_attack(float ratingscale)
1407 {SELFPARAM();
1408         entity g, wp, bestwp;
1409         bool found;
1410         int best;
1411
1412         for(g = ons_worldgeneratorlist; g; g = g.ons_worldgeneratornext)
1413         {
1414                 if(SAME_TEAM(g, self) || g.isshielded)
1415                         continue;
1416
1417                 // Should be attacked
1418                 // Rate waypoints near it
1419                 found = false;
1420                 bestwp = world;
1421                 best = 99999999999;
1422
1423                 for(wp=findradius(g.origin,400); wp; wp=wp.chain)
1424                 {
1425                         if(wp.classname=="waypoint")
1426                         if(checkpvs(wp.origin,g))
1427                         {
1428                                 found = true;
1429                                 if(wp.cnt<best)
1430                                 {
1431                                         bestwp = wp;
1432                                         best = wp.cnt;
1433                                 }
1434                         }
1435                 }
1436
1437                 if(bestwp)
1438                 {
1439                         ons_debug("waypoints found around generator\n");
1440                         navigation_routerating(bestwp, ratingscale, 10000);
1441                         bestwp.cnt += 1;
1442
1443                         self.havocbot_attack_time = 0;
1444                         if(checkpvs(self.view_ofs,g))
1445                         if(checkpvs(self.view_ofs,bestwp))
1446                                 self.havocbot_attack_time = time + 5;
1447
1448                         return true;
1449                 }
1450                 else
1451                 {
1452                         ons_debug("generator found without waypoints around\n");
1453                         // if there aren't waypoints near the generator go straight to it
1454                         navigation_routerating(g, ratingscale, 10000);
1455                         self.havocbot_attack_time = 0;
1456                         return true;
1457                 }
1458         }
1459         return false;
1460 }
1461
1462 void havocbot_role_ons_offense()
1463 {SELFPARAM();
1464         if(self.deadflag != DEAD_NO)
1465         {
1466                 self.havocbot_attack_time = 0;
1467                 havocbot_ons_reset_role(self);
1468                 return;
1469         }
1470
1471         // Set the role timeout if necessary
1472         if (!self.havocbot_role_timeout)
1473                 self.havocbot_role_timeout = time + 120;
1474
1475         if (time > self.havocbot_role_timeout)
1476         {
1477                 havocbot_ons_reset_role(self);
1478                 return;
1479         }
1480
1481         if(self.havocbot_attack_time>time)
1482                 return;
1483
1484         if (self.bot_strategytime < time)
1485         {
1486                 navigation_goalrating_start();
1487                 havocbot_goalrating_enemyplayers(20000, self.origin, 650);
1488                 if(!havocbot_goalrating_ons_generator_attack(20000))
1489                         havocbot_goalrating_ons_controlpoints_attack(20000);
1490                 havocbot_goalrating_ons_offenseitems(10000, self.origin, 10000);
1491                 navigation_goalrating_end();
1492
1493                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1494         }
1495 }
1496
1497 void havocbot_role_ons_assistant()
1498 {SELFPARAM();
1499         havocbot_ons_reset_role(self);
1500 }
1501
1502 void havocbot_role_ons_defense()
1503 {SELFPARAM();
1504         havocbot_ons_reset_role(self);
1505 }
1506
1507 void havocbot_ons_reset_role(entity bot)
1508 {SELFPARAM();
1509         entity head;
1510         int c = 0;
1511
1512         if(self.deadflag != DEAD_NO)
1513                 return;
1514
1515         bot.havocbot_ons_target = world;
1516
1517         // TODO: Defend control points or generator if necessary
1518
1519         // if there is only me on the team switch to offense
1520         c = 0;
1521         FOR_EACH_PLAYER(head)
1522         if(SAME_TEAM(head, self))
1523                 ++c;
1524
1525         if(c==1)
1526         {
1527                 havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
1528                 return;
1529         }
1530
1531         havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
1532 }
1533
1534
1535 /*
1536  * Find control point or generator owned by the same team self which is nearest to pos
1537  * if max_dist is positive, only control points within this range will be considered
1538  */
1539 entity ons_Nearest_ControlPoint(vector pos, float max_dist)
1540 {SELFPARAM();
1541         entity tmp_entity, closest_target = world;
1542         tmp_entity = findchain(classname, "onslaught_controlpoint");
1543         while(tmp_entity)
1544         {
1545                 if(SAME_TEAM(tmp_entity, self))
1546                 if(tmp_entity.iscaptured)
1547                 if(max_dist <= 0 || vlen(tmp_entity.origin - pos) <= max_dist)
1548                 if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world)
1549                         closest_target = tmp_entity;
1550                 tmp_entity = tmp_entity.chain;
1551         }
1552         tmp_entity = findchain(classname, "onslaught_generator");
1553         while(tmp_entity)
1554         {
1555                 if(SAME_TEAM(tmp_entity, self))
1556                 if(max_dist <= 0 || vlen(tmp_entity.origin - pos) < max_dist)
1557                 if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world)
1558                         closest_target = tmp_entity;
1559                 tmp_entity = tmp_entity.chain;
1560         }
1561
1562         return closest_target;
1563 }
1564
1565 /*
1566  * Find control point or generator owned by the same team self which is nearest to pos
1567  * if max_dist is positive, only control points within this range will be considered
1568  * This function only check distances on the XY plane, disregarding Z
1569  */
1570 entity ons_Nearest_ControlPoint_2D(vector pos, float max_dist)
1571 {SELFPARAM();
1572         entity tmp_entity, closest_target = world;
1573         vector delta;
1574         float smallest_distance = 0, distance;
1575
1576         tmp_entity = findchain(classname, "onslaught_controlpoint");
1577         while(tmp_entity)
1578         {
1579                 delta = tmp_entity.origin - pos;
1580                 delta_z = 0;
1581                 distance = vlen(delta);
1582
1583                 if(SAME_TEAM(tmp_entity, self))
1584                 if(tmp_entity.iscaptured)
1585                 if(max_dist <= 0 || distance <= max_dist)
1586                 if(closest_target == world || distance <= smallest_distance )
1587                 {
1588                         closest_target = tmp_entity;
1589                         smallest_distance = distance;
1590                 }
1591
1592                 tmp_entity = tmp_entity.chain;
1593         }
1594         tmp_entity = findchain(classname, "onslaught_generator");
1595         while(tmp_entity)
1596         {
1597                 delta = tmp_entity.origin - pos;
1598                 delta_z = 0;
1599                 distance = vlen(delta);
1600
1601                 if(SAME_TEAM(tmp_entity, self))
1602                 if(max_dist <= 0 || distance <= max_dist)
1603                 if(closest_target == world || distance <= smallest_distance )
1604                 {
1605                         closest_target = tmp_entity;
1606                         smallest_distance = distance;
1607                 }
1608
1609                 tmp_entity = tmp_entity.chain;
1610         }
1611
1612         return closest_target;
1613 }
1614 /**
1615  * find the number of control points and generators in the same team as self
1616  */
1617 int ons_Count_SelfControlPoints()
1618 {SELFPARAM();
1619         entity tmp_entity;
1620         tmp_entity = findchain(classname, "onslaught_controlpoint");
1621         int n = 0;
1622         while(tmp_entity)
1623         {
1624                 if(SAME_TEAM(tmp_entity, self))
1625                 if(tmp_entity.iscaptured)
1626                         n++;
1627                 tmp_entity = tmp_entity.chain;
1628         }
1629         tmp_entity = findchain(classname, "onslaught_generator");
1630         while(tmp_entity)
1631         {
1632                 if(SAME_TEAM(tmp_entity, self))
1633                         n++;
1634                 tmp_entity = tmp_entity.chain;
1635         }
1636         return n;
1637 }
1638
1639 /**
1640  * Teleport player to a random position near tele_target
1641  * if tele_effects is true, teleport sound+particles are created
1642  * return false on failure
1643  */
1644 bool ons_Teleport(entity player, entity tele_target, float range, bool tele_effects)
1645 {
1646         if ( !tele_target )
1647                 return false;
1648
1649         int i;
1650         vector loc;
1651         float theta;
1652         for(i = 0; i < 16; ++i)
1653         {
1654                 theta = random() * 2 * M_PI;
1655                 loc_y = sin(theta);
1656                 loc_x = cos(theta);
1657                 loc_z = 0;
1658                 loc *= random() * range;
1659
1660                 loc += tele_target.origin + '0 0 128';
1661
1662                 tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, player);
1663                 if(trace_fraction == 1.0 && !trace_startsolid)
1664                 {
1665                         traceline(tele_target.origin, loc, MOVE_NOMONSTERS, tele_target); // double check to make sure we're not spawning outside the world
1666                         if(trace_fraction == 1.0 && !trace_startsolid)
1667                         {
1668                                 if ( tele_effects )
1669                                 {
1670                                         Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1);
1671                                         sound (player, CH_TRIGGER, SND_TELEPORT, VOL_BASE, ATTEN_NORM);
1672                                 }
1673                                 setorigin(player, loc);
1674                                 player.angles = '0 1 0' * ( theta * RAD2DEG + 180 );
1675                                 makevectors(player.angles);
1676                                 player.fixangle = true;
1677                                 player.teleport_antispam = time + autocvar_g_onslaught_teleport_wait;
1678
1679                                 if ( tele_effects )
1680                                         Send_Effect(EFFECT_TELEPORT, player.origin + v_forward * 32, '0 0 0', 1);
1681                                 return true;
1682                         }
1683                 }
1684         }
1685
1686         return false;
1687 }
1688
1689 // ==============
1690 // Hook Functions
1691 // ==============
1692
1693 MUTATOR_HOOKFUNCTION(ons, reset_map_global)
1694 {SELFPARAM();
1695         entity e;
1696         FOR_EACH_PLAYER(e)
1697         {
1698                 e.ons_roundlost = false;
1699                 e.ons_deathloc = '0 0 0';
1700                 WITH(entity, self, e, PutClientInServer());
1701         }
1702         return false;
1703 }
1704
1705 MUTATOR_HOOKFUNCTION(ons, ClientDisconnect)
1706 {SELFPARAM();
1707         self.ons_deathloc = '0 0 0';
1708         return false;
1709 }
1710
1711 MUTATOR_HOOKFUNCTION(ons, MakePlayerObserver)
1712 {SELFPARAM();
1713         self.ons_deathloc = '0 0 0';
1714         return false;
1715 }
1716
1717 MUTATOR_HOOKFUNCTION(ons, PlayerSpawn)
1718 {SELFPARAM();
1719         if(!round_handler_IsRoundStarted())
1720         {
1721                 self.player_blocked = true;
1722                 return false;
1723         }
1724
1725         entity l;
1726         for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
1727         {
1728                 l.sprite.SendFlags |= 16;
1729         }
1730         for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
1731         {
1732                 l.sprite.SendFlags |= 16;
1733         }
1734
1735         if(ons_stalemate) { Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); }
1736
1737         if ( autocvar_g_onslaught_spawn_choose )
1738         if ( self.ons_spawn_by )
1739         if ( ons_Teleport(self,self.ons_spawn_by,autocvar_g_onslaught_teleport_radius,false) )
1740         {
1741                 self.ons_spawn_by = world;
1742                 return false;
1743         }
1744
1745         if(autocvar_g_onslaught_spawn_at_controlpoints)
1746         if(random() <= autocvar_g_onslaught_spawn_at_controlpoints_chance)
1747         {
1748                 float random_target = autocvar_g_onslaught_spawn_at_controlpoints_random;
1749                 entity tmp_entity, closest_target = world;
1750                 vector spawn_loc = self.ons_deathloc;
1751
1752                 // new joining player or round reset, don't bother checking
1753                 if(spawn_loc == '0 0 0') { return false; }
1754
1755                 if(random_target) { RandomSelection_Init(); }
1756
1757                 for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext)
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
1766                 if(random_target) { closest_target = RandomSelection_chosen_ent; }
1767
1768                 if(closest_target)
1769                 {
1770                         float i;
1771                         vector loc;
1772                         for(i = 0; i < 10; ++i)
1773                         {
1774                                 loc = closest_target.origin + '0 0 96';
1775                                 loc += ('0 1 0' * random()) * 128;
1776                                 tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self);
1777                                 if(trace_fraction == 1.0 && !trace_startsolid)
1778                                 {
1779                                         traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world
1780                                         if(trace_fraction == 1.0 && !trace_startsolid)
1781                                         {
1782                                                 setorigin(self, loc);
1783                                                 self.angles = normalize(loc - closest_target.origin) * RAD2DEG;
1784                                                 return false;
1785                                         }
1786                                 }
1787                         }
1788                 }
1789         }
1790
1791         if(autocvar_g_onslaught_spawn_at_generator)
1792         if(random() <= autocvar_g_onslaught_spawn_at_generator_chance)
1793         {
1794                 float random_target = autocvar_g_onslaught_spawn_at_generator_random;
1795                 entity tmp_entity, closest_target = world;
1796                 vector spawn_loc = self.ons_deathloc;
1797
1798                 // new joining player or round reset, don't bother checking
1799                 if(spawn_loc == '0 0 0') { return false; }
1800
1801                 if(random_target) { RandomSelection_Init(); }
1802
1803                 for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
1804                 {
1805                         if(random_target)
1806                                 RandomSelection_Add(tmp_entity, 0, string_null, 1, 1);
1807                         else
1808                         {
1809                                 if(SAME_TEAM(tmp_entity, self))
1810                                 if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world)
1811                                         closest_target = tmp_entity;
1812                         }
1813                 }
1814
1815                 if(random_target) { closest_target = RandomSelection_chosen_ent; }
1816
1817                 if(closest_target)
1818                 {
1819                         float i;
1820                         vector loc;
1821                         for(i = 0; i < 10; ++i)
1822                         {
1823                                 loc = closest_target.origin + '0 0 128';
1824                                 loc += ('0 1 0' * random()) * 256;
1825                                 tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self);
1826                                 if(trace_fraction == 1.0 && !trace_startsolid)
1827                                 {
1828                                         traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world
1829                                         if(trace_fraction == 1.0 && !trace_startsolid)
1830                                         {
1831                                                 setorigin(self, loc);
1832                                                 self.angles = normalize(loc - closest_target.origin) * RAD2DEG;
1833                                                 return false;
1834                                         }
1835                                 }
1836                         }
1837                 }
1838         }
1839
1840     return false;
1841 }
1842
1843 MUTATOR_HOOKFUNCTION(ons, PlayerDies)
1844 {SELFPARAM();
1845         frag_target.ons_deathloc = frag_target.origin;
1846         entity l;
1847         for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
1848         {
1849                 l.sprite.SendFlags |= 16;
1850         }
1851         for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
1852         {
1853                 l.sprite.SendFlags |= 16;
1854         }
1855
1856         if ( autocvar_g_onslaught_spawn_choose )
1857         if ( ons_Count_SelfControlPoints() > 1 )
1858                 stuffcmd(self, "qc_cmd_cl hud clickradar\n");
1859
1860         return false;
1861 }
1862
1863 MUTATOR_HOOKFUNCTION(ons, MonsterMove)
1864 {SELFPARAM();
1865         entity e = find(world, targetname, self.target);
1866         if (e != world)
1867                 self.team = e.team;
1868
1869         return false;
1870 }
1871
1872 void ons_MonsterSpawn_Delayed()
1873 {SELFPARAM();
1874         entity e, own = self.owner;
1875
1876         if(!own) { remove(self); return; }
1877
1878         if(own.targetname)
1879         {
1880                 e = find(world, target, own.targetname);
1881                 if(e != world)
1882                 {
1883                         own.team = e.team;
1884
1885                         activator = e;
1886                         own.use();
1887                 }
1888         }
1889
1890         remove(self);
1891 }
1892
1893 MUTATOR_HOOKFUNCTION(ons, MonsterSpawn)
1894 {SELFPARAM();
1895         entity e = spawn();
1896         e.owner = self;
1897         InitializeEntity(e, ons_MonsterSpawn_Delayed, INITPRIO_FINDTARGET);
1898
1899         return false;
1900 }
1901
1902 void ons_TurretSpawn_Delayed()
1903 {SELFPARAM();
1904         entity e, own = self.owner;
1905
1906         if(!own) { remove(self); return; }
1907
1908         if(own.targetname)
1909         {
1910                 e = find(world, target, own.targetname);
1911                 if(e != world)
1912                 {
1913                         own.team = e.team;
1914                         own.active = ACTIVE_NOT;
1915
1916                         activator = e;
1917                         own.use();
1918                 }
1919         }
1920
1921         remove(self);
1922 }
1923
1924 MUTATOR_HOOKFUNCTION(ons, TurretSpawn)
1925 {SELFPARAM();
1926         entity e = spawn();
1927         e.owner = self;
1928         InitializeEntity(e, ons_TurretSpawn_Delayed, INITPRIO_FINDTARGET);
1929
1930         return false;
1931 }
1932
1933 MUTATOR_HOOKFUNCTION(ons, HavocBot_ChooseRole)
1934 {SELFPARAM();
1935         havocbot_ons_reset_role(self);
1936         return true;
1937 }
1938
1939 MUTATOR_HOOKFUNCTION(ons, GetTeamCount)
1940 {
1941         // onslaught is special
1942         for(entity tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
1943         {
1944                 switch(tmp_entity.team)
1945                 {
1946                         case NUM_TEAM_1: c1 = 0; break;
1947                         case NUM_TEAM_2: c2 = 0; break;
1948                         case NUM_TEAM_3: c3 = 0; break;
1949                         case NUM_TEAM_4: c4 = 0; break;
1950                 }
1951         }
1952
1953         return true;
1954 }
1955
1956 MUTATOR_HOOKFUNCTION(ons, SpectateCopy)
1957 {SELFPARAM();
1958         self.ons_roundlost = other.ons_roundlost; // make spectators see it too
1959         return false;
1960 }
1961
1962 MUTATOR_HOOKFUNCTION(ons, SV_ParseClientCommand)
1963 {SELFPARAM();
1964         if(MUTATOR_RETURNVALUE) // command was already handled?
1965                 return false;
1966
1967         if ( cmd_name == "ons_spawn" )
1968         {
1969                 vector pos = self.origin;
1970                 if(cmd_argc > 1)
1971                         pos_x = stof(argv(1));
1972                 if(cmd_argc > 2)
1973                         pos_y = stof(argv(2));
1974                 if(cmd_argc > 3)
1975                         pos_z = stof(argv(3));
1976
1977                 if ( IS_PLAYER(self) )
1978                 {
1979                         if ( !self.frozen )
1980                         {
1981                                 entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius);
1982
1983                                 if ( !source_point && self.health > 0 )
1984                                 {
1985                                         sprint(self, "\nYou need to be next to a control point\n");
1986                                         return 1;
1987                                 }
1988
1989
1990                                 entity closest_target = ons_Nearest_ControlPoint_2D(pos, autocvar_g_onslaught_click_radius);
1991
1992                                 if ( closest_target == world )
1993                                 {
1994                                         sprint(self, "\nNo control point found\n");
1995                                         return 1;
1996                                 }
1997
1998                                 if ( self.health <= 0 )
1999                                 {
2000                                         self.ons_spawn_by = closest_target;
2001                                         self.respawn_flags = self.respawn_flags | RESPAWN_FORCE;
2002                                 }
2003                                 else
2004                                 {
2005                                         if ( source_point == closest_target )
2006                                         {
2007                                                 sprint(self, "\nTeleporting to the same point\n");
2008                                                 return 1;
2009                                         }
2010
2011                                         if ( !ons_Teleport(self,closest_target,autocvar_g_onslaught_teleport_radius,true) )
2012                                                 sprint(self, "\nUnable to teleport there\n");
2013                                 }
2014
2015                                 return 1;
2016                         }
2017
2018                         sprint(self, "\nNo teleportation for you\n");
2019                 }
2020
2021                 return 1;
2022         }
2023         return 0;
2024 }
2025
2026 MUTATOR_HOOKFUNCTION(ons, PlayerUseKey)
2027 {SELFPARAM();
2028         if(MUTATOR_RETURNVALUE || gameover) { return false; }
2029
2030         if((time > self.teleport_antispam) && (self.deadflag == DEAD_NO) && !self.vehicle)
2031         {
2032                 entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius);
2033                 if ( source_point )
2034                 {
2035                         stuffcmd(self, "qc_cmd_cl hud clickradar\n");
2036                         return true;
2037                 }
2038         }
2039
2040         return false;
2041 }
2042
2043 MUTATOR_HOOKFUNCTION(ons, PlayHitsound)
2044 {
2045         return (frag_victim.classname == "onslaught_generator" && !frag_victim.isshielded)
2046                 || (frag_victim.classname == "onslaught_controlpoint_icon" && !frag_victim.owner.isshielded);
2047 }
2048
2049 MUTATOR_HOOKFUNCTION(ons, SendWaypoint)
2050 {
2051         if(wp_sendflags & 16)
2052         {
2053                 if(self.owner.classname == "onslaught_controlpoint")
2054                 {
2055                         entity wp_owner = self.owner;
2056                         entity e = WaypointSprite_getviewentity(wp_sendto);
2057                         if(SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { wp_flag |= 2; }
2058                         if(!ons_ControlPoint_Attackable(wp_owner, e.team)) { wp_flag |= 2; }
2059                 }
2060                 if(self.owner.classname == "onslaught_generator")
2061                 {
2062                         entity wp_owner = self.owner;
2063                         if(wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { wp_flag |= 2; }
2064                         if(wp_owner.health <= 0) { wp_flag |= 2; }
2065                 }
2066         }
2067
2068         return false;
2069 }
2070
2071 MUTATOR_HOOKFUNCTION(ons, TurretValidateTarget)
2072 {
2073         if(substring(turret_target.classname, 0, 10) == "onslaught_") // don't attack onslaught targets, that's the player's job!
2074         {
2075                 ret_float = -3;
2076                 return true;
2077         }
2078
2079         return false;
2080 }
2081
2082 MUTATOR_HOOKFUNCTION(ons, TurretThink)
2083 {
2084         // ONS uses somewhat backwards linking.
2085         if(self.target)
2086         {
2087                 entity e = find(world, targetname, self.target);
2088                 if (e != world)
2089                         self.team = e.team;
2090         }
2091
2092         if(self.team != self.tur_head.team)
2093                 turret_respawn();
2094
2095         return false;
2096 }
2097
2098
2099 // ==========
2100 // Spawnfuncs
2101 // ==========
2102
2103 /*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16)
2104   Link between control points.
2105
2106   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.
2107
2108 keys:
2109 "target" - first control point.
2110 "target2" - second control point.
2111  */
2112 spawnfunc(onslaught_link)
2113 {
2114         if(!g_onslaught) { remove(self); return; }
2115
2116         if (self.target == "" || self.target2 == "")
2117                 objerror("target and target2 must be set\n");
2118
2119         self.ons_worldlinknext = ons_worldlinklist; // link into ons_worldlinklist
2120         ons_worldlinklist = self;
2121
2122         InitializeEntity(self, ons_DelayedLinkSetup, INITPRIO_FINDTARGET);
2123         Net_LinkEntity(self, false, 0, ons_Link_Send);
2124 }
2125
2126 /*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128)
2127   Control point. Be sure to give this enough clearance so that the shootable part has room to exist
2128
2129   This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity.
2130
2131 keys:
2132 "targetname" - name that spawnfunc_onslaught_link entities will use to target this.
2133 "target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities.
2134 "message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc)
2135  */
2136
2137 spawnfunc(onslaught_controlpoint)
2138 {
2139         if(!g_onslaught) { remove(self); return; }
2140
2141         ons_ControlPoint_Setup(self);
2142 }
2143
2144 /*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64)
2145   Base generator.
2146
2147   spawnfunc_onslaught_link entities can target this.
2148
2149 keys:
2150 "team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET.
2151 "targetname" - name that spawnfunc_onslaught_link entities will use to target this.
2152  */
2153 spawnfunc(onslaught_generator)
2154 {
2155         if(!g_onslaught) { remove(self); return; }
2156         if(!self.team) { objerror("team must be set"); }
2157
2158         ons_GeneratorSetup(self);
2159 }
2160
2161 // scoreboard setup
2162 void ons_ScoreRules()
2163 {
2164         CheckAllowedTeams(world);
2165         ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), SFL_SORT_PRIO_PRIMARY, 0, true);
2166         ScoreInfo_SetLabel_TeamScore  (ST_ONS_CAPS,     "destroyed", SFL_SORT_PRIO_PRIMARY);
2167         ScoreInfo_SetLabel_PlayerScore(SP_ONS_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
2168         ScoreInfo_SetLabel_PlayerScore(SP_ONS_TAKES,    "takes",     0);
2169         ScoreRules_basics_end();
2170 }
2171
2172 void ons_DelayedInit() // Do this check with a delay so we can wait for teams to be set up
2173 {
2174         ons_ScoreRules();
2175
2176         round_handler_Spawn(Onslaught_CheckPlayers, Onslaught_CheckWinner, Onslaught_RoundStart);
2177         round_handler_Init(5, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit);
2178 }
2179
2180 void ons_Initialize()
2181 {
2182         g_onslaught = true;
2183         ons_captureshield_force = autocvar_g_onslaught_shield_force;
2184
2185         addstat(STAT_ROUNDLOST, AS_INT, ons_roundlost);
2186
2187         InitializeEntity(world, ons_DelayedInit, INITPRIO_GAMETYPE);
2188 }
2189
2190 REGISTER_MUTATOR(ons, IS_GAMETYPE(ONSLAUGHT))
2191 {
2192         ActivateTeamplay();
2193         SetLimits(autocvar_g_onslaught_point_limit, -1, -1, -1);
2194         have_team_spawns = -1; // request team spawns
2195
2196         MUTATOR_ONADD
2197         {
2198                 if(time > 1) // game loads at time 1
2199                         error("This is a game type and it cannot be added at runtime.");
2200                 ons_Initialize();
2201         }
2202
2203         MUTATOR_ONROLLBACK_OR_REMOVE
2204         {
2205                 // we actually cannot roll back ons_Initialize here
2206                 // BUT: we don't need to! If this gets called, adding always
2207                 // succeeds.
2208         }
2209
2210         MUTATOR_ONREMOVE
2211         {
2212                 LOG_INFO("This is a game type and it cannot be removed at runtime.");
2213                 return -1;
2214         }
2215
2216         return false;
2217 }