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