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 += 1; // 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
676 bool sh;
678
679 if(e.lastteam != e.team + 2 || e.lastshielded != sh || e.iscaptured != e.lastcaptured)
680 {
681 if(e.iscaptured) // don't mess up build bars!
682 {
683 if(sh)
684 {
686 }
687 else
688 {
689 WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health);
690 WaypointSprite_UpdateHealth(e.sprite, GetResource(e.goalentity, RES_HEALTH));
691 }
692 }
693 if(e.lastshielded)
694 {
695 if(e.team)
696 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, false));
697 else
698 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5');
699 }
700 else
701 {
702 if(e.team)
703 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, false));
704 else
705 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75');
706 }
707 WaypointSprite_Ping(e.sprite);
708
709 e.lastteam = e.team + 2;
710 e.lastshielded = sh;
711 e.lastcaptured = e.iscaptured;
712 }
713}
714
716{
717 int attackable;
718
719 if(IS_VEHICLE(toucher) && toucher.owner)
720 {
722 return;
723 toucher = toucher.owner;
724 }
725
726 if(!IS_PLAYER(toucher)) { return; }
727 if(IS_DEAD(toucher)) { return; }
728
729 if ( SAME_TEAM(this,toucher) )
730 if ( this.iscaptured )
731 {
732 if(time <= toucher.teleport_antispam)
733 Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT_ANTISPAM, rint(toucher.teleport_antispam - time));
734 else
735 Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT);
736 }
737
738 attackable = ons_ControlPoint_Attackable(this, toucher.team);
739 if(attackable != 2 && attackable != 4)
740 return;
741 // we've verified that this player has a legitimate claim to this point,
742 // so start building the captured point icon (which only captures this
743 // point if it successfully builds without being destroyed first)
745
746 this.ons_toucher = toucher;
747
749}
750
756
758{
759 if(this.goalentity)
760 delete(this.goalentity);
761
762 this.goalentity = NULL;
763 this.team = 0;
764 this.colormap = 1024;
765 this.iscaptured = false;
766 this.islinked = false;
767 this.isshielded = true;
769 this.ons_toucher = NULL;
771 setmodel(this, MDL_ONS_CP_PAD1);
772
775
777
778 SUB_UseTargets(this, this, NULL); // to reset the structures, playerspawns etc.
779
781}
782
784{
786
787 // captureshield setup
788 ons_CaptureShield_Spawn(this, MDL_ONS_CP_SHIELD);
789
790 CSQCMODEL_AUTOINIT(this);
791}
792
794{
795 // main setup
796 cp.ons_worldcpnext = ons_worldcplist; // link control point into ons_worldcplist
797 ons_worldcplist = cp;
798
799 cp.netname = "Control point";
800 cp.team = 0;
801 cp.solid = SOLID_BBOX;
805 cp.nextthink = time + ONS_CP_THINKRATE;
806 cp.reset = ons_ControlPoint_Reset;
807 cp.colormap = 1024;
808 cp.iscaptured = false;
809 cp.islinked = false;
810 cp.isshielded = true;
811
812 // appearence
813 setmodel(cp, MDL_ONS_CP_PAD1);
814
815 // control point placement
816 if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location
817 {
818 cp.noalign = true;
820 }
821 else // drop to floor, automatically find a platform and set that as spawn origin
822 {
823 setorigin(cp, cp.origin + '0 0 20');
824 cp.noalign = false;
827 }
828
829 // waypointsprites
830 WaypointSprite_SpawnFixed(WP_Null, cp.origin + CPGEN_WAYPOINT_OFFSET, cp, sprite, RADARICON_NONE);
832
834}
835
836
837// =========================
838// Main Generator Functions
839// =========================
840
842{
843 if (e.isshielded)
844 return WP_OnsGenShielded;
845 return WP_OnsGen;
846}
847
849{
852
853 if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded)
854 {
855 e.lastteam = e.team + 2;
856 e.lastshielded = e.isshielded;
857 if(e.lastshielded)
858 {
859 if(e.team)
860 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, false));
861 else
862 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5');
863 }
864 else
865 {
866 if(e.team)
867 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, false));
868 else
869 WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75');
870 }
871 WaypointSprite_Ping(e.sprite);
872 }
873}
874
876bool clientcamera_send(entity this, entity to, int sf)
877{
878 WriteHeader(MSG_ENTITY, ENT_ONSCAMERA);
879 WriteVector(MSG_ENTITY, this.origin);
880 WriteAngleVector(MSG_ENTITY, this.angles);
881 return true;
882}
883
885{
886 vector dir;
887 vector ang = '0 0 0';
888 vector best_ang = '0 0 0';
889 float best_trace_fraction = 0;
890 while(ang.y < 360)
891 {
892 dir = vec2(cos(ang.y * DEG2RAD), sin(ang.y * DEG2RAD));
893 dir *= 500;
894 traceline(this.origin, this.origin - dir, MOVE_WORLDONLY, this);
895 if(trace_fraction > best_trace_fraction)
896 {
897 best_trace_fraction = trace_fraction;
898 best_ang = ang;
899 if(trace_fraction == 1)
900 break;
901 }
902 ang.y += 90;
903 if(ang.y == 360)
904 ang.y = 45;
905 }
906 cam.origin = this.origin;
907 setorigin(cam, cam.origin);
908 cam.angles = best_ang;
910
911 FOREACH_CLIENT(true, it.clientcamera = cam;);
912
913 // NOTE: engine networked
915 WriteAngle(MSG_ALL, cam.angles_x);
916 WriteAngle(MSG_ALL, cam.angles_y);
917 WriteAngle(MSG_ALL, cam.angles_z);
918}
919
920void ons_GeneratorDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
921{
922 if(damage <= 0) return;
923 if(warmup_stage || game_stopped) return;
924 if(!round_handler_IsRoundStarted()) return;
925
926 if (attacker != this)
927 {
928 if (this.isshielded)
929 {
930 // generator is protected by a shield, so ignore the damage
931 if (time > this.pain_finished)
932 if (IS_PLAYER(attacker))
933 {
934 play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
935 attacker.typehitsound += 1;
936 this.pain_finished = time + 1;
937 }
938 return;
939 }
940 if (time > this.pain_finished)
941 {
942 this.pain_finished = time + 10;
943 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && SAME_TEAM(it, this), Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_GENERATOR_UNDERATTACK));
944 play2team(this.team, SND(ONS_GENERATOR_UNDERATTACK));
945 }
946 }
947 TakeResource(this, RES_HEALTH, damage);
948 float hlth = GetResource(this, RES_HEALTH);
950 // choose an animation frame based on health
951 this.frame = 10 * bound(0, (1 - hlth / this.max_health), 1);
952 // see if the generator is still functional, or dying
953 if (hlth > 0)
954 {
955 this.lasthealth = hlth;
956 }
957 else
958 {
959 if (attacker == this)
960 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_ONSLAUGHT_GENDESTROYED_OVERTIME));
961 else
962 {
963 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_ONSLAUGHT_GENDESTROYED));
964 GameRules_scoring_add(attacker, SCORE, 100);
965 }
966 this.iscaptured = false;
967 this.islinked = false;
968 this.isshielded = false;
969 this.takedamage = DAMAGE_NO; // can't be hurt anymore
970 this.event_damage = func_null; // won't do anything if hurt
971 this.event_heal = func_null;
972 this.count = 0; // reset counter
973 setthink(this, func_null);
974 this.nextthink = 0;
975 //this.think(); // do the first explosion now
976
979 //WaypointSprite_Kill(this.sprite); // can't do this yet, code too poor
980
982
983 ons_camSetup(this);
984 }
985
986 // Throw some flaming gibs on damage, more damage = more chance for gib
987 if(random() < damage/220)
988 {
989 sound(this, CH_TRIGGER, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
990 }
991 else
992 {
993 // particles on every hit
994 Send_Effect(EFFECT_SPARKS, hitloc, force * -1, 1);
995
996 //sound on every hit
997 if (random() < 0.5)
998 sound(this, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE, ATTEN_NORM);
999 else
1000 sound(this, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE, ATTEN_NORM);
1001 }
1002
1003 this.SendFlags |= GSF_STATUS;
1004}
1005
1006bool ons_GeneratorHeal(entity targ, entity inflictor, float amount, float limit)
1007{
1008 float true_limit = ((limit != RES_LIMIT_NONE) ? limit : targ.max_health);
1009 float hlth = GetResource(targ, RES_HEALTH);
1010 if (hlth <= 0 || hlth >= true_limit)
1011 return false;
1012
1013 GiveResourceWithLimit(targ, RES_HEALTH, amount, true_limit);
1014 hlth = GetResource(targ, RES_HEALTH);
1015 WaypointSprite_UpdateHealth(targ.sprite, hlth);
1016 targ.frame = 10 * bound(0, (1 - hlth / targ.max_health), 1);
1017 targ.lasthealth = hlth;
1018 targ.SendFlags |= GSF_STATUS;
1019 return true;
1020}
1021
1023{
1024 this.nextthink = time + GEN_THINKRATE;
1025
1026 if (game_stopped || this.isshielded || time < this.wait)
1027 return;
1028
1029 this.wait = time + 5;
1031 {
1032 if (SAME_TEAM(it, this))
1033 {
1034 Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM);
1035 msg_entity = it;
1036 soundto(MSG_ONE, this, CHAN_AUTO, SND(ONS_GENERATOR_ALARM), VOL_BASE, ATTEN_NONE, 0);
1037 }
1038 else
1039 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(this.team, CENTER_ONS_NOTSHIELDED));
1040 });
1041}
1042
1044{
1045 this.team = this.team_saved;
1048 this.takedamage = DAMAGE_AIM;
1049 this.bot_attack = true;
1050 if(!IL_CONTAINS(g_bot_targets, this))
1051 IL_PUSH(g_bot_targets, this);
1052 this.iscaptured = true;
1053 this.islinked = true;
1054 this.isshielded = true;
1055 this.event_damage = ons_GeneratorDamage;
1056 this.event_heal = ons_GeneratorHeal;
1058 this.nextthink = time + GEN_THINKRATE;
1059
1060 Net_LinkEntity(this, false, 0, generator_send);
1061
1062 this.SendFlags = GSF_SETUP; // just incase
1063 this.SendFlags |= GSF_STATUS;
1064
1066 WaypointSprite_UpdateHealth(this.sprite, GetResource(this, RES_HEALTH));
1068
1070}
1071
1073{
1074 // bot waypoints
1076 this.nearestwaypointtimeout = 0; // activate waypointing again
1078
1079 // captureshield setup
1080 ons_CaptureShield_Spawn(this, MDL_ONS_GEN_SHIELD);
1081
1083
1084 Net_LinkEntity(this, false, 0, generator_send);
1085}
1086
1087
1089{
1090 if ( IS_PLAYER(toucher) )
1091 if ( SAME_TEAM(this,toucher) )
1092 if ( this.iscaptured )
1093 {
1094 Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT);
1095 }
1096}
1097
1098void ons_GeneratorSetup(entity gen) // called when spawning a generator entity on the map as a spawnfunc
1099{
1100 // declarations
1101 int teamnum = gen.team;
1102
1103 // main setup
1104 gen.ons_worldgeneratornext = ons_worldgeneratorlist; // link generator into ons_worldgeneratorlist
1106
1107 gen.netname = sprintf("%s generator", Team_ColoredFullName(teamnum));
1108 gen.solid = SOLID_BBOX;
1109 gen.team_saved = teamnum;
1110 IL_PUSH(g_saved_team, gen);
1112 gen.lasthealth = gen.max_health = autocvar_g_onslaught_gen_health;
1114 gen.takedamage = DAMAGE_AIM;
1115 gen.bot_attack = true;
1116 IL_PUSH(g_bot_targets, gen);
1117 gen.event_damage = ons_GeneratorDamage;
1118 gen.event_heal = ons_GeneratorHeal;
1119 gen.reset = ons_GeneratorReset;
1121 gen.nextthink = time + GEN_THINKRATE;
1122 gen.iscaptured = true;
1123 gen.islinked = true;
1124 gen.isshielded = true;
1126
1127 // appearence
1128 // model handled by CSQC
1129 setsize(gen, GENERATOR_MIN, GENERATOR_MAX);
1130 setorigin(gen, (gen.origin + CPGEN_SPAWN_OFFSET));
1131 gen.colormap = 1024 + (teamnum - 1) * 17;
1132
1133 // generator placement
1135
1136 // waypointsprites
1137 WaypointSprite_SpawnFixed(WP_Null, gen.origin + CPGEN_WAYPOINT_OFFSET, gen, sprite, RADARICON_NONE);
1138 WaypointSprite_UpdateRule(gen.sprite, gen.team, SPRITERULE_TEAMPLAY);
1139 WaypointSprite_UpdateMaxHealth(gen.sprite, gen.max_health);
1140 WaypointSprite_UpdateHealth(gen.sprite, GetResource(gen, RES_HEALTH));
1141
1143}
1144
1145
1146// ===============
1147// Round Handler
1148// ===============
1149
1152{
1153 entity e;
1154 total_generators = 0;
1155 for (int i = 1; i <= NUM_TEAMS; ++i)
1156 {
1158 }
1159 for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext)
1160 {
1162 if (GetResource(e, RES_HEALTH) < 1)
1163 {
1164 continue;
1165 }
1166 entity team_ = Entity_GetTeam(e);
1167 int num_generators = Team_GetNumberOfOwnedItems(team_);
1168 ++num_generators;
1169 Team_SetNumberOfOwnedItems(team_, num_generators);
1170 }
1171}
1172
1174{
1176 {
1177 ons_stalemate = true;
1178
1180 {
1181 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT);
1182 sound(NULL, CH_INFO, SND_ONS_GENERATOR_DECAY, VOL_BASE, ATTEN_NONE);
1183
1184 wpforenemy_announced = true;
1185 }
1186
1187 entity tmp_entity; // temporary entity
1188 float d;
1189 for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) if(time >= tmp_entity.ons_overtime_damagedelay)
1190 {
1191 // tmp_entity.max_health / 300 gives 5 minutes of overtime.
1192 // control points reduce the overtime duration.
1193 d = 1;
1194 entity e;
1195 for(e = ons_worldcplist; e; e = e.ons_worldcpnext)
1196 {
1197 if(DIFF_TEAM(e, tmp_entity))
1198 if(e.islinked)
1199 d = d + 1;
1200 }
1201
1203 d = d * tmp_entity.max_health;
1204 else
1205 d = d * tmp_entity.max_health / max(30, 60 * autocvar_timelimit_suddendeath);
1206
1207 Damage(tmp_entity, tmp_entity, tmp_entity, d, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, tmp_entity.origin, '0 0 0');
1208
1209 tmp_entity.sprite.SendFlags |= 16;
1210
1211 tmp_entity.ons_overtime_damagedelay = time + 1;
1212 }
1213 }
1214 else { wpforenemy_announced = false; ons_stalemate = false; }
1215
1217 int winner_team = Team_GetWinnerTeam_WithOwnedItems(1);
1218 if (!winner_team)
1219 return 0;
1220
1221 if(winner_team > 0)
1222 {
1223 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
1224 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
1225 TeamScore_AddToTeam(winner_team, ST_ONS_GENS, +1);
1226 }
1227 else if(winner_team == -1)
1228 {
1229 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
1230 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
1231 }
1232
1233 ons_stalemate = false;
1234
1235 play2all(SND(CTF_CAPTURE(winner_team)));
1236
1238
1240 STAT(ROUNDLOST, it) = true;
1241 it.player_blocked = true;
1243 });
1244
1245 game_stopped = true;
1246 return 1;
1247}
1248
1250{
1251 return 1;
1252}
1253
1255{
1256 entity tmp_entity;
1257 FOREACH_CLIENT(IS_PLAYER(it), it.player_blocked = false);
1258
1259 for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext)
1260 tmp_entity.sprite.SendFlags |= 16;
1261
1262 for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
1263 tmp_entity.sprite.SendFlags |= 16;
1264}
1265
1266
1267// ================
1268// Bot player logic
1269// ================
1270
1271// NOTE: LEGACY CODE, needs to be re-written!
1272
1274{
1275 switch(role)
1276 {
1278 LOG_DEBUG(this.netname, " switched to defense");
1279 this.havocbot_role = havocbot_role_ons_defense;
1280 this.havocbot_role_timeout = 0;
1281 break;
1283 LOG_DEBUG(this.netname, " switched to assistant");
1284 this.havocbot_role = havocbot_role_ons_assistant;
1285 this.havocbot_role_timeout = 0;
1286 break;
1288 LOG_DEBUG(this.netname, " switched to offense");
1289 this.havocbot_role = havocbot_role_ons_offense;
1290 this.havocbot_role_timeout = 0;
1291 break;
1292 }
1293}
1294
1296{
1297 entity cp, cp1, cp2, best, wp;
1298 float radius, bestvalue;
1299 int c;
1300 bool found;
1301
1302 // Filter control points
1303 for(cp2 = ons_worldcplist; cp2; cp2 = cp2.ons_worldcpnext)
1304 {
1305 cp2.wpcost = c = 0;
1306 cp2.wpconsidered = false;
1307
1308 if(cp2.isshielded)
1309 continue;
1310
1311 // Ignore owned controlpoints
1312 if(!((cp2.aregensneighbor & BIT(this.team)) || (cp2.arecpsneighbor & BIT(this.team))))
1313 continue;
1314
1315 // Count teammates interested in this control point
1316 // (easier and cleaner than keeping counters per cp and teams)
1317 FOREACH_CLIENT(it != this && IS_PLAYER(it), {
1318 if(SAME_TEAM(it, this))
1319 if(it.havocbot_role == havocbot_role_ons_offense)
1320 if(it.havocbot_ons_target == cp2)
1321 ++c;
1322 });
1323
1324 // NOTE: probably decrease the cost of attackable control points
1325 cp2.wpcost = c;
1326 cp2.wpconsidered = true;
1327 }
1328
1329 // We'll consider only the best case
1330 bestvalue = FLOAT_MAX;
1331 cp = NULL;
1332 for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext)
1333 {
1334 if (!cp1.wpconsidered)
1335 continue;
1336
1337 if(cp1.wpcost<bestvalue)
1338 {
1339 bestvalue = cp1.wpcost;
1340 cp = cp1;
1341 this.havocbot_ons_target = cp1;
1342 }
1343 }
1344
1345 if (!cp)
1346 return;
1347
1348 LOG_DEBUG(this.netname, " chose cp ranked ", ftos(bestvalue));
1349
1350 if(cp.goalentity)
1351 {
1352 // Should be attacked
1353 // Rate waypoints near it
1354 found = false;
1355 best = NULL;
1356 bestvalue = FLOAT_MAX;
1357 for (radius = 500; radius <= 1000 && !found; radius += 500)
1358 {
1359 IL_EACH(g_waypoints, vdist(cp.origin - it.origin, <, radius),
1360 {
1361 if (!(it.wpflags & WAYPOINTFLAG_GENERATED) && checkpvs(it.origin, cp))
1362 {
1363 found = true;
1364 if (it.cnt < bestvalue)
1365 {
1366 best = it;
1367 bestvalue = it.cnt;
1368 }
1369 }
1370 });
1371 }
1372
1373 if(best)
1374 {
1375 navigation_routerating(this, best, ratingscale, 10000);
1376 best.cnt += 1;
1377
1378 this.havocbot_attack_time = 0;
1379 if(checkpvs(this.origin + this.view_ofs, cp))
1380 if(checkpvs(this.origin + this.view_ofs, best))
1381 this.havocbot_attack_time = time + 2;
1382 }
1383 else
1384 {
1385 navigation_routerating(this, cp, ratingscale, 10000);
1386 }
1387 LOG_DEBUG(this.netname, " found an attackable controlpoint at ", vtos(cp.origin));
1388 }
1389 else
1390 {
1391 // Should be touched
1392 LOG_DEBUG(this.netname, " found a touchable controlpoint at ", vtos(cp.origin));
1393 navigation_routerating(this, cp, ratingscale * 2, 10000);
1394 }
1395}
1396
1398{
1399 entity g, wp, bestwp;
1400 bool found;
1401 int bestvalue;
1402
1403 for(g = ons_worldgeneratorlist; g; g = g.ons_worldgeneratornext)
1404 {
1405 if(SAME_TEAM(g, this) || g.isshielded)
1406 continue;
1407
1408 // Should be attacked
1409 // Rate waypoints near it
1410 found = false;
1411 bestwp = NULL;
1412 bestvalue = FLOAT_MAX;
1413
1414 IL_EACH(g_waypoints, vdist(g.origin - it.origin, <, 400),
1415 {
1416 if (checkpvs(it.origin, g))
1417 {
1418 found = true;
1419 if (it.cnt < bestvalue)
1420 {
1421 bestwp = it;
1422 bestvalue = it.cnt;
1423 }
1424 }
1425 });
1426
1427 if(bestwp)
1428 {
1429 LOG_DEBUG("waypoints found around generator");
1430 navigation_routerating(this, bestwp, ratingscale, 10000);
1431 bestwp.cnt += 1;
1432
1433 this.havocbot_attack_time = 0;
1434 if(checkpvs(this.origin + this.view_ofs, g))
1435 if(checkpvs(this.origin + this.view_ofs, bestwp))
1436 this.havocbot_attack_time = time + 5;
1437
1438 return true;
1439 }
1440 else
1441 {
1442 LOG_DEBUG("generator found without waypoints around");
1443 // if there aren't waypoints near the generator go straight to it
1444 navigation_routerating(this, g, ratingscale, 10000);
1445 this.havocbot_attack_time = 0;
1446 return true;
1447 }
1448 }
1449 return false;
1450}
1451
1453{
1454 if(IS_DEAD(this))
1455 {
1456 this.havocbot_attack_time = 0;
1458 return;
1459 }
1460
1461 // Set the role timeout if necessary
1462 if (!this.havocbot_role_timeout)
1463 this.havocbot_role_timeout = time + 120;
1464
1465 if (time > this.havocbot_role_timeout)
1466 {
1468 return;
1469 }
1470
1471 if(this.havocbot_attack_time>time)
1472 return;
1473
1475 {
1477 havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
1480 havocbot_goalrating_items(this, 25000, this.origin, 10000);
1482
1484 }
1485}
1486
1491
1496
1498{
1499 if(IS_DEAD(this))
1500 return;
1501
1503
1504 // TODO: Defend control points or generator if necessary
1505
1507}
1508
1509
1510/*
1511 * Find control point or generator owned by the same team self which is nearest to pos
1512 * if max_dist is positive, only control points within this range will be considered
1513 */
1515{
1516 entity closest_target = NULL;
1517 for(entity cp = ons_worldcplist; cp; cp = cp.ons_worldcpnext)
1518 {
1519 if(SAME_TEAM(cp, this))
1520 if(cp.iscaptured)
1521 if(max_dist <= 0 || vdist(cp.origin - pos, <=, max_dist))
1522 if(vlen2(cp.origin - pos) <= vlen2(closest_target.origin - pos) || closest_target == NULL)
1523 closest_target = cp;
1524 }
1525 for(entity gen = ons_worldgeneratorlist; gen; gen = gen.ons_worldgeneratornext)
1526 {
1527 if(SAME_TEAM(gen, this))
1528 if(max_dist <= 0 || vdist(gen.origin - pos, <, max_dist))
1529 if(vlen2(gen.origin - pos) <= vlen2(closest_target.origin - pos) || closest_target == NULL)
1530 closest_target = gen;
1531 }
1532
1533 return closest_target;
1534}
1535
1536/*
1537 * Find control point or generator owned by the same team self which is nearest to pos
1538 * if max_dist is positive, only control points within this range will be considered
1539 * This function only check distances on the XY plane, disregarding Z
1540 */
1542{
1543 entity closest_target = NULL;
1544 vector delta;
1545 float smallest_distance = 0, distance;
1546
1547 for(entity cp = ons_worldcplist; cp; cp = cp.ons_worldcpnext)
1548 {
1549 delta = cp.origin - pos;
1550 delta_z = 0;
1551 distance = vlen(delta);
1552
1553 if(SAME_TEAM(cp, this))
1554 if(cp.iscaptured)
1555 if(max_dist <= 0 || distance <= max_dist)
1556 if(closest_target == NULL || distance <= smallest_distance )
1557 {
1558 closest_target = cp;
1559 smallest_distance = distance;
1560 }
1561 }
1562 for(entity gen = ons_worldgeneratorlist; gen; gen = gen.ons_worldgeneratornext)
1563 {
1564 delta = gen.origin - pos;
1565 delta_z = 0;
1566 distance = vlen(delta);
1567
1568 if(SAME_TEAM(gen, this))
1569 if(max_dist <= 0 || distance <= max_dist)
1570 if(closest_target == NULL || distance <= smallest_distance )
1571 {
1572 closest_target = gen;
1573 smallest_distance = distance;
1574 }
1575 }
1576
1577 return closest_target;
1578}
1579
1583{
1584 int n = 0;
1585 for(entity cp = ons_worldcplist; cp; cp = cp.ons_worldcpnext)
1586 {
1587 if(SAME_TEAM(cp, this))
1588 if(cp.iscaptured)
1589 n++;
1590 }
1591 for(entity gen = ons_worldgeneratorlist; gen; gen = gen.ons_worldgeneratornext)
1592 {
1593 if(SAME_TEAM(gen, this))
1594 n++;
1595 }
1596 return n;
1597}
1598
1604bool ons_Teleport(entity player, entity tele_target, float range, bool tele_effects)
1605{
1606 if ( !tele_target )
1607 return false;
1608
1609 int i;
1610 vector loc;
1611 float theta;
1612 // narrow the range for each iteration to increase chances that a spawnpoint
1613 // can be found even if there's little room around the control point
1614 float iteration_scale = 1;
1615 for(i = 0; i < 16; ++i)
1616 {
1617 iteration_scale -= i / 16;
1618 theta = random() * (2 * M_PI);
1619 loc_y = sin(theta);
1620 loc_x = cos(theta);
1621 loc_z = 0;
1622 loc *= random() * range * iteration_scale;
1623
1624 loc += tele_target.origin + '0 0 128' * iteration_scale;
1625
1626 tracebox(loc, STAT(PL_MIN, player), STAT(PL_MAX, player), loc, MOVE_NORMAL, player);
1627 if(trace_fraction == 1.0 && !trace_startsolid)
1628 {
1629 traceline(tele_target.origin, loc, MOVE_NOMONSTERS, tele_target); // double check to make sure we're not spawning outside the NULL
1630 if(trace_fraction == 1.0 && !trace_startsolid)
1631 {
1632 if ( tele_effects )
1633 {
1634 Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1);
1635 sound (player, CH_TRIGGER, SND_TELEPORT, VOL_BASE, ATTEN_NORM);
1636 }
1637 setorigin(player, loc);
1638 player.angles = '0 1 0' * ( theta * RAD2DEG + 180 );
1639 makevectors(player.angles);
1640 player.fixangle = true;
1641 if (IS_BOT_CLIENT(player))
1642 {
1643 player.v_angle = player.angles;
1644 bot_aim_reset(player);
1645 }
1646 player.teleport_antispam = time + autocvar_g_onslaught_teleport_wait;
1647
1648 if ( tele_effects )
1649 Send_Effect(EFFECT_TELEPORT, player.origin + v_forward * 32, '0 0 0', 1);
1650 return true;
1651 }
1652 }
1653 }
1654
1655 return false;
1656}
1657
1658// ==============
1659// Hook Functions
1660// ==============
1661
1662MUTATOR_HOOKFUNCTION(ons, reset_map_global)
1663{
1665 STAT(ROUNDLOST, it) = false;
1666 it.ons_deathloc = '0 0 0';
1668 it.clientcamera = it;
1669 });
1670 return false;
1671}
1672
1674{
1675 entity player = M_ARGV(0, entity);
1676
1677 player.ons_deathloc = '0 0 0';
1678}
1679
1680MUTATOR_HOOKFUNCTION(ons, MakePlayerObserver)
1681{
1682 entity player = M_ARGV(0, entity);
1683
1684 player.ons_deathloc = '0 0 0';
1685}
1686
1687MUTATOR_HOOKFUNCTION(ons, PlayerSpawn)
1688{
1689 entity player = M_ARGV(0, entity);
1690
1692 {
1693 player.player_blocked = true;
1694 return false;
1695 }
1696
1697 entity l;
1698 for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
1699 {
1700 l.sprite.SendFlags |= 16;
1701 }
1702 for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
1703 {
1704 l.sprite.SendFlags |= 16;
1705 }
1706
1707 if(ons_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); }
1708
1710 if ( player.ons_spawn_by )
1711 if ( ons_Teleport(player,player.ons_spawn_by,autocvar_g_onslaught_teleport_radius,false) )
1712 {
1713 player.ons_spawn_by = NULL;
1714 return false;
1715 }
1716
1719 {
1721 entity tmp_entity, closest_target = NULL;
1722 vector spawn_loc = player.ons_deathloc;
1723
1724 // new joining player or round reset, don't bother checking
1725 if(spawn_loc == '0 0 0') { return false; }
1726
1727 if(random_target) { RandomSelection_Init(); }
1728
1729 for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext)
1730 {
1731 if(SAME_TEAM(tmp_entity, player))
1732 {
1733 if(random_target)
1734 RandomSelection_AddEnt(tmp_entity, 1, 1);
1735 else if(vlen2(tmp_entity.origin - spawn_loc) <= vlen2(closest_target.origin - spawn_loc) || closest_target == NULL)
1736 closest_target = tmp_entity;
1737 }
1738 }
1739
1740 if(random_target) { closest_target = RandomSelection_chosen_ent; }
1741
1742 if(closest_target)
1743 {
1744 float i;
1745 vector loc;
1746 float iteration_scale = 1;
1747 for(i = 0; i < 10; ++i)
1748 {
1749 iteration_scale -= i / 10;
1750 loc = closest_target.origin + '0 0 96' * iteration_scale;
1751 loc += ('0 1 0' * random()) * 128 * iteration_scale;
1752 tracebox(loc, STAT(PL_MIN, player), STAT(PL_MAX, player), loc, MOVE_NORMAL, player);
1753 if(trace_fraction == 1.0 && !trace_startsolid)
1754 {
1755 traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the NULL
1756 if(trace_fraction == 1.0 && !trace_startsolid)
1757 {
1758 setorigin(player, loc);
1759 player.angles = normalize(loc - closest_target.origin) * RAD2DEG;
1760 return false;
1761 }
1762 }
1763 }
1764 }
1765 }
1766
1769 {
1771 entity tmp_entity, closest_target = NULL;
1772 vector spawn_loc = player.ons_deathloc;
1773
1774 // new joining player or round reset, don't bother checking
1775 if(spawn_loc == '0 0 0') { return false; }
1776
1777 if(random_target) { RandomSelection_Init(); }
1778
1779 for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
1780 {
1781 if(random_target)
1782 RandomSelection_AddEnt(tmp_entity, 1, 1);
1783 else
1784 {
1785 if(SAME_TEAM(tmp_entity, player))
1786 if(vlen2(tmp_entity.origin - spawn_loc) <= vlen2(closest_target.origin - spawn_loc) || closest_target == NULL)
1787 closest_target = tmp_entity;
1788 }
1789 }
1790
1791 if(random_target) { closest_target = RandomSelection_chosen_ent; }
1792
1793 if(closest_target)
1794 {
1795 float i;
1796 vector loc;
1797 float iteration_scale = 1;
1798 for(i = 0; i < 10; ++i)
1799 {
1800 iteration_scale -= i / 10;
1801 loc = closest_target.origin + '0 0 128' * iteration_scale;
1802 loc += ('0 1 0' * random()) * 256 * iteration_scale;
1803 tracebox(loc, STAT(PL_MIN, player), STAT(PL_MAX, player), loc, MOVE_NORMAL, player);
1804 if(trace_fraction == 1.0 && !trace_startsolid)
1805 {
1806 traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the NULL
1807 if(trace_fraction == 1.0 && !trace_startsolid)
1808 {
1809 setorigin(player, loc);
1810 player.angles = normalize(loc - closest_target.origin) * RAD2DEG;
1811 return false;
1812 }
1813 }
1814 }
1815 }
1816 }
1817
1818 return false;
1819}
1820
1821MUTATOR_HOOKFUNCTION(ons, PlayerDies)
1822{
1824
1825 frag_target.ons_deathloc = frag_target.origin;
1826 entity l;
1827 for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
1828 {
1829 l.sprite.SendFlags |= 16;
1830 }
1831 for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
1832 {
1833 l.sprite.SendFlags |= 16;
1834 }
1835
1838 stuffcmd(frag_target, "qc_cmd_cl hud clickradar\n");
1839
1840 return false;
1841}
1842
1843MUTATOR_HOOKFUNCTION(ons, MonsterMove)
1844{
1845 entity mon = M_ARGV(0, entity);
1846
1847 entity e = find(NULL, targetname, mon.target);
1848 if (e != NULL)
1849 mon.team = e.team;
1850}
1851
1853{
1854 entity own = this.owner;
1855
1856 if(!own) { delete(this); return; }
1857
1858 if(own.targetname)
1859 {
1860 entity e = find(NULL, target, own.targetname);
1861 if(e != NULL)
1862 {
1863 own.team = e.team;
1864
1865 own.use(own, e, NULL);
1866 }
1867 }
1868
1869 delete(this);
1870}
1871
1872MUTATOR_HOOKFUNCTION(ons, MonsterSpawn)
1873{
1874 entity mon = M_ARGV(0, entity);
1875
1876 entity e = spawn();
1877 e.owner = mon;
1879}
1880
1882{
1883 entity own = this.owner;
1884
1885 if(!own) { delete(this); return; }
1886
1887 if(own.targetname)
1888 {
1889 entity e = find(NULL, target, own.targetname);
1890 if(e != NULL)
1891 {
1892 own.team = e.team;
1893 own.active = ACTIVE_NOT;
1894
1895 own.use(own, e, NULL);
1896 }
1897 }
1898
1899 delete(this);
1900}
1901
1902MUTATOR_HOOKFUNCTION(ons, TurretSpawn)
1903{
1904 entity turret = M_ARGV(0, entity);
1905
1906 entity e = spawn();
1907 e.owner = turret;
1909
1910 return false;
1911}
1912
1913MUTATOR_HOOKFUNCTION(ons, HavocBot_ChooseRole)
1914{
1915 entity bot = M_ARGV(0, entity);
1916
1918 return true;
1919}
1920
1922{
1923 // onslaught is special
1924 for(entity tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
1925 {
1926 if (Team_IsValidTeam(tmp_entity.team))
1927 {
1928 M_ARGV(0, float) |= Team_TeamToBit(tmp_entity.team);
1929 }
1930 }
1931
1932 return true;
1933}
1934
1936{
1937 entity spectatee = M_ARGV(0, entity);
1938 entity client = M_ARGV(1, entity);
1939
1940 STAT(ROUNDLOST, client) = STAT(ROUNDLOST, spectatee); // make spectators see it too
1941}
1942
1944{
1945 if(MUTATOR_RETURNVALUE) // command was already handled?
1946 return false;
1947
1948 entity player = M_ARGV(0, entity);
1949 string cmd_name = M_ARGV(1, string);
1950 int cmd_argc = M_ARGV(2, int);
1951
1952 if ( cmd_name == "ons_spawn" )
1953 {
1954 vector pos = player.origin;
1955 if(cmd_argc > 1)
1956 pos_x = stof(argv(1));
1957 if(cmd_argc > 2)
1958 pos_y = stof(argv(2));
1959 if(cmd_argc > 3)
1960 pos_z = stof(argv(3));
1961
1962 if ( IS_PLAYER(player) )
1963 {
1964 entity source_point = ons_Nearest_ControlPoint(player, player.origin, autocvar_g_onslaught_teleport_radius);
1965
1966 if ( !source_point && GetResource(player, RES_HEALTH) > 0 )
1967 {
1968 sprint(player, "\nYou need to be next to a control point\n");
1969 return true;
1970 }
1971
1973
1974 if ( closest_target == NULL )
1975 {
1976 sprint(player, "\nNo control point found\n");
1977 return true;
1978 }
1979
1980 if ( GetResource(player, RES_HEALTH) <= 0 )
1981 {
1982 player.ons_spawn_by = closest_target;
1983 player.respawn_flags = player.respawn_flags | RESPAWN_FORCE;
1984 }
1985 else
1986 {
1987 if ( source_point == closest_target )
1988 {
1989 sprint(player, "\nTeleporting to the same point\n");
1990 return true;
1991 }
1992
1993 if ( !ons_Teleport(player,closest_target,autocvar_g_onslaught_teleport_radius,true) )
1994 sprint(player, "\nUnable to teleport there\n");
1995 }
1996
1997 return true;
1998 }
1999
2000 return true;
2001 }
2002 return false;
2003}
2004
2006{
2007 if(MUTATOR_RETURNVALUE || game_stopped) return false;
2008
2009 entity player = M_ARGV(0, entity);
2010
2011 if((time > player.teleport_antispam) && (!IS_DEAD(player)) && !player.vehicle)
2012 {
2013 entity source_point = ons_Nearest_ControlPoint(player, player.origin, autocvar_g_onslaught_teleport_radius);
2014 if ( source_point )
2015 {
2016 stuffcmd(player, "qc_cmd_cl hud clickradar\n");
2017 return true;
2018 }
2019 }
2020}
2021
2022MUTATOR_HOOKFUNCTION(ons, PlayHitsound)
2023{
2024 entity frag_victim = M_ARGV(0, entity);
2025
2026 return (frag_victim.classname == "onslaught_generator" && !frag_victim.isshielded)
2027 || (frag_victim.classname == "onslaught_controlpoint_icon" && !frag_victim.owner.isshielded);
2028}
2029
2030MUTATOR_HOOKFUNCTION(ons, SendWaypoint)
2031{
2032 entity wp = M_ARGV(0, entity);
2033 entity to = M_ARGV(1, entity);
2034 int sf = M_ARGV(2, int);
2035 int wp_flag = M_ARGV(3, int);
2036
2037 if(sf & 16)
2038 {
2039 if(wp.owner.classname == "onslaught_controlpoint")
2040 {
2041 entity wp_owner = wp.owner;
2043 if(SAME_TEAM(e, wp_owner) && GetResource(wp_owner.goalentity, RES_HEALTH) >= wp_owner.goalentity.max_health) { wp_flag |= 2; }
2044 if(!ons_ControlPoint_Attackable(wp_owner, e.team)) { wp_flag |= 2; }
2045 }
2046 if(wp.owner.classname == "onslaught_generator")
2047 {
2048 entity wp_owner = wp.owner;
2049 if(wp_owner.isshielded && GetResource(wp_owner, RES_HEALTH) >= wp_owner.max_health) { wp_flag |= 2; }
2050 if(GetResource(wp_owner, RES_HEALTH) <= 0) { wp_flag |= 2; }
2051 }
2052 }
2053
2054 M_ARGV(3, int) = wp_flag;
2055}
2056
2057MUTATOR_HOOKFUNCTION(ons, TurretValidateTarget)
2058{
2059 entity turret_target = M_ARGV(1, entity);
2060
2061 if(substring(turret_target.classname, 0, 10) == "onslaught_") // don't attack onslaught targets, that's the player's job!
2062 {
2063 M_ARGV(3, float) = -3;
2064 return true;
2065 }
2066
2067 return false;
2068}
2069
2070MUTATOR_HOOKFUNCTION(ons, TurretThink)
2071{
2072 entity turret = M_ARGV(0, entity);
2073
2074 // ONS uses somewhat backwards linking.
2075 if(turret.target)
2076 {
2077 entity e = find(NULL, targetname, turret.target);
2078 if (e != NULL)
2079 turret.team = e.team;
2080 }
2081
2082 if(turret.team != turret.tur_head.team)
2083 turret_respawn(turret);
2084}
2085
2086
2087// ==========
2088// Spawnfuncs
2089// ==========
2090
2091/*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16)
2092 Link between control points.
2093
2094 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.
2095
2096keys:
2097"target" - first control point.
2098"target2" - second control point.
2099 */
2100spawnfunc(onslaught_link)
2101{
2102 if(!g_onslaught) { delete(this); return; }
2103
2104 if (this.target == "" || this.target2 == "")
2105 objerror(this, "target and target2 must be set\n");
2106
2107 this.ons_worldlinknext = ons_worldlinklist; // link into ons_worldlinklist
2108 ons_worldlinklist = this;
2109
2111 Net_LinkEntity(this, false, 0, ons_Link_Send);
2112}
2113
2114/*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128)
2115 Control point. Be sure to give this enough clearance so that the shootable part has room to exist
2116
2117 This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity.
2118
2119keys:
2120"targetname" - name that spawnfunc_onslaught_link entities will use to target this.
2121"target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities.
2122"message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc)
2123 */
2124
2125spawnfunc(onslaught_controlpoint)
2126{
2127 if(!g_onslaught) { delete(this); return; }
2128
2130}
2131
2132/*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64)
2133 Base generator.
2134
2135 spawnfunc_onslaught_link entities can target this.
2136
2137keys:
2138"team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET.
2139"targetname" - name that spawnfunc_onslaught_link entities will use to target this.
2140 */
2141spawnfunc(onslaught_generator)
2142{
2143 if(!g_onslaught) { delete(this); return; }
2144 if(!this.team) { objerror(this, "team must be set"); }
2145
2146 ons_GeneratorSetup(this);
2147}
2148
2149// scoreboard setup
2151{
2153 int teams = TeamBalance_GetAllowedTeams(balance);
2154 TeamBalance_Destroy(balance);
2156 field_team(ST_ONS_GENS, "generators", SFL_SORT_PRIO_PRIMARY);
2157 field(SP_ONS_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2158 field(SP_ONS_TAKES, "takes", 0);
2159 });
2160}
2161
2169
2171{
2172 g_onslaught = true;
2173 g_onsshields = IL_NEW();
2175
2176 cam = new(objective_camera);
2177
2179}
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:245
#define IS_PLAYER(s)
Definition player.qh:243
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:503
#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:124
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:82
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:118
const int MSG_ENTITY
Definition net.qh:115
#define WriteHeader(to, id)
Definition net.qh:221
void Net_LinkEntity(entity e, bool docull, float dt, bool(entity this, entity to, int sendflags) sendfunc)
Definition net.qh:123
#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:747
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:107
#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:1793
void PlayerUseKey(entity this)
Definition client.qc:2584
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:2321
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)
Definition sv_nades.qc:885
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:50
#define IS_VEHICLE(v)
Definition utils.qh:22
#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:2407
void InitializeEntity(entity e, void(entity this) func, int order)
Definition world.qc:2209
float autocvar_timelimit_suddendeath
Definition world.qh:30