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