495 lines
19 KiB
Plaintext
495 lines
19 KiB
Plaintext
#using scripts\codescripts\struct;
|
|
#using scripts\shared\array_shared;
|
|
#using scripts\shared\callbacks_shared;
|
|
#using scripts\shared\clientfield_shared;
|
|
#using scripts\shared\flagsys_shared;
|
|
#using scripts\shared\hud_util_shared;
|
|
#using scripts\shared\system_shared;
|
|
#using scripts\shared\util_shared;
|
|
#using scripts\shared\abilities\_ability_util;
|
|
|
|
|
|
|
|
|
|
|
|
#precache( "string", "MP_CANNOT_LOCKON_TO_TARGET" );
|
|
|
|
#namespace multilockap_guidance;
|
|
|
|
//#define MAX_SIMULTANEOUS_LOCKONS 5 this number corresponds to a code define
|
|
|
|
function autoexec __init__sytem__() { system::register("multilockap_guidance",&__init__,undefined,undefined); }
|
|
|
|
function __init__()
|
|
{
|
|
callback::on_spawned( &on_player_spawned );
|
|
SetDvar( "scr_max_simLocks",3 );
|
|
}
|
|
|
|
function on_player_spawned()
|
|
{
|
|
self endon( "disconnect" );
|
|
|
|
self ClearAPTarget();
|
|
thread APToggleLoop();
|
|
self thread APFiredNotify();
|
|
}
|
|
|
|
function ClearAPTarget( weapon, whom )
|
|
{
|
|
if (!IsDefined(self.multiLockList))
|
|
{
|
|
self.multiLockList=[];
|
|
}
|
|
|
|
if (IsDefined(whom))
|
|
{
|
|
for(i=0;i<self.multiLockList.size;i++)
|
|
{
|
|
if (whom.apTarget == self.multiLockList[i].apTarget)
|
|
{
|
|
if (isdefined(self.multiLockList[i].apTarget))
|
|
{
|
|
self.multiLockList[i].apTarget notify( "missile_unlocked" );
|
|
}
|
|
self notify( "stop_sound"+whom.apSoundId );
|
|
self WeaponLockRemoveSlot(i);
|
|
ArrayRemoveValue(self.multiLockList,whom,false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(i=0;i<self.multiLockList.size;i++)
|
|
{
|
|
self.multiLockList[i].apTarget notify( "missile_unlocked" );
|
|
self notify( "stop_sound"+self.multiLockList[i].apSoundId );
|
|
}
|
|
self.multiLockList = [];
|
|
}
|
|
|
|
if (self.multiLockList.size==0)
|
|
{
|
|
self StopRumble( "stinger_lock_rumble" );
|
|
|
|
self WeaponLockRemoveSlot(-1);
|
|
|
|
if (IsDefined(weapon))
|
|
{
|
|
if (IsDefined(weapon.lockonSeekerSearchSound))
|
|
self StopLocalSound( weapon.lockonSeekerSearchSound );
|
|
if (IsDefined(weapon.lockonSeekerLockedSound))
|
|
self StopLocalSound( weapon.lockonSeekerLockedSound );
|
|
}
|
|
|
|
self DestroyLockOnCanceledMessage();
|
|
}
|
|
}
|
|
|
|
|
|
function APFiredNotify()
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon ( "death" );
|
|
|
|
while ( true )
|
|
{
|
|
self waittill( "missile_fire", missile, weapon );
|
|
|
|
if ( weapon.lockonType == "AP Multi" )
|
|
{
|
|
foreach(target in self.multiLockList)
|
|
{
|
|
if( isdefined(target.apTarget) && target.apLockFinalized )
|
|
{
|
|
target.apTarget notify( "stinger_fired_at_me", missile, weapon, self );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function APToggleLoop()
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon ( "death" );
|
|
|
|
for (;;)
|
|
{
|
|
self waittill( "weapon_change", weapon );
|
|
|
|
while ( weapon.lockonType == "AP Multi" )
|
|
{
|
|
abort = false;
|
|
|
|
while( !( self PlayerAds() == 1.0 ) )
|
|
{
|
|
{wait(.05);};
|
|
|
|
currentWeapon = self GetCurrentWeapon();
|
|
if ( currentWeapon.lockonType != "AP Multi" )
|
|
{
|
|
abort = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( abort )
|
|
{
|
|
break;
|
|
}
|
|
|
|
self thread APLockLoop(weapon);
|
|
|
|
while( ( self PlayerAds() == 1.0 ) )
|
|
{
|
|
{wait(.05);};
|
|
}
|
|
|
|
self notify( "ap_off" );
|
|
self ClearAPTarget(weapon);
|
|
|
|
weapon = self GetCurrentWeapon();
|
|
}
|
|
}
|
|
}
|
|
|
|
function APLockLoop(weapon)
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "death" );
|
|
self endon( "ap_off" );
|
|
|
|
lockLength = self getLockOnSpeed();
|
|
self.multiLockList = [];
|
|
|
|
for (;;)
|
|
{
|
|
{wait(.05);};
|
|
|
|
//-------------------------
|
|
// 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.
|
|
//-------------------------
|
|
|
|
do
|
|
{
|
|
done=true;
|
|
foreach(target in self.multiLockList)
|
|
{
|
|
if ( target.apLockFinalized )
|
|
{
|
|
if ( ! IsStillValidTarget( weapon, target.apTarget ) )
|
|
{
|
|
self ClearAPTarget(weapon, target);
|
|
//start over because the array has changed and it'll mess up foreach
|
|
done = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
while (!done);
|
|
|
|
|
|
inLockingState=false;
|
|
|
|
do
|
|
{
|
|
done=true;
|
|
for(i=0;i<self.multiLockList.size;i++)
|
|
{
|
|
target=self.multiLockList[i];
|
|
if ( target.apLocking )
|
|
{
|
|
if ( ! IsStillValidTarget( weapon, target.apTarget ) )
|
|
{
|
|
self ClearAPTarget(weapon, target);
|
|
//start over because the array has changed and it'll mess up foreach
|
|
done=false;
|
|
break;
|
|
}
|
|
|
|
inLockingState=true;
|
|
|
|
timePassed = getTime() - target.apLockStartTime;
|
|
if ( timePassed < lockLength )
|
|
continue;
|
|
|
|
assert( isdefined( target.apTarget ) );
|
|
target.apLockFinalized = true;
|
|
target.apLocking = false;
|
|
target.apLockPending = false;
|
|
self WeaponLockFinalize( target.apTarget, i );
|
|
self thread SeekerSound( weapon.lockonSeekerLockedSound, weapon.lockonSeekerLockedSoundLoops, target.apSoundId );
|
|
target.apTarget notify( "missile_lock", self, weapon );
|
|
}
|
|
}
|
|
}
|
|
while (!done);
|
|
|
|
//if anyone is in the locking state, then don't allow pending to change state to locking (this will make locking sequential)
|
|
if (!inLockingState)
|
|
{
|
|
do
|
|
{
|
|
done=true;
|
|
for(i=0;i<self.multiLockList.size;i++)
|
|
{
|
|
target=self.multiLockList[i];
|
|
if ( target.apLockPending )
|
|
{
|
|
if ( ! IsStillValidTarget( weapon, target.apTarget ) )
|
|
{
|
|
self ClearAPTarget(weapon, target);
|
|
//start over because the array has changed and it'll mess up foreach
|
|
done=false;
|
|
break;
|
|
}
|
|
|
|
target.apLockStartTime = getTime();
|
|
|
|
target.apLockFinalized = false;
|
|
target.apLockPending = false;
|
|
target.apLocking = true;
|
|
|
|
self thread SeekerSound( weapon.lockonSeekerSearchSound, weapon.lockonSeekerSearchSoundLoops, target.apSoundId );
|
|
|
|
done=true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
while (!done);
|
|
}
|
|
|
|
if (self.multiLockList.size>=GetDvarInt( "scr_max_simLocks" ) || self.multiLockList.size>=self GetWeaponAmmoClip(weapon))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bestTarget = self GetBestTarget(weapon);
|
|
|
|
if ( !IsDefined(bestTarget) && self.multiLockList.size==0 )
|
|
{
|
|
self DestroyLockOnCanceledMessage();
|
|
continue;
|
|
}
|
|
|
|
//append to the lock list if we have the space
|
|
if (IsDefined(bestTarget) && self.multiLockList.size<GetDvarInt( "scr_max_simLocks" ) && self.multiLockList.size<self GetWeaponAmmoClip(weapon))
|
|
{
|
|
self WeaponLockStart(bestTarget.apTarget,self.multiLockList.size);
|
|
self.multiLockList[self.multiLockList.size] = bestTarget;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 GetBestTarget(weapon)
|
|
{
|
|
playerTargets = GetPlayers();
|
|
vehicleTargets = target_getArray();
|
|
targetsAll = GetAITeamArray();
|
|
targetsAll = ArrayCombine(targetsAll, playerTargets, false, false);
|
|
targetsAll = ArrayCombine(targetsAll, vehicleTargets, false, false);
|
|
targetsValid = [];
|
|
|
|
for ( idx = 0; idx < targetsAll.size; idx++ )
|
|
{
|
|
if ( level.teamBased ) //team based game modes
|
|
{
|
|
if ( isdefined(targetsAll[idx].team) && targetsAll[idx].team != self.team)
|
|
{
|
|
if ( self InsideAPReticleNoLock( targetsAll[idx] ) )
|
|
{
|
|
if (self LockSightTest(targetsAll[idx]))
|
|
{
|
|
targetsValid[targetsValid.size] = targetsAll[idx];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( self InsideAPReticleNoLock( targetsAll[idx] ) ) //Free for all
|
|
{
|
|
if( isdefined( targetsAll[idx].owner ) && self != targetsAll[idx].owner )
|
|
{
|
|
if (self LockSightTest(targetsAll[idx]))
|
|
{
|
|
targetsValid[targetsValid.size] = targetsAll[idx];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( targetsValid.size == 0 )
|
|
return undefined;
|
|
|
|
//find the best target that isn't already in the list
|
|
|
|
playerForward = AnglesToForward(self GetPlayerAngles());
|
|
dots=[];
|
|
for (i=0;i<targetsValid.size;i++)
|
|
{
|
|
newitem=spawnstruct();
|
|
newitem.index = i;
|
|
newitem.dot = VectorDot(playerForward,VectorNormalize(targetsValid[i].origin-self.origin));
|
|
array::insertion_sort(dots,&TargetInsertionSortCompare,newitem);
|
|
}
|
|
|
|
index=0;
|
|
foreach(dot in dots)
|
|
{
|
|
found = false;
|
|
foreach(lock in self.multiLockList)
|
|
{
|
|
if (lock.apTarget == targetsValid[dot.index])
|
|
{
|
|
found=true;
|
|
}
|
|
}
|
|
|
|
if (found)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
newEntry = spawnstruct();
|
|
newEntry.apTarget = targetsValid[dot.index];
|
|
newEntry.apLockStartTime = getTime();
|
|
newEntry.apLockPending = true;
|
|
newEntry.apLocking = false;
|
|
newEntry.apLockFinalized = false;
|
|
newEntry.apLostSightlineTime = 0;
|
|
newEntry.apSoundId = RandomInt(0x7FFFFFFF);
|
|
return newEntry;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function TargetInsertionSortCompare(a, b)
|
|
{
|
|
if (a.dot<b.dot)
|
|
return -1;
|
|
if (a.dot>b.dot)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
function InsideAPReticleNoLock( target )
|
|
{
|
|
radius = self getLockOnRadius();
|
|
return target_isincircle( target, self, 65, radius );
|
|
}
|
|
|
|
function InsideAPReticleLocked( target )
|
|
{
|
|
radius = (self getLockOnLossRadius());
|
|
return target_isincircle( target, self, 65, radius );
|
|
}
|
|
|
|
function IsStillValidTarget( weapon, ent )
|
|
{
|
|
if ( ! isdefined( ent ) )
|
|
return false;
|
|
if ( ! InsideAPReticleLocked( ent ) )
|
|
return false;
|
|
if ( !IsAlive( ent ) )
|
|
return false;
|
|
if (!LockSightTest(ent))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
function SeekerSound( alias, looping, id )
|
|
{
|
|
self notify( "stop_sound"+id);
|
|
|
|
self endon ( "stop_sound"+id );
|
|
self endon( "disconnect" );
|
|
self endon ( "death" );
|
|
|
|
if (IsDefined(alias))
|
|
{
|
|
self PlayRumbleOnEntity( "stinger_lock_rumble" );
|
|
time = SoundGetPlaybackTime(alias)*0.001;
|
|
do
|
|
{
|
|
self playLocalSound( alias );
|
|
wait(time);
|
|
}
|
|
while (looping);
|
|
self StopRumble( "stinger_lock_rumble" );
|
|
}
|
|
}
|
|
|
|
function LockSightTest( target )
|
|
{
|
|
eyePos = self GetEye();
|
|
|
|
if ( !isdefined( target ) ) //targets can disapear during targeting.
|
|
return false;
|
|
|
|
if ( !IsAlive( target ) )
|
|
return false;
|
|
|
|
pos = target GetShootAtPos();
|
|
if (IsDefined(pos))
|
|
{
|
|
passed = BulletTracePassed( eyePos, pos, false, target, undefined, true, true );
|
|
if ( passed )
|
|
return true;
|
|
}
|
|
|
|
pos = target GetCentroid();
|
|
if (IsDefined(pos))
|
|
{
|
|
passed = BulletTracePassed( eyePos, pos, false, target, undefined, true, true );
|
|
if ( passed )
|
|
return true;
|
|
}
|
|
|
|
pos=target.origin;
|
|
passed = BulletTracePassed( eyePos, pos, false, target, undefined, true, true );
|
|
if ( passed )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|