boiii-scripts/shared/weapons/_heatseekingmissile.gsc
2023-04-13 17:30:38 +02:00

1261 lines
36 KiB
Plaintext

#using scripts\codescripts\struct;
#using scripts\shared\callbacks_shared;
#using scripts\shared\challenges_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\dev_shared;
#using scripts\shared\system_shared;
#using scripts\shared\util_shared;
#using scripts\shared\weapons\_weapon_utils;
#precache( "string", "MP_CANNOT_LOCKON_TO_TARGET" );
#precache( "fx", "killstreaks/fx_heli_chaff" );
#namespace heatseekingmissile;
function init_shared()
{
game["locking_on_sound"] = "uin_alert_lockon_start";
game["locked_on_sound"] = "uin_alert_lockon";
callback::on_spawned( &on_player_spawned );
level.fx_flare = "killstreaks/fx_heli_chaff";
//Dvar is used with the dev gui so as to let the player target friendly vehicles with heat-seekers.
/#
SetDvar("scr_freelock", "0");
#/
}
function on_player_spawned()
{
self endon( "disconnect" );
self ClearIRTarget();
thread StingerToggleLoop();
//thread TraceConstantTest();
self thread StingerFiredNotify();
}
function ClearIRTarget()
{
self notify( "stop_lockon_sound" );
self notify( "stop_locked_sound" );
self.stingerlocksound = undefined;
self StopRumble( "stinger_lock_rumble" );
self.stingerLockStartTime = 0;
self.stingerLockStarted = false;
self.stingerLockFinalized = false;
self.stingerLockDetected = false;
if( isdefined(self.stingerTarget) )
{
self.stingerTarget notify( "missile_unlocked" );
self LockingOn(self.stingerTarget, false);
self LockedOn(self.stingerTarget, false);
}
self.stingerTarget = undefined;
self WeaponLockFree();
self WeaponLockTargetTooClose( false );
self WeaponLockNoClearance( false );
self StopLocalSound( game["locking_on_sound"] );
self StopLocalSound( game["locked_on_sound"] );
self DestroyLockOnCanceledMessage();
}
function StingerFiredNotify()
{
self endon( "disconnect" );
self endon ( "death" );
while ( true )
{
self waittill( "missile_fire", missile, weapon );
/# thread debug_missile( missile ); #/
if ( weapon.lockonType == "Legacy Single" )
{
if( isdefined(self.stingerTarget) && self.stingerLockFinalized )
{
self.stingerTarget notify( "stinger_fired_at_me", missile, weapon, self );
}
}
}
}
/#
function debug_missile( missile )
{
level notify( "debug_missile" );
level endon( "debug_missile" );
level.debug_missile_dots = [];
while( 1 )
{
if ( GetDvarInt( "scr_debug_missile", 0 ) == 0 )
{
wait 0.5;
continue;
}
if ( isdefined( missile ) )
{
missile_info = SpawnStruct();
missile_info.origin = missile.origin;
target = missile Missile_GetTarget();
missile_info.targetEntNum = ( isdefined( target ) ? target GetEntityNumber() : undefined );
if ( !isdefined( level.debug_missile_dots ) ) level.debug_missile_dots = []; else if ( !IsArray( level.debug_missile_dots ) ) level.debug_missile_dots = array( level.debug_missile_dots ); level.debug_missile_dots[level.debug_missile_dots.size]=missile_info;;
}
foreach( missile_info in level.debug_missile_dots )
{
dot_color = ( isdefined( missile_info.targetEntNum ) ? ( 1, 0, 0 ) : ( 0, 1, 0 ) );
dev::debug_sphere( missile_info.origin, 10, dot_color, 0.66, 1 );
}
{wait(.05);};
}
}
#/
function StingerWaitForAds()
{
while( !self PlayerStingerAds() )
{
{wait(.05);};
currentWeapon = self GetCurrentWeapon();
if ( currentWeapon.lockonType != "Legacy Single" )
{
return false;
}
}
return true;
}
function StingerToggleLoop()
{
self endon( "disconnect" );
self endon ( "death" );
for (;;)
{
self waittill( "weapon_change", weapon );
while ( weapon.lockonType == "Legacy Single" )
{
if ( self GetWeaponAmmoClip( weapon ) == 0 )
{
{wait(.05);};
weapon = self GetCurrentWeapon();
continue;
}
if ( !StingerWaitForAds() )
{
break;
}
self thread StingerIRTLoop( weapon );
while( self PlayerStingerAds() )
{
{wait(.05);};
}
self notify( "stinger_IRT_off" );
self ClearIRTarget();
weapon = self GetCurrentWeapon();
}
}
}
function StingerIRTLoop( weapon )
{
self endon( "disconnect" );
self endon( "death" );
self endon( "stinger_IRT_off" );
lockLength = self getLockOnSpeed();
for (;;)
{
{wait(.05);};
if ( !(self HasWeapon(weapon)) )//don't have the weapon any longer.....
break;
//-------------------------
// Four possible states:
// No missile in the tube, so CLU will not search for targets.
// CLU has a lock.
// CLU is locking on to a target.
// CLU is searching for a target to begin locking on to.
//-------------------------
if ( self.stingerLockFinalized )
{
passed = SoftSightTest();
if ( !passed )
continue;
if ( ! self IsStillValidTarget( self.stingerTarget, weapon ) || self InsideStingerReticleLocked( self.stingerTarget, weapon ) == false )
{
self SetWeaponLockOnPercent( weapon, 0 );
self ClearIRTarget();
continue;
}
if ( !self.stingerTarget.locked_on )
{
self.stingerTarget notify( "missile_lock", self, self GetCurrentWeapon() );
}
self LockingOn(self.stingerTarget, false);
self LockedOn(self.stingerTarget, true);
if ( isdefined( weapon ) )
{
heatseekingmissile::setFriendlyFlags( weapon, self.stingerTarget );
}
thread LoopLocalLockSound( game["locked_on_sound"], 0.75 );
//print3D( self.stingerTarget.origin, "* LOCKED!", (.2, 1, .3), 1, 5 );
continue;
}
if ( self.stingerLockStarted )
{
if ( !self IsStillValidTarget( self.stingerTarget, weapon ) || self InsideStingerReticleLocked( self.stingerTarget, weapon ) == false )
{
self SetWeaponLockOnPercent( weapon, 0 );
self ClearIRTarget();
continue;
}
//print3D( self.stingerTarget.origin, "* locking...!", (.2, 1, .3), 1, 5 );
self LockingOn(self.stingerTarget, true);
self LockedOn(self.stingerTarget, false);
if ( isdefined( weapon ) )
{
heatseekingmissile::setFriendlyFlags( weapon, self.stingerTarget );
}
passed = SoftSightTest();
if ( !passed )
continue;
timePassed = getTime() - self.stingerLockStartTime;
if ( isdefined( weapon ) )
{
self SetWeaponLockOnPercent( weapon, ( ( timePassed / lockLength ) * 100 ) );
heatseekingmissile::setFriendlyFlags( weapon, self.stingerTarget );
}
if ( timePassed < lockLength )
continue;
assert( isdefined( self.stingerTarget ) );
self notify( "stop_lockon_sound" );
self.stingerLockFinalized = true;
self WeaponLockFinalize( self.stingerTarget );
continue;
}
bestTarget = self GetBestStingerTarget( weapon );
if ( !isdefined( bestTarget ) || ( isdefined( self.stingerTarget ) && self.stingerTarget != bestTarget ) )
{
self DestroyLockOnCanceledMessage();
if ( self.stingerLockDetected == true )
{
self WeaponLockFree();
self.stingerLockDetected = false;
}
continue;
}
if ( !( self LockSightTest( bestTarget ) ) )
{
self DestroyLockOnCanceledMessage();
continue;
}
//check for delay allowing helicopters to enter the play area
if( isdefined( bestTarget.lockOnDelay ) && bestTarget.lockOnDelay )
{
self DisplayLockOnCanceledMessage();
continue;
}
if( !TargetWithinRangeOfPlaySpace( bestTarget ) )
{
self DisplayLockOnCanceledMessage();
continue;
}
self DestroyLockOnCanceledMessage();
if ( self InsideStingerReticleLocked( bestTarget, weapon ) == false )
{
if ( self.stingerLockDetected == false )
{
self WeaponLockDetect( bestTarget );
}
self.stingerLockDetected = true;
if ( isdefined( weapon ) )
{
heatseekingmissile::setFriendlyFlags( weapon, bestTarget );
}
continue;
}
self.stingerLockDetected = false;
InitLockField( bestTarget );
self.stingerTarget = bestTarget;
self.stingerLockStartTime = getTime();
self.stingerLockStarted = true;
self.stingerLostSightlineTime = 0;
self WeaponLockStart( bestTarget );
self thread LoopLocalSeekSound( game["locking_on_sound"], 0.6 );
}
}
function TargetWithinRangeOfPlaySpace( target )
{
/#
// for tuning
if ( GetDvarInt( "scr_missilelock_playspace_extra_radius_override_enabled", 0 ) > 0 )
{
extraRadiusDvar = GetDvarInt( "scr_missilelock_playspace_extra_radius", 5000 );
if ( extraRadiusDvar != (isdefined(level.missileLockPlaySpaceCheckExtraRadius)?level.missileLockPlaySpaceCheckExtraRadius:0) )
{
level.missileLockPlaySpaceCheckExtraRadius = extraRadiusDvar;
level.missileLockPlaySpaceCheckRadiusSqr = undefined;
}
}
#/
// only allow targetting of targets that are within the play space by a specified radius
// use level.missileLockPlaySpaceCheckExtraRadius to set it per level
if ( level.missileLockPlaySpaceCheckEnabled === true )
{
if ( !isdefined( target ) )
return false;
if ( !isdefined( level.playSpaceCenter ) )
level.playSpaceCenter = util::GetPlaySpaceCenter();
if ( !isdefined( level.missileLockPlaySpaceCheckRadiusSqr ) )
level.missileLockPlaySpaceCheckRadiusSqr = ( (( util::GetPlaySpaceMaxWidth() * 0.5 ) + level.missileLockPlaySpaceCheckExtraRadius) * (( util::GetPlaySpaceMaxWidth() * 0.5 ) + level.missileLockPlaySpaceCheckExtraRadius) );
if ( Distance2DSquared( target.origin, level.playSpaceCenter ) > level.missileLockPlaySpaceCheckRadiusSqr )
return false;
}
return true;
}
function DestroyLockOnCanceledMessage()
{
if( isdefined( self.LockOnCanceledMessage ) )
self.LockOnCanceledMessage destroy();
}
function DisplayLockOnCanceledMessage()
{
if( isdefined( self.LockOnCanceledMessage ) )
return;
self.LockOnCanceledMessage = newclienthudelem( self );
self.LockOnCanceledMessage.fontScale = 1.25;
self.LockOnCanceledMessage.x = 0;
self.LockOnCanceledMessage.y = 50;
self.LockOnCanceledMessage.alignX = "center";
self.LockOnCanceledMessage.alignY = "top";
self.LockOnCanceledMessage.horzAlign = "center";
self.LockOnCanceledMessage.vertAlign = "top";
self.LockOnCanceledMessage.foreground = true;
self.LockOnCanceledMessage.hidewhendead = false;
self.LockOnCanceledMessage.hidewheninmenu = true;
self.LockOnCanceledMessage.archived = false;
self.LockOnCanceledMessage.alpha = 1.0;
self.LockOnCanceledMessage SetText( &"MP_CANNOT_LOCKON_TO_TARGET" );
}
function GetBestStingerTarget( weapon )
{
targetsAll = [];
if ( isdefined( self.get_stinger_target_override ) )
{
targetsAll = self [ [ self.get_stinger_target_override ] ]();
}
else
{
targetsAll = target_getArray();
}
targetsValid = [];
for ( idx = 0; idx < targetsAll.size; idx++ )
{
/#
//This variable is set and managed by the 'dev_friendly_lock' function, which works with the dev_gui
if( GetDvarString( "scr_freelock") == "1" )
{
//If the dev_gui dvar is set, only check if the target is in the reticule.
if( self InsideStingerReticleNoLock( targetsAll[idx], weapon ) )
{
targetsValid[targetsValid.size] = targetsAll[idx];
}
continue;
}
#/
target = targetsAll[idx];
if ( level.teamBased || level.use_team_based_logic_for_locking_on === true ) //team based game modes
{
if ( isdefined(target.team) && target.team != self.team)
{
if ( self InsideStingerReticleDetect( target, weapon ) )
{
if ( !isdefined( self.is_valid_target_for_stinger_override ) || self [ [ self.is_valid_target_for_stinger_override ] ]( target ) )
{
hascamo = isdefined( target.camo_state ) && ( target.camo_state == 1 ) && !self hasPerk( "specialty_showenemyequipment" );
if( !hascamo )
targetsValid[targetsValid.size] = target;
}
}
}
}
else
{
if( self InsideStingerReticleDetect( target, weapon ) ) //Free for all
{
if( ( isdefined( target.owner ) && self != target.owner ) || ( isplayer( target ) && self != target ) )
{
if ( !isdefined( self.is_valid_target_for_stinger_override ) || self [ [ self.is_valid_target_for_stinger_override ] ]( target ) )
targetsValid[targetsValid.size] = target;
}
}
}
}
if ( targetsValid.size == 0 )
return undefined;
bestTarget = targetsValid[0];
if ( targetsValid.size > 1 )
{
closestRatio = 0.0;
foreach( target in targetsValid )
{
ratio = RatioDistanceFromScreenCenter( target, weapon );
if ( ratio > closestRatio )
{
closestRatio = ratio;
bestTarget = target;
}
}
}
return bestTarget;
}
function CalcLockOnRadius( target, weapon )
{
radius = self getLockOnRadius();
if( isdefined( weapon ) && isdefined( weapon.lockOnScreenRadius ) && ( weapon.lockOnScreenRadius > radius ) )
{
radius = weapon.lockOnScreenRadius;
}
if( isdefined( level.lockOnCloseRange ) && isdefined( level.lockOnCloseRadiusScaler ) )
{
dist2 = DistanceSquared( target.origin, self.origin );
if( dist2 < level.lockOnCloseRange * level.lockOnCloseRange )
radius = radius * level.lockOnCloseRadiusScaler;
}
return radius;
}
function CalcLockOnLossRadius( target, weapon )
{
radius = self getLockOnLossRadius();
if( isdefined( weapon ) && isdefined( weapon.lockOnScreenRadius ) && ( weapon.lockOnScreenRadius > radius ) )
{
radius = weapon.lockOnScreenRadius;
}
if( isdefined( level.lockOnCloseRange ) && isdefined( level.lockOnCloseRadiusScaler ) )
{
dist2 = DistanceSquared( target.origin, self.origin );
if( dist2 < level.lockOnCloseRange * level.lockOnCloseRange )
radius = radius * level.lockOnCloseRadiusScaler;
}
return radius;
}
function RatioDistanceFromScreenCenter( target, weapon )
{
radius = CalcLockOnRadius( target, weapon );
return Target_ScaleMinMaxRadius( target, self, 65, 0, radius );
}
function InsideStingerReticleDetect( target, weapon )
{
radius = CalcLockOnRadius( target, weapon );
return target_isincircle( target, self, 65, radius );
}
function InsideStingerReticleNoLock( target, weapon )
{
radius = CalcLockOnRadius( target, weapon );
return target_isincircle( target, self, 65, radius );
}
function InsideStingerReticleLocked( target, weapon )
{
radius = CalcLockOnLossRadius( target, weapon );
return target_isincircle( target, self, 65, radius );
}
function IsStillValidTarget( ent, weapon )
{
if ( ! isdefined( ent ) )
return false;
if ( isdefined( self.is_still_valid_target_for_stinger_override ) )
return self [ [ self.is_still_valid_target_for_stinger_override ] ]( ent, weapon );
if ( ! target_isTarget( ent ) && !( isdefined( ent.allowContinuedLockonAfterInvis ) && ent.allowContinuedLockonAfterInvis ) )
return false;
if ( ! InsideStingerReticleDetect( ent, weapon ) )
return false;
return true;
}
function PlayerStingerAds()
{
return ( self PlayerAds() == 1.0 );
}
function LoopLocalSeekSound( alias, interval )
{
self endon ( "stop_lockon_sound" );
self endon( "disconnect" );
self endon ( "death" );
for (;;)
{
self PlaySoundForLocalPlayer( alias );
self PlayRumbleOnEntity( "stinger_lock_rumble" );
wait interval/2;
}
}
function PlaySoundForLocalPlayer( alias )
{
if ( self IsInVehicle() )
{
sound_target = self GetVehicleOccupied();
if ( isdefined( sound_target ) )
{
sound_target PlaySoundToPlayer( alias, self );
}
}
else
{
self playLocalSound( alias );
}
}
function LoopLocalLockSound( alias, interval )
{
self endon ( "stop_locked_sound" );
self endon( "disconnect" );
self endon ( "death" );
if ( isdefined( self.stingerlocksound ) )
return;
self.stingerlocksound = true;
for (;;)
{
// TODO make lock loop audio work correctly CDC
self PlaySoundForLocalPlayer( alias );
self PlayRumbleOnEntity( "stinger_lock_rumble" );
wait interval/6;
self PlaySoundForLocalPlayer( alias );
self PlayRumbleOnEntity( "stinger_lock_rumble" );
wait interval/6;
self PlaySoundForLocalPlayer( alias );
self PlayRumbleOnEntity( "stinger_lock_rumble" );
wait interval/6;
self StopRumble( "stinger_lock_rumble" );
}
self.stingerlocksound = undefined;
}
function LockSightTest( target )
{
cameraPos = self getplayercamerapos();
if ( !isdefined( target ) ) //targets can disapear during targeting.
return false;
if( isdefined( target.parent ) )
passed = BulletTracePassed( cameraPos, target.origin, false, target, target.parent );
else
passed = BulletTracePassed( cameraPos, target.origin, false, target );
if ( passed )
return true;
front = target GetPointInBounds( 1, 0, 0 );
if( isdefined( target.parent ) )
passed = BulletTracePassed( cameraPos, front, false, target, target.parent );
else
passed = BulletTracePassed( cameraPos, front, false, target );
if ( passed )
return true;
back = target GetPointInBounds( -1, 0, 0 );
if( isdefined( target.parent ) )
passed = BulletTracePassed( cameraPos, back, false, target, target.parent );
else
passed = BulletTracePassed( cameraPos, back, false, target );
if ( passed )
return true;
return false;
}
function SoftSightTest()
{
LOST_SIGHT_LIMIT = 500;
if ( self LockSightTest( self.stingerTarget ) )
{
self.stingerLostSightlineTime = 0;
return true;
}
if ( self.stingerLostSightlineTime == 0 )
self.stingerLostSightlineTime = getTime();
timePassed = GetTime() - self.stingerLostSightlineTime;
//PrintLn( "Losing sight of target [", timePassed, "]..." );
if ( timePassed >= LOST_SIGHT_LIMIT )
{
//PrintLn( "Lost sight of target." );
self ClearIRTarget();
return false;
}
return true;
}
function InitLockField( target )
{
if ( isdefined( target.locking_on ) )
return;
target.locking_on = 0;
target.locked_on = 0;
target.locking_on_hacking = 0;
}
function LockingOn( target, lock )
{
Assert( isdefined( target.locking_on ) );
clientNum = self getEntityNumber();
if ( lock )
{
target notify( "locking on" );
target.locking_on |= ( 1 << clientNum );
self thread watchClearLockingOn( target, clientNum );
}
else
{
self notify( "locking_on_cleared" );
target.locking_on &= ~( 1 << clientNum );
}
}
function watchClearLockingOn( target, clientNum )
{
target endon("death");
self endon( "locking_on_cleared" );
self util::waittill_any( "death", "disconnect" );
target.locking_on &= ~( 1 << clientNum );
}
function LockedOn( target, lock )
{
Assert( isdefined( target.locked_on ) );
clientNum = self getEntityNumber();
if ( lock )
{
target.locked_on |= ( 1 << clientNum );
self thread watchClearLockedOn( target, clientNum );
}
else
{
self notify( "locked_on_cleared" );
target.locked_on &= ~( 1 << clientNum );
}
}
function TargetingHacking( target, lock )
{
Assert( isdefined( target.locking_on_hacking ) );
clientNum = self getEntityNumber();
if ( lock )
{
target notify( "locking on hacking" );
target.locking_on_hacking |= ( 1 << clientNum );
self thread watchClearHacking( target, clientNum );
}
else
{
self notify( "locking_on_hacking_cleared" );
target.locking_on_hacking &= ~( 1 << clientNum );
}
}
function watchClearHacking( target, clientNum )
{
target endon("death");
self endon( "locking_on_hacking_cleared" );
self util::waittill_any( "death", "disconnect" );
target.locking_on_hacking &= ~( 1 << clientNum );
}
function setFriendlyFlags( weapon, target )
{
if ( !self isinvehicle() )
{
self setFriendlyHacking( weapon, target );
self setFriendlyTargetting( weapon, target );
self setFriendlyTargetLocked( weapon, target );
if ( isdefined( level.killstreakMaxHealthFunction ) )
{
if ( isdefined( target.useVTOLTime ) && isdefined( level.vtol ) )
{
killstreakEndTime = level.vtol.killstreakEndTime;
if ( isdefined( killstreakEndTime ) )
{
self settargetedentityendtime( weapon, killstreakEndTime );
}
}
else if ( isdefined( target.killstreakEndTime ) )
{
self settargetedentityendtime( weapon, target.killstreakEndTime );
}
else if ( isdefined( target.parentstruct ) && isdefined( target.parentStruct.killstreakEndTime ) )
{
self settargetedentityendtime( weapon, target.parentStruct.killstreakEndTime );
}
else
{
self settargetedentityendtime( weapon, 0 );
}
self settargetedmissilesremaining( weapon, 0 );
killstreakType = target.killstreakType;
if ( !isdefined( target.killstreakType ) && isdefined( target.parentstruct ) && isdefined( target.parentStruct.killstreakType ) )
{
killstreakType = target.parentStruct.killstreakType;
}
else if ( isdefined( target.useVTOLTime ) && isdefined( level.vtol.killstreakType ) )
{
killstreakType = level.vtol.killstreakType;
}
if ( isdefined ( killstreakType ) && isdefined( level.killstreakbundle[killstreakType] ) )
{
if ( isdefined( target.forceOneMissile ) )
{
self settargetedmissilesremaining( weapon, 1 );
}
else if ( isdefined( target.useVTOLTime ) && isdefined( level.vtol ) && isdefined( level.vtol.totalRocketHits ) && isdefined( level.vtol.missileToDestroy ) )
{
self settargetedmissilesremaining( weapon, level.vtol.missileToDestroy - level.vtol.totalRocketHits );
}
else
{
maxHealth = [[level.killstreakMaxHealthFunction]]( killstreakType );
damageTaken = target.damageTaken;
if ( !isdefined( damageTaken ) && isdefined( target.parentstruct ) )
{
damageTaken = target.parentstruct.damageTaken;
}
if ( isdefined( target.missileTrackDamage ) )
{
damageTaken = target.missileTrackDamage;
}
if ( isdefined( damageTaken ) && isdefined( maxHealth ) )
{
damagePerRocket = ( maxHealth / level.killstreakbundle[killstreakType].ksRocketsToKill ) + 1;
remainingHealth = maxHealth - damageTaken;
if ( remaininghealth > 0 )
{
missilesRemaining = int( ceil( remainingHealth / damageperrocket ) );
if ( isdefined( target.numflares ) && target.numflares > 0 )
{
missilesRemaining += target.numFlares;
}
if ( isdefined( target.flak_drone ) )
{
missilesRemaining += 1;
}
self settargetedmissilesremaining( weapon, missilesRemaining );
}
}
}
}
}
}
}
function setFriendlyHacking( weapon, target )
{
if ( level.teambased )
{
friendlyHackingMask = target.locking_on_hacking;
if ( isdefined( friendlyHackingMask ) )
{
friendlyHacking = false;
clientNum = self getEntityNumber();
friendlyHackingMask &= ~( 1 << clientNum );
if ( friendlyHackingMask != 0 )
{
friendlyHacking = true;
}
self SetWeaponFriendlyHacking( weapon, friendlyHacking );
}
}
}
function setFriendlyTargetting( weapon, target )
{
if ( level.teambased )
{
friendlyTargetingMask = target.locking_on;
if ( isdefined( friendlyTargetingMask ) )
{
friendlyTargeting = false;
clientNum = self getEntityNumber();
friendlyTargetingMask &= ~( 1 << clientNum );
if ( friendlyTargetingMask != 0 )
{
friendlyTargeting = true;
}
self SetWeaponFriendlyTargeting( weapon, friendlyTargeting );
}
}
}
function setFriendlyTargetLocked( weapon, target )
{
if ( level.teambased )
{
friendlyTargetLocked = false;
friendlyLockingOnMask = target.locked_on;
if ( isdefined( friendlyLockingOnMask ) )
{
friendlyTargetLocked = false;
clientNum = self getEntityNumber();
friendlyLockingOnMask &= ~( 1 << clientNum );
if ( friendlylockingonMask != 0 )
{
friendlyTargetLocked = true;
}
}
if ( friendlyTargetLocked == false )
{
friendlyTargetLocked = target MissileTarget_isOtherPlayerMissileIncoming( self );
}
self SetWeaponFriendlyTargetLocked( weapon, friendlyTargetLocked );
}
}
function watchClearLockedOn( target, clientNum )
{
self endon( "locked_on_cleared" );
self util::waittill_any( "death", "disconnect" );
if ( isdefined( target ) )
{
target.locked_on &= ~( 1 << clientNum );
}
}
function MissileTarget_LockOnMonitor( player, endon1, endon2 )
{
self endon( "death" );
if ( isdefined(endon1) )
self endon( endon1 );
if ( isdefined(endon2) )
self endon( endon2 );
for( ;; )
{
if( target_isTarget( self ) )
{
if ( self MissileTarget_isMissileIncoming() )
{
self clientfield::set( "heli_warn_fired", 1 );
self clientfield::set( "heli_warn_locked", 0 );
self clientfield::set( "heli_warn_targeted", 0 );
}
else if( isdefined(self.locked_on) && self.locked_on )
{
self clientfield::set( "heli_warn_locked", 1 );
self clientfield::set( "heli_warn_fired", 0 );
self clientfield::set( "heli_warn_targeted", 0 );
}
else if( isdefined(self.locking_on) && self.locking_on )
{
self clientfield::set( "heli_warn_targeted", 1 );
self clientfield::set( "heli_warn_fired", 0 );
self clientfield::set( "heli_warn_locked", 0 );
}
else
{
self clientfield::set( "heli_warn_fired", 0 );
self clientfield::set( "heli_warn_targeted", 0 );
self clientfield::set( "heli_warn_locked", 0 );
}
}
wait( 0.1 );
}
}
function _incomingMissile( missile, attacker )
{
if ( !isdefined(self.incoming_missile) )
{
self.incoming_missile = 0;
}
if ( !isdefined(self.incoming_missile_owner) )
{
self.incoming_missile_owner = [];
}
if ( !isdefined( self.incoming_missile_owner[attacker.entnum] ) )
{
self.incoming_missile_owner[attacker.entnum] = 0;
}
self.incoming_missile++;
self.incoming_missile_owner[attacker.entnum]++;
attacker LockedOn(self, true);
self thread _incomingMissileTracker( missile, attacker );
}
function _incomingMissileTracker( missile, attacker )
{
self endon("death");
attacker_entnum = attacker.entnum;
missile waittill("death");
self.incoming_missile--;
self.incoming_missile_owner[attacker_entnum]--;
if ( self.incoming_missile_owner[attacker_entnum] == 0 )
{
self.incoming_missile_owner[attacker_entnum] = undefined;
}
if ( isdefined( attacker ) )
{
attacker LockedOn( self, false );
}
assert( self.incoming_missile >= 0 );
}
function MissileTarget_isMissileIncoming()
{
if ( !isdefined(self.incoming_missile) )
return false;
if ( self.incoming_missile )
return true;
return false;
}
function MissileTarget_isOtherPlayerMissileIncoming( attacker )
{
if ( !isdefined(self.incoming_missile_owner) )
return false;
if ( self.incoming_missile_owner.size == 0 )
return false;
if ( self.incoming_missile_owner.size == 1 && isdefined( self.incoming_missile_owner[attacker.entnum] ) )
return false;
return true;
}
function MissileTarget_HandleIncomingMissile(responseFunc, endon1, endon2, allowDirectDamage )
{
level endon( "game_ended" );
self endon( "death" );
if ( isdefined(endon1) )
self endon( endon1 );
if ( isdefined(endon2) )
self endon( endon2 );
for( ;; )
{
self waittill( "stinger_fired_at_me", missile, weapon, attacker );
_incomingMissile(missile, attacker);
if ( isdefined(responseFunc) )
[[responseFunc]]( missile, attacker, weapon, endon1, endon2, allowDirectDamage );
}
}
function MissileTarget_ProximityDetonateIncomingMissile( endon1, endon2, allowDirectDamage )
{
MissileTarget_HandleIncomingMissile(&MissileTarget_ProximityDetonate, endon1, endon2, allowDirectDamage );
}
function _missileDetonate( attacker, weapon, range, minDamage, maxDamage, allowDirectDamage )
{
origin = self.origin;
target = self Missile_GetTarget();
self detonate();
// guarantee a locked_on target is directly damaged if allowed and reasonably close enough
if ( ( allowDirectDamage === true ) && isdefined( target ) && isdefined( target.origin ) )
{
minDistSq = (isdefined(target.locked_missile_min_distsq)?target.locked_missile_min_distsq:( (range) * (range) )); // sanity check distance for "bad" direct hits
distSq = DistanceSquared( self.origin, target.origin );
if ( distSq < minDistSq )
{
target DoDamage( maxDamage, origin, attacker, self, "none", "MOD_PROJECTILE", 0, weapon );
}
}
radiusDamage( origin, range, maxDamage, minDamage, attacker, "MOD_PROJECTILE_SPLASH", weapon );
}
function MissileTarget_ProximityDetonate( missile, attacker, weapon, endon1, endon2, allowDirectDamage )
{
level endon( "game_ended" );
missile endon ( "death" );
if ( isdefined(endon1) )
self endon( endon1 );
if ( isdefined(endon2) )
self endon( endon2 );
minDist = DistanceSquared( missile.origin, self.origin );
lastCenter = self.origin;
missile Missile_SetTarget( self, (isdefined(Target_GetOffset( self ))?Target_GetOffset( self ):( 0, 0, 0 )) );
if ( isdefined( self.missileTargetMissDistance ) )
{
missedDistanceSq = self.missileTargetMissDistance * self.missileTargetMissDistance;
}
else
{
missedDistanceSq = 500 * 500;
}
flareDistanceSq = 3500 * 3500;
for ( ;; )
{
// target already destroyed
if ( !isdefined( self ) )
center = lastCenter;
else
center = self.origin;
lastCenter = center;
curDist = DistanceSquared( missile.origin, center );
if( curDist < flareDistanceSq && isdefined(self.numFlares) && self.numFlares > 0 )
{
self.numFlares--;
self thread MissileTarget_PlayFlareFx();
self challenges::trackAssists( attacker, 0, true );
newTarget = self MissileTarget_DeployFlares(missile.origin, missile.angles);
missile Missile_SetTarget( newTarget, (isdefined(Target_GetOffset( newTarget ))?Target_GetOffset( newTarget ):( 0, 0, 0 )) );
missileTarget = newTarget;
return;
}
if ( curDist < minDist )
minDist = curDist;
if ( curDist > minDist )
{
if ( curDist > missedDistanceSq )
return;
missile thread _missileDetonate( attacker, weapon, 500, 600, 600, allowDirectDamage );
return;
}
{wait(.05);};
}
}
function MissileTarget_PlayFlareFx()
{
if ( !isdefined( self ) )
return;
flare_fx = level.fx_flare;
if ( isdefined( self.fx_flare ) )
{
flare_fx = self.fx_flare;
}
if( isdefined( self.flare_ent ) )
{
PlayFXOnTag( flare_fx, self.flare_ent, "tag_origin" );
}
else
{
PlayFXOnTag( flare_fx, self, "tag_origin" );
}
if ( isdefined( self.owner ) )
{
self playsoundtoplayer ( "veh_huey_chaff_drop_plr", self.owner );
}
self PlaySound ( "veh_huey_chaff_explo_npc" );
}
function MissileTarget_DeployFlares(origin, angles) // self == missile target
{
vec_toForward = anglesToForward( self.angles );
vec_toRight = AnglesToRight( self.angles );
vec_toMissileForward = anglesToForward( angles );
delta = self.origin - origin;
dot = VectorDot(vec_toMissileForward,vec_toRight);
sign = 1;
if ( dot > 0 )
sign = -1;
// out to one side or the other slightly backwards
flare_dir = VectorNormalize(VectorScale( vec_toForward, -0.5 ) + VectorScale( vec_toRight, sign ));
velocity = VectorScale( flare_dir, RandomIntRange(200, 400));
velocity = (velocity[0], velocity[1], velocity[2] - RandomIntRange(10, 100) );
flareOrigin = self.origin;
flareOrigin = flareOrigin + VectorScale( flare_dir, RandomIntRange(600, 800));
// some height will allow a missle going twards a low hovering plane to
// have enough radius to turn to the new target
flareOrigin = flareOrigin + ( 0, 0, 500 );
if ( isdefined( self.flareOffset ) )
flareOrigin = flareOrigin + self.flareOffset;
flareObject = spawn( "script_origin", flareOrigin );
flareObject.angles = self.angles;
flareObject SetModel( "tag_origin" );
flareObject MoveGravity( velocity, 5.0 );
flareObject thread util::deleteAfterTime( 5.0 );
/#
self thread debug_tracker( flareObject );
#/
return flareObject;
}
/#
function debug_tracker( target )
{
target endon( "death");
while(1)
{
dev::debug_sphere( target.origin, 10, (1,0,0), 1, 1 );
{wait(.05);};
}
}
#/