1 #using scripts\codescripts\struct;
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;
11 #insert scripts\shared\shared.gsh;
13 #precache( "string", "MP_CANNOT_LOCKON_TO_TARGET" );
15 #precache( "fx", "killstreaks/fx_heli_chaff" );
17 #define MISSED_BY_FAR_DISTANCE 500
18 #define FLARE_DISTANCE 3500
20 #namespace heatseekingmissile;
24 game[
"locking_on_sound"] =
"uin_alert_lockon_start";
25 game[
"locked_on_sound"] =
"uin_alert_lockon";
29 level.fx_flare =
"killstreaks/fx_heli_chaff";
33 SetDvar(
"scr_freelock",
"0");
39 self endon(
"disconnect" );
49 self notify(
"stop_lockon_sound" );
50 self notify(
"stop_locked_sound" );
51 self.stingerlocksound = undefined;
52 self StopRumble(
"stinger_lock_rumble" );
54 self.stingerLockStartTime = 0;
55 self.stingerLockStarted =
false;
56 self.stingerLockFinalized =
false;
57 self.stingerLockDetected =
false;
58 if( isdefined(
self.stingerTarget) )
60 self.stingerTarget notify(
"missile_unlocked" );
62 self LockedOn(
self.stingerTarget,
false);
64 self.stingerTarget = undefined;
66 self WeaponLockFree();
67 self WeaponLockTargetTooClose(
false );
68 self WeaponLockNoClearance(
false );
70 self StopLocalSound( game[
"locking_on_sound"] );
71 self StopLocalSound( game[
"locked_on_sound"] );
79 self endon(
"disconnect" );
80 self endon (
"death" );
84 self waittill(
"missile_fire", missile, weapon );
88 if ( weapon.lockonType ==
"Legacy Single" )
90 if( isdefined(
self.stingerTarget) &&
self.stingerLockFinalized )
92 self.stingerTarget notify(
"stinger_fired_at_me", missile, weapon,
self );
101 level notify(
"debug_missile" );
102 level endon(
"debug_missile" );
104 level.debug_missile_dots = [];
108 if ( GetDvarInt(
"scr_debug_missile", 0 ) == 0 )
114 if ( isdefined( missile ) )
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 );
123 foreach( missile_info
in level.debug_missile_dots )
125 dot_color = ( isdefined( missile_info.targetEntNum ) ?
RED :
GREEN );
140 currentWeapon =
self GetCurrentWeapon();
141 if ( currentWeapon.lockonType !=
"Legacy Single" )
152 self endon(
"disconnect" );
153 self endon (
"death" );
157 self waittill(
"weapon_change", weapon );
159 while ( weapon.lockonType ==
"Legacy Single" )
161 if (
self GetWeaponAmmoClip( weapon ) == 0 )
164 weapon =
self GetCurrentWeapon();
180 self notify(
"stinger_IRT_off" );
183 weapon =
self GetCurrentWeapon();
190 self endon(
"disconnect" );
191 self endon(
"death" );
192 self endon(
"stinger_IRT_off" );
194 lockLength =
self getLockOnSpeed();
208 if (
self.stingerLockFinalized )
216 self SetWeaponLockOnPercent( weapon, 0 );
221 if ( !
self.stingerTarget.locked_on )
223 self.stingerTarget notify(
"missile_lock",
self,
self GetCurrentWeapon() );
227 self LockedOn(
self.stingerTarget,
true);
228 if ( isdefined( weapon ) )
240 if (
self.stingerLockStarted )
244 self SetWeaponLockOnPercent( weapon, 0 );
252 self LockedOn(
self.stingerTarget,
false);
253 if ( isdefined( weapon ) )
262 timePassed = getTime() -
self.stingerLockStartTime;
264 if ( isdefined( weapon ) )
266 self SetWeaponLockOnPercent( weapon, ( ( timePassed / lockLength ) * 100 ) );
270 if ( timePassed < lockLength )
273 assert( isdefined(
self.stingerTarget ) );
274 self notify(
"stop_lockon_sound" );
275 self.stingerLockFinalized =
true;
276 self WeaponLockFinalize(
self.stingerTarget );
283 if ( !isdefined( bestTarget ) || ( isdefined(
self.stingerTarget ) &&
self.stingerTarget != bestTarget ) )
286 if (
self.stingerLockDetected ==
true )
288 self WeaponLockFree();
289 self.stingerLockDetected =
false;
301 if( isdefined( bestTarget.lockOnDelay ) && bestTarget.lockOnDelay )
317 if (
self.stingerLockDetected ==
false )
319 self WeaponLockDetect( bestTarget );
321 self.stingerLockDetected =
true;
322 if ( isdefined( weapon ) )
329 self.stingerLockDetected =
false;
333 self.stingerTarget = bestTarget;
334 self.stingerLockStartTime = getTime();
335 self.stingerLockStarted =
true;
336 self.stingerLostSightlineTime = 0;
338 self WeaponLockStart( bestTarget );
349 if ( GetDvarInt(
"scr_missilelock_playspace_extra_radius_override_enabled", 0 ) > 0 )
351 extraRadiusDvar = GetDvarInt(
"scr_missilelock_playspace_extra_radius", 5000 );
352 if ( extraRadiusDvar !=
VAL( level.missileLockPlaySpaceCheckExtraRadius, 0 ) )
354 level.missileLockPlaySpaceCheckExtraRadius = extraRadiusDvar;
355 level.missileLockPlaySpaceCheckRadiusSqr = undefined;
362 if ( level.missileLockPlaySpaceCheckEnabled ===
true )
364 if ( !isdefined( target ) )
367 if ( !isdefined( level.playSpaceCenter ) )
370 if ( !isdefined( level.missileLockPlaySpaceCheckRadiusSqr ) )
373 if ( Distance2DSquared( target.origin, level.playSpaceCenter ) > level.missileLockPlaySpaceCheckRadiusSqr )
382 if( isdefined(
self.LockOnCanceledMessage ) )
383 self.LockOnCanceledMessage
destroy();
388 if( isdefined(
self.LockOnCanceledMessage ) )
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" );
411 if ( isdefined(
self.get_stinger_target_override ) )
413 targetsAll =
self [ [
self.get_stinger_target_override ] ]();
417 targetsAll = target_getArray();
422 for ( idx = 0; idx < targetsAll.size; idx++ )
426 if( GetDvarString(
"scr_freelock") ==
"1" )
431 targetsValid[targetsValid.size] = targetsAll[idx];
437 target = targetsAll[idx];
439 if ( level.teamBased || level.use_team_based_logic_for_locking_on ===
true )
441 if ( isdefined(target.team) && target.team !=
self.team)
445 if ( !isdefined(
self.is_valid_target_for_stinger_override ) ||
self [ [
self.is_valid_target_for_stinger_override ] ]( target ) )
447 hascamo = isdefined( target.camo_state ) && ( target.camo_state == 1 ) && !
self hasPerk(
"specialty_showenemyequipment" );
449 targetsValid[targetsValid.size] = target;
458 if( ( isdefined( target.owner ) &&
self != target.owner ) || ( isplayer( target ) &&
self != target ) )
460 if ( !isdefined(
self.is_valid_target_for_stinger_override ) ||
self [ [
self.is_valid_target_for_stinger_override ] ]( target ) )
461 targetsValid[targetsValid.size] = target;
467 if ( targetsValid.size == 0 )
470 bestTarget = targetsValid[0];
471 if ( targetsValid.size > 1 )
475 foreach( target
in targetsValid )
478 if ( ratio > closestRatio )
480 closestRatio = ratio;
491 radius =
self getLockOnRadius();
493 if( isdefined( weapon ) && isdefined( weapon.lockOnScreenRadius ) && ( weapon.lockOnScreenRadius > radius ) )
495 radius = weapon.lockOnScreenRadius;
498 if( isdefined( level.lockOnCloseRange ) && isdefined( level.lockOnCloseRadiusScaler ) )
500 dist2 = DistanceSquared( target.origin,
self.origin );
501 if( dist2 < level.lockOnCloseRange * level.lockOnCloseRange )
502 radius = radius * level.lockOnCloseRadiusScaler;
510 radius =
self getLockOnLossRadius();
512 if( isdefined( weapon ) && isdefined( weapon.lockOnScreenRadius ) && ( weapon.lockOnScreenRadius > radius ) )
514 radius = weapon.lockOnScreenRadius;
517 if( isdefined( level.lockOnCloseRange ) && isdefined( level.lockOnCloseRadiusScaler ) )
519 dist2 = DistanceSquared( target.origin,
self.origin );
520 if( dist2 < level.lockOnCloseRange * level.lockOnCloseRange )
521 radius = radius * level.lockOnCloseRadiusScaler;
529 return Target_ScaleMinMaxRadius( target,
self, 65, 0, radius );
535 return target_isincircle( target,
self, 65, radius );
541 return target_isincircle( target,
self, 65, radius );
547 return target_isincircle( target,
self, 65, radius );
552 if ( ! isdefined( ent ) )
555 if ( isdefined(
self.is_still_valid_target_for_stinger_override ) )
556 return self [ [
self.is_still_valid_target_for_stinger_override ] ]( ent, weapon );
558 if ( ! target_isTarget( ent ) && !( isdefined( ent.allowContinuedLockonAfterInvis ) && ent.allowContinuedLockonAfterInvis ) )
569 return (
self PlayerAds() == 1.0 );
574 self endon (
"stop_lockon_sound" );
575 self endon(
"disconnect" );
576 self endon (
"death" );
581 self PlayRumbleOnEntity(
"stinger_lock_rumble" );
589 if (
self IsInVehicle() )
591 sound_target =
self GetVehicleOccupied();
592 if ( isdefined( sound_target ) )
594 sound_target PlaySoundToPlayer( alias,
self );
599 self playLocalSound( alias );
605 self endon (
"stop_locked_sound" );
606 self endon(
"disconnect" );
607 self endon (
"death" );
609 if ( isdefined(
self.stingerlocksound ) )
612 self.stingerlocksound =
true;
620 self PlayRumbleOnEntity(
"stinger_lock_rumble" );
624 self PlayRumbleOnEntity(
"stinger_lock_rumble" );
628 self PlayRumbleOnEntity(
"stinger_lock_rumble" );
631 self StopRumble(
"stinger_lock_rumble" );
633 self.stingerlocksound = undefined;
638 cameraPos =
self getplayercamerapos();
640 if ( !isdefined( target ) )
643 if( isdefined( target.parent ) )
644 passed = BulletTracePassed( cameraPos, target.origin,
false, target, target.parent );
646 passed = BulletTracePassed( cameraPos, target.origin,
false, target );
650 front = target GetPointInBounds( 1, 0, 0 );
651 if( isdefined( target.parent ) )
652 passed = BulletTracePassed( cameraPos, front,
false, target, target.parent );
654 passed = BulletTracePassed( cameraPos, front,
false, target );
658 back = target GetPointInBounds( -1, 0, 0 );
659 if( isdefined( target.parent ) )
660 passed = BulletTracePassed( cameraPos, back,
false, target, target.parent );
662 passed = BulletTracePassed( cameraPos, back,
false, target );
671 LOST_SIGHT_LIMIT = 500;
675 self.stingerLostSightlineTime = 0;
679 if (
self.stingerLostSightlineTime == 0 )
680 self.stingerLostSightlineTime = getTime();
682 timePassed = GetTime() -
self.stingerLostSightlineTime;
685 if ( timePassed >= LOST_SIGHT_LIMIT )
697 if ( isdefined( target.locking_on ) )
700 target.locking_on = 0;
701 target.locked_on = 0;
702 target.locking_on_hacking = 0;
707 Assert( isdefined( target.locking_on ) );
709 clientNum =
self getEntityNumber();
712 target notify(
"locking on" );
713 target.locking_on |= ( 1 << clientNum );
719 self notify(
"locking_on_cleared" );
720 target.locking_on &= ~( 1 << clientNum );
726 target endon(
"death");
727 self endon(
"locking_on_cleared" );
731 target.locking_on &= ~( 1 << clientNum );
736 Assert( isdefined( target.locked_on ) );
738 clientNum =
self getEntityNumber();
741 target.locked_on |= ( 1 << clientNum );
747 self notify(
"locked_on_cleared" );
748 target.locked_on &= ~( 1 << clientNum );
755 Assert( isdefined( target.locking_on_hacking ) );
757 clientNum =
self getEntityNumber();
760 target notify(
"locking on hacking" );
761 target.locking_on_hacking |= ( 1 << clientNum );
767 self notify(
"locking_on_hacking_cleared" );
768 target.locking_on_hacking &= ~( 1 << clientNum );
774 target endon(
"death");
775 self endon(
"locking_on_hacking_cleared" );
779 target.locking_on_hacking &= ~( 1 << clientNum );
785 if ( !
self isinvehicle() )
791 if ( isdefined( level.killstreakMaxHealthFunction ) )
793 if ( isdefined( target.useVTOLTime ) && isdefined( level.vtol ) )
795 killstreakEndTime = level.vtol.killstreakEndTime;
796 if ( isdefined( killstreakEndTime ) )
798 self settargetedentityendtime( weapon, killstreakEndTime );
801 else if ( isdefined( target.killstreakEndTime ) )
803 self settargetedentityendtime( weapon, target.killstreakEndTime );
805 else if ( isdefined( target.parentstruct ) && isdefined( target.parentStruct.killstreakEndTime ) )
807 self settargetedentityendtime( weapon, target.parentStruct.killstreakEndTime );
811 self settargetedentityendtime( weapon, 0 );
813 self settargetedmissilesremaining( weapon, 0 );
815 killstreakType = target.killstreakType;
816 if ( !isdefined( target.killstreakType ) && isdefined( target.parentstruct ) && isdefined( target.parentStruct.killstreakType ) )
818 killstreakType = target.parentStruct.killstreakType;
820 else if ( isdefined( target.useVTOLTime ) && isdefined( level.vtol.killstreakType ) )
822 killstreakType = level.vtol.killstreakType;
825 if ( isdefined ( killstreakType ) && isdefined( level.killstreakbundle[killstreakType] ) )
827 if ( isdefined( target.forceOneMissile ) )
829 self settargetedmissilesremaining( weapon, 1 );
831 else if ( isdefined( target.useVTOLTime ) && isdefined( level.vtol ) && isdefined( level.vtol.totalRocketHits ) && isdefined( level.vtol.missileToDestroy ) )
833 self settargetedmissilesremaining( weapon, level.vtol.missileToDestroy - level.vtol.totalRocketHits );
837 maxHealth = [[level.killstreakMaxHealthFunction]]( killstreakType );
838 damageTaken = target.damageTaken;
839 if ( !isdefined( damageTaken ) && isdefined( target.parentstruct ) )
841 damageTaken = target.parentstruct.damageTaken;
843 if ( isdefined( target.missileTrackDamage ) )
845 damageTaken = target.missileTrackDamage;
848 if ( isdefined( damageTaken ) && isdefined( maxHealth ) )
850 damagePerRocket = ( maxHealth / level.killstreakbundle[killstreakType].ksRocketsToKill ) + 1;
851 remainingHealth = maxHealth - damageTaken;
852 if ( remaininghealth > 0 )
854 missilesRemaining = int( ceil( remainingHealth / damageperrocket ) );
855 if ( isdefined( target.numflares ) && target.numflares > 0 )
857 missilesRemaining += target.numFlares;
859 if ( isdefined( target.flak_drone ) )
861 missilesRemaining += 1;
864 self settargetedmissilesremaining( weapon, missilesRemaining );
875 if ( level.teambased )
877 friendlyHackingMask = target.locking_on_hacking;
879 if ( isdefined( friendlyHackingMask ) )
881 friendlyHacking =
false;
882 clientNum =
self getEntityNumber();
883 friendlyHackingMask &= ~( 1 << clientNum );
884 if ( friendlyHackingMask != 0 )
886 friendlyHacking =
true;
888 self SetWeaponFriendlyHacking( weapon, friendlyHacking );
895 if ( level.teambased )
897 friendlyTargetingMask = target.locking_on;
899 if ( isdefined( friendlyTargetingMask ) )
901 friendlyTargeting =
false;
902 clientNum =
self getEntityNumber();
903 friendlyTargetingMask &= ~( 1 << clientNum );
904 if ( friendlyTargetingMask != 0 )
906 friendlyTargeting =
true;
908 self SetWeaponFriendlyTargeting( weapon, friendlyTargeting );
915 if ( level.teambased )
917 friendlyTargetLocked =
false;
918 friendlyLockingOnMask = target.locked_on;
920 if ( isdefined( friendlyLockingOnMask ) )
922 friendlyTargetLocked =
false;
923 clientNum =
self getEntityNumber();
924 friendlyLockingOnMask &= ~( 1 << clientNum );
925 if ( friendlylockingonMask != 0 )
927 friendlyTargetLocked =
true;
930 if ( friendlyTargetLocked ==
false )
934 self SetWeaponFriendlyTargetLocked( weapon, friendlyTargetLocked );
940 self endon(
"locked_on_cleared" );
944 if ( isdefined( target ) )
946 target.locked_on &= ~( 1 << clientNum );
952 self endon(
"death" );
954 if ( isdefined(endon1) )
955 self endon( endon1 );
956 if ( isdefined(endon2) )
957 self endon( endon2 );
962 if( target_isTarget(
self ) )
970 else if( isdefined(
self.locked_on) &&
self.locked_on )
976 else if( isdefined(
self.locking_on) &&
self.locking_on )
996 if ( !isdefined(
self.incoming_missile) )
998 self.incoming_missile = 0;
1000 if ( !isdefined(
self.incoming_missile_owner) )
1002 self.incoming_missile_owner = [];
1004 if ( !isdefined(
self.incoming_missile_owner[attacker.entnum] ) )
1006 self.incoming_missile_owner[attacker.entnum] = 0;
1009 self.incoming_missile++;
1010 self.incoming_missile_owner[attacker.entnum]++;
1019 self endon(
"death");
1021 attacker_entnum = attacker.entnum;
1023 missile waittill(
"death");
1025 self.incoming_missile--;
1026 self.incoming_missile_owner[attacker_entnum]--;
1027 if (
self.incoming_missile_owner[attacker_entnum] == 0 )
1029 self.incoming_missile_owner[attacker_entnum] = undefined;
1032 if ( isdefined( attacker ) )
1037 assert(
self.incoming_missile >= 0 );
1042 if ( !isdefined(
self.incoming_missile) )
1045 if (
self.incoming_missile )
1053 if ( !isdefined(
self.incoming_missile_owner) )
1056 if (
self.incoming_missile_owner.size == 0 )
1059 if (
self.incoming_missile_owner.size == 1 && isdefined(
self.incoming_missile_owner[attacker.entnum] ) )
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 );
1076 self waittill(
"stinger_fired_at_me", missile, weapon, attacker );
1080 if ( isdefined(responseFunc) )
1081 [[responseFunc]]( missile, attacker, weapon, endon1, endon2, allowDirectDamage );
1092 origin =
self.origin;
1094 target =
self Missile_GetTarget();
1099 if ( ( allowDirectDamage ===
true ) && isdefined( target ) && isdefined( target.origin ) )
1101 minDistSq =
VAL( target.locked_missile_min_distsq,
SQR( range ) );
1102 distSq = DistanceSquared(
self.origin, target.origin );
1103 if ( distSq < minDistSq )
1105 target DoDamage( maxDamage, origin, attacker,
self,
"none",
"MOD_PROJECTILE", 0, weapon );
1109 radiusDamage( origin, range, maxDamage, minDamage, attacker,
"MOD_PROJECTILE_SPLASH", weapon );
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 );
1121 minDist = DistanceSquared( missile.origin,
self.origin );
1122 lastCenter =
self.origin;
1124 missile Missile_SetTarget(
self,
VAL( Target_GetOffset(
self ), ( 0, 0, 0 ) ) );
1126 if ( isdefined(
self.missileTargetMissDistance ) )
1128 missedDistanceSq =
self.missileTargetMissDistance *
self.missileTargetMissDistance;
1139 if ( !isdefined(
self ) )
1140 center = lastCenter;
1142 center =
self.origin;
1144 lastCenter = center;
1146 curDist = DistanceSquared( missile.origin, center );
1148 if( curDist < flareDistanceSq && isdefined(
self.numFlares) &&
self.numFlares > 0 )
1156 missile Missile_SetTarget( newTarget,
VAL( Target_GetOffset( newTarget ), ( 0, 0, 0 ) ) );
1157 missileTarget = newTarget;
1162 if ( curDist < minDist )
1165 if ( curDist > minDist )
1167 if ( curDist > missedDistanceSq )
1180 if ( !isdefined(
self ) )
1183 flare_fx = level.fx_flare;
1185 if ( isdefined(
self.fx_flare ) )
1187 flare_fx =
self.fx_flare;
1189 if( isdefined(
self.flare_ent ) )
1191 PlayFXOnTag( flare_fx,
self.flare_ent,
"tag_origin" );
1195 PlayFXOnTag( flare_fx,
self,
"tag_origin" );
1198 if ( isdefined(
self.owner ) )
1200 self playsoundtoplayer (
"veh_huey_chaff_drop_plr",
self.owner );
1202 self PlaySound (
"veh_huey_chaff_explo_npc" );
1207 vec_toForward = anglesToForward(
self.angles );
1208 vec_toRight = AnglesToRight(
self.angles );
1210 vec_toMissileForward = anglesToForward( angles );
1212 delta =
self.origin - origin;
1213 dot = VectorDot(vec_toMissileForward,vec_toRight);
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) );
1224 flareOrigin =
self.origin;
1225 flareOrigin = flareOrigin + VectorScale( flare_dir, RandomIntRange(600, 800));
1229 flareOrigin = flareOrigin + ( 0, 0, 500 );
1231 if ( isdefined(
self.flareOffset ) )
1232 flareOrigin = flareOrigin +
self.flareOffset;
1234 flareObject =
spawn(
"script_origin", flareOrigin );
1235 flareObject.angles =
self.angles;
1237 flareObject SetModel(
"tag_origin" );
1238 flareObject MoveGravity( velocity, 5.0 );
1250 target endon(
"death");