]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_onslaught.qc
Merge branch 'TimePath/waypointsprites' into 'master'
[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, W_Sound("grenade_impact"), 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 entity 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 WP_OnsCPDefend; } // defend now
614                 if(a == -1 || a == 1 || a == 2) { return WP_OnsCP; } // touch
615                 if(a == 3 || a == 4) { return WP_OnsCPAttack; } // attack
616         }
617         else
618                 return WP_OnsCP;
619
620         return WP_Null;
621 }
622
623 void ons_ControlPoint_UpdateSprite(entity e)
624 {
625         entity s1 = ons_ControlPoint_Waypoint(e);
626         WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1);
627
628         bool sh;
629         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));
630
631         if(e.lastteam != e.team + 2 || e.lastshielded != sh || e.iscaptured != e.lastcaptured)
632         {
633                 if(e.iscaptured) // don't mess up build bars!
634                 {
635                         if(sh)
636                         {
637                                 WaypointSprite_UpdateMaxHealth(e.sprite, 0);
638                         }
639                         else
640                         {
641                                 WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health);
642                                 WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health);
643                         }
644                 }
645                 if(e.lastshielded)
646                 {
647                         if(e.team)
648                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, false));
649                         else
650                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5');
651                 }
652                 else
653                 {
654                         if(e.team)
655                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, false));
656                         else
657                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75');
658                 }
659                 WaypointSprite_Ping(e.sprite);
660
661                 e.lastteam = e.team + 2;
662                 e.lastshielded = sh;
663                 e.lastcaptured = e.iscaptured;
664         }
665 }
666
667 void ons_ControlPoint_Touch()
668 {
669         entity toucher = other;
670         int attackable;
671
672         if(IS_VEHICLE(toucher) && toucher.owner)
673         if(autocvar_g_onslaught_allow_vehicle_touch)
674                 toucher = toucher.owner;
675         else
676                 return;
677
678         if(!IS_PLAYER(toucher)) { return; }
679         if(toucher.frozen) { return; }
680         if(toucher.deadflag != DEAD_NO) { return; }
681
682         if ( SAME_TEAM(self,toucher) )
683         if ( self.iscaptured )
684         {
685                 if(time <= toucher.teleport_antispam)
686                         Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT_ANTISPAM, rint(toucher.teleport_antispam - time));
687                 else
688                         Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT);
689         }
690
691         attackable = ons_ControlPoint_Attackable(self, toucher.team);
692         if(attackable != 2 && attackable != 4)
693                 return;
694         // we've verified that this player has a legitimate claim to this point,
695         // so start building the captured point icon (which only captures this
696         // point if it successfully builds without being destroyed first)
697         ons_ControlPoint_Icon_Spawn(self, toucher);
698
699         self.ons_toucher = toucher;
700
701         onslaught_updatelinks();
702 }
703
704 void ons_ControlPoint_Think()
705 {
706         self.nextthink = time + ONS_CP_THINKRATE;
707         CSQCMODEL_AUTOUPDATE();
708 }
709
710 void ons_ControlPoint_Reset()
711 {
712         if(self.goalentity)
713                 remove(self.goalentity);
714
715         self.goalentity = world;
716         self.team = 0;
717         self.colormap = 1024;
718         self.iscaptured = false;
719         self.islinked = false;
720         self.isshielded = true;
721         self.think = ons_ControlPoint_Think;
722         self.ons_toucher = world;
723         self.nextthink = time + ONS_CP_THINKRATE;
724         setmodel_fixsize(self, "models/onslaught/controlpoint_pad.md3");
725
726         WaypointSprite_UpdateMaxHealth(self.sprite, 0);
727         WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY);
728
729         onslaught_updatelinks();
730
731         activator = self;
732         SUB_UseTargets(); // to reset the structures, playerspawns etc.
733
734         CSQCMODEL_AUTOUPDATE();
735 }
736
737 void ons_DelayedControlPoint_Setup(void)
738 {
739         onslaught_updatelinks();
740
741         // captureshield setup
742         ons_CaptureShield_Spawn(self, false);
743
744         CSQCMODEL_AUTOINIT();
745 }
746
747 void ons_ControlPoint_Setup(entity cp)
748 {
749         // declarations
750         self = cp; // for later usage with droptofloor()
751
752         // main setup
753         cp.ons_worldcpnext = ons_worldcplist; // link control point into ons_worldcplist
754         ons_worldcplist = cp;
755
756         cp.netname = "Control point";
757         cp.team = 0;
758         cp.solid = SOLID_BBOX;
759         cp.movetype = MOVETYPE_NONE;
760         cp.touch = ons_ControlPoint_Touch;
761         cp.think = ons_ControlPoint_Think;
762         cp.nextthink = time + ONS_CP_THINKRATE;
763         cp.reset = ons_ControlPoint_Reset;
764         cp.colormap = 1024;
765         cp.iscaptured = false;
766         cp.islinked = false;
767         cp.isshielded = true;
768
769         if(cp.message == "") { cp.message = "a"; }
770
771         // precache - TODO: clean up!
772         precache_model("models/onslaught/controlpoint_pad.md3");
773         precache_model("models/onslaught/controlpoint_pad2.md3");
774         precache_model("models/onslaught/controlpoint_shield.md3");
775         precache_model("models/onslaught/controlpoint_icon.md3");
776         precache_model("models/onslaught/controlpoint_icon_dmg1.md3");
777         precache_model("models/onslaught/controlpoint_icon_dmg2.md3");
778         precache_model("models/onslaught/controlpoint_icon_dmg3.md3");
779         precache_model("models/onslaught/controlpoint_icon_gib1.md3");
780         precache_model("models/onslaught/controlpoint_icon_gib2.md3");
781         precache_model("models/onslaught/controlpoint_icon_gib4.md3");
782         precache_sound("onslaught/controlpoint_build.wav");
783         precache_sound("onslaught/controlpoint_built.wav");
784         precache_sound(W_Sound("grenade_impact"));
785         precache_sound("onslaught/damageblockedbyshield.wav");
786         precache_sound("onslaught/controlpoint_underattack.wav");
787         precache_sound("onslaught/ons_spark1.wav");
788         precache_sound("onslaught/ons_spark2.wav");
789
790         // appearence
791         setmodel_fixsize(cp, "models/onslaught/controlpoint_pad.md3");
792
793         // control point placement
794         if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location
795         {
796                 cp.noalign = true;
797                 cp.movetype = MOVETYPE_NONE;
798         }
799         else // drop to floor, automatically find a platform and set that as spawn origin
800         {
801                 setorigin(cp, cp.origin + '0 0 20');
802                 cp.noalign = false;
803                 self = cp;
804                 droptofloor();
805                 cp.movetype = MOVETYPE_TOSS;
806         }
807
808         // waypointsprites
809         WaypointSprite_SpawnFixed(WP_Null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE);
810         WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY);
811
812         InitializeEntity(cp, ons_DelayedControlPoint_Setup, INITPRIO_SETLOCATION);
813 }
814
815
816 // =========================
817 // Main Generator Functions
818 // =========================
819
820 entity ons_Generator_Waypoint(entity e)
821 {
822         if (e.isshielded)
823                 return WP_OnsGenShielded;
824         return WP_OnsGen;
825 }
826
827 void ons_Generator_UpdateSprite(entity e)
828 {
829         entity s1 = ons_Generator_Waypoint(e);
830         WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1);
831
832         if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded)
833         {
834                 e.lastteam = e.team + 2;
835                 e.lastshielded = e.isshielded;
836                 if(e.lastshielded)
837                 {
838                         if(e.team)
839                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, false));
840                         else
841                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5');
842                 }
843                 else
844                 {
845                         if(e.team)
846                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, false));
847                         else
848                                 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75');
849                 }
850                 WaypointSprite_Ping(e.sprite);
851         }
852 }
853
854 void ons_GeneratorDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
855 {
856         if(damage <= 0) { return; }
857         if(warmup_stage || gameover) { return; }
858         if(!round_handler_IsRoundStarted()) { return; }
859
860         if (attacker != self)
861         {
862                 if (self.isshielded)
863                 {
864                         // this is protected by a shield, so ignore the damage
865                         if (time > self.pain_finished)
866                                 if (IS_PLAYER(attacker))
867                                 {
868                                         play2(attacker, "onslaught/damageblockedbyshield.wav");
869                                         attacker.typehitsound += 1;
870                                         self.pain_finished = time + 1;
871                                 }
872                         return;
873                 }
874                 if (time > self.pain_finished)
875                 {
876                         self.pain_finished = time + 10;
877                         entity head;
878                         FOR_EACH_REALPLAYER(head) if(SAME_TEAM(head, self)) { Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_GENERATOR_UNDERATTACK); }
879                         play2team(self.team, "onslaught/generator_underattack.wav");
880                 }
881         }
882         self.health = self.health - damage;
883         WaypointSprite_UpdateHealth(self.sprite, self.health);
884         // choose an animation frame based on health
885         self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1);
886         // see if the generator is still functional, or dying
887         if (self.health > 0)
888         {
889                 self.lasthealth = self.health;
890         }
891         else
892         {
893                 if (attacker == self)
894                         Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_OVERTIME_));
895                 else
896                 {
897                         Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_));
898                         PlayerScore_Add(attacker, SP_SCORE, 100);
899                 }
900                 self.iscaptured = false;
901                 self.islinked = false;
902                 self.isshielded = false;
903                 self.takedamage = DAMAGE_NO; // can't be hurt anymore
904                 self.event_damage = func_null; // won't do anything if hurt
905                 self.count = 0; // reset counter
906                 self.think = func_null;
907                 self.nextthink = 0;
908                 //self.think(); // do the first explosion now
909
910                 WaypointSprite_UpdateMaxHealth(self.sprite, 0);
911                 WaypointSprite_Ping(self.sprite);
912                 //WaypointSprite_Kill(self.sprite); // can't do this yet, code too poor
913
914                 onslaught_updatelinks();
915         }
916
917         // Throw some flaming gibs on damage, more damage = more chance for gib
918         if(random() < damage/220)
919         {
920                 sound(self, CH_TRIGGER, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM);
921         }
922         else
923         {
924                 // particles on every hit
925                 Send_Effect("sparks", hitloc, force * -1, 1);
926
927                 //sound on every hit
928                 if (random() < 0.5)
929                         sound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE, ATTEN_NORM);
930                 else
931                         sound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM);
932         }
933
934         self.SendFlags |= GSF_STATUS;
935 }
936
937 void ons_GeneratorThink()
938 {
939         entity e;
940         self.nextthink = time + GEN_THINKRATE;
941         if (!gameover)
942         {
943         if(!self.isshielded && self.wait < time)
944         {
945             self.wait = time + 5;
946             FOR_EACH_REALPLAYER(e)
947             {
948                                 if(SAME_TEAM(e, self))
949                                 {
950                                         Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM);
951                     soundto(MSG_ONE, e, CHAN_AUTO, "kh/alarm.wav", VOL_BASE, ATTEN_NONE);    // FIXME: unique sound?
952                 }
953                                 else
954                                         Send_Notification(NOTIF_ONE, e, MSG_CENTER, APP_TEAM_NUM_4(self.team, CENTER_ONS_NOTSHIELDED_));
955             }
956         }
957         }
958 }
959
960 void ons_GeneratorReset()
961 {
962         self.team = self.team_saved;
963         self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health;
964         self.takedamage = DAMAGE_AIM;
965         self.bot_attack = true;
966         self.iscaptured = true;
967         self.islinked = true;
968         self.isshielded = true;
969         self.event_damage = ons_GeneratorDamage;
970         self.think = ons_GeneratorThink;
971         self.nextthink = time + GEN_THINKRATE;
972
973         Net_LinkEntity(self, false, 0, generator_send);
974
975         self.SendFlags = GSF_SETUP; // just incase
976         self.SendFlags |= GSF_STATUS;
977
978         WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
979         WaypointSprite_UpdateHealth(self.sprite, self.health);
980         WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY);
981
982         onslaught_updatelinks();
983 }
984
985 void ons_DelayedGeneratorSetup()
986 {
987         // bot waypoints
988         waypoint_spawnforitem_force(self, self.origin);
989         self.nearestwaypointtimeout = 0; // activate waypointing again
990         self.bot_basewaypoint = self.nearestwaypoint;
991
992         // captureshield setup
993         ons_CaptureShield_Spawn(self, true);
994
995         onslaught_updatelinks();
996
997         Net_LinkEntity(self, false, 0, generator_send);
998 }
999
1000
1001 void onslaught_generator_touch()
1002 {
1003         if ( IS_PLAYER(other) )
1004         if ( SAME_TEAM(self,other) )
1005         if ( self.iscaptured )
1006         {
1007                 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_TELEPORT);
1008         }
1009 }
1010
1011 void ons_GeneratorSetup(entity gen) // called when spawning a generator entity on the map as a spawnfunc
1012 {
1013         // declarations
1014         int teamnumber = gen.team;
1015         self = gen; // for later usage with droptofloor()
1016
1017         // main setup
1018         gen.ons_worldgeneratornext = ons_worldgeneratorlist; // link generator into ons_worldgeneratorlist
1019         ons_worldgeneratorlist = gen;
1020
1021         gen.netname = sprintf("%s generator", Team_ColoredFullName(teamnumber));
1022         gen.classname = "onslaught_generator";
1023         gen.solid = SOLID_BBOX;
1024         gen.team_saved = teamnumber;
1025         gen.movetype = MOVETYPE_NONE;
1026         gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health;
1027         gen.takedamage = DAMAGE_AIM;
1028         gen.bot_attack = true;
1029         gen.event_damage = ons_GeneratorDamage;
1030         gen.reset = ons_GeneratorReset;
1031         gen.think = ons_GeneratorThink;
1032         gen.nextthink = time + GEN_THINKRATE;
1033         gen.iscaptured = true;
1034         gen.islinked = true;
1035         gen.isshielded = true;
1036         gen.touch = onslaught_generator_touch;
1037
1038         // precache - TODO: clean up!
1039         precache_model("models/onslaught/generator_shield.md3");
1040         precache_model("models/onslaught/gen_gib1.md3");
1041         precache_model("models/onslaught/gen_gib2.md3");
1042         precache_model("models/onslaught/gen_gib3.md3");
1043         precache_sound("onslaught/generator_decay.wav");
1044         precache_sound(W_Sound("grenade_impact"));
1045         precache_sound(W_Sound("rocket_impact"));
1046         precache_sound("onslaught/generator_underattack.wav");
1047         precache_sound("onslaught/shockwave.wav");
1048         precache_sound("onslaught/ons_hit1.wav");
1049         precache_sound("onslaught/ons_hit2.wav");
1050         precache_sound("onslaught/generator_underattack.wav");
1051
1052         // appearence
1053         // model handled by CSQC
1054         setsize(gen, GENERATOR_MIN, GENERATOR_MAX);
1055         setorigin(gen, (gen.origin + CPGEN_SPAWN_OFFSET));
1056         gen.colormap = 1024 + (teamnumber - 1) * 17;
1057
1058         // generator placement
1059         self = gen;
1060         droptofloor();
1061
1062         // waypointsprites
1063         WaypointSprite_SpawnFixed(WP_Null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE);
1064         WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY);
1065         WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
1066         WaypointSprite_UpdateHealth(self.sprite, self.health);
1067
1068         InitializeEntity(gen, ons_DelayedGeneratorSetup, INITPRIO_SETLOCATION);
1069 }
1070
1071
1072 // ===============
1073 //  Round Handler
1074 // ===============
1075
1076 int total_generators;
1077 void Onslaught_count_generators()
1078 {
1079         entity e;
1080         total_generators = redowned = blueowned = yellowowned = pinkowned = 0;
1081         for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext)
1082         {
1083                 ++total_generators;
1084                 redowned += (e.team == NUM_TEAM_1 && e.health > 0);
1085                 blueowned += (e.team == NUM_TEAM_2 && e.health > 0);
1086                 yellowowned += (e.team == NUM_TEAM_3 && e.health > 0);
1087                 pinkowned += (e.team == NUM_TEAM_4 && e.health > 0);
1088         }
1089 }
1090
1091 int Onslaught_GetWinnerTeam()
1092 {
1093         int winner_team = 0;
1094         if(redowned > 0)
1095                 winner_team = NUM_TEAM_1;
1096         if(blueowned > 0)
1097         {
1098                 if(winner_team) return 0;
1099                 winner_team = NUM_TEAM_2;
1100         }
1101         if(yellowowned > 0)
1102         {
1103                 if(winner_team) return 0;
1104                 winner_team = NUM_TEAM_3;
1105         }
1106         if(pinkowned > 0)
1107         {
1108                 if(winner_team) return 0;
1109                 winner_team = NUM_TEAM_4;
1110         }
1111         if(winner_team)
1112                 return winner_team;
1113         return -1; // no generators left?
1114 }
1115
1116 #define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
1117 #define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1)
1118 bool Onslaught_CheckWinner()
1119 {
1120         entity e;
1121
1122         if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0))
1123         {
1124                 ons_stalemate = true;
1125
1126                 if (!wpforenemy_announced)
1127                 {
1128                         Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT);
1129                         sound(world, CH_INFO, "onslaught/generator_decay.wav", VOL_BASE, ATTEN_NONE);
1130
1131                         wpforenemy_announced = true;
1132                 }
1133
1134                 entity tmp_entity; // temporary entity
1135                 float d;
1136                 for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) if(time >= tmp_entity.ons_overtime_damagedelay)
1137                 {
1138                         // tmp_entity.max_health / 300 gives 5 minutes of overtime.
1139                         // control points reduce the overtime duration.
1140                         d = 1;
1141                         for(e = ons_worldcplist; e; e = e.ons_worldcpnext)
1142                         {
1143                                 if(DIFF_TEAM(e, tmp_entity))
1144                                 if(e.islinked)
1145                                         d = d + 1;
1146                         }
1147
1148                         if(autocvar_g_campaign && autocvar__campaign_testrun)
1149                                 d = d * tmp_entity.max_health;
1150                         else
1151                                 d = d * tmp_entity.max_health / max(30, 60 * autocvar_timelimit_suddendeath);
1152
1153                         Damage(tmp_entity, tmp_entity, tmp_entity, d, DEATH_HURTTRIGGER, tmp_entity.origin, '0 0 0');
1154
1155                         tmp_entity.sprite.SendFlags |= 16;
1156
1157                         tmp_entity.ons_overtime_damagedelay = time + 1;
1158                 }
1159         }
1160         else { wpforenemy_announced = false; ons_stalemate = false; }
1161
1162         Onslaught_count_generators();
1163
1164         if(ONS_OWNED_GENERATORS_OK())
1165                 return 0;
1166
1167         int winner_team = Onslaught_GetWinnerTeam();
1168
1169         if(winner_team > 0)
1170         {
1171                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
1172                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
1173                 TeamScore_AddToTeam(winner_team, ST_ONS_CAPS, +1);
1174         }
1175         else if(winner_team == -1)
1176         {
1177                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
1178                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
1179         }
1180
1181         ons_stalemate = false;
1182
1183         play2all(sprintf("ctf/%s_capture.wav", Static_Team_ColorName_Lower(winner_team)));
1184
1185         round_handler_Init(7, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit);
1186
1187         FOR_EACH_PLAYER(e)
1188         {
1189                 e.ons_roundlost = true;
1190                 e.player_blocked = true;
1191
1192                 nades_Clear(e);
1193         }
1194
1195         return 1;
1196 }
1197
1198 bool Onslaught_CheckPlayers()
1199 {
1200         return 1;
1201 }
1202
1203 void Onslaught_RoundStart()
1204 {
1205         entity tmp_entity;
1206         FOR_EACH_PLAYER(tmp_entity) { tmp_entity.player_blocked = false; }
1207
1208         for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext)
1209                 tmp_entity.sprite.SendFlags |= 16;
1210
1211         for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
1212                 tmp_entity.sprite.SendFlags |= 16;
1213 }
1214
1215
1216 // ================
1217 // Bot player logic
1218 // ================
1219
1220 // NOTE: LEGACY CODE, needs to be re-written!
1221
1222 void havocbot_goalrating_ons_offenseitems(float ratingscale, vector org, float sradius)
1223 {
1224         entity head;
1225         float t, c;
1226         int i;
1227         bool needarmor = false, needweapons = false;
1228
1229         // Needs armor/health?
1230         if(self.health<100)
1231                 needarmor = true;
1232
1233         // Needs weapons?
1234         c = 0;
1235         for(i = WEP_FIRST; i <= WEP_LAST ; ++i)
1236         {
1237                 // Find weapon
1238                 if(self.weapons & WepSet_FromWeapon(i))
1239                 if(++c>=4)
1240                         break;
1241         }
1242
1243         if(c<4)
1244                 needweapons = true;
1245
1246         if(!needweapons && !needarmor)
1247                 return;
1248
1249         ons_debug(strcat(self.netname, " needs weapons ", ftos(needweapons) , "\n"));
1250         ons_debug(strcat(self.netname, " needs armor ", ftos(needarmor) , "\n"));
1251
1252         // See what is around
1253         head = findchainfloat(bot_pickup, true);
1254         while (head)
1255         {
1256                 // gather health and armor only
1257                 if (head.solid)
1258                 if ( ((head.health || head.armorvalue) && needarmor) || (head.weapons && needweapons ) )
1259                 if (vlen(head.origin - org) < sradius)
1260                 {
1261                         t = head.bot_pickupevalfunc(self, head);
1262                         if (t > 0)
1263                                 navigation_routerating(head, t * ratingscale, 500);
1264                 }
1265                 head = head.chain;
1266         }
1267 }
1268
1269 void havocbot_role_ons_setrole(entity bot, int role)
1270 {
1271         ons_debug(strcat(bot.netname," switched to "));
1272         switch(role)
1273         {
1274                 case HAVOCBOT_ONS_ROLE_DEFENSE:
1275                         ons_debug("defense");
1276                         bot.havocbot_role = havocbot_role_ons_defense;
1277                         bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE;
1278                         bot.havocbot_role_timeout = 0;
1279                         break;
1280                 case HAVOCBOT_ONS_ROLE_ASSISTANT:
1281                         ons_debug("assistant");
1282                         bot.havocbot_role = havocbot_role_ons_assistant;
1283                         bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT;
1284                         bot.havocbot_role_timeout = 0;
1285                         break;
1286                 case HAVOCBOT_ONS_ROLE_OFFENSE:
1287                         ons_debug("offense");
1288                         bot.havocbot_role = havocbot_role_ons_offense;
1289                         bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE;
1290                         bot.havocbot_role_timeout = 0;
1291                         break;
1292         }
1293         ons_debug("\n");
1294 }
1295
1296 int havocbot_ons_teamcount(entity bot, int role)
1297 {
1298         int c = 0;
1299         entity head;
1300
1301         FOR_EACH_PLAYER(head)
1302         if(SAME_TEAM(head, self))
1303         if(head.havocbot_role_flags & role)
1304                 ++c;
1305
1306         return c;
1307 }
1308
1309 void havocbot_goalrating_ons_controlpoints_attack(float ratingscale)
1310 {
1311         entity cp, cp1, cp2, best, pl, wp;
1312         float radius, bestvalue;
1313         int c;
1314         bool found;
1315
1316         // Filter control points
1317         for(cp2 = ons_worldcplist; cp2; cp2 = cp2.ons_worldcpnext)
1318         {
1319                 cp2.wpcost = c = 0;
1320                 cp2.wpconsidered = false;
1321
1322                 if(cp2.isshielded)
1323                         continue;
1324
1325                 // Ignore owned controlpoints
1326                 if(!(cp2.isgenneighbor[self.team] || cp2.iscpneighbor[self.team]))
1327                         continue;
1328
1329                 // Count team mates interested in this control point
1330                 // (easier and cleaner than keeping counters per cp and teams)
1331                 FOR_EACH_PLAYER(pl)
1332                 if(SAME_TEAM(pl, self))
1333                 if(pl.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE)
1334                 if(pl.havocbot_ons_target==cp2)
1335                         ++c;
1336
1337                 // NOTE: probably decrease the cost of attackable control points
1338                 cp2.wpcost = c;
1339                 cp2.wpconsidered = true;
1340         }
1341
1342         // We'll consider only the best case
1343         bestvalue = 99999999999;
1344         cp = world;
1345         for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext)
1346         {
1347                 if (!cp1.wpconsidered)
1348                         continue;
1349
1350                 if(cp1.wpcost<bestvalue)
1351                 {
1352                         bestvalue = cp1.wpcost;
1353                         cp = cp1;
1354                         self.havocbot_ons_target = cp1;
1355                 }
1356         }
1357
1358         if (!cp)
1359                 return;
1360
1361         ons_debug(strcat(self.netname, " chose cp ranked ", ftos(bestvalue), "\n"));
1362
1363         if(cp.goalentity)
1364         {
1365                 // Should be attacked
1366                 // Rate waypoints near it
1367                 found = false;
1368                 best = world;
1369                 bestvalue = 99999999999;
1370                 for(radius=0; radius<1000 && !found; radius+=500)
1371                 {
1372                         for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
1373                         {
1374                                 if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
1375                                 if(wp.classname=="waypoint")
1376                                 if(checkpvs(wp.origin,cp))
1377                                 {
1378                                         found = true;
1379                                         if(wp.cnt<bestvalue)
1380                                         {
1381                                                 best = wp;
1382                                                 bestvalue = wp.cnt;
1383                                         }
1384                                 }
1385                         }
1386                 }
1387
1388                 if(best)
1389                 {
1390                         navigation_routerating(best, ratingscale, 10000);
1391                         best.cnt += 1;
1392
1393                         self.havocbot_attack_time = 0;
1394                         if(checkpvs(self.view_ofs,cp))
1395                         if(checkpvs(self.view_ofs,best))
1396                                 self.havocbot_attack_time = time + 2;
1397                 }
1398                 else
1399                 {
1400                         navigation_routerating(cp, ratingscale, 10000);
1401                 }
1402                 ons_debug(strcat(self.netname, " found an attackable controlpoint at ", vtos(cp.origin) ,"\n"));
1403         }
1404         else
1405         {
1406                 // Should be touched
1407                 ons_debug(strcat(self.netname, " found a touchable controlpoint at ", vtos(cp.origin) ,"\n"));
1408                 found = false;
1409
1410                 // Look for auto generated waypoint
1411                 if (!bot_waypoints_for_items)
1412                 for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
1413                 {
1414                         if(wp.classname=="waypoint")
1415                         {
1416                                 navigation_routerating(wp, ratingscale, 10000);
1417                                 found = true;
1418                         }
1419                 }
1420
1421                 // Nothing found, rate the controlpoint itself
1422                 if (!found)
1423                         navigation_routerating(cp, ratingscale, 10000);
1424         }
1425 }
1426
1427 bool havocbot_goalrating_ons_generator_attack(float ratingscale)
1428 {
1429         entity g, wp, bestwp;
1430         bool found;
1431         int best;
1432
1433         for(g = ons_worldgeneratorlist; g; g = g.ons_worldgeneratornext)
1434         {
1435                 if(SAME_TEAM(g, self) || g.isshielded)
1436                         continue;
1437
1438                 // Should be attacked
1439                 // Rate waypoints near it
1440                 found = false;
1441                 bestwp = world;
1442                 best = 99999999999;
1443
1444                 for(wp=findradius(g.origin,400); wp; wp=wp.chain)
1445                 {
1446                         if(wp.classname=="waypoint")
1447                         if(checkpvs(wp.origin,g))
1448                         {
1449                                 found = true;
1450                                 if(wp.cnt<best)
1451                                 {
1452                                         bestwp = wp;
1453                                         best = wp.cnt;
1454                                 }
1455                         }
1456                 }
1457
1458                 if(bestwp)
1459                 {
1460                         ons_debug("waypoints found around generator\n");
1461                         navigation_routerating(bestwp, ratingscale, 10000);
1462                         bestwp.cnt += 1;
1463
1464                         self.havocbot_attack_time = 0;
1465                         if(checkpvs(self.view_ofs,g))
1466                         if(checkpvs(self.view_ofs,bestwp))
1467                                 self.havocbot_attack_time = time + 5;
1468
1469                         return true;
1470                 }
1471                 else
1472                 {
1473                         ons_debug("generator found without waypoints around\n");
1474                         // if there aren't waypoints near the generator go straight to it
1475                         navigation_routerating(g, ratingscale, 10000);
1476                         self.havocbot_attack_time = 0;
1477                         return true;
1478                 }
1479         }
1480         return false;
1481 }
1482
1483 void havocbot_role_ons_offense()
1484 {
1485         if(self.deadflag != DEAD_NO)
1486         {
1487                 self.havocbot_attack_time = 0;
1488                 havocbot_ons_reset_role(self);
1489                 return;
1490         }
1491
1492         // Set the role timeout if necessary
1493         if (!self.havocbot_role_timeout)
1494                 self.havocbot_role_timeout = time + 120;
1495
1496         if (time > self.havocbot_role_timeout)
1497         {
1498                 havocbot_ons_reset_role(self);
1499                 return;
1500         }
1501
1502         if(self.havocbot_attack_time>time)
1503                 return;
1504
1505         if (self.bot_strategytime < time)
1506         {
1507                 navigation_goalrating_start();
1508                 havocbot_goalrating_enemyplayers(20000, self.origin, 650);
1509                 if(!havocbot_goalrating_ons_generator_attack(20000))
1510                         havocbot_goalrating_ons_controlpoints_attack(20000);
1511                 havocbot_goalrating_ons_offenseitems(10000, self.origin, 10000);
1512                 navigation_goalrating_end();
1513
1514                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1515         }
1516 }
1517
1518 void havocbot_role_ons_assistant()
1519 {
1520         havocbot_ons_reset_role(self);
1521 }
1522
1523 void havocbot_role_ons_defense()
1524 {
1525         havocbot_ons_reset_role(self);
1526 }
1527
1528 void havocbot_ons_reset_role(entity bot)
1529 {
1530         entity head;
1531         int c = 0;
1532
1533         if(self.deadflag != DEAD_NO)
1534                 return;
1535
1536         bot.havocbot_ons_target = world;
1537
1538         // TODO: Defend control points or generator if necessary
1539
1540         // if there is only me on the team switch to offense
1541         c = 0;
1542         FOR_EACH_PLAYER(head)
1543         if(SAME_TEAM(head, self))
1544                 ++c;
1545
1546         if(c==1)
1547         {
1548                 havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
1549                 return;
1550         }
1551
1552         havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
1553 }
1554
1555
1556 /*
1557  * Find control point or generator owned by the same team self which is nearest to pos
1558  * if max_dist is positive, only control points within this range will be considered
1559  */
1560 entity ons_Nearest_ControlPoint(vector pos, float max_dist)
1561 {
1562         entity tmp_entity, closest_target = world;
1563         tmp_entity = findchain(classname, "onslaught_controlpoint");
1564         while(tmp_entity)
1565         {
1566                 if(SAME_TEAM(tmp_entity, self))
1567                 if(tmp_entity.iscaptured)
1568                 if(max_dist <= 0 || vlen(tmp_entity.origin - pos) <= max_dist)
1569                 if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world)
1570                         closest_target = tmp_entity;
1571                 tmp_entity = tmp_entity.chain;
1572         }
1573         tmp_entity = findchain(classname, "onslaught_generator");
1574         while(tmp_entity)
1575         {
1576                 if(SAME_TEAM(tmp_entity, self))
1577                 if(max_dist <= 0 || vlen(tmp_entity.origin - pos) < max_dist)
1578                 if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world)
1579                         closest_target = tmp_entity;
1580                 tmp_entity = tmp_entity.chain;
1581         }
1582
1583         return closest_target;
1584 }
1585
1586 /*
1587  * Find control point or generator owned by the same team self which is nearest to pos
1588  * if max_dist is positive, only control points within this range will be considered
1589  * This function only check distances on the XY plane, disregarding Z
1590  */
1591 entity ons_Nearest_ControlPoint_2D(vector pos, float max_dist)
1592 {
1593         entity tmp_entity, closest_target = world;
1594         vector delta;
1595         float smallest_distance = 0, distance;
1596
1597         tmp_entity = findchain(classname, "onslaught_controlpoint");
1598         while(tmp_entity)
1599         {
1600                 delta = tmp_entity.origin - pos;
1601                 delta_z = 0;
1602                 distance = vlen(delta);
1603
1604                 if(SAME_TEAM(tmp_entity, self))
1605                 if(tmp_entity.iscaptured)
1606                 if(max_dist <= 0 || distance <= max_dist)
1607                 if(closest_target == world || distance <= smallest_distance )
1608                 {
1609                         closest_target = tmp_entity;
1610                         smallest_distance = distance;
1611                 }
1612
1613                 tmp_entity = tmp_entity.chain;
1614         }
1615         tmp_entity = findchain(classname, "onslaught_generator");
1616         while(tmp_entity)
1617         {
1618                 delta = tmp_entity.origin - pos;
1619                 delta_z = 0;
1620                 distance = vlen(delta);
1621
1622                 if(SAME_TEAM(tmp_entity, self))
1623                 if(max_dist <= 0 || distance <= max_dist)
1624                 if(closest_target == world || distance <= smallest_distance )
1625                 {
1626                         closest_target = tmp_entity;
1627                         smallest_distance = distance;
1628                 }
1629
1630                 tmp_entity = tmp_entity.chain;
1631         }
1632
1633         return closest_target;
1634 }
1635 /**
1636  * find the number of control points and generators in the same team as self
1637  */
1638 int ons_Count_SelfControlPoints()
1639 {
1640         entity tmp_entity;
1641         tmp_entity = findchain(classname, "onslaught_controlpoint");
1642         int n = 0;
1643         while(tmp_entity)
1644         {
1645                 if(SAME_TEAM(tmp_entity, self))
1646                 if(tmp_entity.iscaptured)
1647                         n++;
1648                 tmp_entity = tmp_entity.chain;
1649         }
1650         tmp_entity = findchain(classname, "onslaught_generator");
1651         while(tmp_entity)
1652         {
1653                 if(SAME_TEAM(tmp_entity, self))
1654                         n++;
1655                 tmp_entity = tmp_entity.chain;
1656         }
1657         return n;
1658 }
1659
1660 /**
1661  * Teleport player to a random position near tele_target
1662  * if tele_effects is true, teleport sound+particles are created
1663  * return false on failure
1664  */
1665 bool ons_Teleport(entity player, entity tele_target, float range, bool tele_effects)
1666 {
1667         if ( !tele_target )
1668                 return false;
1669
1670         int i;
1671         vector loc;
1672         float theta;
1673         for(i = 0; i < 16; ++i)
1674         {
1675                 theta = random() * 2 * M_PI;
1676                 loc_y = sin(theta);
1677                 loc_x = cos(theta);
1678                 loc_z = 0;
1679                 loc *= random() * range;
1680
1681                 loc += tele_target.origin + '0 0 128';
1682
1683                 tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, player);
1684                 if(trace_fraction == 1.0 && !trace_startsolid)
1685                 {
1686                         traceline(tele_target.origin, loc, MOVE_NOMONSTERS, tele_target); // double check to make sure we're not spawning outside the world
1687                         if(trace_fraction == 1.0 && !trace_startsolid)
1688                         {
1689                                 if ( tele_effects )
1690                                 {
1691                                         Send_Effect("teleport", player.origin, '0 0 0', 1);
1692                                         sound (player, CH_TRIGGER, "misc/teleport.wav", VOL_BASE, ATTEN_NORM);
1693                                 }
1694                                 setorigin(player, loc);
1695                                 player.angles = '0 1 0' * ( theta * RAD2DEG + 180 );
1696                                 makevectors(player.angles);
1697                                 player.fixangle = true;
1698                                 player.teleport_antispam = time + autocvar_g_onslaught_teleport_wait;
1699
1700                                 if ( tele_effects )
1701                                         Send_Effect("teleport", player.origin + v_forward * 32, '0 0 0', 1);
1702                                 return true;
1703                         }
1704                 }
1705         }
1706
1707         return false;
1708 }
1709
1710 // ==============
1711 // Hook Functions
1712 // ==============
1713
1714 MUTATOR_HOOKFUNCTION(ons_ResetMap)
1715 {
1716         FOR_EACH_PLAYER(self)
1717         {
1718                 self.ons_roundlost = false;
1719                 self.ons_deathloc = '0 0 0';
1720                 PutClientInServer();
1721         }
1722         return false;
1723 }
1724
1725 MUTATOR_HOOKFUNCTION(ons_RemovePlayer)
1726 {
1727         self.ons_deathloc = '0 0 0';
1728         return false;
1729 }
1730
1731 MUTATOR_HOOKFUNCTION(ons_PlayerSpawn)
1732 {
1733         if(!round_handler_IsRoundStarted())
1734         {
1735                 self.player_blocked = true;
1736                 return false;
1737         }
1738
1739         entity l;
1740         for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
1741         {
1742                 l.sprite.SendFlags |= 16;
1743         }
1744         for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
1745         {
1746                 l.sprite.SendFlags |= 16;
1747         }
1748
1749         if(ons_stalemate) { Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); }
1750
1751         if ( autocvar_g_onslaught_spawn_choose )
1752         if ( self.ons_spawn_by )
1753         if ( ons_Teleport(self,self.ons_spawn_by,autocvar_g_onslaught_teleport_radius,false) )
1754         {
1755                 self.ons_spawn_by = world;
1756                 return false;
1757         }
1758
1759         if(autocvar_g_onslaught_spawn_at_controlpoints)
1760         if(random() <= autocvar_g_onslaught_spawn_at_controlpoints_chance)
1761         {
1762                 float random_target = autocvar_g_onslaught_spawn_at_controlpoints_random;
1763                 entity tmp_entity, closest_target = world;
1764                 vector spawn_loc = self.ons_deathloc;
1765
1766                 // new joining player or round reset, don't bother checking
1767                 if(spawn_loc == '0 0 0') { return false; }
1768
1769                 if(random_target) { RandomSelection_Init(); }
1770
1771                 for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext)
1772                 {
1773                         if(SAME_TEAM(tmp_entity, self))
1774                         if(random_target)
1775                                 RandomSelection_Add(tmp_entity, 0, string_null, 1, 1);
1776                         else if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world)
1777                                 closest_target = tmp_entity;
1778                 }
1779
1780                 if(random_target) { closest_target = RandomSelection_chosen_ent; }
1781
1782                 if(closest_target)
1783                 {
1784                         float i;
1785                         vector loc;
1786                         for(i = 0; i < 10; ++i)
1787                         {
1788                                 loc = closest_target.origin + '0 0 96';
1789                                 loc += ('0 1 0' * random()) * 128;
1790                                 tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self);
1791                                 if(trace_fraction == 1.0 && !trace_startsolid)
1792                                 {
1793                                         traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world
1794                                         if(trace_fraction == 1.0 && !trace_startsolid)
1795                                         {
1796                                                 setorigin(self, loc);
1797                                                 self.angles = normalize(loc - closest_target.origin) * RAD2DEG;
1798                                                 return false;
1799                                         }
1800                                 }
1801                         }
1802                 }
1803         }
1804
1805         if(autocvar_g_onslaught_spawn_at_generator)
1806         if(random() <= autocvar_g_onslaught_spawn_at_generator_chance)
1807         {
1808                 float random_target = autocvar_g_onslaught_spawn_at_generator_random;
1809                 entity tmp_entity, closest_target = world;
1810                 vector spawn_loc = self.ons_deathloc;
1811
1812                 // new joining player or round reset, don't bother checking
1813                 if(spawn_loc == '0 0 0') { return false; }
1814
1815                 if(random_target) { RandomSelection_Init(); }
1816
1817                 for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
1818                 {
1819                         if(random_target)
1820                                 RandomSelection_Add(tmp_entity, 0, string_null, 1, 1);
1821                         else
1822                         {
1823                                 if(SAME_TEAM(tmp_entity, self))
1824                                 if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world)
1825                                         closest_target = tmp_entity;
1826                         }
1827                 }
1828
1829                 if(random_target) { closest_target = RandomSelection_chosen_ent; }
1830
1831                 if(closest_target)
1832                 {
1833                         float i;
1834                         vector loc;
1835                         for(i = 0; i < 10; ++i)
1836                         {
1837                                 loc = closest_target.origin + '0 0 128';
1838                                 loc += ('0 1 0' * random()) * 256;
1839                                 tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self);
1840                                 if(trace_fraction == 1.0 && !trace_startsolid)
1841                                 {
1842                                         traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world
1843                                         if(trace_fraction == 1.0 && !trace_startsolid)
1844                                         {
1845                                                 setorigin(self, loc);
1846                                                 self.angles = normalize(loc - closest_target.origin) * RAD2DEG;
1847                                                 return false;
1848                                         }
1849                                 }
1850                         }
1851                 }
1852         }
1853
1854     return false;
1855 }
1856
1857 MUTATOR_HOOKFUNCTION(ons_PlayerDies)
1858 {
1859         frag_target.ons_deathloc = frag_target.origin;
1860         entity l;
1861         for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
1862         {
1863                 l.sprite.SendFlags |= 16;
1864         }
1865         for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
1866         {
1867                 l.sprite.SendFlags |= 16;
1868         }
1869
1870         if ( autocvar_g_onslaught_spawn_choose )
1871         if ( ons_Count_SelfControlPoints() > 1 )
1872                 stuffcmd(self, "qc_cmd_cl hud clickradar\n");
1873
1874         return false;
1875 }
1876
1877 MUTATOR_HOOKFUNCTION(ons_MonsterThink)
1878 {
1879         entity e = find(world, targetname, self.target);
1880         if (e != world)
1881                 self.team = e.team;
1882
1883         return false;
1884 }
1885
1886 void ons_MonsterSpawn_Delayed()
1887 {
1888         entity e, own = self.owner;
1889
1890         if(!own) { remove(self); return; }
1891
1892         if(own.targetname)
1893         {
1894                 e = find(world, target, own.targetname);
1895                 if(e != world)
1896                 {
1897                         own.team = e.team;
1898
1899                         activator = e;
1900                         own.use();
1901                 }
1902         }
1903
1904         remove(self);
1905 }
1906
1907 MUTATOR_HOOKFUNCTION(ons_MonsterSpawn)
1908 {
1909         entity e = spawn();
1910         e.owner = self;
1911         InitializeEntity(e, ons_MonsterSpawn_Delayed, INITPRIO_FINDTARGET);
1912
1913         return false;
1914 }
1915
1916 void ons_TurretSpawn_Delayed()
1917 {
1918         entity e, own = self.owner;
1919
1920         if(!own) { remove(self); return; }
1921
1922         if(own.targetname)
1923         {
1924                 e = find(world, target, own.targetname);
1925                 if(e != world)
1926                 {
1927                         own.team = e.team;
1928                         own.active = ACTIVE_NOT;
1929
1930                         activator = e;
1931                         own.use();
1932                 }
1933         }
1934
1935         remove(self);
1936 }
1937
1938 MUTATOR_HOOKFUNCTION(ons_TurretSpawn)
1939 {
1940         entity e = spawn();
1941         e.owner = self;
1942         InitializeEntity(e, ons_TurretSpawn_Delayed, INITPRIO_FINDTARGET);
1943
1944         return false;
1945 }
1946
1947 MUTATOR_HOOKFUNCTION(ons_BotRoles)
1948 {
1949         havocbot_ons_reset_role(self);
1950         return true;
1951 }
1952
1953 MUTATOR_HOOKFUNCTION(ons_GetTeamCount)
1954 {
1955         // onslaught is special
1956         entity tmp_entity;
1957         for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
1958         {
1959                 switch(tmp_entity.team)
1960                 {
1961                         case NUM_TEAM_1: c1 = 0; break;
1962                         case NUM_TEAM_2: c2 = 0; break;
1963                         case NUM_TEAM_3: c3 = 0; break;
1964                         case NUM_TEAM_4: c4 = 0; break;
1965                 }
1966         }
1967
1968         return true;
1969 }
1970
1971 MUTATOR_HOOKFUNCTION(ons_SpectateCopy)
1972 {
1973         self.ons_roundlost = other.ons_roundlost; // make spectators see it too
1974         return false;
1975 }
1976
1977 MUTATOR_HOOKFUNCTION(ons_SV_ParseClientCommand)
1978 {
1979         if(MUTATOR_RETURNVALUE) // command was already handled?
1980                 return false;
1981
1982         if ( cmd_name == "ons_spawn" )
1983         {
1984                 vector pos = self.origin;
1985                 if(cmd_argc > 1)
1986                         pos_x = stof(argv(1));
1987                 if(cmd_argc > 2)
1988                         pos_y = stof(argv(2));
1989                 if(cmd_argc > 3)
1990                         pos_z = stof(argv(3));
1991
1992                 if ( IS_PLAYER(self) )
1993                 {
1994                         if ( !self.frozen )
1995                         {
1996                                 entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius);
1997
1998                                 if ( !source_point && self.health > 0 )
1999                                 {
2000                                         sprint(self, "\nYou need to be next to a control point\n");
2001                                         return 1;
2002                                 }
2003
2004
2005                                 entity closest_target = ons_Nearest_ControlPoint_2D(pos, autocvar_g_onslaught_click_radius);
2006
2007                                 if ( closest_target == world )
2008                                 {
2009                                         sprint(self, "\nNo control point found\n");
2010                                         return 1;
2011                                 }
2012
2013                                 if ( self.health <= 0 )
2014                                 {
2015                                         self.ons_spawn_by = closest_target;
2016                                         self.respawn_flags = self.respawn_flags | RESPAWN_FORCE;
2017                                 }
2018                                 else
2019                                 {
2020                                         if ( source_point == closest_target )
2021                                         {
2022                                                 sprint(self, "\nTeleporting to the same point\n");
2023                                                 return 1;
2024                                         }
2025
2026                                         if ( !ons_Teleport(self,closest_target,autocvar_g_onslaught_teleport_radius,true) )
2027                                                 sprint(self, "\nUnable to teleport there\n");
2028                                 }
2029
2030                                 return 1;
2031                         }
2032
2033                         sprint(self, "\nNo teleportation for you\n");
2034                 }
2035
2036                 return 1;
2037         }
2038         return 0;
2039 }
2040
2041 MUTATOR_HOOKFUNCTION(ons_PlayerUseKey)
2042 {
2043         if(MUTATOR_RETURNVALUE || gameover) { return false; }
2044
2045         if((time > self.teleport_antispam) && (self.deadflag == DEAD_NO) && !self.vehicle)
2046         {
2047                 entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius);
2048                 if ( source_point )
2049                 {
2050                         stuffcmd(self, "qc_cmd_cl hud clickradar\n");
2051                         return true;
2052                 }
2053         }
2054
2055         return false;
2056 }
2057
2058 MUTATOR_HOOKFUNCTION(ons_PlayHitsound)
2059 {
2060         return (frag_victim.classname == "onslaught_generator" && !frag_victim.isshielded)
2061                 || (frag_victim.classname == "onslaught_controlpoint_icon" && !frag_victim.owner.isshielded);
2062 }
2063
2064 // ==========
2065 // Spawnfuncs
2066 // ==========
2067
2068 /*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16)
2069   Link between control points.
2070
2071   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.
2072
2073 keys:
2074 "target" - first control point.
2075 "target2" - second control point.
2076  */
2077 void spawnfunc_onslaught_link()
2078 {
2079         if(!g_onslaught) { remove(self); return; }
2080
2081         if (self.target == "" || self.target2 == "")
2082                 objerror("target and target2 must be set\n");
2083
2084         self.ons_worldlinknext = ons_worldlinklist; // link into ons_worldlinklist
2085         ons_worldlinklist = self;
2086
2087         InitializeEntity(self, ons_DelayedLinkSetup, INITPRIO_FINDTARGET);
2088         Net_LinkEntity(self, false, 0, ons_Link_Send);
2089 }
2090
2091 /*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128)
2092   Control point. Be sure to give this enough clearance so that the shootable part has room to exist
2093
2094   This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity.
2095
2096 keys:
2097 "targetname" - name that spawnfunc_onslaught_link entities will use to target this.
2098 "target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities.
2099 "message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc)
2100  */
2101
2102 void spawnfunc_onslaught_controlpoint()
2103 {
2104         if(!g_onslaught) { remove(self); return; }
2105
2106         ons_ControlPoint_Setup(self);
2107 }
2108
2109 /*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64)
2110   Base generator.
2111
2112   spawnfunc_onslaught_link entities can target this.
2113
2114 keys:
2115 "team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET.
2116 "targetname" - name that spawnfunc_onslaught_link entities will use to target this.
2117  */
2118 void spawnfunc_onslaught_generator()
2119 {
2120         if(!g_onslaught) { remove(self); return; }
2121         if(!self.team) { objerror("team must be set"); }
2122
2123         ons_GeneratorSetup(self);
2124 }
2125
2126 // scoreboard setup
2127 void ons_ScoreRules()
2128 {
2129         CheckAllowedTeams(world);
2130         ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), SFL_SORT_PRIO_PRIMARY, 0, true);
2131         ScoreInfo_SetLabel_TeamScore  (ST_ONS_CAPS,     "destroyed", SFL_SORT_PRIO_PRIMARY);
2132         ScoreInfo_SetLabel_PlayerScore(SP_ONS_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
2133         ScoreInfo_SetLabel_PlayerScore(SP_ONS_TAKES,    "takes",     0);
2134         ScoreRules_basics_end();
2135 }
2136
2137 void ons_DelayedInit() // Do this check with a delay so we can wait for teams to be set up
2138 {
2139         ons_ScoreRules();
2140
2141         round_handler_Spawn(Onslaught_CheckPlayers, Onslaught_CheckWinner, Onslaught_RoundStart);
2142         round_handler_Init(5, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit);
2143 }
2144
2145 void ons_Initialize()
2146 {
2147         precache_sound("ctf/red_capture.wav");
2148         precache_sound("ctf/blue_capture.wav");
2149         precache_sound("ctf/yellow_capture.wav");
2150         precache_sound("ctf/pink_capture.wav");
2151
2152         ons_captureshield_force = autocvar_g_onslaught_shield_force;
2153
2154         addstat(STAT_ROUNDLOST, AS_INT, ons_roundlost);
2155
2156         InitializeEntity(world, ons_DelayedInit, INITPRIO_GAMETYPE);
2157 }
2158
2159 MUTATOR_DEFINITION(gamemode_onslaught)
2160 {
2161         MUTATOR_HOOK(reset_map_global, ons_ResetMap, CBC_ORDER_ANY);
2162         MUTATOR_HOOK(MakePlayerObserver, ons_RemovePlayer, CBC_ORDER_ANY);
2163         MUTATOR_HOOK(ClientDisconnect, ons_RemovePlayer, CBC_ORDER_ANY);
2164         MUTATOR_HOOK(PlayerSpawn, ons_PlayerSpawn, CBC_ORDER_ANY);
2165         MUTATOR_HOOK(PlayerDies, ons_PlayerDies, CBC_ORDER_ANY);
2166         MUTATOR_HOOK(MonsterMove, ons_MonsterThink, CBC_ORDER_ANY);
2167         MUTATOR_HOOK(MonsterSpawn, ons_MonsterSpawn, CBC_ORDER_ANY);
2168         MUTATOR_HOOK(TurretSpawn, ons_TurretSpawn, CBC_ORDER_ANY);
2169         MUTATOR_HOOK(HavocBot_ChooseRole, ons_BotRoles, CBC_ORDER_ANY);
2170         MUTATOR_HOOK(GetTeamCount, ons_GetTeamCount, CBC_ORDER_ANY);
2171         MUTATOR_HOOK(SpectateCopy, ons_SpectateCopy, CBC_ORDER_ANY);
2172         MUTATOR_HOOK(SV_ParseClientCommand, ons_SV_ParseClientCommand, CBC_ORDER_ANY);
2173         MUTATOR_HOOK(PlayerUseKey, ons_PlayerUseKey, CBC_ORDER_ANY);
2174         MUTATOR_HOOK(PlayHitsound, ons_PlayHitsound, CBC_ORDER_ANY);
2175
2176         MUTATOR_ONADD
2177         {
2178                 if(time > 1) // game loads at time 1
2179                         error("This is a game type and it cannot be added at runtime.");
2180                 ons_Initialize();
2181         }
2182
2183         MUTATOR_ONROLLBACK_OR_REMOVE
2184         {
2185                 // we actually cannot roll back ons_Initialize here
2186                 // BUT: we don't need to! If this gets called, adding always
2187                 // succeeds.
2188         }
2189
2190         MUTATOR_ONREMOVE
2191         {
2192                 print("This is a game type and it cannot be removed at runtime.");
2193                 return -1;
2194         }
2195
2196         return false;
2197 }