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