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