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