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