‪Black Ops 3 Source Code Explorer  0.1
‪An script explorer for Black Ops 3 by ZeRoY
_siegebot_theia.gsc
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // #using
3 // ----------------------------------------------------------------------------
4 #using scripts\codescripts\struct;
5 
6 #using scripts\shared\gameskill_shared;
7 #using scripts\shared\math_shared;
8 #using scripts\shared\statemachine_shared;
9 #using scripts\shared\system_shared;
10 #using scripts\shared\array_shared;
11 #using scripts\shared\util_shared;
12 #using scripts\shared\clientfield_shared;
13 
14 #using scripts\shared\ai_shared;
15 #using scripts\shared\vehicle_shared;
16 #using scripts\shared\vehicle_death_shared;
17 #using scripts\shared\vehicle_ai_shared;
18 
19 #using scripts\shared\ai\systems\blackboard;
20 #using scripts\shared\ai\blackboard_vehicle;
21 
22 #using scripts\shared\turret_shared;
23 #using scripts\shared\weapons\_spike_charge_siegebot;
24 
25 #using scripts\mp\vehicles\_siegebot;
26 #using scripts\shared\callbacks_shared;
27 #using scripts\shared\laststand_shared;
28 
29 #using scripts\shared\util_shared;
30 
31 #insert scripts\shared\shared.gsh;
32 #insert scripts\shared\version.gsh;
33 #insert scripts\shared\statemachine.gsh;
34 #insert scripts\shared\archetype_shared\archetype_shared.gsh;
35 
36 #insert scripts\shared\ai\utility.gsh;
37 
38 #using scripts\mp\killstreaks\_killstreaks;
39 #using scripts\mp\killstreaks\_killstreak_bundles;
40 
41 // ----------------------------------------------------------------------------
42 // #define
43 // ----------------------------------------------------------------------------
44 #define WEAPON_GUN_NORMAL "siegebot_gun_turret"
45 #define WEAPON_GUN_SPRAY "siegebot_gun_turret_spray"
46 #define WEAPON_JAVELIN "siegebot_javelin_turret"
47 
48 #define JUMP_COOLDOWN 11
49 #define IGNORE_COOLDOWN 12
50 
51 #define DEBUG_ON false
52 
53 #define SIEGEBOT_THEIA_BUNDLE "siegebot_theia"
54 
55 // ----------------------------------------------------------------------------
56 // #namespace
57 // ----------------------------------------------------------------------------
58 #namespace siegebot_theia;
59 
60 ‪REGISTER_SYSTEM( "siegebot_theia", &‪__init__, undefined )
61 
62 #using_animtree( "generic" );
63 
64 function ‪__init__()
65 {
66  vehicle::add_main_callback( "siegebot_theia", &‪siegebot_initialize );
67 
68  ‪clientfield::register( "vehicle", "sarah_rumble_on_landing", ‪VERSION_SHIP, 1, "counter" );
69  ‪clientfield::register( "vehicle", "sarah_minigun_spin", ‪VERSION_SHIP, 1, "int" );
70 }
71 
73 {
74  self useanimtree( #animtree );
75 
78 
79  self.health = self.healthdefault;
80 
82 
83  self EnableAimAssist();
84  self SetNearGoalNotifyDist( self.radius * 1.2 );
85 
86  Target_Set( self, ( 0, 0, 150 ) );
87 
88  self.fovcosine = 0; // +/-90 degrees = 180 fov, err 0 actually means 360 degree view
89  self.fovcosinebusy = 0;
90  self.maxsightdistsqrd = ‪SQR( 10000 );
91 
92  assert( isdefined( self.scriptbundlesettings ) );
93 
94  self.settings = ‪struct::get_script_bundle( "vehiclecustomsettings", self.scriptbundlesettings );
95 
96  self.goalRadius = 9999999;
97  self.goalHeight = 5000;
98  self SetGoal( self.origin, false, self.goalRadius, self.goalHeight );
99 
100  self.overrideVehicleDamage = &‪theia_callback_damage;
101 
102  self ‪pain_toggle( true );
103 
104  //util::magic_bullet_shield( self ); // we will disable death for the entire battle, until health get below threshold and theia get to designated location, then we grant her the privilege to die
105 
106  if( !SessionModeIsMultiplayerGame() )
107  self ‪initJumpStruct();
108 
109  self SetGunnerTurretOnTargetRange( 0, self.settings.gunner_turret_on_target_range );
110 
111  self ‪locomotion_start();
112 
113  self thread ‪init_clientfields();
114 
115  self.damageLevel = 0;
116  self.newDamageLevel = self.damageLevel;
117 
119  self ‪init_fake_targets();
120 
121  if ( isdefined( self.combat_goal_volume ) )
122  {
123  self SetGoal( self.combat_goal_volume );
124  }
125 
126  if ( !isdefined( self.height ) )
127  {
128  self.height = self.radius;
129  }
130 
131  self.noCybercom = true;
132  self.ignoreFireFly = true;
133  self.ignoreDecoy = true;
135 
138  self.heatlh = self.maxhealth;
139 }
140 
142 {
143  self ‪vehicle::lights_on();
144  self ‪vehicle::toggle_lights_group( 1, true );
145  self ‪vehicle::toggle_lights_group( 2, true );
146  self ‪vehicle::toggle_lights_group( 3, true );
147  self ‪clientfield::set( "sarah_minigun_spin", 0 );
148 }
149 
150 function ‪defaultRole()
151 {
153 
157 
158  self ‪vehicle_ai::get_state_callbacks( "pain" ).enter_func = &‪pain_enter;
159  self ‪vehicle_ai::get_state_callbacks( "pain" ).update_func = &‪pain_update;
160  self ‪vehicle_ai::get_state_callbacks( "pain" ).exit_func = &‪pain_exit;
161 
162  self ‪vehicle_ai::get_state_callbacks( "scripted" ).exit_func = &‪scripted_exit;
163 
164  self ‪vehicle_ai::get_state_callbacks( "death" ).update_func = &‪state_death_update;
165 
166  self ‪vehicle_ai::add_state( "jumpUp",
170 
171  self ‪vehicle_ai::add_state( "jumpDown",
175 
176  self ‪vehicle_ai::add_state( "jumpGroundToGround",
180 
181  // this is the normal siegebot ground combat
182  self ‪vehicle_ai::add_state( "groundCombat",
183  undefined,
186 
187  self ‪vehicle_ai::add_state( "prepareDeath",
188  undefined,
190  undefined );
191 
192  ‪vehicle_ai::add_interrupt_connection( "groundCombat", "pain", "pain" );
193 
194  ‪vehicle_ai::add_utility_connection( "emped", "groundCombat" );
195  ‪vehicle_ai::add_utility_connection( "pain", "groundCombat" );
196 
198  ‪vehicle_ai::add_utility_connection( "jumpDown", "groundCombat" );
199 
200  ‪vehicle_ai::add_utility_connection( "groundCombat", "jumpGroundToGround", &‪can_jump_ground_to_ground );
201  ‪vehicle_ai::add_utility_connection( "jumpGroundToGround", "groundCombat" );
202 
203  ‪vehicle_ai::add_utility_connection( "groundCombat", "jumpUp", &‪can_jump_up );
204  ‪vehicle_ai::add_utility_connection( "jumpUp", "combat" );
205 
206  ‪vehicle_ai::add_utility_connection( "groundCombat", "prepareDeath", &‪should_prepare_death );
207 
209  ‪vehicle_ai::Cooldown( "jumpUp", ‪JUMP_COOLDOWN * 3 );
210  ‪vehicle_ai::StartInitialState( "groundCombat" );
211 }
212 
213 // ----------------------------------------------
214 // State: death
215 // ----------------------------------------------
216 function ‪state_death_update( params )
217 {
218  self endon( "death" );
219  self endon( "nodeath_thread" );
220 
221  self SetTurretSpinning( false );
222  self ‪clean_up_spawned();
223 
225  self SetTurretTargetRelativeAngles( (0,0,0) ); // Reset the turret angles so the animation lines up
226 
228  self ‪vehicle_death::set_death_model( self.deathmodel, self.modelswapdelay );
230  self playsound("veh_quadtank_sparks");
231 }
232 
234 {
235  if ( isdefined( self.jump ) )
236  {
237  self.jump.linkEnt Delete();
238  }
239 
240  if ( isdefined( self.fakeTargetEnt ) )
241  {
242  self.fakeTargetEnt Delete();
243  }
244 
245  if ( isdefined( self.spikeFakeTargets ) )
246  {
247  foreach( target in self.spikeFakeTargets )
248  {
249  target Delete();
250  }
251  }
252 }
253 // State: death ----------------------------------
254 
255 // ----------------------------------------------
256 // State: pain
257 // ----------------------------------------------
258 function ‪pain_toggle( enabled )
259 {
260  self._enablePain = enabled;
261 }
262 
264 {
266  return isdefined( state ) && state != "pain" && self._enablePain;
267 }
268 
269 function ‪pain_enter( params )
270 {
272 }
273 
274 function ‪pain_exit( params )
275 {
276  self SetBrake( 0 );
277 }
278 
279 function ‪pain_update( params )
280 {
281  self endon( "death" );
282 
283  if ( 1 <= self.damagelevel && self.damagelevel <= 4 )
284  {
285  asmState = "damage_" + self.damageLevel + "@pain";
286  }
287  else
288  {
289  asmState = "normal@pain";
290  }
291 
292  self ASMRequestSubstate( asmState );
293  self ‪vehicle_ai::waittill_asm_complete( asmState, 5 );
294 
297 
298  previous_state = ‪vehicle_ai::get_previous_state();
299  self ‪vehicle_ai::set_state( previous_state );
301 }
302 // State: pain ----------------------------------
303 
304 // ----------------------------------------------
305 // State: prepare_death
306 // ----------------------------------------------
307 function ‪should_prepare_death( from_state, to_state, connection )
308 {
309  prepare_death_threshold = self.healthdefault * 0.1;
310  if ( self.health < prepare_death_threshold )
311  {
312  return 99999999; // big number so we are guarenteed to take this state
313  }
314 
315  return 0;
316 }
317 
318 function ‪prepare_death_update( params )
319 {
320  self endon ( "death" );
321  self endon( "change_state" );
322 
323  // don't shoot spike immediately
324  ‪vehicle_ai::Cooldown( "spike_on_ground", 2 );
325  self thread ‪Attack_Thread_Gun();
326  self thread ‪Attack_Thread_Rocket();
328 
329  ‪startTime = GetTime();
330  while ( Distance2DSquared( self.origin, self.death_goal_point ) > 1200 && ‪vehicle_ai::TimeSince( ‪startTime ) < 8 )
331  {
332  self SetVehGoalPos( self.death_goal_point, false, true );
333  self SetBrake( 0 );
334  wait 1;
335  }
336 
337  self CancelAIMove();
338  self ClearVehGoalPos();
339  self SetBrake( 1 );
340 
341  self notify( "end_attack_thread" );
342  self notify( "end_movement_thread" );
343  self.jump.highground_history = self.jump.highgrounds[0];
344  self ‪state_jumpUp_enter( params );
345  self ‪state_jump_update( params );
346 
347  // now she is allowed to die
348  //util::stop_magic_bullet_shield( self );
349  self.disable_side_step = true;
350  self ‪state_balconyCombat_update( params );
351 }
352 // State: prepare_death -------------------------
353 
354 // ----------------------------------------------
355 // State: scripted
356 // ----------------------------------------------
357 function ‪scripted_exit( params )
358 {
360  ‪vehicle_ai::Cooldown( "jumpUp", ‪JUMP_COOLDOWN * 3 );
361 }
362 // State: scripted ------------------------------
363 
364 // ----------------------------------------------
365 // State: jump
366 // ----------------------------------------------
368 {
369  if ( isdefined( self.jump ) )
370  {
371  self Unlink();
372  self.jump.linkEnt Delete();
373  self.jump Delete();
374  }
375 
376  self.jump = spawnstruct();
377  self.jump.linkEnt = ‪Spawn( "script_origin", self.origin );
378  self.jump.in_air = false;
379  self.jump.highgrounds = ‪struct::get_array( "balcony_point" );
380  self.jump.groundpoints = ‪struct::get_array( "ground_point" );
381  self.arena_center = ‪struct::get( "arena_center" ).origin;
382  self.death_goal_point = ‪struct::get( "death_goal_point" ).origin;
383  self.combat_goal_volume = GetEnt( "theia_combat_region", "targetname" );
384 
385  foreach( point in self.jump.highgrounds )
386  {
387  // fixing point -24566.2 23972.5 -20000
388  if ( DistanceSquared( point.origin, (-24566.2, 23972.5, -20000) ) < ‪SQR( 100 ) )
389  {
390  point.origin += (20, -20, -100);
391  }
392  // fixing point -27291.2 25825.6 -20072
393  else if ( DistanceSquared( point.origin, (-27291.2, 25825.6, -20072) ) < ‪SQR( 100 ) )
394  {
395  point.origin += (0, 35, 0);
396  }
397  }
398 
399  assert( self.jump.highgrounds.size > 0 );
400  assert( self.jump.groundpoints.size > 0 );
401  assert( isdefined( self.arena_center ) );
402 }
403 
404 function ‪can_jump_up( from_state, to_state, connection )
405 {
406  if ( !‪vehicle_ai::IsCooldownReady( "jump" ) || !‪vehicle_ai::IsCooldownReady( "jumpUp" ) )
407  {
408  return 0;
409  }
410 
411  target = ‪highGroundPoint( 800, 2000, self.jump.highgrounds, 1200 );
412 
413  if ( isdefined( target ) )
414  {
415  self.jump.highground_history = target;
416  return 500;
417  }
418 
419  return 0;
420 }
421 
422 function ‪state_jumpUp_enter( params )
423 {
424  goal = self.jump.highground_history.origin;
425 
426  ‪trace = PhysicsTrace( goal + ( 0, 0, 200 ), goal - ( 0, 0, 10000 ), ( -10, -10, -10 ), ( 10, 10, 10 ), self, ‪PHYSICS_TRACE_MASK_VEHICLE );
427  if ( ‪DEBUG_ON )
428  {
429  /#debugstar( goal, 60000, (0,1,0) ); #/
430  /#debugstar( ‪trace[ "position" ], 60000, (0,1,0) ); #/
431  /#line(goal, ‪trace[ "position" ], (0,1,0), 1, false, 60000 ); #/
432  }
433  if ( ‪trace[ "fraction" ] < 1 )
434  {
435  goal = ‪trace[ "position" ];
436  }
437 
438  self.jump.highground_history = goal;
439  self.jump.goal = goal;
440 
441  params.scaleForward = 70;
442  params.gravityForce = (0, 0, -5);
443  params.upByHeight = 10;
444  params.landingState = "land_turn@jump";
445 
446  self ‪pain_toggle( false );
447 
449 }
450 
451 function ‪can_jump_down( from_state, to_state, connection )
452 {
453  if ( !‪vehicle_ai::IsCooldownReady( "jump" ) || self.dontchangestate === true )
454  {
455  return 0;
456  }
457 
458  target = ‪get_jumpon_target( 800, 2000, 1300 );
459 
460  if ( isdefined( target ) )
461  {
462  self.jump.lowground_history = target;
463  return 500;
464  }
465 
466  return 0;
467 }
468 
469 function ‪state_jumpDown_enter( params )
470 {
471  goal = self.jump.lowground_history;
472 
473  ‪trace = PhysicsTrace( goal + ( 0, 0, 500 ), goal - ( 0, 0, 10000 ), ( -10, -10, -10 ), ( 10, 10, 10 ), self, ‪PHYSICS_TRACE_MASK_VEHICLE );
474  if ( ‪DEBUG_ON )
475  {
476  /#debugstar( goal, 60000, (0,1,0) ); #/
477  /#debugstar( ‪trace[ "position" ], 60000, (0,1,0) ); #/
478  /#line(goal, ‪trace[ "position" ], (0,1,0), 1, false, 60000 ); #/
479  }
480  if ( ‪trace[ "fraction" ] < 1 )
481  {
482  goal = ‪trace[ "position" ];
483  }
484 
485  self.jump.lowground_history = goal;
486  self.jump.goal = goal;
487 
488  params.scaleForward = 70;
489  params.gravityForce = (0, 0, -5);
490  params.upByHeight = -5;
491  params.landingState = "land@jump";
492 
493  self ‪pain_toggle( false );
494 
496 }
497 
498 function ‪can_jump_ground_to_ground( from_state, to_state, connection )
499 {
500  if ( !‪vehicle_ai::IsCooldownReady( "jump" ) )
501  {
502  return 0;
503  }
504 
505  target = ‪get_jumpon_target( 800, 1800, 1300, false, 0, false );
506 
507  if ( isdefined( target ) )
508  {
509  self.jump.lowground_history = target;
510  return 400;
511  }
512 
513  return 0;
514 }
515 
516 function ‪state_jump_exit( params )
517 {
518  self ‪pain_toggle( true );
519 }
520 
521 function ‪state_jumpDown_exit( params )
522 {
523  self ‪pain_toggle( true );
524  self ‪vehicle_ai::Cooldown( "jumpUp", ‪JUMP_COOLDOWN + randomFloatRange( -1, 3 ) );
525 }
526 
527 function ‪state_jump_update( params )
528 {
529  self endon( "change_state" );
530  self endon( "death" );
531 
532  goal = self.jump.goal;
533 
534  self ‪face_target( goal );
535 
536  self.jump.linkEnt.origin = self.origin;
537  self.jump.linkEnt.angles = self.angles;
538 
540 
541  self LinkTo( self.jump.linkEnt );
542 
543  self.jump.in_air = true;
544 
545  if ( ‪DEBUG_ON )
546  {
547  /#debugstar( goal, 60000, (0,1,0) ); #/
548  /#debugstar( goal + (0,0,100), 60000, (0,1,0) ); #/
549  /#line(goal, goal + (0,0,100), (0,1,0), 1, false, 60000 ); #/
550  }
551 
552  // calculate distance and forces
553  totalDistance = Distance2D(goal, self.jump.linkEnt.origin);
554  forward = ‪FLAT_ORIGIN( ((goal - self.jump.linkEnt.origin) / totalDistance) );
555  upByDistance = MapFloat( 500, 2000, 46, 52, totalDistance );
556  antiGravityByDistance = MapFloat( 500, 2000, 0, 0.5, totalDistance );
557 
558  initVelocityUp = (0,0,1) * ( upByDistance + params.upByHeight );
559  initVelocityForward = forward * params.scaleForward * MapFloat( 500, 2000, 0.8, 1, totalDistance );
560  velocity = initVelocityUp + initVelocityForward;
561 
562  // start jumping
563  self ASMRequestSubstate( "inair@jump" );
564  self waittill( "engine_startup" );
565  self ‪vehicle::impact_fx( self.settings.startupfx1 );
566  self waittill( "leave_ground" );
567  self ‪vehicle::impact_fx( self.settings.takeofffx1 );
568 
569  jumpStart = GetTime();
570  while( true )
571  {
572  distanceToGoal = Distance2D(self.jump.linkEnt.origin, goal);
573 
574  antiGravityScaleUp = MapFloat( 0, 0.5, 0.6, 0, abs( 0.5 - distanceToGoal / totalDistance ) );
575  antiGravityScale = MapFloat( (self.radius * 1.0), (self.radius * 3), 0, 1, distanceToGoal );
576  antiGravity = antiGravityScale * antiGravityScaleUp * (-params.gravityForce) + (0,0,antiGravityByDistance);
577  if ( ‪DEBUG_ON ) /#line(self.jump.linkEnt.origin, self.jump.linkEnt.origin + antiGravity, (0,1,0), 1, false, 60000 ); #/
578 
579  velocityForwardScale = MapFloat( (self.radius * 1), (self.radius * 4), 0.2, 1, distanceToGoal );
580  velocityForward = initVelocityForward * velocityForwardScale;
581  if ( ‪DEBUG_ON ) /#line(self.jump.linkEnt.origin, self.jump.linkEnt.origin + velocityForward, (0,1,0), 1, false, 60000 ); #/
582 
583  oldVerticleSpeed = velocity[2];
584  velocity = (0,0, velocity[2]);
585  velocity += velocityForward + params.gravityForce + antiGravity;
586 
587  if ( oldVerticleSpeed > 0 && velocity[2] <= 0 )
588  {
589  self ASMRequestSubstate( "fall@jump" );
590  }
591 
592  if ( ( velocity[2] <= 0 && self.jump.linkEnt.origin[2] + velocity[2] <= goal[2] ) || ‪vehicle_ai::TimeSince( jumpStart ) > 10 )
593  {
594  break;
595  }
596 
597  heightThreshold = goal[2] + 110;
598  oldHeight = self.jump.linkEnt.origin[2];
599  self.jump.linkEnt.origin += velocity;
600 
601  if ( self.jump.linkEnt.origin[2] < heightThreshold && ( oldHeight > heightThreshold || ( oldVerticleSpeed > 0 && velocity[2] < 0 ) ) )
602  {
603  self notify( "start_landing" );
604  self ASMRequestSubstate( params.landingState );
605  }
606 
607  if ( ‪DEBUG_ON ) /#debugstar( self.jump.linkEnt.origin, 60000, (1,0,0) ); #/
609  }
610 
611  // landed
612  self.jump.linkEnt.origin = ‪FLAT_ORIGIN( self.jump.linkEnt.origin ) + ( 0, 0, goal[2] );
613  self notify( "land_crush" );
614 
615  // don't damage player, but crush player vehicle
616  foreach( player in level.players )
617  {
618  player._takedamage_old = player.takedamage;
619  player.takedamage = false;
620  }
621  self RadiusDamage( self.origin + ( 0,0,15 ), self.radiusdamageradius, self.radiusdamagemax, self.radiusdamagemin, self, "MOD_EXPLOSIVE" );
622 
623  foreach( player in level.players )
624  {
625  player.takedamage = player._takedamage_old;
626  player._takedamage_old = undefined;
627 
628  if ( Distance2DSquared( self.origin, player.origin ) < ‪SQR( 200 ) )
629  {
630  direction = ‪FLAT_ORIGIN( ( player.origin - self.origin ) );
631  if ( Abs( direction[0] ) < 0.01 && Abs( direction[1] ) < 0.01 )
632  {
633  direction = ( RandomFloatRange( 1, 2 ), RandomFloatRange( 1, 2 ), 0 );
634  }
635  direction = VectorNormalize( direction );
636  strength = 700;
637  player SetVelocity( player GetVelocity() + direction * strength );
638 
639  if ( player.health > 80 )
640  {
641  player DoDamage( player.health - 70, self.origin, self );
642  }
643  else
644  {
645  player DoDamage( 20, self.origin, self );
646  }
647  }
648  }
649 
650  self ‪vehicle::impact_fx( self.settings.landingfx1 );
652 
653  //rumble for landing from jump
654  self ‪clientfield::increment( "sarah_rumble_on_landing" );
655 
656  wait 0.3;
657 
658  self Unlink();
659 
661 
662  self.jump.in_air = false;
663 
664  self notify ( "jump_finished" );
665 
667  ‪vehicle_ai::Cooldown( "ignore_player", ‪IGNORE_COOLDOWN );
668 
669  self ‪vehicle_ai::waittill_asm_complete( params.landingState, 3 );
670 
672 }
673 // State: jump ----------------------------------
674 
675 // ----------------------------------------------
676 // State: combat
677 // ----------------------------------------------
679 {
681  self SetTurretTargetRelativeAngles( (0,0,0), 0 );
682  self SetTurretTargetRelativeAngles( (0,0,0), 1 );
683  self SetTurretTargetRelativeAngles( (0,0,0), 2 );
684  self SetTurretTargetRelativeAngles( (0,0,0), 3 );
685  self SetTurretTargetRelativeAngles( (0,0,0), 4 );
686 }
687 
689 {
690  self endon( "change_state" );
691  self endon( "death" );
692 
693  // face the correct direction
694  currentHighGround = undefined;
695  foreach( highGround in self.jump.highgrounds )
696  {
697  if ( distance2DSquared( highGround.origin, self.origin ) < ‪SQR( self.radius * 6 ) )
698  {
699  currentHighGround = highGround;
700  break;
701  }
702  }
703 
704  if ( !isdefined( currentHighGround ) )
705  {
706  self ‪vehicle_ai::ClearCooldown( "jump" );
708  }
709 
710  forward = anglesToForward( currentHighGround.angles );
711 
712  while ( true )
713  {
714  while ( !isdefined(self.enemy) )
715  {
716  wait 1;
717  }
718 
719  self ‪face_target( self.origin + forward * 10000 );
720 
721  javelinChance = self.damageLevel * 0.15;
722  if ( randomFloat( 1.0 ) < javelinChance )
723  {
725 
726  level notify( "theia_finished_platform_attack" );
728  wait 0.8;
729  }
730 
732 
733  level notify( "theia_finished_platform_attack" );
735 
736  if ( RandomFloat( 1 ) > 0.4 && self.disable_side_step !== true )
737  {
738  wait 0.2;
739  self ‪side_step();
740  }
741  wait 0.8;
742 
744 
745  level notify( "theia_finished_platform_attack" );
747  wait 0.8;
748  }
749 }
750 
751 function ‪side_step()
752 {
753  step_size = 180; // trace length, not the actual move length. need to match with animation
754 
755  right_dir = AnglesToRight( self.angles );
756  start = self.origin + (0,0,10);
757 
758  traceDir = right_dir;
759  jukeState = "juke_r@movement";
760  oppositeJukeState = "juke_l@movement";
761 
762  if ( ‪math::cointoss() )
763  {
764  traceDir = -traceDir;
765  jukeState = "juke_l@movement";
766  oppositeJukeState = "juke_r@movement";
767  }
768 
769  ‪trace = PhysicsTrace( start, start + traceDir * step_size, 0.8 * ( -self.radius, -self.radius, 0 ), 0.8 * ( self.radius, self.radius, self.height ), self, ‪PHYSICS_TRACE_MASK_VEHICLE );
770 
771  if ( ‪DEBUG_ON )
772  {
773  /#line(start, start + traceDir * step_size, (1,0,0), 1, false, 100 ); #/
774  }
775 
776  if ( ‪trace["fraction"] < 1 )
777  {
778  traceDir = -traceDir;
779  ‪trace = PhysicsTrace( start, start + traceDir * step_size, 0.8 * ( -self.radius, -self.radius, 0 ), 0.8 * ( self.radius, self.radius, self.height ), self, ‪PHYSICS_TRACE_MASK_VEHICLE );
780  jukeState = oppositeJukeState;
781  if ( ‪DEBUG_ON )
782  {
783  /#line(start, start + traceDir * step_size, (1,0,0), 1, false, 100 ); #/
784  }
785  }
786 
787  if ( ‪trace["fraction"] >= 1 )
788  {
789  self ASMRequestSubstate( jukeState );
790  self ‪vehicle_ai::waittill_asm_complete( jukeState, 3 );
791  self ‪locomotion_start();
792  return true;
793  }
794 
795  return false;
796 }
797 
798 function ‪state_balconyCombat_exit( params )
799 {
801  self SetTurretTargetRelativeAngles( (0,0,0), 0 );
802  self SetTurretTargetRelativeAngles( (0,0,0), 1 );
803  self SetTurretTargetRelativeAngles( (0,0,0), 2 );
804  self SetTurretTargetRelativeAngles( (0,0,0), 3 );
805  self SetTurretTargetRelativeAngles( (0,0,0), 4 );
806 }
807 // State: combat ----------------------------------
808 
809 // ----------------------------------------------
810 // State: groundCombat
811 // ----------------------------------------------
813 {
814  self endon( "death" );
815  self endon( "change_state" );
816 
817  // don't shoot spike immediately
818  if( ‪vehicle_ai::get_previous_state() === "jump" )
819  {
820  ‪vehicle_ai::Cooldown( "spike_on_ground", 2 );
821  }
822 
823  self thread ‪Attack_Thread_Gun();
824  self thread ‪Attack_Thread_Rocket();
825  self thread ‪Movement_Thread();
826  self thread ‪footstep_left_monitor();
827  self thread ‪footstep_right_monitor();
828 
829  while ( true )
830  {
832  wait 1;
833  }
834 }
835 
837 {
838  origin = self GetTagOrigin( ‪tag_name );
839 
840  // don't damage player, but crush player vehicle
841  foreach( player in level.players )
842  {
843  player._takedamage_old = player.takedamage;
844  player.takedamage = false;
845  }
846  self RadiusDamage( origin + ( 0,0,10 ), self.radius, 200, 200, self, "MOD_EXPLOSIVE" );
847 
848  foreach( player in level.players )
849  {
850  player.takedamage = player._takedamage_old;
851  player._takedamage_old = undefined;
852 
853  if ( Distance2DSquared( origin, player.origin ) < ‪SQR( self.radius ) )
854  {
855  player DoDamage( 15, origin, self );
856  }
857  }
858 }
859 
861 {
862  self endon( "death" );
863  self endon( "change_state" );
864  self notify( "stop_left_footstep_damage" );
865  self endon( "stop_left_footstep_damage" );
866 
867  while ( true )
868  {
869  self waittill( "footstep_left_large_theia" );
870  ‪footstep_damage( "tag_leg_left_foot_animate" );
871  }
872 }
873 
875 {
876  self endon( "death" );
877  self endon( "change_state" );
878  self notify( "stop_right_footstep_damage" );
879  self endon( "stop_right_footstep_damage" );
880 
881  while ( true )
882  {
883  self waittill( "footstep_right_large_theia" );
884  ‪footstep_damage( "tag_leg_right_foot_animate" );
885  }
886 }
887 
888 function ‪highGroundPoint( distanceLimitMin, distanceLimitMax, pointsArray, idealDist )
889 {
890  /# Record3DText( "range: [" + distanceLimitMin + "," + distanceLimitMax + "]", self.origin, (1,0.5,0), "Script", self ); #/
891 
892  bestScore = 1000000; // lower the better
893  ‪result = undefined;
894  foreach( point in pointsArray )
895  {
896  distanceToTarget = Distance2D( point.origin, self.origin );
897  if ( distanceToTarget < distanceLimitMin || distanceLimitMax < distanceToTarget )
898  {
899  /# RecordStar( point.origin, (1,0.5,0) ); #/
900  /# Record3DText( "out of range: " + distanceToTarget, point.origin, (1,0.5,0), "Script", self ); #/
901  continue;
902  }
903 
904  score = Abs( distanceToTarget - idealDist );
905  if ( score < 200 )
906  {
907  score = randomFloat( 200 );
908  }
909 
910  if ( isdefined( self.jump.highground_history ) && Distance2DSquared( point.origin, self.jump.highground_history ) < ‪SQR( 50 ) )
911  {
912  score += 1000;
913  }
914 
915  /# RecordStar( point.origin, (1,0.5,0) ); #/
916  /# Record3DText( "dist: " + distanceToTarget + " score: " + score, point.origin, (1,0.5,0), "Script", self ); #/
917 
918  if ( score < bestScore )
919  {
920  bestScore = score;
921  ‪result = point;
922  }
923  }
924 
925  if ( isdefined( ‪result ) )
926  {
927  return ‪result;
928  }
929 
930  return undefined;
931 }
932 
933 function ‪state_groundCombat_exit( params )
934 {
935  self notify( "end_attack_thread" );
936  self notify( "end_movement_thread" );
937  self ClearTurretTarget();
938  self SetTurretSpinning( false );
939 }
940 
941 function ‪get_player_vehicle( player )
942 {
943  if ( isPlayer( player ) )
944  {
945  if ( player.usingvehicle && isdefined( player.viewlockedentity ) && isVehicle( player.viewlockedentity ) )
946  {
947  return player.viewlockedentity;
948  }
949  }
950 
951  return undefined;
952 }
953 
955 {
956  targets = level.players;
957 
958  vehicles = [];
959  foreach ( player in level.players )
960  {
961  vehicle = ‪get_player_vehicle( player );
962  if ( isdefined( vehicle ) )
963  {
964  ‪ARRAY_ADD( vehicles, vehicle );
965  }
966  }
967 
968  targets = ArrayCombine( targets, vehicles, false, false );
969  return targets;
970 }
971 
972 function ‪init_player_threat( player )
973 {
974  index = player GetEntityNumber();
975 
976  if ( !isdefined( self.player_threat ) )
977  {
978  self.player_threat = [];
979 
980  for( i = 0; i < 4; i++ )
981  {
982  self.player_threat[self.player_threat.size] = SpawnStruct();
983  }
984  }
985 
986  if ( !isdefined( self.player_threat[index].‪damage ) ||
987  !isdefined( self.player_threat[index].tempBoost ) ||
988  !isdefined( self.player_threat[index].tempBoostTimeout ) )
989  {
990  ‪reset_player_threat( player );
991  }
992 }
993 
994 // self == vehicle
996 {
1000 
1001  foreach( player in level.players )
1002  {
1003  self ‪init_player_threat( player );
1004  }
1005 }
1006 
1007 // self == vehicle
1008 function ‪reset_player_threat( player )
1009 {
1010  index = player GetEntityNumber();
1011 
1012  // find out other player's minDamage. this is to prevent hot join player never getting picked as target because damage factor is 0.
1013  minDamage = self.player_threat[index].damage;
1014  if ( !isdefined( minDamage ) )
1015  {
1016  minDamage = 1000000;
1017  }
1018 
1019  if ( self.player_threat.size > 0 )
1020  {
1021  foreach( threat in self.player_threat )
1022  {
1023  if ( isdefined( threat.damage ) )
1024  {
1025  minDamage = min( minDamage, threat.damage );
1026  }
1027  }
1028  }
1029  else
1030  {
1031  minDamage = 0;
1032  }
1033 
1034  self.player_threat[index].damage = minDamage;
1035  self.player_threat[index].tempBoost = 0;
1036  self.player_threat[index].tempBoostTimeout = 0;
1037 }
1038 
1039 // self == vehicle
1041 {
1042  index = player GetEntityNumber();
1043  self.player_threat[index].damage += ‪damage;
1044 }
1045 
1046 // self == vehicle
1047 function ‪add_player_threat_boost( player, boost, timeSeconds )
1048 {
1049  index = player GetEntityNumber();
1050 
1051  if ( self.player_threat[index].tempBoostTimeout <= GetTime() )
1052  {
1053  self.player_threat[index].tempBoost = 0;
1054  }
1055 
1056  self.player_threat[index].tempBoost += boost;
1057  self.player_threat[index].tempBoostTimeout = GetTime() + timeSeconds * 1000;
1058 }
1059 
1060 // self == vehicle
1061 function ‪get_player_threat( player )
1062 {
1063  if ( !‪is_valid_target( player ) )
1064  {
1065  return;
1066  }
1067 
1068  timeIgnoreOnSpawn = 7; //seconds
1069  currentTime = GetTime();
1070  if ( isdefined( player._spawn_time ) && ( player._spawn_time + timeIgnoreOnSpawn * 1000 > currentTime ) )
1071  {
1072  return;
1073  }
1074 
1075  index = player GetEntityNumber();
1076 
1077  if ( !isdefined( self.player_threat ) || !isdefined( self.player_threat[ index ] ) )
1078  {
1079  return;
1080  }
1081 
1082  threat = self.player_threat[index].damage;
1083 
1084  if ( self.player_threat[index].tempBoostTimeout > GetTime() )
1085  {
1086  threat += self.player_threat[index].tempBoost;
1087  }
1088 
1089  if ( self.main_target === player )
1090  {
1091  threat += 1000;
1092  }
1093 
1094  if( self VehSeenRecently( player, 3 ) )
1095  {
1096  threat += 1000;
1097  }
1098 
1099  if ( player.health < 50 )
1100  {
1101  threat -= 800;
1102  }
1103 
1104  distanceSqr = Distance2DSquared( self.origin, player.origin );
1105  if ( distanceSqr < ‪SQR( 800 ) )
1106  {
1107  threat += 800;
1108  }
1109  else if ( distanceSqr < ‪SQR( 1500 ) )
1110  {
1111  threat += 400;
1112  }
1113 
1114  return threat;
1115 }
1116 
1117 // self == vehicle
1119 {
1120  best_threat = -1000000;
1121  self.main_target = undefined;
1122  foreach( player in level.players )
1123  {
1124  threat = ‪get_player_threat( player );
1125  if ( isdefined( threat ) && threat > best_threat )
1126  {
1127  best_threat = threat;
1128  self.main_target = player;
1129  }
1130  }
1131 }
1132 
1133 function ‪shoulder_light_focus( target )
1134 {
1135  if ( !isdefined( target ) )
1136  {
1137  self SetTurretTargetRelativeAngles( (0,0,0), 3 );
1138  self SetTurretTargetRelativeAngles( (0,0,0), 4 );
1139  }
1140  else
1141  {
1142  self ‪vehicle_ai::SetTurretTarget( target, 3 );
1143  self ‪vehicle_ai::SetTurretTarget( target, 4 );
1144  }
1145 }
1146 
1147 function ‪Debug_line_to_target( target, time, color )
1148 {
1149  self endon( "death" );
1150  point1 = self.origin;
1151  point2 = target.origin;
1152  if ( ‪DEBUG_ON )
1153  {
1154  ‪stopTime = GetTime() + time * 1000;
1155  while ( GetTime() <= ‪stopTime )
1156  {
1157  /#line(point1, point2, color, 1, false, 3 ); #/
1159  }
1160  }
1161 }
1162 
1164 {
1165  self endon( "death" );
1166 
1167  wait ‪delay;
1168  for( i = 0; i < 3 && i < self.spikeFakeTargets.size; i++ )
1169  {
1170  spike = self.spikeFakeTargets[ i ];
1171  spike ‪pin_to_ground();
1172  wait 0.15;
1173  }
1174 }
1175 
1177 {
1178  self endon( "death" );
1179  self endon( "change_state" );
1180  self endon( "end_attack_thread" );
1181  self notify( "end_attack_thread_gun" );
1182  self endon( "end_attack_thread_gun" );
1183 
1184  while( 1 )
1185  {
1186  enemy = self.enemy;
1187  if( !isdefined( enemy ) )
1188  {
1189  self SetTurretTargetRelativeAngles( (0,0,0) );
1190  wait 0.4;
1191  continue;
1192  }
1193 
1194  if( !enemy.allowdeath && !IsPlayer(enemy) )
1195  {
1196  self SetPersonalThreatBias( enemy, -2000, 8.0 );
1197  wait 0.4;
1198  continue;
1199  }
1200 
1201  distSq = DistanceSquared( enemy.origin, self.origin );
1202  if ( self VehCanSee( enemy ) && ( IsPlayer( enemy ) || ( ‪SQR( 200 ) < distSq && distSq < ‪SQR( 2000 ) ) ) ) // don't shoot enemy that's not in good range unless it's player
1203  {
1204  self SetPersonalThreatBias( enemy, 1000, 1.0 );
1205  }
1206  else
1207  {
1208  self SetPersonalThreatBias( enemy, -1000, 1.0 );
1209  }
1210 
1211  self ‪vehicle_ai::SetTurretTarget( enemy, 0 );
1212  self ‪vehicle_ai::SetTurretTarget( enemy, 1 );
1213  self ‪shoulder_light_focus( enemy );
1214 
1215  gun_on_target = GetTime();
1216  self SetTurretSpinning( true );
1217  while( isdefined( enemy ) && !self.gunner1ontarget && ‪vehicle_ai::TimeSince( gun_on_target ) < 2 )
1218  {
1219  wait 0.4;
1220  }
1221 
1222  if( !isdefined( enemy ) )
1223  {
1224  self SetTurretSpinning( false );
1225  continue;
1226  }
1227 
1228  attack_start = GetTime();
1229  while ( isdefined( enemy ) && enemy === self.enemy && self VehSeenRecently( enemy, 1.0 ) && ‪vehicle_ai::TimeSince( attack_start ) < 5 )
1230  {
1231  self ‪vehicle_ai::fire_for_time( 1.0 + RandomFloat( 0.4 ), 1 );
1232 
1233  if ( isdefined( enemy ) && isPlayer( enemy ) )
1234  {
1235  wait( 0.6 + RandomFloat( 0.2 ) );
1236  }
1237  wait 0.1;
1238  }
1239 
1240  self SetTurretSpinning( false );
1241 
1242  wait 0.1; // avoid infinite loop
1243  }
1244 }
1245 
1247 {
1248  self endon( "death" );
1249  self endon( "change_state" );
1250  self endon( "end_attack_thread" );
1251  self notify( "end_attack_thread_rocket" );
1252  self endon( "end_attack_thread_rocket" );
1253 
1254  while( 1 )
1255  {
1256  enemy = self.enemy;
1257  if( !isdefined( enemy ) )
1258  {
1259  wait 0.4;
1260  continue;
1261  }
1262 
1263  if ( ‪vehicle_ai::IsCooldownReady( "spike_on_ground", 2 ) && self.rocketaim !== true )
1264  {
1265  self ‪toggle_rocketaim( true );
1266  }
1267 
1268  if ( !‪vehicle_ai::IsCooldownReady( "spike_on_ground" ) )
1269  {
1270  wait 0.4;
1271  continue;
1272  }
1273 
1274  // select a secondary enemy
1275  primaryEnemy = enemy;
1276 
1277  targets = GetAITeamArray( "allies" );
1278  targets = ArrayCombine( targets, level.players, false, false );
1279 
1280  dirToPrimaryEnemy = VectorNormalize( ‪FLAT_ORIGIN( (primaryEnemy.origin - self.origin) ) );
1281 
1282  bestCloseScore = 0.0;
1283  bestTarget = undefined;
1284  foreach( target in targets )
1285  {
1286  if ( target IsNoTarget() || target == primaryEnemy )
1287  {
1288  continue;
1289  }
1290 
1291  dirToTarget = VectorNormalize( ‪FLAT_ORIGIN( (target.origin - self.origin) ) );
1292  angleDot = VectorDot( dirToTarget, dirToPrimaryEnemy );
1293  if ( angleDot < 0.2 )
1294  {
1295  continue;
1296  }
1297 
1298  distanceSelfToTargetSqr = Distance2DSquared( target.origin, self.origin );
1299  if ( distanceSelfToTargetSqr < ‪SQR( 400 ) || distanceSelfToTargetSqr > ‪SQR( 1200 ) )
1300  {
1301  continue;
1302  }
1303 
1304  closeTargetScore = ‪spike_score( target );
1305 
1306  closeTargetScore += 1 - angleDot;
1307 
1308  if ( isPlayer( target ) )
1309  {
1310  closeTargetScore += 0.5;
1311  }
1312 
1313  distancePrimaryEnemyToTargetSqr = Distance2DSquared( target.origin, primaryEnemy.origin );
1314  if ( distancePrimaryEnemyToTargetSqr < ‪SQR( 200 ) )
1315  {
1316  closeTargetScore -= 0.3;
1317  }
1318 
1319  if ( bestCloseScore <= closeTargetScore )
1320  {
1321  bestCloseScore = closeTargetScore;
1322  bestTarget = target;
1323  }
1324  }
1325 
1326  enemy = bestTarget;
1327 
1328  if ( isAlive( enemy ) )
1329  {
1330  if ( ‪DEBUG_ON )
1331  {
1332  self thread ‪Debug_line_to_target( enemy, 5, (1,0,0) );
1333  }
1334 
1335  turretOrigin = self GetTagOrigin( "tag_gunner_flash2" );
1336  distToEnemy = Distance2D( self.origin, enemy.origin );
1337  shootHeight = ‪math::clamp( distToEnemy * 0.35, 100, 350 );
1338  points = GeneratePointsAroundCenter( enemy.origin + (0,0,shootHeight), 300, 80, 50 );
1339  pinDelay = Mapfloat( 300, 700, 0.1, 1.0, distToEnemy );
1340 
1341  // get the turret angle looking correct
1342  spike = self.spikeFakeTargets[ 0 ];
1343  spike.origin = points[ 0 ];
1344  self SetGunnerTargetEnt( spike, (0,0,0), 1 );
1345 
1346  rocket_on_target = GetTime();
1347  while( !self.gunner2ontarget && ‪vehicle_ai::TimeSince( rocket_on_target ) < 2 )
1348  {
1349  wait 0.4;
1350  }
1351 
1352  self thread ‪Pin_first_three_spikes_to_ground( pinDelay );
1353 
1354  for( i = 0; i < 3 && i < self.spikeFakeTargets.size && i < points.size; i++ )
1355  {
1356  spike = self.spikeFakeTargets[ i ];
1357  spike.origin = points[ i ];
1358  self SetGunnerTargetEnt( spike, (0,0,0), 1 );
1359  self FireWeapon( 2, enemy );
1360  ‪vehicle_ai::Cooldown( "spike_on_ground", randomFloatRange( 6, 10 ) );
1361 
1362  if ( ‪DEBUG_ON )
1363  {
1364  /#debugstar( spike.origin, 200, (1,0,0) ); #/
1365  /#Circle( spike.origin, 150, (1,0,0), false, true, 200 ); #/
1366  }
1367 
1368  wait 0.1;
1369  }
1370 
1371  wait 0.5;
1372  self SetTurretTargetRelativeAngles( (0,0,0), 2 );
1373  self ‪toggle_rocketaim( false );
1374  }
1375  else
1376  {
1377  wait 0.4;
1378  }
1379  }
1380 }
1381 
1382 function ‪toggle_rocketaim( is_aiming )
1383 {
1384  self.rocketaim = is_aiming;
1385  self ‪locomotion_start();
1386 }
1387 
1389 {
1390  if ( self.rocketaim === true )
1391  {
1392  locomotion = "locomotion@movement";
1393  }
1394  else
1395  {
1396  locomotion = "locomotion_rocketup@movement";
1397  }
1398 
1399  self ASMRequestSubstate( locomotion );
1400 }
1401 
1403 {
1404  minDist = 400;
1405 
1406  ai_array = GetAITeamArray( "allies" );
1407  ai_array = array::randomize( ai_array );
1408  foreach( ai in ai_array )
1409  {
1410  awayFromPlayer = true;
1411  foreach( player in level.players )
1412  {
1413  if ( ‪is_valid_target( player ) && Distance2DSquared( ai.origin, player.origin ) < ‪SQR( minDist ) )
1414  {
1415  awayFromPlayer = false;
1416  break;
1417  }
1418  }
1419 
1420  if ( !awayFromPlayer )
1421  {
1422  continue;
1423  }
1424  }
1425 
1426  return undefined;
1427 }
1428 
1430 {
1431  self endon( "death" );
1432  self endon( "change_state" );
1433  self notify( "end_movement_thread" );
1434  self endon( "end_movement_thread" );
1435 
1436  while( true )
1437  {
1438  // try to get player as target
1439  self ‪update_target_player();
1440  enemy = self.main_target;
1441 
1442  // if there is only one player, ignore player once every once in a while
1443  if ( level.players.size <= 1 && ‪vehicle_ai::IsCooldownReady( "ignore_player" ) )
1444  {
1445  ‪vehicle_ai::Cooldown( "ignore_player", ‪IGNORE_COOLDOWN );
1446  enemy = ‪Get_Strong_Target();
1447  foreach( player in level.players )
1448  {
1449  self SetPersonalThreatBias( player, -1000, 2.0 );
1450  }
1451  }
1452 
1453  // fallback to general enemy
1454  if ( !isdefined(enemy) )
1455  {
1456  enemy = self.enemy;
1457  }
1458 
1459  // no enemy, just don't move
1460  if ( !isdefined(enemy) )
1461  {
1463  continue;
1464  }
1465 
1466  self.current_pathto_pos = self ‪GetNextMovePosition( enemy );
1467  self.current_enemy_pos = enemy.origin;
1468 
1469  self SetSpeed( self.settings.defaultMoveSpeed );
1470 
1471  foundpath = self SetVehGoalPos( self.current_pathto_pos, false, true );
1472  if ( foundPath )
1473  {
1474  self SetLookAtEnt( enemy );
1475  self SetBrake( 0 );
1477  self thread ‪path_update_interrupt();
1479  self notify( "end_path_interrupt" );
1480  self CancelAIMove();
1481  self ClearVehGoalPos();
1482  self SetBrake( 1 );
1483  }
1484 
1486  }
1487 }
1488 
1490 {
1491  self endon( "death" );
1492  self endon( "change_state" );
1493  self endon( "end_movement_thread" );
1494  self notify( "end_path_interrupt" );
1495  self endon( "end_path_interrupt" );
1496 
1497  while( true )
1498  {
1499  if ( isdefined( self.current_enemy_pos ) && isdefined( self.main_target ) )
1500  {
1501  if ( Distance2DSquared( self.current_enemy_pos, self.main_target.origin ) > ‪SQR( 200 ) )
1502  {
1503  self notify( "near_goal" );
1504  }
1505  }
1506  wait 0.8;
1507  }
1508 }
1509 
1510 function ‪GetNextMovePosition( enemy )
1511 {
1512  if( self.goalforced )
1513  {
1514  return self.goalpos;
1515  }
1516 
1517  halfHeight = 400;
1518  spacing = 80;
1519  queryOrigin = self.origin;
1520 
1521  if ( isdefined( enemy ) && self CanPath( self.origin, enemy.origin ) )
1522  {
1523  queryOrigin = enemy.origin;
1524  }
1525 
1526  queryResult = PositionQuery_Source_Navigation( queryOrigin, 0, self.settings.engagementDistMax + 200, halfHeight, spacing, self );
1527 
1528  if ( isdefined( enemy ) )
1529  {
1530  PositionQuery_Filter_Sight( queryResult, enemy.origin, self GetEye() - self.origin, self, 0, enemy );
1531  ‪vehicle_ai::PositionQuery_Filter_EngagementDist( queryResult, enemy, self.settings.engagementDistMin, self.settings.engagementDistMax );
1532  }
1533  PositionQuery_Filter_DistanceToGoal( queryResult, self );
1535 
1536  forward = AnglesToForward( self.angles );
1537  if ( isdefined( enemy ) )
1538  {
1539  enemyDir = VectorNormalize( enemy.origin - self.origin );
1540  forward = VectorNormalize( forward + 5 * enemyDir );
1541  }
1542 
1543  foreach ( point in queryResult.data )
1544  {
1545  if( Distance2DSquared( self.origin, point.origin ) < ‪SQR( 300 ) )
1546  {
1547  ADD_POINT_SCORE( point, "tooCloseToSelf", -700 );
1548  }
1549 
1550  if( isdefined( enemy ) )
1551  {
1552  ADD_POINT_SCORE( point, "engagementDist", -point.distAwayFromEngagementArea );
1553 
1554  if ( !point.visibility )
1555  {
1556  ADD_POINT_SCORE( point, "visibility", -600 );
1557  }
1558  }
1559 
1560  pointDirection = VectorNormalize( point.origin - self.origin );
1561  factor = VectorDot( pointDirection, forward );
1562  if ( factor > 0.7 )
1563  {
1564  ADD_POINT_SCORE( point, "directionDiff", 600 );
1565  }
1566  else if ( factor > 0 )
1567  {
1568  ADD_POINT_SCORE( point, "directionDiff", 0 );
1569  }
1570  else if ( factor > -0.5 )
1571  {
1572  ADD_POINT_SCORE( point, "directionDiff", -600 );
1573  }
1574  else
1575  {
1576  ADD_POINT_SCORE( point, "directionDiff", -1200 );
1577  }
1578  }
1579 
1581  self ‪vehicle_ai::PositionQuery_DebugScores( queryResult );
1582 
1583  if( queryResult.data.size == 0 )
1584  return self.origin;
1585 
1586  return queryResult.data[0].origin;
1587 }
1588 // State: groundCombat ----------------------------------
1589 
1590 function ‪_sort_by_distance2d( left, right, point )
1591 {
1592  distanceSqrToLeft = distance2DSquared( left.origin, point );
1593  distanceSqrToRight = distance2DSquared( right.origin, point );
1594  return distanceSqrToLeft > distanceSqrToRight;
1595 }
1596 
1597 function ‪too_close_to_high_ground( point, minDistance )
1598 {
1599  foreach( highGround in self.jump.highgrounds )
1600  {
1601  if ( Distance2DSquared( point, highGround.origin ) < ‪SQR( minDistance ) )
1602  {
1603  return true;
1604  break;
1605  }
1606  }
1607 
1608  return false;
1609 }
1610 
1611 function ‪get_jumpon_target( distanceLimitMin, distanceLimitMax, idealDist, includingAI, minAngleDiffCos, mustJump )
1612 {
1613  targets = level.players;
1614 
1615  if ( includingAI === true )
1616  {
1617  targets = ArrayCombine( targets, GetAITeamArray( "allies" ), false, false );
1618  targets = array::merge_sort( targets, &‪_sort_by_distance2d, self.origin );
1619  }
1620 
1621  angles = ( 0, self.angles[1], 0 );
1622 
1623  forward = AnglesToForward( angles );
1624 
1625  bestTarget = undefined;
1626  bestScore = 1000000; // lower the better
1627 
1628  minDistAwayFromHighGround = 300;
1629  maxDistAwayFromArenaCenter = 1800;
1630 
1631  /# RecordStar( self.origin, (1,0.5,0) ); #/
1632  /# Record3DText( "JUMP TO GROUND", self.origin, (1,0.5,0), "Script", self ); #/
1633 
1634  foreach( target in targets )
1635  {
1636  if ( !‪is_valid_target( target ) || !target.allowdeath || IsAirBorne( target ) )
1637  {
1638  continue;
1639  }
1640 
1641  if ( Distance2DSquared( self.arena_center, target.origin ) > ‪SQR( maxDistAwayFromArenaCenter ) )
1642  {
1643  /# RecordStar( target.origin, (0,0.5,1) ); #/
1644  /# Record3DText( "too far from center: " + distance2d( self.arena_center, target.origin ), target.origin, (0,0.5,1), "Script", self ); #/
1645  continue;
1646  }
1647 
1648  if ( ‪too_close_to_high_ground( target.origin, minDistAwayFromHighGround ) )
1649  {
1650  /# RecordStar( target.origin, (0,0.5,1) ); #/
1651  /# Record3DText( "too close to platform", target.origin, (0,0.5,1), "Script", self ); #/
1652  continue;
1653  }
1654 
1655  distanceToTarget = Distance2D( target.origin, self.origin );
1656  if ( distanceToTarget < distanceLimitMin || distanceLimitMax < distanceToTarget )
1657  {
1658  /# RecordStar( target.origin, (1,0.5,0) ); #/
1659  /# Record3DText( "out of range: " + distanceToTarget, target.origin, (1,0.5,0), "Script", self ); #/
1660  continue;
1661  }
1662 
1663  vectorToTarget = ‪FLAT_ORIGIN( ( target.origin - self.origin ) );
1664  vectorToTarget = vectorToTarget / distanceToTarget;
1665  if ( isdefined( minAngleDiffCos ) && VectorDot( forward, vectorToTarget ) < minAngleDiffCos )
1666  {
1667  continue;
1668  }
1669 
1670  score = Abs( distanceToTarget - idealDist );
1671  if ( score < 200 )
1672  {
1673  score = randomFloat( 200 );
1674  }
1675 
1676  /# RecordStar( target.origin, (1,0.5,0) ); #/
1677  /# Record3DText( "dist: " + distanceToTarget + " score: " + score, target.origin, (1,0.5,0), "Script", self ); #/
1678 
1679  if ( isPlayer( target ) && !isVehicle( target ) )
1680  {
1681  minRadius = 0;
1682  maxRadius = 300;
1683  }
1684  else
1685  {
1686  minRadius = 200;
1687  maxRadius = 400;
1688  }
1689  queryResult = PositionQuery_Source_Navigation( target.origin, minRadius, maxRadius, 500, self.radius * 0.5, self.radius * 1.1 );
1690  if ( queryResult.data.size > 0 )
1691  {
1692  element = queryResult.data[0];
1693  if ( score < bestScore )
1694  {
1695  bestScore = score;
1696  bestTarget = element;
1697  }
1698  }
1699  }
1700 
1701  if ( isdefined( bestTarget ) )
1702  {
1703  return bestTarget.origin;
1704  }
1705 
1706  if ( mustJump === false )
1707  {
1708  return undefined;
1709  }
1710 
1711  // pick random point using arena_center
1712  queryResult = PositionQuery_Source_Navigation( self.arena_center, 100, 1300, 500, self.radius, self.radius * 1.1 );
1713 
1714  assert ( queryResult.data.size > 0 );
1715  pointList = array::randomize( queryResult.data );
1716  foreach ( point in pointList )
1717  {
1718  distanceToTargetSqr = Distance2DSquared( point.origin, self.origin );
1719  if ( ‪SQR( distanceLimitMin ) < distanceToTargetSqr && distanceToTargetSqr < ‪SQR( distanceLimitMax ) && !‪too_close_to_high_ground( point.origin, minDistAwayFromHighGround ) )
1720  {
1721  return point.origin;
1722  }
1723  }
1724 
1725  return self.arena_center;
1726 }
1727 
1729 {
1730  self notify( "end_movement_thread" );
1731  self notify( "near_goal" );
1732  self CancelAIMove();
1733  self ClearVehGoalPos();
1734  self ClearTurretTarget();
1735  self ClearLookAtEnt();
1736  self SetBrake( 1 );
1737 }
1738 
1739 function ‪face_target( position, targetAngleDiff )
1740 {
1741  if ( !isdefined( targetAngleDiff ) )
1742  {
1743  targetAngleDiff = 30;
1744  }
1745 
1746  v_to_enemy = ‪FLAT_ORIGIN( (position - self.origin) );
1747  v_to_enemy = VectorNormalize( v_to_enemy );
1748  goalAngles = VectortoAngles( v_to_enemy );
1749 
1750  angleDiff = AbsAngleClamp180( self.angles[1] - goalAngles[1] );
1751  if ( angleDiff <= targetAngleDiff )
1752  {
1753  return;
1754  }
1755 
1756  self SetLookAtOrigin( position );
1757  self SetTurretTargetVec( position );
1758  self ‪locomotion_start();
1759 
1760  angleAdjustingStart = GetTime();
1761  while( angleDiff > targetAngleDiff && ‪vehicle_ai::TimeSince( angleAdjustingStart ) < 4 )
1762  {
1763  if ( ‪DEBUG_ON ) /#line(self.origin, position, (1,0,1), 1, false, 5 ); #/
1764  angleDiff = AbsAngleClamp180( self.angles[1] - goalAngles[1] );
1766  }
1767 
1768  self ClearVehGoalPos();
1769  self ClearLookAtEnt();
1770  self ClearTurretTarget();
1771  self CancelAIMove();
1772 }
1773 
1774 function ‪theia_callback_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal )
1775 {
1776  // Don't allow friendlies to kill sarah
1777  if( !IsPlayer(eAttacker) )
1778  {
1779  iDamage = 0;
1780  return iDamage;
1781  }
1782 
1783  iDamage = self ‪killstreaks::OnDamagePerWeapon( ‪SIEGEBOT_THEIA_BUNDLE, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, self.maxhealth, undefined, self.maxhealth * 0.4, undefined, 0, undefined, true, 1.0 );
1784  if( iDamage == 0 )
1785  return 0;
1786 
1787  newDamageLevel = ‪vehicle::should_update_damage_fx_level( self.health, iDamage, self.healthdefault );
1788  if ( newDamageLevel > self.damageLevel )
1789  {
1790  self.newDamageLevel = newDamageLevel;
1791  }
1792 
1793  if ( self.newDamageLevel > self.damageLevel && ‪pain_canenter() )
1794  {
1795  self.damageLevel = self.newDamageLevel;
1796  self notify( "pain" );
1797  ‪vehicle::set_damage_fx_level( self.damageLevel );
1798 
1799  if ( self.damageLevel >= 2 )
1800  {
1801  self ‪vehicle::toggle_lights_group( 1, false );
1802  }
1803  }
1804 
1805  return iDamage;
1806 }
1807 
1808 // ----------------------------------------------------------------------------
1809 // attack_javelin
1810 // ----------------------------------------------------------------------------
1812 {
1813  if( level.players.size < 1 )
1814  {
1815  return;
1816  }
1817 
1818  enemy = array::random( level.players );
1819 
1820  if ( !isdefined( enemy ) )
1821  {
1822  return;
1823  }
1824 
1825  // aim up and fire
1826  forward = AnglesToForward( self.angles );
1827  shootpos = self.origin + forward * 200 + (0,0,500);
1828  //self SetTurretTargetVec( shootpos );
1829  //self util::waittill_any_timeout( 0.5, "turret_on_target" );
1830 
1831  self ASMRequestSubstate( "javelin@stationary" );
1832  self waittill( "fire_javelin" );
1833  level notify( "theia_preparing_javelin_attack", enemy );
1834 
1835  current_weapon = self SeatGetWeapon( 0 );
1836  weapon = GetWeapon( ‪WEAPON_JAVELIN );
1837  self thread ‪javeline_incoming(weapon);
1838  self SetVehWeapon( weapon );
1839  self thread ‪vehicle_ai::Javelin_LoseTargetAtRightTime( enemy );
1840  self FireWeapon( 0, enemy );
1841 
1842  self ‪vehicle_ai::waittill_asm_complete( "javelin@stationary", 3 );
1843 
1844  self SetVehWeapon( current_weapon );
1845 
1846  // aim back down
1847  shootpos = self.origin + forward * 500;
1848  self SetTurretTargetVec( shootpos );
1849  self ‪util::waittill_any_timeout( 2, "turret_on_target" );
1850 
1851  self ClearTurretTarget();
1852 
1853  if( isdefined( enemy ) && !self VehCanSee( enemy ) ) // use VehCanSee, if recently attacked this will return true and not use FOV check
1854  {
1855  forward = AnglesToForward( self.angles );
1856 
1857  aimpos = self.origin + forward * 1000;
1858  self SetTurretTargetVec( aimpos );
1859  msg = self ‪util::waittill_any_timeout( 3.0, "turret_on_target" );
1860  self ClearTurretTarget();
1861  }
1862 
1863  self ‪locomotion_start();
1864 }
1865 function ‪javeline_incoming(projectile)
1866 {
1867  self endon( "entityshutdown" );
1868  self endon ("death");
1869 
1870  self waittill( "weapon_fired", projectile );
1871 
1872  distance = 1400;
1873  alias = "prj_javelin_incoming";
1874 
1875  wait(3);
1876 
1877  if(!isdefined( projectile ) )
1878  return;
1879 
1880  while(isdefined(projectile) && isdefined( projectile.origin ))
1881  {
1882  if ( isdefined( self.enemy ) && isdefined( self.enemy.origin ))
1883  {
1884  projectileDistance = DistanceSquared( projectile.origin, self.enemy.origin);
1885 
1886  if( projectileDistance <= distance * distance )
1887  {
1888  projectile playsound (alias);
1889  return;
1890  }
1891  }
1892 
1893  wait (.05);
1894  }
1895 }
1896 
1897 // ----------------------------------------------------------------------------
1898 // attack_spike_minefield
1899 // ----------------------------------------------------------------------------
1901 {
1902  count = 6;
1903 
1904  if( !isdefined( self.spikeFakeTargets ) || self.spikeFakeTargets.size < 1 )
1905  {
1906  self.spikeFakeTargets = [];
1907  for ( i = 0; i < count; i++ )
1908  {
1909  newFakeTarget = ‪Spawn( "script_origin", self.origin );
1910  ‪ARRAY_ADD( self.spikeFakeTargets, newFakeTarget );
1911  }
1912  }
1913 
1914  if( !isdefined( self.fakeTargetEnt ) )
1915  {
1916  self.fakeTargetEnt = ‪Spawn( "script_origin", self.origin );
1917  }
1918 }
1919 
1920 // self == spike
1922 {
1923  ‪trace = BulletTrace( self.origin, self.origin + (0,0,-800), false, self );
1924 
1925  if( ‪trace["fraction"] < 1.0 )
1926  {
1927  self.origin = ‪trace["position"] + (0,0,-20);
1928  }
1929  else
1930  {
1931  self.origin = self.origin + (0,0,-500);
1932  }
1933 }
1934 
1936 {
1937  self endon("death");
1938 
1939  wait 0.1;
1940 
1941  spikeTargets = array::randomize( self.spikeFakeTargets );
1942  foreach ( target in spikeTargets )
1943  {
1944  target ‪pin_to_ground();
1945  wait randomFloatRange(0.05, 0.1);
1946  }
1947 
1948  if ( ‪DEBUG_ON )
1949  {
1950  foreach ( spike in spikeTargets )
1951  {
1952  /#debugstar( spike.origin, 200, (1,0,0) ); #/
1953  /#Circle( spike.origin, 150, (1,0,0), false, true, 200 ); #/
1954  }
1955  }
1956 }
1957 
1958 function ‪spike_score( target )
1959 {
1960  score = 1.0;
1961  if ( target IsNoTarget() )
1962  {
1963  score = 0.2;
1964  }
1965  else if ( !target.allowdeath )
1966  {
1967  score = 0.4;
1968  }
1969  else if ( IsAirBorne( target ) )
1970  {
1971  score = 0.2;
1972  }
1973  /*
1974  else if ( !self VehCanSee( target ) )
1975  {
1976  score = 0.6;
1977  }
1978  */
1979 
1980  return score;
1981 }
1982 
1983 function ‪spike_group_score( target, targetList, radius )
1984 {
1985  closeTargetScore = ‪spike_score( target );
1986  foreach ( otherTarget in targetList )
1987  {
1988  closeEnough = ( Distance2DSquared( target.origin, otherTarget.origin ) < ‪SQR( radius ) );
1989  if ( closeEnough )
1990  {
1991  closeTargetScore = closeTargetScore + ‪spike_score( otherTarget );
1992  }
1993  }
1994 
1995  return closeTargetScore;
1996 }
1997 
1999 {
2000  spikeCoverRadius = 600;
2001  randomScale = 40;
2002 
2004 
2005  forward = AnglesToForward( self.angles );
2006  self SetTurretTargetVec( self.origin + forward * 1000 );
2007  self ‪util::waittill_any_timeout( 2, "turret_on_target" );
2008 
2009  forward = AnglesToForward( self.angles );
2010 
2011  targets = GetAITeamArray( "allies" );
2012  targets = ArrayCombine( targets, level.players, false, false );
2013 
2014  bestCloseScore = 0.0;
2015  bestTarget = undefined;
2016  foreach( target in targets )
2017  {
2018  if ( target IsNoTarget() || IsAirBorne( target ) )
2019  {
2020  continue;
2021  }
2022 
2023  distanceSelfToTargetSqr = Distance2DSquared( target.origin, self.origin );
2024  if ( distanceSelfToTargetSqr < ‪SQR( 500 ) || distanceSelfToTargetSqr > ‪SQR( 2100 ) )
2025  {
2026  continue;
2027  }
2028 
2029  dirToTarget = ‪FLAT_ORIGIN( (target.origin - self.origin) );
2030  if ( VectorDot( dirToTarget, forward ) < 0.1 )
2031  {
2032  continue;
2033  }
2034 
2035  closeTargetScore = ‪spike_group_score( target, targets, spikeCoverRadius );
2036 
2037  if ( bestCloseScore <= closeTargetScore )
2038  {
2039  bestCloseScore = closeTargetScore;
2040  bestTarget = target;
2041  }
2042  }
2043 
2044  if ( !isdefined( bestTarget ) )
2045  {
2046  bestTarget = array::random( GeneratePointsAroundCenter( self.arena_center, 2000, 200 ) );
2047  }
2048  else
2049  {
2050  bestTarget = bestTarget.origin;
2051  }
2052 
2053  if ( ‪DEBUG_ON )
2054  {
2055  /#debugstar( bestTarget, 200, (1,0,0) ); #/
2056  /#Circle( bestTarget, spikeCoverRadius, (1,0,0), false, true, 200 ); #/
2057  }
2058 
2059  //tell the level theia is about to fire spikes
2060  level notify( "theia_preparing_spike_attack", bestTarget );
2061 
2062  targetOrigin = ‪FLAT_ORIGIN( bestTarget ) + (0,0,self.origin[2]);
2063  targetPoints = GeneratePointsAroundCenter( targetOrigin, 1200, 120 );
2064 
2065  numOfSpikeAssigned = 0;
2066  for( i = 0; i < self.spikeFakeTargets.size && i < targetPoints.size; i++ )
2067  {
2068  spike = self.spikeFakeTargets[ i ];
2069  spike.origin = targetPoints[i];
2070  numOfSpikeAssigned++;
2071  }
2072 
2073  self ASMRequestSubstate( "arm_rocket@stationary" );
2074  self waittill( "fire_spikes" );
2075 
2076  for ( i = 0; i < numOfSpikeAssigned; i++ )
2077  {
2078  spike = self.spikeFakeTargets[ i ];
2079  self SetGunnerTargetEnt( spike, (0,0,0), 1 );
2080  self FireWeapon( 2 );
2081  wait .05;
2082  }
2083 
2084  self thread ‪pin_spike_to_ground();
2085 
2086  self ClearGunnerTarget( 1 );
2087  self ClearTurretTarget();
2088 
2089  self ‪vehicle_ai::waittill_asm_complete( "arm_rocket@stationary", 3 );
2090  self ‪locomotion_start();
2091 }
2092 
2093 // ----------------------------------------------------------------------------
2094 // attack_minigun_sweep
2095 // ----------------------------------------------------------------------------
2096 function ‪Delay_Target_ToEnemy_Thread( point, enemy, timeToHit )
2097 {
2098  offset = (0, 0, 10);
2099 
2100  self.fakeTargetEnt Unlink();
2101 
2102  if ( DistanceSquared( self.fakeTargetEnt.origin, enemy.origin ) > ‪SQR( 20 ) )
2103  {
2104  self.fakeTargetEnt.origin = point;
2105  self ‪vehicle_ai::SetTurretTarget( self.fakeTargetEnt, 1 );
2106  self ‪util::waittill_any_timeout( 2, "turret_on_target" );
2107 
2108  timeStart = GetTime();
2109 
2110  while( GetTime() < timeStart + timeToHit * 1000 )
2111  {
2112  self.fakeTargetEnt.origin = LerpVector( point, enemy.origin + offset, ( GetTime() - timeStart ) / ( timeToHit * 1000 ) );
2113  if ( ‪DEBUG_ON ) /#debugstar( self.fakeTargetEnt.origin, 100, (0,1,0) ); #/
2115  }
2116  }
2117 
2118  self.fakeTargetEnt.origin = enemy.origin + offset;
2120  self.fakeTargetEnt LinkTo( enemy );
2121 }
2122 
2123 function ‪is_valid_target( target )
2124 {
2125  if ( ‪IS_TRUE( target.ignoreme ) || ( target.health <= 0 ) )
2126  {
2127  return false;
2128  }
2129  else if ( isPlayer( target ) && target ‪laststand::player_is_in_laststand() )
2130  {
2131  return false;
2132  }
2133  else if ( IsSentient( target ) && ( target IsNoTarget() || !IsAlive( target ) ) )
2134  {
2135  return false;
2136  }
2137 
2138  return true;
2139 }
2140 
2141 function ‪get_enemy()
2142 {
2143  if ( isdefined( self.enemy ) && ‪is_valid_target( self.enemy ) )
2144  {
2145  return self.enemy;
2146  }
2147 
2148  targets = GetAITeamArray( "allies" );
2149  targets = ArrayCombine( targets, level.players, false, false );
2150 
2151  validTargets = [];
2152  foreach( target in targets )
2153  {
2154  if ( ‪is_valid_target( target ) )
2155  {
2156  ‪ARRAY_ADD( validTargets, target );
2157  }
2158  }
2159 
2160  targets = array::merge_sort( validTargets, &‪_sort_by_distance2d, self.origin );
2161  return targets[0];
2162 }
2163 
2165 {
2166  duration = 4;
2167  interval = 1;
2168  self.turretrotscale = 0.4; // how fast to rotate the upper body turret
2169 
2170  self ClearTurretTarget();
2171  self ClearGunnerTarget( 1 );
2172  self SetTurretTargetRelativeAngles( (0,0,0), 0 );
2173  self SetTurretTargetRelativeAngles( (0,0,0), 1 );
2174  self ASMRequestSubstate( "sweep@gun" );
2175  self waittill( "barrelspin_start" );
2176  self ‪clientfield::set( "sarah_minigun_spin", 1 );
2177  self SetTurretSpinning( true );
2178 
2179  self waittill( "barrelspin_loop" );
2180 
2181  enemy = ‪get_enemy();
2182  vectorFromEnemy = VectorNormalize( ‪FLAT_ORIGIN( (self.origin - enemy.origin) ) );
2183  position = enemy.origin + vectorFromEnemy * 500;
2184  ‪stopTime = GetTime() + duration * 1000;
2185  self thread ‪vehicle_ai::fire_for_time( duration * 2, 1 );
2186 
2187  while ( GetTime() < ‪stopTime )
2188  {
2189  enemy = ‪get_enemy();
2190 
2191  v_gunner_barrel1 = self GetTagOrigin( "tag_gunner_flash1" );
2192  v_bullet_trace_end = enemy.origin + ( 0, 0, 30 );
2193  ‪trace = BulletTrace( v_gunner_barrel1, v_bullet_trace_end, true, enemy );
2194  if( ‪trace["fraction"] == 1 )
2195  {
2196  self GetPerfectInfo( enemy, true );
2197  }
2198  else if ( !IsPlayer(enemy) )
2199  {
2200  self SetPersonalThreatBias( enemy, -2000, 3.0 );
2201  }
2202 
2203  if( !enemy.allowdeath && !IsPlayer(enemy) )
2204  {
2205  self SetPersonalThreatBias( enemy, -900, 8.0 );
2206  }
2207 
2208  self ‪vehicle_ai::SetTurretTarget( enemy, 0 );
2209 
2210  if ( IsPlayer( enemy ) )
2211  {
2212  vectorFromEnemy = VectorNormalize( ‪FLAT_ORIGIN( (self.origin - enemy.origin) ) );
2213  self ‪Delay_Target_ToEnemy_Thread( enemy.origin + vectorFromEnemy * 500, enemy, 0.7 );
2214  }
2215  else
2216  {
2217  self ‪vehicle_ai::SetTurretTarget( enemy, 1 );
2218  }
2219 
2220  self ‪util::waittill_any_timeout( interval, "enemy" );
2221  }
2222 
2223  self SetTurretSpinning( false );
2224  self notify( "fire_stop" );
2225 
2226  self ‪locomotion_start();
2227 
2228  self waittill( "barrelspin_end" );
2229  self ‪clientfield::set( "sarah_minigun_spin", 0 );
2230 
2231  self.turretrotscale = 1.0;
2232 
2233  wait 0.2;
2234 }
2235 
‪pin_spike_to_ground
‪function pin_spike_to_ground()
Definition: _siegebot_theia.gsc:1935
‪set_death_model
‪function set_death_model(sModel, fDelay)
Definition: vehicle_death_shared.gsc:258
‪Movement_Thread
‪function Movement_Thread()
Definition: _siegebot_theia.gsc:1429
‪add_interrupt_connection
‪function add_interrupt_connection(from_state_name, to_state_name, on_notify, checkfunc)
Definition: statemachine_shared.gsc:90
‪reset_player_threat
‪function reset_player_threat(player)
Definition: _siegebot_theia.gsc:1008
‪can_jump_ground_to_ground
‪function can_jump_ground_to_ground(from_state, to_state, connection)
Definition: _siegebot_theia.gsc:498
‪javeline_incoming
‪function javeline_incoming(projectile)
Definition: _siegebot_theia.gsc:1865
‪startTime
‪class AnimationAdjustmentInfoZ startTime
‪highGroundPoint
‪function highGroundPoint(distanceLimitMin, distanceLimitMax, pointsArray, idealDist)
Definition: _siegebot_theia.gsc:888
‪add_state
‪function add_state(name, enter_func, update_func, exit_func, reenter_func)
Definition: statemachine_shared.gsc:59
‪get_enemy
‪function get_enemy()
Definition: _siegebot_theia.gsc:2141
‪state_balconyCombat_exit
‪function state_balconyCombat_exit(params)
Definition: _siegebot_theia.gsc:798
‪set_state
‪function set_state(name, state_params)
Definition: statemachine_shared.gsc:133
‪evaluate_connections
‪function evaluate_connections(eval_func, params)
Definition: statemachine_shared.gsc:266
‪face_target
‪function face_target(position, targetAngleDiff)
Definition: _siegebot_theia.gsc:1739
‪state_death_update
‪function state_death_update(params)
Definition: _siegebot_theia.gsc:216
‪init_fake_targets
‪function init_fake_targets()
Definition: _siegebot_theia.gsc:1900
‪ClearAllLookingAndTargeting
‪function ClearAllLookingAndTargeting()
Definition: vehicle_ai_shared.gsc:697
‪Attack_Thread_Gun
‪function Attack_Thread_Gun()
Definition: _siegebot_theia.gsc:1176
‪attack_javelin
‪function attack_javelin()
Definition: _siegebot_theia.gsc:1811
‪set_damage_fx_level
‪function set_damage_fx_level(damage_level)
Definition: vehicle_shared.gsc:3012
‪IsCooldownReady
‪function IsCooldownReady(name, timeForward_seconds)
Definition: vehicle_ai_shared.gsc:1968
‪InitThreatBias
‪function InitThreatBias()
Definition: vehicle_ai_shared.gsc:60
‪get_current_state
‪function get_current_state()
Definition: vehicle_ai_shared.gsc:960
‪SIEGEBOT_THEIA_BUNDLE
‪#define SIEGEBOT_THEIA_BUNDLE
Definition: _siegebot_theia.gsc:53
‪SetTurretTarget
‪function SetTurretTarget(target, turretIdx=0, offset=(0, 0, 0))
Definition: vehicle_ai_shared.gsc:277
‪get_player_and_vehicle_array
‪function get_player_and_vehicle_array()
Definition: _siegebot_theia.gsc:954
‪register_killstreak_bundle
‪function register_killstreak_bundle(killstreakType)
Definition: _killstreak_bundles.gsc:20
‪theia_callback_damage
‪function theia_callback_damage(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal)
Definition: _siegebot_theia.gsc:1774
‪stopTime
‪var stopTime
Definition: archetype_apothicon_fury.gsc:282
‪Javelin_LoseTargetAtRightTime
‪function Javelin_LoseTargetAtRightTime(target)
Definition: vehicle_ai_shared.gsc:314
‪VERSION_SHIP
‪#define VERSION_SHIP
Definition: version.gsh:36
‪should_prepare_death
‪function should_prepare_death(from_state, to_state, connection)
Definition: _siegebot_theia.gsc:307
‪attack_minigun_sweep
‪function attack_minigun_sweep()
Definition: _siegebot_theia.gsc:2164
‪cointoss
‪function cointoss()
Definition: math_shared.csc:171
‪get_array
‪function get_array(kvp_value, kvp_key="targetname")
Definition: struct.csc:34
‪get_player_threat
‪function get_player_threat(player)
Definition: _siegebot_theia.gsc:1061
‪state_groundCombat_update
‪function state_groundCombat_update(params)
Definition: _siegebot_theia.gsc:812
‪state_jumpDown_exit
‪function state_jumpDown_exit(params)
Definition: _siegebot_theia.gsc:521
‪OnDamagePerWeapon
‪function OnDamagePerWeapon(killstreak_ref, attacker, damage, flags, type, weapon, max_health, destroyed_callback, low_health, low_health_callback, emp_damage, emp_callback, allow_bullet_damage, chargeLevel)
Definition: _killstreaks.gsc:2667
‪trace
‪function trace(from, to, target)
Definition: grapple.gsc:369
‪can_jump_up
‪function can_jump_up(from_state, to_state, connection)
Definition: _siegebot_theia.gsc:404
‪get_previous_state
‪function get_previous_state()
Definition: vehicle_ai_shared.gsc:971
‪on_laststand
‪function on_laststand(func, obj)
Definition: callbacks_shared.gsc:543
‪defaultRole
‪function defaultRole()
Definition: _siegebot_theia.gsc:150
‪waittill_any_timeout
‪function waittill_any_timeout(n_timeout, string1, string2, string3, string4, string5)
Definition: util_shared.csc:423
‪IS_TRUE
‪#define IS_TRUE(__a)
Definition: shared.gsh:251
‪get
‪function get(kvp_value, kvp_key="targetname")
Definition: struct.csc:13
‪locomotion_start
‪function locomotion_start()
Definition: _siegebot_theia.gsc:1388
‪SQR
‪#define SQR(__var)
Definition: shared.gsh:293
‪footstep_right_monitor
‪function footstep_right_monitor()
Definition: _siegebot_theia.gsc:874
‪delay
‪function delay(time_or_notify, str_endon, func, arg1, arg2, arg3, arg4, arg5, arg6)
Definition: util_shared.csc:784
‪prepare_death_update
‪function prepare_death_update(params)
Definition: _siegebot_theia.gsc:318
‪scripted_exit
‪function scripted_exit(params)
Definition: _siegebot_theia.gsc:357
‪shoulder_light_focus
‪function shoulder_light_focus(target)
Definition: _siegebot_theia.gsc:1133
‪DEBUG_ON
‪#define DEBUG_ON
Definition: _siegebot_theia.gsc:51
‪damage
‪function damage(trap)
Definition: _zm_trap_electric.gsc:116
‪StartInitialState
‪function StartInitialState(defaultState="combat")
Definition: vehicle_ai_shared.gsc:866
‪impact_fx
‪function impact_fx(fxname, surfaceTypes)
Definition: vehicle_shared.gsc:2765
‪state_balconyCombat_update
‪function state_balconyCombat_update(params)
Definition: _siegebot_theia.gsc:688
‪pain_canenter
‪function pain_canenter()
Definition: _siegebot_theia.gsc:263
‪on_spawned
‪function on_spawned(func, obj)
Definition: callbacks_shared.csc:245
‪can_jump_down
‪function can_jump_down(from_state, to_state, connection)
Definition: _siegebot_theia.gsc:451
‪friendly_fire_shield
‪function friendly_fire_shield()
Definition: vehicle_shared.gsc:2194
‪Delay_Target_ToEnemy_Thread
‪function Delay_Target_ToEnemy_Thread(point, enemy, timeToHit)
Definition: _siegebot_theia.gsc:2096
‪__init__
‪function __init__()
Definition: _siegebot_theia.gsc:64
‪pain_toggle
‪function pain_toggle(enabled)
Definition: _siegebot_theia.gsc:258
‪pin_to_ground
‪function pin_to_ground()
Definition: _siegebot_theia.gsc:1921
‪init_clientfields
‪function init_clientfields()
Definition: _siegebot_theia.gsc:141
‪Pin_first_three_spikes_to_ground
‪function Pin_first_three_spikes_to_ground(delay)
Definition: _siegebot_theia.gsc:1163
‪toggle_rocketaim
‪function toggle_rocketaim(is_aiming)
Definition: _siegebot_theia.gsc:1382
‪get_player_vehicle
‪function get_player_vehicle(player)
Definition: _siegebot_theia.gsc:941
‪Cooldown
‪function Cooldown(name, time_seconds)
Definition: vehicle_ai_shared.gsc:1943
‪init_state_machine_for_role
‪function init_state_machine_for_role(rolename)
Definition: vehicle_ai_shared.gsc:1034
‪death_fx
‪function death_fx()
Definition: _qrdrone.gsc:958
‪waittill_pathing_done
‪function waittill_pathing_done(maxtime=15)
Definition: vehicle_ai_shared.gsc:340
‪PositionQuery_DebugScores
‪function PositionQuery_DebugScores(queryResult)
Definition: vehicle_ai_shared.gsc:2010
‪ARRAY_ADD
‪#define ARRAY_ADD(__array, __item)
Definition: shared.gsh:304
‪pain_enter
‪function pain_enter(params)
Definition: _siegebot_theia.gsc:269
‪fire_for_time
‪function fire_for_time(n_time, n_index=0)
Definition: turret_shared.gsc:953
‪Attack_Thread_Rocket
‪function Attack_Thread_Rocket()
Definition: _siegebot_theia.gsc:1246
‪get_max_health
‪function get_max_health(killstreakType)
Definition: _killstreak_bundles.gsc:188
‪PositionQuery_PostProcess_SortScore
‪function PositionQuery_PostProcess_SortScore(queryResult, descending=true)
Definition: vehicle_ai_shared.gsc:2097
‪clean_up_spawned
‪function clean_up_spawned()
Definition: _siegebot_theia.gsc:233
‪spike_group_score
‪function spike_group_score(target, targetList, radius)
Definition: _siegebot_theia.gsc:1983
‪PositionQuery_Filter_OutOfGoalAnchor
‪function PositionQuery_Filter_OutOfGoalAnchor(queryResult, tolerance=1)
Definition: vehicle_ai_shared.gsc:2104
‪increment
‪function increment(str_field_name, n_increment_count=1)
Definition: clientfield_shared.gsc:110
‪ClearCooldown
‪function ClearCooldown(name)
Definition: vehicle_ai_shared.gsc:1981
‪on_player_killed
‪function on_player_killed()
Definition: _friendicons.gsc:44
‪init_player_threat_all
‪function init_player_threat_all()
Definition: _siegebot_theia.gsc:995
‪REGISTER_SYSTEM
‪#define REGISTER_SYSTEM(__sys, __func_init_preload, __reqs)
Definition: shared.gsh:204
‪add_player_threat_boost
‪function add_player_threat_boost(player, boost, timeSeconds)
Definition: _siegebot_theia.gsc:1047
‪Get_Strong_Target
‪function Get_Strong_Target()
Definition: _siegebot_theia.gsc:1402
‪PHYSICS_TRACE_MASK_VEHICLE
‪#define PHYSICS_TRACE_MASK_VEHICLE
Definition: shared.gsh:131
‪footstep_left_monitor
‪function footstep_left_monitor()
Definition: _siegebot_theia.gsc:860
‪state_jumpUp_enter
‪function state_jumpUp_enter(params)
Definition: _siegebot_theia.gsc:422
‪Spawn
‪function Spawn(parent, onDeathCallback)
Definition: _flak_drone.gsc:427
‪IGNORE_COOLDOWN
‪#define IGNORE_COOLDOWN
Definition: _siegebot_theia.gsc:49
‪too_close_to_high_ground
‪function too_close_to_high_ground(point, minDistance)
Definition: _siegebot_theia.gsc:1597
‪waittill_asm_complete
‪function waittill_asm_complete(substate_to_wait, timeout=10)
Definition: vehicle_ai_shared.gsc:374
‪path_update_interrupt
‪function path_update_interrupt()
Definition: _siegebot_theia.gsc:1489
‪siegebot_initialize
‪function siegebot_initialize()
Definition: _siegebot_theia.gsc:72
‪PositionQuery_Filter_EngagementDist
‪function PositionQuery_Filter_EngagementDist(queryResult, enemy, engagementDistanceMin, engagementDistanceMax)
Definition: vehicle_ai_shared.gsc:2116
‪pain_exit
‪function pain_exit(params)
Definition: _siegebot_theia.gsc:274
‪state_jump_exit
‪function state_jump_exit(params)
Definition: _siegebot_theia.gsc:516
‪state_jump_update
‪function state_jump_update(params)
Definition: _siegebot_theia.gsc:527
‪pain_update
‪function pain_update(params)
Definition: _siegebot_theia.gsc:279
‪state_balconyCombat_enter
‪function state_balconyCombat_enter(params)
Definition: _siegebot_theia.gsc:678
‪RegisterVehicleBlackBoardAttributes
‪function RegisterVehicleBlackBoardAttributes()
Definition: blackboard_vehicle.gsc:24
‪set
‪function set(str_field_name, n_value)
Definition: clientfield_shared.gsc:34
‪initJumpStruct
‪function initJumpStruct()
Definition: _siegebot_theia.gsc:367
‪JUMP_COOLDOWN
‪#define JUMP_COOLDOWN
Definition: _siegebot_theia.gsc:48
‪CreateBlackBoardForEntity
‪function CreateBlackBoardForEntity(entity)
Definition: blackboard.gsc:77
‪is_valid_target
‪function is_valid_target(target)
Definition: _siegebot_theia.gsc:2123
‪player_is_in_laststand
‪function player_is_in_laststand()
Definition: laststand_shared.gsc:18
‪TimeSince
‪function TimeSince(startTimeInMilliseconds)
Definition: vehicle_ai_shared.gsc:1930
‪WEAPON_JAVELIN
‪#define WEAPON_JAVELIN
Definition: _siegebot_theia.gsc:46
‪add_player_threat_damage
‪function add_player_threat_damage(player, damage)
Definition: _siegebot_theia.gsc:1040
‪should_update_damage_fx_level
‪function should_update_damage_fx_level(currentHealth, damage, maxHealth)
Definition: vehicle_shared.gsc:2900
‪attack_spike_minefield
‪function attack_spike_minefield()
Definition: _siegebot_theia.gsc:1998
‪add_utility_connection
‪function add_utility_connection(from_state_name, to_state_name, checkfunc, defaultScore)
Definition: statemachine_shared.gsc:112
‪toggle_lights_group
‪function toggle_lights_group(groupID, on)
Definition: vehicle_shared.gsc:2833
‪side_step
‪function side_step()
Definition: _siegebot_theia.gsc:751
‪stopMovementAndSetBrake
‪function stopMovementAndSetBrake()
Definition: _siegebot_theia.gsc:1728
‪GetNextMovePosition
‪function GetNextMovePosition(enemy)
Definition: _siegebot_theia.gsc:1510
‪state_jumpDown_enter
‪function state_jumpDown_enter(params)
Definition: _siegebot_theia.gsc:469
‪register
‪function register()
Definition: _ai_tank.gsc:126
‪FLAT_ORIGIN
‪#define FLAT_ORIGIN(__origin)
Definition: shared.gsh:256
‪get_script_bundle
‪function get_script_bundle(str_type, str_name)
Definition: struct.csc:45
‪_sort_by_distance2d
‪function _sort_by_distance2d(left, right, point)
Definition: _siegebot_theia.gsc:1590
‪footstep_damage
‪function footstep_damage(tag_name)
Definition: _siegebot_theia.gsc:836
‪init_player_threat
‪function init_player_threat(player)
Definition: _siegebot_theia.gsc:972
‪clamp
‪function clamp(val, val_min, val_max)
Definition: math_shared.csc:16
‪result
‪function result(death, attacker, mod, weapon)
Definition: _zm_aat_blast_furnace.gsc:46
‪spike_score
‪function spike_score(target)
Definition: _siegebot_theia.gsc:1958
‪lights_on
‪function lights_on(localClientNum, team)
Definition: vehicle_shared.csc:404
‪update_target_player
‪function update_target_player()
Definition: _siegebot_theia.gsc:1118
‪tag_name
‪var tag_name
Definition: _driving_fx.csc:74
‪Debug_line_to_target
‪function Debug_line_to_target(target, time, color)
Definition: _siegebot_theia.gsc:1147
‪get_jumpon_target
‪function get_jumpon_target(distanceLimitMin, distanceLimitMax, idealDist, includingAI, minAngleDiffCos, mustJump)
Definition: _siegebot_theia.gsc:1611
‪get_state_callbacks
‪function get_state_callbacks(statename)
Definition: vehicle_ai_shared.gsc:927
‪state_groundCombat_exit
‪function state_groundCombat_exit(params)
Definition: _siegebot_theia.gsc:933
‪WAIT_SERVER_FRAME
‪#define WAIT_SERVER_FRAME
Definition: shared.gsh:265
‪AddCooldownTime
‪function AddCooldownTime(name, time_seconds)
Definition: vehicle_ai_shared.gsc:1988