‪Black Ops 3 Source Code Explorer  0.1
‪An script explorer for Black Ops 3 by ZeRoY
_heatseekingmissile.gsc
Go to the documentation of this file.
1 #using scripts\codescripts\struct;
2 
3 #using scripts\shared\callbacks_shared;
4 #using scripts\shared\challenges_shared;
5 #using scripts\shared\clientfield_shared;
6 #using scripts\shared\dev_shared;
7 #using scripts\shared\system_shared;
8 #using scripts\shared\util_shared;
9 #using scripts\shared\weapons\_weapon_utils;
10 
11 #insert scripts\shared\shared.gsh;
12 
13 #precache( "string", "MP_CANNOT_LOCKON_TO_TARGET" );
14 
15 #precache( "fx", "killstreaks/fx_heli_chaff" );
16 
17 #define MISSED_BY_FAR_DISTANCE 500
18 #define FLARE_DISTANCE 3500
19 
20 #namespace heatseekingmissile;
21 
22 function ‪init_shared()
23 {
24  game["locking_on_sound"] = "uin_alert_lockon_start";
25  game["locked_on_sound"] = "uin_alert_lockon";
26 
28 
29  level.fx_flare = "killstreaks/fx_heli_chaff";
30 
31  //Dvar is used with the dev gui so as to let the player target friendly vehicles with heat-seekers.
32  /#
33  SetDvar("scr_freelock", "0");
34  #/
35 }
36 
38 {
39  self endon( "disconnect" );
40 
41  self ‪ClearIRTarget();
42  thread ‪StingerToggleLoop();
43  //thread TraceConstantTest();
44  self thread ‪StingerFiredNotify();
45 }
46 
47 function ‪ClearIRTarget()
48 {
49  self notify( "stop_lockon_sound" );
50  self notify( "stop_locked_sound" );
51  self.stingerlocksound = undefined;
52  self StopRumble( "stinger_lock_rumble" );
53 
54  self.stingerLockStartTime = 0;
55  self.stingerLockStarted = false;
56  self.stingerLockFinalized = false;
57  self.stingerLockDetected = false;
58  if( isdefined(self.stingerTarget) )
59  {
60  self.stingerTarget notify( "missile_unlocked" );
61  self ‪LockingOn(self.stingerTarget, false);
62  self ‪LockedOn(self.stingerTarget, false);
63  }
64  self.stingerTarget = undefined;
65 
66  self WeaponLockFree();
67  self WeaponLockTargetTooClose( false );
68  self WeaponLockNoClearance( false );
69 
70  self StopLocalSound( game["locking_on_sound"] );
71  self StopLocalSound( game["locked_on_sound"] );
72 
74 }
75 
76 
78 {
79  self endon( "disconnect" );
80  self endon ( "death" );
81 
82  while ( true )
83  {
84  self waittill( "missile_fire", missile, weapon );
85 
86  /# thread ‪debug_missile( missile ); #/
87 
88  if ( weapon.lockonType == "Legacy Single" )
89  {
90  if( isdefined(self.stingerTarget) && self.stingerLockFinalized )
91  {
92  self.stingerTarget notify( "stinger_fired_at_me", missile, weapon, self );
93  }
94  }
95  }
96 }
97 
98 /#
99 function ‪debug_missile( missile )
100 {
101  level notify( "debug_missile" );
102  level endon( "debug_missile" );
103 
104  level.debug_missile_dots = [];
105 
106  while( 1 )
107  {
108  if ( GetDvarInt( "scr_debug_missile", 0 ) == 0 )
109  {
110  wait 0.5;
111  continue;
112  }
113 
114  if ( isdefined( missile ) )
115  {
116  missile_info = SpawnStruct();
117  missile_info.origin = missile.origin;
118  target = missile Missile_GetTarget();
119  missile_info.targetEntNum = ( isdefined( target ) ? target GetEntityNumber() : undefined );
120  ‪ARRAY_ADD( level.debug_missile_dots, missile_info );
121  }
122 
123  foreach( missile_info in level.debug_missile_dots )
124  {
125  dot_color = ( isdefined( missile_info.targetEntNum ) ? ‪RED : ‪GREEN );
126  ‪util::debug_sphere( missile_info.origin, 10, dot_color, 0.66, 1 );
127  }
128 
130  }
131 }
132 #/
133 
135 {
136  while( !self ‪PlayerStingerAds() )
137  {
139 
140  currentWeapon = self GetCurrentWeapon();
141  if ( currentWeapon.lockonType != "Legacy Single" )
142  {
143  return false;
144  }
145  }
146 
147  return true;
148 }
149 
151 {
152  self endon( "disconnect" );
153  self endon ( "death" );
154 
155  for (;;)
156  {
157  self waittill( "weapon_change", weapon );
158 
159  while ( weapon.lockonType == "Legacy Single" )
160  {
161  if ( self GetWeaponAmmoClip( weapon ) == 0 )
162  {
164  weapon = self GetCurrentWeapon();
165  continue;
166  }
167 
168  if ( !‪StingerWaitForAds() )
169  {
170  break;
171  }
172 
173  self thread ‪StingerIRTLoop( weapon );
174 
175  while( self ‪PlayerStingerAds() )
176  {
178  }
179 
180  self notify( "stinger_IRT_off" );
181  self ‪ClearIRTarget();
182 
183  weapon = self GetCurrentWeapon();
184  }
185  }
186 }
187 
188 function ‪StingerIRTLoop( weapon )
189 {
190  self endon( "disconnect" );
191  self endon( "death" );
192  self endon( "stinger_IRT_off" );
193 
194  lockLength = self getLockOnSpeed();
195 
196  for (;;)
197  {
199 
200  //-------------------------
201  // Four possible states:
202  // No missile in the tube, so CLU will not search for targets.
203  // CLU has a lock.
204  // CLU is locking on to a target.
205  // CLU is searching for a target to begin locking on to.
206  //-------------------------
207 
208  if ( self.stingerLockFinalized )
209  {
210  passed = ‪SoftSightTest();
211  if ( !passed )
212  continue;
213 
214  if ( ! self ‪IsStillValidTarget( self.stingerTarget, weapon ) || self ‪InsideStingerReticleLocked( self.stingerTarget, weapon ) == false )
215  {
216  self SetWeaponLockOnPercent( weapon, 0 );
217  self ‪ClearIRTarget();
218  continue;
219  }
220 
221  if ( !self.stingerTarget.locked_on )
222  {
223  self.stingerTarget notify( "missile_lock", self, self GetCurrentWeapon() );
224  }
225 
226  self ‪LockingOn(self.stingerTarget, false);
227  self ‪LockedOn(self.stingerTarget, true);
228  if ( isdefined( weapon ) )
229  {
230  ‪heatseekingmissile::setFriendlyFlags( weapon, self.stingerTarget );
231  }
232 
233  thread ‪LoopLocalLockSound( game["locked_on_sound"], 0.75 );
234 
235 
236  //print3D( self.stingerTarget.origin, "* LOCKED!", (.2, 1, .3), 1, 5 );
237  continue;
238  }
239 
240  if ( self.stingerLockStarted )
241  {
242  if ( !self ‪IsStillValidTarget( self.stingerTarget, weapon ) || self ‪InsideStingerReticleLocked( self.stingerTarget, weapon ) == false )
243  {
244  self SetWeaponLockOnPercent( weapon, 0 );
245  self ‪ClearIRTarget();
246  continue;
247  }
248 
249  //print3D( self.stingerTarget.origin, "* locking...!", (.2, 1, .3), 1, 5 );
250 
251  self ‪LockingOn(self.stingerTarget, true);
252  self ‪LockedOn(self.stingerTarget, false);
253  if ( isdefined( weapon ) )
254  {
255  ‪heatseekingmissile::setFriendlyFlags( weapon, self.stingerTarget );
256  }
257 
258  passed = ‪SoftSightTest();
259  if ( !passed )
260  continue;
261 
262  timePassed = getTime() - self.stingerLockStartTime;
263 
264  if ( isdefined( weapon ) )
265  {
266  self SetWeaponLockOnPercent( weapon, ( ( timePassed / lockLength ) * 100 ) );
267  ‪heatseekingmissile::setFriendlyFlags( weapon, self.stingerTarget );
268  }
269 
270  if ( timePassed < lockLength )
271  continue;
272 
273  assert( isdefined( self.stingerTarget ) );
274  self notify( "stop_lockon_sound" );
275  self.stingerLockFinalized = true;
276  self WeaponLockFinalize( self.stingerTarget );
277 
278  continue;
279  }
280 
281 
282  bestTarget = self ‪GetBestStingerTarget( weapon );
283  if ( !isdefined( bestTarget ) || ( isdefined( self.stingerTarget ) && self.stingerTarget != bestTarget ) )
284  {
286  if ( self.stingerLockDetected == true )
287  {
288  self WeaponLockFree();
289  self.stingerLockDetected = false;
290  }
291  continue;
292  }
293 
294  if ( !( self ‪LockSightTest( bestTarget ) ) )
295  {
297  continue;
298  }
299 
300  //check for delay allowing helicopters to enter the play area
301  if( isdefined( bestTarget.lockOnDelay ) && bestTarget.lockOnDelay )
302  {
304  continue;
305  }
306 
307  if( !‪TargetWithinRangeOfPlaySpace( bestTarget ) )
308  {
310  continue;
311  }
312 
314 
315  if ( self ‪InsideStingerReticleLocked( bestTarget, weapon ) == false )
316  {
317  if ( self.stingerLockDetected == false )
318  {
319  self WeaponLockDetect( bestTarget );
320  }
321  self.stingerLockDetected = true;
322  if ( isdefined( weapon ) )
323  {
324  ‪heatseekingmissile::setFriendlyFlags( weapon, bestTarget );
325  }
326  continue;
327  }
328 
329  self.stingerLockDetected = false;
330 
331  ‪InitLockField( bestTarget );
332 
333  self.stingerTarget = bestTarget;
334  self.stingerLockStartTime = getTime();
335  self.stingerLockStarted = true;
336  self.stingerLostSightlineTime = 0;
337 
338  self WeaponLockStart( bestTarget );
339 
340  self thread ‪LoopLocalSeekSound( game["locking_on_sound"], 0.6 );
341  }
342 }
343 
345 {
346 
347 /#
348  // for tuning
349  if ( GetDvarInt( "scr_missilelock_playspace_extra_radius_override_enabled", 0 ) > 0 )
350  {
351  extraRadiusDvar = GetDvarInt( "scr_missilelock_playspace_extra_radius", 5000 );
352  if ( extraRadiusDvar != ‪VAL( level.missileLockPlaySpaceCheckExtraRadius, 0 ) )
353  {
354  level.missileLockPlaySpaceCheckExtraRadius = extraRadiusDvar;
355  level.missileLockPlaySpaceCheckRadiusSqr = undefined;
356  }
357  }
358 #/
359 
360  // only allow targetting of targets that are within the play space by a specified radius
361  // use level.missileLockPlaySpaceCheckExtraRadius to set it per level
362  if ( level.missileLockPlaySpaceCheckEnabled === true )
363  {
364  if ( !isdefined( target ) )
365  return false;
366 
367  if ( !isdefined( level.playSpaceCenter ) )
368  level.playSpaceCenter = ‪util::GetPlaySpaceCenter();
369 
370  if ( !isdefined( level.missileLockPlaySpaceCheckRadiusSqr ) )
371  level.missileLockPlaySpaceCheckRadiusSqr = ‪SQR( ( ‪util::GetPlaySpaceMaxWidth() * 0.5 ) + level.missileLockPlaySpaceCheckExtraRadius );
372 
373  if ( Distance2DSquared( target.origin, level.playSpaceCenter ) > level.missileLockPlaySpaceCheckRadiusSqr )
374  return false;
375  }
376 
377  return true;
378 }
379 
381 {
382  if( isdefined( self.LockOnCanceledMessage ) )
383  self.LockOnCanceledMessage ‪destroy();
384 }
385 
387 {
388  if( isdefined( self.LockOnCanceledMessage ) )
389  return;
390 
391  self.LockOnCanceledMessage = newclienthudelem( self );
392  self.LockOnCanceledMessage.fontScale = 1.25;
393  self.LockOnCanceledMessage.x = 0;
394  self.LockOnCanceledMessage.y = 50;
395  self.LockOnCanceledMessage.alignX = "center";
396  self.LockOnCanceledMessage.alignY = "top";
397  self.LockOnCanceledMessage.horzAlign = "center";
398  self.LockOnCanceledMessage.vertAlign = "top";
399  self.LockOnCanceledMessage.foreground = true;
400  self.LockOnCanceledMessage.hidewhendead = false;
401  self.LockOnCanceledMessage.hidewheninmenu = true;
402  self.LockOnCanceledMessage.archived = false;
403  self.LockOnCanceledMessage.alpha = 1.0;
404  self.LockOnCanceledMessage SetText( &"MP_CANNOT_LOCKON_TO_TARGET" );
405 }
406 
407 function ‪GetBestStingerTarget( weapon )
408 {
409  targetsAll = [];
410 
411  if ( isdefined( self.get_stinger_target_override ) )
412  {
413  targetsAll = self [ [ self.get_stinger_target_override ] ]();
414  }
415  else
416  {
417  targetsAll = target_getArray();
418  }
419 
420  targetsValid = [];
421 
422  for ( idx = 0; idx < targetsAll.size; idx++ )
423  {
424  /#
425  //This variable is set and managed by the 'dev_friendly_lock' function, which works with the dev_gui
426  if( GetDvarString( "scr_freelock") == "1" )
427  {
428  //If the dev_gui dvar is set, only check if the target is in the reticule.
429  if( self ‪InsideStingerReticleNoLock( targetsAll[idx], weapon ) )
430  {
431  targetsValid[targetsValid.size] = targetsAll[idx];
432  }
433  continue;
434  }
435  #/
436 
437  target = targetsAll[idx];
438 
439  if ( level.teamBased || level.use_team_based_logic_for_locking_on === true ) //team based game modes
440  {
441  if ( isdefined(target.team) && target.team != self.team)
442  {
443  if ( self ‪InsideStingerReticleDetect( target, weapon ) )
444  {
445  if ( !isdefined( self.is_valid_target_for_stinger_override ) || self [ [ self.is_valid_target_for_stinger_override ] ]( target ) )
446  {
447  hascamo = isdefined( target.camo_state ) && ( target.camo_state == 1 ) && !self hasPerk( "specialty_showenemyequipment" );
448  if( !hascamo )
449  targetsValid[targetsValid.size] = target;
450  }
451  }
452  }
453  }
454  else
455  {
456  if( self ‪InsideStingerReticleDetect( target, weapon ) ) //Free for all
457  {
458  if( ( isdefined( target.owner ) && self != target.owner ) || ( isplayer( target ) && self != target ) )
459  {
460  if ( !isdefined( self.is_valid_target_for_stinger_override ) || self [ [ self.is_valid_target_for_stinger_override ] ]( target ) )
461  targetsValid[targetsValid.size] = target;
462  }
463  }
464  }
465  }
466 
467  if ( targetsValid.size == 0 )
468  return undefined;
469 
470  bestTarget = targetsValid[0];
471  if ( targetsValid.size > 1 )
472  {
473  closestRatio = 0.0;
474 
475  foreach( target in targetsValid )
476  {
477  ratio = ‪RatioDistanceFromScreenCenter( target, weapon );
478  if ( ratio > closestRatio )
479  {
480  closestRatio = ratio;
481  bestTarget = target;
482  }
483  }
484  }
485 
486  return bestTarget;
487 }
488 
489 function ‪CalcLockOnRadius( target, weapon )
490 {
491  radius = self getLockOnRadius();
492 
493  if( isdefined( weapon ) && isdefined( weapon.lockOnScreenRadius ) && ( weapon.lockOnScreenRadius > radius ) )
494  {
495  radius = weapon.lockOnScreenRadius;
496  }
497 
498  if( isdefined( level.lockOnCloseRange ) && isdefined( level.lockOnCloseRadiusScaler ) )
499  {
500  dist2 = DistanceSquared( target.origin, self.origin );
501  if( dist2 < level.lockOnCloseRange * level.lockOnCloseRange )
502  radius = radius * level.lockOnCloseRadiusScaler;
503  }
504 
505  return radius;
506 }
507 
508 function ‪CalcLockOnLossRadius( target, weapon )
509 {
510  radius = self getLockOnLossRadius();
511 
512  if( isdefined( weapon ) && isdefined( weapon.lockOnScreenRadius ) && ( weapon.lockOnScreenRadius > radius ) )
513  {
514  radius = weapon.lockOnScreenRadius;
515  }
516 
517  if( isdefined( level.lockOnCloseRange ) && isdefined( level.lockOnCloseRadiusScaler ) )
518  {
519  dist2 = DistanceSquared( target.origin, self.origin );
520  if( dist2 < level.lockOnCloseRange * level.lockOnCloseRange )
521  radius = radius * level.lockOnCloseRadiusScaler;
522  }
523  return radius;
524 }
525 
526 function ‪RatioDistanceFromScreenCenter( target, weapon )
527 {
528  radius = ‪CalcLockOnRadius( target, weapon );
529  return Target_ScaleMinMaxRadius( target, self, 65, 0, radius );
530 }
531 
532 function ‪InsideStingerReticleDetect( target, weapon )
533 {
534  radius = ‪CalcLockOnRadius( target, weapon );
535  return target_isincircle( target, self, 65, radius );
536 }
537 
538 function ‪InsideStingerReticleNoLock( target, weapon )
539 {
540  radius = ‪CalcLockOnRadius( target, weapon );
541  return target_isincircle( target, self, 65, radius );
542 }
543 
544 function ‪InsideStingerReticleLocked( target, weapon )
545 {
546  radius = ‪CalcLockOnLossRadius( target, weapon );
547  return target_isincircle( target, self, 65, radius );
548 }
549 
550 function ‪IsStillValidTarget( ent, weapon )
551 {
552  if ( ! isdefined( ent ) )
553  return false;
554 
555  if ( isdefined( self.is_still_valid_target_for_stinger_override ) )
556  return self [ [ self.is_still_valid_target_for_stinger_override ] ]( ent, weapon );
557 
558  if ( ! target_isTarget( ent ) && !( isdefined( ent.allowContinuedLockonAfterInvis ) && ent.allowContinuedLockonAfterInvis ) )
559  return false;
560 
561  if ( ! ‪InsideStingerReticleDetect( ent, weapon ) )
562  return false;
563 
564  return true;
565 }
566 
568 {
569  return ( self PlayerAds() == 1.0 );
570 }
571 
572 function ‪LoopLocalSeekSound( alias, interval )
573 {
574  self endon ( "stop_lockon_sound" );
575  self endon( "disconnect" );
576  self endon ( "death" );
577 
578  for (;;)
579  {
580  self ‪PlaySoundForLocalPlayer( alias );
581  self PlayRumbleOnEntity( "stinger_lock_rumble" );
582 
583  wait interval/2;
584  }
585 }
586 
587 function ‪PlaySoundForLocalPlayer( alias )
588 {
589  if ( self IsInVehicle() )
590  {
591  sound_target = self GetVehicleOccupied();
592  if ( isdefined( sound_target ) )
593  {
594  sound_target PlaySoundToPlayer( alias, self );
595  }
596  }
597  else
598  {
599  self playLocalSound( alias );
600  }
601 }
602 
603 function ‪LoopLocalLockSound( alias, interval )
604 {
605  self endon ( "stop_locked_sound" );
606  self endon( "disconnect" );
607  self endon ( "death" );
608 
609  if ( isdefined( self.stingerlocksound ) )
610  return;
611 
612  self.stingerlocksound = true;
613 
614 
615  for (;;)
616  {
617  // TODO make lock loop audio work correctly CDC
618 
619  self ‪PlaySoundForLocalPlayer( alias );
620  self PlayRumbleOnEntity( "stinger_lock_rumble" );
621  wait interval/6;
622 
623  self ‪PlaySoundForLocalPlayer( alias );
624  self PlayRumbleOnEntity( "stinger_lock_rumble" );
625  wait interval/6;
626 
627  self ‪PlaySoundForLocalPlayer( alias );
628  self PlayRumbleOnEntity( "stinger_lock_rumble" );
629  wait interval/6;
630 
631  self StopRumble( "stinger_lock_rumble" );
632  }
633  self.stingerlocksound = undefined;
634 }
635 
636 function ‪LockSightTest( target )
637 {
638  cameraPos = self getplayercamerapos();
639 
640  if ( !isdefined( target ) ) //targets can disapear during targeting.
641  return false;
642 
643  if( isdefined( target.parent ) )
644  passed = BulletTracePassed( cameraPos, target.origin, false, target, target.parent );
645  else
646  passed = BulletTracePassed( cameraPos, target.origin, false, target );
647  if ( passed )
648  return true;
649 
650  front = target GetPointInBounds( 1, 0, 0 );
651  if( isdefined( target.parent ) )
652  passed = BulletTracePassed( cameraPos, front, false, target, target.parent );
653  else
654  passed = BulletTracePassed( cameraPos, front, false, target );
655  if ( passed )
656  return true;
657 
658  back = target GetPointInBounds( -1, 0, 0 );
659  if( isdefined( target.parent ) )
660  passed = BulletTracePassed( cameraPos, back, false, target, target.parent );
661  else
662  passed = BulletTracePassed( cameraPos, back, false, target );
663  if ( passed )
664  return true;
665 
666  return false;
667 }
668 
670 {
671  LOST_SIGHT_LIMIT = 500;
672 
673  if ( self ‪LockSightTest( self.stingerTarget ) )
674  {
675  self.stingerLostSightlineTime = 0;
676  return true;
677  }
678 
679  if ( self.stingerLostSightlineTime == 0 )
680  self.stingerLostSightlineTime = getTime();
681 
682  timePassed = GetTime() - self.stingerLostSightlineTime;
683  //PrintLn( "Losing sight of target [", timePassed, "]..." );
684 
685  if ( timePassed >= LOST_SIGHT_LIMIT )
686  {
687  //PrintLn( "Lost sight of target." );
688  self ‪ClearIRTarget();
689  return false;
690  }
691 
692  return true;
693 }
694 
695 function ‪InitLockField( target )
696 {
697  if ( isdefined( target.locking_on ) )
698  return;
699 
700  target.locking_on = 0;
701  target.locked_on = 0;
702  target.locking_on_hacking = 0;
703 }
704 
705 function ‪LockingOn( target, ‪lock )
706 {
707  Assert( isdefined( target.locking_on ) );
708 
709  clientNum = self getEntityNumber();
710  if ( ‪lock )
711  {
712  target notify( "locking on" );
713  target.locking_on |= ( 1 << clientNum );
714 
715  self thread ‪watchClearLockingOn( target, clientNum );
716  }
717  else
718  {
719  self notify( "locking_on_cleared" );
720  target.locking_on &= ~( 1 << clientNum );
721  }
722 }
723 
724 function ‪watchClearLockingOn( target, clientNum )
725 {
726  target endon("death");
727  self endon( "locking_on_cleared" );
728 
729  self ‪util::waittill_any( "death", "disconnect" );
730 
731  target.locking_on &= ~( 1 << clientNum );
732 }
733 
734 function ‪LockedOn( target, ‪lock )
735 {
736  Assert( isdefined( target.locked_on ) );
737 
738  clientNum = self getEntityNumber();
739  if ( ‪lock )
740  {
741  target.locked_on |= ( 1 << clientNum );
742 
743  self thread ‪watchClearLockedOn( target, clientNum );
744  }
745  else
746  {
747  self notify( "locked_on_cleared" );
748  target.locked_on &= ~( 1 << clientNum );
749  }
750 }
751 
752 
753 function ‪TargetingHacking( target, ‪lock )
754 {
755  Assert( isdefined( target.locking_on_hacking ) );
756 
757  clientNum = self getEntityNumber();
758  if ( ‪lock )
759  {
760  target notify( "locking on hacking" );
761  target.locking_on_hacking |= ( 1 << clientNum );
762 
763  self thread ‪watchClearHacking( target, clientNum );
764  }
765  else
766  {
767  self notify( "locking_on_hacking_cleared" );
768  target.locking_on_hacking &= ~( 1 << clientNum );
769  }
770 }
771 
772 function ‪watchClearHacking( target, clientNum )
773 {
774  target endon("death");
775  self endon( "locking_on_hacking_cleared" );
776 
777  self ‪util::waittill_any( "death", "disconnect" );
778 
779  target.locking_on_hacking &= ~( 1 << clientNum );
780 }
781 
782 
783 function ‪setFriendlyFlags( weapon, target )
784 {
785  if ( !self isinvehicle() )
786  {
787  self ‪setFriendlyHacking( weapon, target );
788  self ‪setFriendlyTargetting( weapon, target );
789  self ‪setFriendlyTargetLocked( weapon, target );
790 
791  if ( isdefined( level.killstreakMaxHealthFunction ) )
792  {
793  if ( isdefined( target.useVTOLTime ) && isdefined( level.vtol ) )
794  {
795  killstreakEndTime = level.vtol.killstreakEndTime;
796  if ( isdefined( killstreakEndTime ) )
797  {
798  self settargetedentityendtime( weapon, killstreakEndTime );
799  }
800  }
801  else if ( isdefined( target.killstreakEndTime ) )
802  {
803  self settargetedentityendtime( weapon, target.killstreakEndTime );
804  }
805  else if ( isdefined( target.parentstruct ) && isdefined( target.parentStruct.killstreakEndTime ) )
806  {
807  self settargetedentityendtime( weapon, target.parentStruct.killstreakEndTime );
808  }
809  else
810  {
811  self settargetedentityendtime( weapon, 0 );
812  }
813  self settargetedmissilesremaining( weapon, 0 );
814 
815  killstreakType = target.killstreakType;
816  if ( !isdefined( target.killstreakType ) && isdefined( target.parentstruct ) && isdefined( target.parentStruct.killstreakType ) )
817  {
818  killstreakType = target.parentStruct.killstreakType;
819  }
820  else if ( isdefined( target.useVTOLTime ) && isdefined( level.vtol.killstreakType ) )
821  {
822  killstreakType = level.vtol.killstreakType;
823  }
824 
825  if ( isdefined ( killstreakType ) && isdefined( level.killstreakbundle[killstreakType] ) )
826  {
827  if ( isdefined( target.forceOneMissile ) )
828  {
829  self settargetedmissilesremaining( weapon, 1 );
830  }
831  else if ( isdefined( target.useVTOLTime ) && isdefined( level.vtol ) && isdefined( level.vtol.totalRocketHits ) && isdefined( level.vtol.missileToDestroy ) )
832  {
833  self settargetedmissilesremaining( weapon, level.vtol.missileToDestroy - level.vtol.totalRocketHits );
834  }
835  else
836  {
837  maxHealth = [[level.killstreakMaxHealthFunction]]( killstreakType );
838  damageTaken = target.damageTaken;
839  if ( !isdefined( damageTaken ) && isdefined( target.parentstruct ) )
840  {
841  damageTaken = target.parentstruct.damageTaken;
842  }
843  if ( isdefined( target.missileTrackDamage ) )
844  {
845  damageTaken = target.missileTrackDamage;
846  }
847 
848  if ( isdefined( damageTaken ) && isdefined( maxHealth ) )
849  {
850  damagePerRocket = ( maxHealth / level.killstreakbundle[killstreakType].ksRocketsToKill ) + 1;
851  remainingHealth = maxHealth - damageTaken;
852  if ( remaininghealth > 0 )
853  {
854  missilesRemaining = int( ceil( remainingHealth / damageperrocket ) );
855  if ( isdefined( target.numflares ) && target.numflares > 0 )
856  {
857  missilesRemaining += target.numFlares;
858  }
859  if ( isdefined( target.flak_drone ) )
860  {
861  missilesRemaining += 1;
862  }
863 
864  self settargetedmissilesremaining( weapon, missilesRemaining );
865  }
866  }
867  }
868  }
869  }
870  }
871 }
872 
873 function ‪setFriendlyHacking( weapon, target )
874 {
875  if ( level.teambased )
876  {
877  friendlyHackingMask = target.locking_on_hacking;
878 
879  if ( isdefined( friendlyHackingMask ) )
880  {
881  friendlyHacking = false;
882  clientNum = self getEntityNumber();
883  friendlyHackingMask &= ~( 1 << clientNum );
884  if ( friendlyHackingMask != 0 )
885  {
886  friendlyHacking = true;
887  }
888  self SetWeaponFriendlyHacking( weapon, friendlyHacking );
889  }
890  }
891 }
892 
893 function ‪setFriendlyTargetting( weapon, target )
894 {
895  if ( level.teambased )
896  {
897  friendlyTargetingMask = target.locking_on;
898 
899  if ( isdefined( friendlyTargetingMask ) )
900  {
901  friendlyTargeting = false;
902  clientNum = self getEntityNumber();
903  friendlyTargetingMask &= ~( 1 << clientNum );
904  if ( friendlyTargetingMask != 0 )
905  {
906  friendlyTargeting = true;
907  }
908  self SetWeaponFriendlyTargeting( weapon, friendlyTargeting );
909  }
910  }
911 }
912 
913 function ‪setFriendlyTargetLocked( weapon, target )
914 {
915  if ( level.teambased )
916  {
917  friendlyTargetLocked = false;
918  friendlyLockingOnMask = target.locked_on;
919 
920  if ( isdefined( friendlyLockingOnMask ) )
921  {
922  friendlyTargetLocked = false;
923  clientNum = self getEntityNumber();
924  friendlyLockingOnMask &= ~( 1 << clientNum );
925  if ( friendlylockingonMask != 0 )
926  {
927  friendlyTargetLocked = true;
928  }
929  }
930  if ( friendlyTargetLocked == false )
931  {
932  friendlyTargetLocked = target ‪MissileTarget_isOtherPlayerMissileIncoming( self );
933  }
934  self SetWeaponFriendlyTargetLocked( weapon, friendlyTargetLocked );
935  }
936 }
937 
938 function ‪watchClearLockedOn( target, clientNum )
939 {
940  self endon( "locked_on_cleared" );
941 
942  self ‪util::waittill_any( "death", "disconnect" );
943 
944  if ( isdefined( target ) )
945  {
946  target.locked_on &= ~( 1 << clientNum );
947  }
948 }
949 
950 function ‪MissileTarget_LockOnMonitor( player, endon1, endon2 )
951 {
952  self endon( "death" );
953 
954  if ( isdefined(endon1) )
955  self endon( endon1 );
956  if ( isdefined(endon2) )
957  self endon( endon2 );
958 
959  for( ;; )
960  {
961 
962  if( target_isTarget( self ) )
963  {
965  {
966  self ‪clientfield::set( "heli_warn_fired", 1 );
967  self ‪clientfield::set( "heli_warn_locked", 0 );
968  self ‪clientfield::set( "heli_warn_targeted", 0 );
969  }
970  else if( isdefined(self.locked_on) && self.locked_on )
971  {
972  self ‪clientfield::set( "heli_warn_locked", 1 );
973  self ‪clientfield::set( "heli_warn_fired", 0 );
974  self ‪clientfield::set( "heli_warn_targeted", 0 );
975  }
976  else if( isdefined(self.locking_on) && self.locking_on )
977  {
978  self ‪clientfield::set( "heli_warn_targeted", 1 );
979  self ‪clientfield::set( "heli_warn_fired", 0 );
980  self ‪clientfield::set( "heli_warn_locked", 0 );
981  }
982  else
983  {
984  self ‪clientfield::set( "heli_warn_fired", 0 );
985  self ‪clientfield::set( "heli_warn_targeted", 0 );
986  self ‪clientfield::set( "heli_warn_locked", 0 );
987  }
988  }
989 
990  wait( 0.1 );
991  }
992 }
993 
994 function ‪_incomingMissile( missile, attacker )
995 {
996  if ( !isdefined(self.incoming_missile) )
997  {
998  self.incoming_missile = 0;
999  }
1000  if ( !isdefined(self.incoming_missile_owner) )
1001  {
1002  self.incoming_missile_owner = [];
1003  }
1004  if ( !isdefined( self.incoming_missile_owner[attacker.entnum] ) )
1005  {
1006  self.incoming_missile_owner[attacker.entnum] = 0;
1007  }
1008 
1009  self.incoming_missile++;
1010  self.incoming_missile_owner[attacker.entnum]++;
1011 
1012  attacker ‪LockedOn(self, true);
1013 
1014  self thread ‪_incomingMissileTracker( missile, attacker );
1015 }
1016 
1017 function ‪_incomingMissileTracker( missile, attacker )
1018 {
1019  self endon("death");
1020 
1021  attacker_entnum = attacker.entnum;
1022 
1023  missile waittill("death");
1024 
1025  self.incoming_missile--;
1026  self.incoming_missile_owner[attacker_entnum]--;
1027  if ( self.incoming_missile_owner[attacker_entnum] == 0 )
1028  {
1029  self.incoming_missile_owner[attacker_entnum] = undefined;
1030  }
1031 
1032  if ( isdefined( attacker ) )
1033  {
1034  attacker ‪LockedOn( self, false );
1035  }
1036 
1037  assert( self.incoming_missile >= 0 );
1038 }
1039 
1041 {
1042  if ( !isdefined(self.incoming_missile) )
1043  return false;
1044 
1045  if ( self.incoming_missile )
1046  return true;
1047 
1048  return false;
1049 }
1050 
1052 {
1053  if ( !isdefined(self.incoming_missile_owner) )
1054  return false;
1055 
1056  if ( self.incoming_missile_owner.size == 0 )
1057  return false;
1058 
1059  if ( self.incoming_missile_owner.size == 1 && isdefined( self.incoming_missile_owner[attacker.entnum] ) )
1060  return false;
1061 
1062  return true;
1063 }
1064 
1065 function ‪MissileTarget_HandleIncomingMissile(responseFunc, endon1, endon2, allowDirectDamage )
1066 {
1067  level endon( "game_ended" );
1068  self endon( "death" );
1069  if ( isdefined(endon1) )
1070  self endon( endon1 );
1071  if ( isdefined(endon2) )
1072  self endon( endon2 );
1073 
1074  for( ;; )
1075  {
1076  self waittill( "stinger_fired_at_me", missile, weapon, attacker );
1077 
1078  ‪_incomingMissile(missile, attacker);
1079 
1080  if ( isdefined(responseFunc) )
1081  [[responseFunc]]( missile, attacker, weapon, endon1, endon2, allowDirectDamage );
1082  }
1083 }
1084 
1085 function ‪MissileTarget_ProximityDetonateIncomingMissile( endon1, endon2, allowDirectDamage )
1086 {
1087  ‪MissileTarget_HandleIncomingMissile(&‪MissileTarget_ProximityDetonate, endon1, endon2, allowDirectDamage );
1088 }
1089 
1090 function ‪_missileDetonate( attacker, weapon, range, minDamage, maxDamage, allowDirectDamage )
1091 {
1092  origin = self.origin;
1093 
1094  target = self Missile_GetTarget();
1095 
1096  self ‪detonate();
1097 
1098  // guarantee a locked_on target is directly damaged if allowed and reasonably close enough
1099  if ( ( allowDirectDamage === true ) && isdefined( target ) && isdefined( target.origin ) )
1100  {
1101  minDistSq = ‪VAL( target.locked_missile_min_distsq, ‪SQR( range ) ); // sanity check distance for "bad" direct hits
1102  distSq = DistanceSquared( self.origin, target.origin );
1103  if ( distSq < minDistSq )
1104  {
1105  target DoDamage( maxDamage, origin, attacker, self, "none", "MOD_PROJECTILE", 0, weapon );
1106  }
1107  }
1108 
1109  radiusDamage( origin, range, maxDamage, minDamage, attacker, "MOD_PROJECTILE_SPLASH", weapon );
1110 }
1111 
1112 function ‪MissileTarget_ProximityDetonate( missile, attacker, weapon, endon1, endon2, allowDirectDamage )
1113 {
1114  level endon( "game_ended" );
1115  missile endon ( "death" );
1116  if ( isdefined(endon1) )
1117  self endon( endon1 );
1118  if ( isdefined(endon2) )
1119  self endon( endon2 );
1120 
1121  minDist = DistanceSquared( missile.origin, self.origin );
1122  lastCenter = self.origin;
1123 
1124  missile Missile_SetTarget( self, ‪VAL( Target_GetOffset( self ), ( 0, 0, 0 ) ) );
1125 
1126  if ( isdefined( self.missileTargetMissDistance ) )
1127  {
1128  missedDistanceSq = self.missileTargetMissDistance * self.missileTargetMissDistance;
1129  }
1130  else
1131  {
1133  }
1134  flareDistanceSq = ‪FLARE_DISTANCE * ‪FLARE_DISTANCE;
1135 
1136  for ( ;; )
1137  {
1138  // target already destroyed
1139  if ( !isdefined( self ) )
1140  center = lastCenter;
1141  else
1142  center = self.origin;
1143 
1144  lastCenter = center;
1145 
1146  curDist = DistanceSquared( missile.origin, center );
1147 
1148  if( curDist < flareDistanceSq && isdefined(self.numFlares) && self.numFlares > 0 )
1149  {
1150  self.numFlares--;
1151 
1152  self thread ‪MissileTarget_PlayFlareFx();
1153  self ‪challenges::trackAssists( attacker, 0, true );
1154  newTarget = self ‪MissileTarget_DeployFlares(missile.origin, missile.angles);
1155 
1156  missile Missile_SetTarget( newTarget, ‪VAL( Target_GetOffset( newTarget ), ( 0, 0, 0 ) ) );
1157  missileTarget = newTarget;
1158 
1159  return;
1160  }
1161 
1162  if ( curDist < minDist )
1163  minDist = curDist;
1164 
1165  if ( curDist > minDist )
1166  {
1167  if ( curDist > missedDistanceSq )
1168  return;
1169 
1170  missile thread ‪_missileDetonate( attacker, weapon, ‪MISSED_BY_FAR_DISTANCE, 600, 600, allowDirectDamage );
1171  return;
1172  }
1173 
1175  }
1176 }
1177 
1179 {
1180  if ( !isdefined( self ) )
1181  return;
1182 
1183  flare_fx = level.fx_flare;
1184 
1185  if ( isdefined( self.fx_flare ) )
1186  {
1187  flare_fx = self.fx_flare;
1188  }
1189  if( isdefined( self.flare_ent ) )
1190  {
1191  PlayFXOnTag( flare_fx, self.flare_ent, "tag_origin" );
1192  }
1193  else
1194  {
1195  PlayFXOnTag( flare_fx, self, "tag_origin" );
1196  }
1197 
1198  if ( isdefined( self.owner ) )
1199  {
1200  self playsoundtoplayer ( "veh_huey_chaff_drop_plr", self.owner );
1201  }
1202  self PlaySound ( "veh_huey_chaff_explo_npc" );
1203 }
1204 
1205 function ‪MissileTarget_DeployFlares(origin, angles) // self == missile target
1206 {
1207  vec_toForward = anglesToForward( self.angles );
1208  vec_toRight = AnglesToRight( self.angles );
1209 
1210  vec_toMissileForward = anglesToForward( angles );
1211 
1212  delta = self.origin - origin;
1213  dot = VectorDot(vec_toMissileForward,vec_toRight);
1214 
1215  ‪sign = 1;
1216  if ( dot > 0 )
1217  ‪sign = -1;
1218 
1219  // out to one side or the other slightly backwards
1220  flare_dir = VectorNormalize(VectorScale( vec_toForward, -0.5 ) + VectorScale( vec_toRight, ‪sign ));
1221  velocity = VectorScale( flare_dir, RandomIntRange(200, 400));
1222  velocity = (velocity[0], velocity[1], velocity[2] - RandomIntRange(10, 100) );
1223 
1224  flareOrigin = self.origin;
1225  flareOrigin = flareOrigin + VectorScale( flare_dir, RandomIntRange(600, 800));
1226 
1227  // some height will allow a missle going twards a low hovering plane to
1228  // have enough radius to turn to the new target
1229  flareOrigin = flareOrigin + ( 0, 0, 500 );
1230 
1231  if ( isdefined( self.flareOffset ) )
1232  flareOrigin = flareOrigin + self.flareOffset;
1233 
1234  flareObject = ‪spawn( "script_origin", flareOrigin );
1235  flareObject.angles = self.angles;
1236 
1237  flareObject SetModel( "tag_origin" );
1238  flareObject MoveGravity( velocity, 5.0 );
1239 
1240  flareObject thread ‪util::deleteAfterTime( 5.0 );
1241 /#
1242  self thread ‪debug_tracker( flareObject );
1243 #/
1244  return flareObject;
1245 }
1246 
1247 /#
1248 function ‪debug_tracker( target )
1249 {
1250  target endon( "death");
1251 
1252  while(1)
1253  {
1254  ‪util::debug_sphere( target.origin, 10, (1,0,0), 1, 1 );
1256  }
1257 }
1258 #/
‪MissileTarget_PlayFlareFx
‪function MissileTarget_PlayFlareFx()
Definition: _heatseekingmissile.gsc:1178
‪TargetWithinRangeOfPlaySpace
‪function TargetWithinRangeOfPlaySpace(target)
Definition: _heatseekingmissile.gsc:344
‪SoftSightTest
‪function SoftSightTest()
Definition: _heatseekingmissile.gsc:669
‪LoopLocalSeekSound
‪function LoopLocalSeekSound(alias, interval)
Definition: _heatseekingmissile.gsc:572
‪DisplayLockOnCanceledMessage
‪function DisplayLockOnCanceledMessage()
Definition: _heatseekingmissile.gsc:386
‪FLARE_DISTANCE
‪#define FLARE_DISTANCE
Definition: _heatseekingmissile.gsc:18
‪MissileTarget_isOtherPlayerMissileIncoming
‪function MissileTarget_isOtherPlayerMissileIncoming(attacker)
Definition: _heatseekingmissile.gsc:1051
‪watchClearLockingOn
‪function watchClearLockingOn(target, clientNum)
Definition: _heatseekingmissile.gsc:724
‪PlaySoundForLocalPlayer
‪function PlaySoundForLocalPlayer(alias)
Definition: _heatseekingmissile.gsc:587
‪InsideStingerReticleDetect
‪function InsideStingerReticleDetect(target, weapon)
Definition: _heatseekingmissile.gsc:532
‪sign
‪function sign(x)
Definition: math_shared.csc:164
‪MissileTarget_HandleIncomingMissile
‪function MissileTarget_HandleIncomingMissile(responseFunc, endon1, endon2, allowDirectDamage)
Definition: _heatseekingmissile.gsc:1065
‪MissileTarget_ProximityDetonateIncomingMissile
‪function MissileTarget_ProximityDetonateIncomingMissile(endon1, endon2, allowDirectDamage)
Definition: _heatseekingmissile.gsc:1085
‪MissileTarget_LockOnMonitor
‪function MissileTarget_LockOnMonitor(player, endon1, endon2)
Definition: _heatseekingmissile.gsc:950
‪debug_sphere
‪function debug_sphere(origin, radius, color, alpha, time)
Definition: _airsupport.gsc:1153
‪detonate
‪function detonate(attacker)
Definition: vehicle_ai_shared.gsc:1650
‪LoopLocalLockSound
‪function LoopLocalLockSound(alias, interval)
Definition: _heatseekingmissile.gsc:603
‪GetPlaySpaceCenter
‪function GetPlaySpaceCenter()
Definition: util_shared.gsc:4243
‪_incomingMissileTracker
‪function _incomingMissileTracker(missile, attacker)
Definition: _heatseekingmissile.gsc:1017
‪RatioDistanceFromScreenCenter
‪function RatioDistanceFromScreenCenter(target, weapon)
Definition: _heatseekingmissile.gsc:526
‪deleteAfterTime
‪function deleteAfterTime(time)
Definition: util_shared.gsc:2710
‪on_player_spawned
‪function on_player_spawned()
Definition: _heatseekingmissile.gsc:37
‪VAL
‪#define VAL(__var, __default)
Definition: shared.gsh:272
‪watchClearHacking
‪function watchClearHacking(target, clientNum)
Definition: _heatseekingmissile.gsc:772
‪GetPlaySpaceMaxWidth
‪function GetPlaySpaceMaxWidth()
Definition: util_shared.gsc:4254
‪watchClearLockedOn
‪function watchClearLockedOn(target, clientNum)
Definition: _heatseekingmissile.gsc:938
‪lock
‪function lock()
Definition: doors_shared.gsc:138
‪spawn
‪function spawn(v_origin=(0, 0, 0), v_angles=(0, 0, 0))
Definition: struct.csc:23
‪SQR
‪#define SQR(__var)
Definition: shared.gsh:293
‪GREEN
‪#define GREEN
Definition: shared.gsh:176
‪MissileTarget_DeployFlares
‪function MissileTarget_DeployFlares(origin, angles)
Definition: _heatseekingmissile.gsc:1205
‪_missileDetonate
‪function _missileDetonate(attacker, weapon, range, minDamage, maxDamage, allowDirectDamage)
Definition: _heatseekingmissile.gsc:1090
‪setFriendlyHacking
‪function setFriendlyHacking(weapon, target)
Definition: _heatseekingmissile.gsc:873
‪RED
‪#define RED
Definition: shared.gsh:175
‪debug_tracker
‪function debug_tracker(target)
Definition: _heatseekingmissile.gsc:1248
‪on_spawned
‪function on_spawned(func, obj)
Definition: callbacks_shared.csc:245
‪CalcLockOnLossRadius
‪function CalcLockOnLossRadius(target, weapon)
Definition: _heatseekingmissile.gsc:508
‪StingerToggleLoop
‪function StingerToggleLoop()
Definition: _heatseekingmissile.gsc:150
‪LockedOn
‪function LockedOn(target, lock)
Definition: _heatseekingmissile.gsc:734
‪InitLockField
‪function InitLockField(target)
Definition: _heatseekingmissile.gsc:695
‪DestroyLockOnCanceledMessage
‪function DestroyLockOnCanceledMessage()
Definition: _heatseekingmissile.gsc:380
‪waittill_any
‪function waittill_any(str_notify1, str_notify2, str_notify3, str_notify4, str_notify5)
Definition: util_shared.csc:375
‪LockingOn
‪function LockingOn(target, lock)
Definition: _heatseekingmissile.gsc:705
‪ARRAY_ADD
‪#define ARRAY_ADD(__array, __item)
Definition: shared.gsh:304
‪GetBestStingerTarget
‪function GetBestStingerTarget(weapon)
Definition: _heatseekingmissile.gsc:407
‪ClearIRTarget
‪function ClearIRTarget()
Definition: _heatseekingmissile.gsc:47
‪StingerFiredNotify
‪function StingerFiredNotify()
Definition: _heatseekingmissile.gsc:77
‪InsideStingerReticleNoLock
‪function InsideStingerReticleNoLock(target, weapon)
Definition: _heatseekingmissile.gsc:538
‪IsStillValidTarget
‪function IsStillValidTarget(ent, weapon)
Definition: _heatseekingmissile.gsc:550
‪setFriendlyTargetting
‪function setFriendlyTargetting(weapon, target)
Definition: _heatseekingmissile.gsc:893
‪set
‪function set(str_field_name, n_value)
Definition: clientfield_shared.gsc:34
‪TargetingHacking
‪function TargetingHacking(target, lock)
Definition: _heatseekingmissile.gsc:753
‪trackAssists
‪function trackAssists(attacker, damage, isFlare)
Definition: challenges_shared.gsc:26
‪setFriendlyTargetLocked
‪function setFriendlyTargetLocked(weapon, target)
Definition: _heatseekingmissile.gsc:913
‪StingerWaitForAds
‪function StingerWaitForAds()
Definition: _heatseekingmissile.gsc:134
‪CalcLockOnRadius
‪function CalcLockOnRadius(target, weapon)
Definition: _heatseekingmissile.gsc:489
‪debug_missile
‪function debug_missile(missile)
Definition: _heatseekingmissile.gsc:99
‪destroy
‪function destroy(watcher, owner)
Definition: _decoy.gsc:108
‪InsideStingerReticleLocked
‪function InsideStingerReticleLocked(target, weapon)
Definition: _heatseekingmissile.gsc:544
‪StingerIRTLoop
‪function StingerIRTLoop(weapon)
Definition: _heatseekingmissile.gsc:188
‪LockSightTest
‪function LockSightTest(target)
Definition: _heatseekingmissile.gsc:636
‪MISSED_BY_FAR_DISTANCE
‪#define MISSED_BY_FAR_DISTANCE
Definition: _heatseekingmissile.gsc:17
‪PlayerStingerAds
‪function PlayerStingerAds()
Definition: _heatseekingmissile.gsc:567
‪setFriendlyFlags
‪function setFriendlyFlags(weapon, target)
Definition: _heatseekingmissile.gsc:783
‪MissileTarget_ProximityDetonate
‪function MissileTarget_ProximityDetonate(missile, attacker, weapon, endon1, endon2, allowDirectDamage)
Definition: _heatseekingmissile.gsc:1112
‪init_shared
‪function init_shared()
Definition: _heatseekingmissile.gsc:22
‪MissileTarget_isMissileIncoming
‪function MissileTarget_isMissileIncoming()
Definition: _heatseekingmissile.gsc:1040
‪_incomingMissile
‪function _incomingMissile(missile, attacker)
Definition: _heatseekingmissile.gsc:994
‪WAIT_SERVER_FRAME
‪#define WAIT_SERVER_FRAME
Definition: shared.gsh:265