996 lines
28 KiB
Ucode
996 lines
28 KiB
Ucode
//=============================================================================
|
|
// KFWeap_DualBase
|
|
//=============================================================================
|
|
// Dual weapon base class
|
|
//=============================================================================
|
|
// Killing Floor 2
|
|
// Copyright (C) 2015 Tripwire Interactive LLC
|
|
// Jeff Robinson
|
|
//=============================================================================
|
|
|
|
class KFWeap_DualBase extends KFWeap_PistolBase
|
|
native; // trader needs this to be native for now (6/10/15) so we can access SingleClass
|
|
|
|
/** Animations to play when the weapon is fired */
|
|
var(Animations) const editconst name LeftFireAnim;
|
|
|
|
/** Animation to play when the weapon is fired */
|
|
var(Animations) const editconst array<name> LeftFireSightedAnims;
|
|
|
|
/** A muzzle flash instance for left weapon */
|
|
var KFMuzzleFlash LeftMuzzleFlash;
|
|
|
|
/** Holds an offest for spawning protectile effects for left weapon */
|
|
var() vector LeftFireOffset;
|
|
|
|
/** Whether we should fire from the right-side weapon (alternates right and left fire) */
|
|
var transient bool bFireFromRightWeapon;
|
|
|
|
/** class of the single variant of this weapon */
|
|
var class<KFWeapon> SingleClass;
|
|
|
|
/*********************************************************************************************
|
|
* @name Animations (Using const anim names to reduce instanced data cost)
|
|
********************************************************************************************* */
|
|
|
|
/** Cached anim nodes */
|
|
var AnimNodeBlendPerBone EmptyMagBlendNode_L;
|
|
/** array of bones to lock when out of ammo */
|
|
var array<name> BonesToLockOnEmpty_L;
|
|
|
|
/** Anims for ironsight and alternate ironsight mode **/
|
|
var(Animations) const editconst name IdleToIronSightAnim;
|
|
var(Animations) const editconst name IdleToIronSightAnim_Alt;
|
|
var(Animations) const editconst name IronSightToIdleAnim;
|
|
var(Animations) const editconst name IronSightToIdleAnim_Alt;
|
|
var(Animations) const editconst array<name> IdleSightedAnims_Alt;
|
|
var(Animations) const editconst name FireSightedAnim_Alt;
|
|
var(Animations) const editconst name LeftFireSightedAnim_Alt;
|
|
var(Animations) const editconst name EquipAnimIS;
|
|
var(Animations) const editconst name EquipAnimISAlt;
|
|
|
|
var(Animations) const editconst name LeftFireLastAnim;
|
|
var(Animations) const editconst name LeftFireLastSightedAnim;
|
|
var(Animations) const editconst name FireLastSightedAnim_Alt;
|
|
var(Animations) const editconst name LeftFireLastSightedAnim_Alt;
|
|
|
|
const ReloadOneEmptyAnim = 'Reload_Empty_Half';
|
|
const ReloadOneEmptyEliteAnim = 'Reload_Empty_Half_Elite';
|
|
|
|
/*********************************************************************************************
|
|
* @name Revolver vars
|
|
********************************************************************************************* */
|
|
|
|
var CylinderRotationInfo CylinderRotInfo_L;
|
|
|
|
cpptext
|
|
{
|
|
virtual void PreBeginPlay();
|
|
virtual void RefreshSkinItemId();
|
|
|
|
// cylinder is incrementally rotated
|
|
virtual void TickSpecial( FLOAT DeltaSeconds );
|
|
};
|
|
|
|
/** Cache Anim Nodes from the tree
|
|
* @note: skipped on server because AttachComponent/AttachWeaponTo is not called
|
|
*/
|
|
simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
|
|
{
|
|
local KFGameEngine KFGEngine;
|
|
super.PostInitAnimTree( SkelComp );
|
|
|
|
EmptyMagBlendNode_L = AnimNodeBlendPerBone(SkelComp.FindAnimNode('EmptyMagBlend_L'));
|
|
if( EmptyMagBlendNode_L != none && BonesToLockOnEmpty_L.Length > 0 )
|
|
{
|
|
BuildEmptyMagNodeWeightList( EmptyMagBlendNode_L, BonesToLockOnEmpty_L );
|
|
}
|
|
|
|
KFGEngine = KFGameEngine(Class'KFGameEngine'.static.GetEngine());
|
|
|
|
if(KFGEngine != none)
|
|
{
|
|
bUseAltFireMode = KFGEngine.bUseAltAimOnDual;
|
|
}
|
|
|
|
if( !bRevolver )
|
|
{
|
|
return;
|
|
}
|
|
|
|
CylinderRotInfo_L.Control = SkelControlSingleBone( SkelComp.FindSkelControl('CylinderControl_L') );
|
|
if( CylinderRotInfo_L.Control != none )
|
|
{
|
|
CylinderRotInfo_L.Control.SetSkelControlActive( true );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called on a client, this function Attaches the WeaponAttachment
|
|
* to the Mesh.
|
|
*
|
|
* Overridden to attach LeftMuzzleFlash
|
|
*/
|
|
simulated function AttachMuzzleFlash()
|
|
{
|
|
super.AttachMuzzleFlash();
|
|
|
|
if ( MySkelMesh != none )
|
|
{
|
|
if (MuzzleFlashTemplate != None)
|
|
{
|
|
LeftMuzzleFlash = new(self) Class'KFMuzzleFlash'(MuzzleFlashTemplate);
|
|
LeftMuzzleFlash.AttachMuzzleFlash(MySkelMesh, 'MuzzleFlash_L', 'ShellEject_L');
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* State Active
|
|
* A Weapon this is being held by a pawn should be in the active state. In this state,
|
|
* a weapon should loop any number of idle animations, as well as check the PendingFire flags
|
|
* to see if a shot has been fired.
|
|
*********************************************************************************************/
|
|
simulated state Active
|
|
{
|
|
simulated function ZoomIn(bool bAnimateTransition, float ZoomTimeToGo)
|
|
{
|
|
GotoState('ActiveIronSights');
|
|
}
|
|
|
|
simulated function PlayIdleAnim()
|
|
{
|
|
local int IdleIndex;
|
|
|
|
if ( Instigator.IsFirstPerson() )
|
|
{
|
|
if( bUsingSights && IdleSightedAnims.Length > 0 )
|
|
{
|
|
if( bUseAltFireMode )
|
|
{
|
|
IdleIndex = Rand(IdleSightedAnims_Alt.Length);
|
|
PlayAnimation(IdleSightedAnims_Alt[IdleIndex], 0.0, true, 0.1);
|
|
}
|
|
else
|
|
{
|
|
IdleIndex = Rand(IdleSightedAnims.Length);
|
|
PlayAnimation(IdleSightedAnims[IdleIndex], 0.0, true, 0.1);
|
|
}
|
|
}
|
|
else if ( IdleAnims.Length > 0 )
|
|
{
|
|
IdleIndex = Rand(IdleAnims.Length);
|
|
PlayAnimation(IdleAnims[IdleIndex], 0.0, true, 0.2);
|
|
}
|
|
|
|
StartIdleFidgetTimer();
|
|
ToggleAdditiveBobAnim(!bUsingSights);
|
|
}
|
|
}
|
|
|
|
simulated function bool CanPlayIdleFidget(optional bool bOnReload)
|
|
{
|
|
// Make sure we can't play idle fidget if one of the pistols is empty.
|
|
if(AmmoCount[0] < 2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return Super.CanPlayIdleFidget(bOnReload);
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* State WeaponEquipping
|
|
* The Weapon is in this state while transitioning from Inactive to Active state.
|
|
* Typically, the weapon will remain in this state while its selection animation is being played.
|
|
* While in this state, the weapon cannot be fired.
|
|
*********************************************************************************************/
|
|
|
|
/** Get equip anim name (overridden as necessary) */
|
|
simulated function name GetEquipAnimName()
|
|
{
|
|
// since duals use anims to get into / out of iron sights,
|
|
// we need to play equip anims that blend to iron sight idle (including alt iron)
|
|
if( bIronSightOnBringUp )
|
|
{
|
|
return bUseAltFireMode ? EquipAnimISAlt : EquipAnimIS;
|
|
}
|
|
else
|
|
{
|
|
return EquipAnim;
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* State ActiveIronSights
|
|
* Only duals play an animation when transitioning to or from iron sights, but this animation
|
|
* should only be played in the "Active" state. This state allows us to encapsulate this
|
|
* functionality.
|
|
*********************************************************************************************/
|
|
simulated state ActiveIronSights extends Active
|
|
{
|
|
simulated function ZoomOut(bool bAnimateTransition, float ZoomTimeToGo)
|
|
{
|
|
local name IronToIdleAnimName;
|
|
|
|
IronToIdleAnimName = GetIronToIdleAnim();
|
|
|
|
ZoomTimeToGo = MySkelMesh.GetAnimLength(IronToIdleAnimName);
|
|
|
|
Global.ZoomOut( true, ZoomTimeToGo );
|
|
|
|
PlayAnimation( IronToIdleAnimName, ZoomTime, false );
|
|
|
|
GotoState('Active');
|
|
}
|
|
|
|
simulated function BeginState( Name PreviousStateName )
|
|
{
|
|
local float ZoomTimeToGo;
|
|
local name IdleToIronAnimName;
|
|
|
|
IdleToIronAnimName = GetIdleToIronAnim();
|
|
|
|
ZoomTimeToGo = MySkelMesh.GetAnimLength(IdleToIronAnimName);
|
|
|
|
Global.ZoomIn( true, ZoomTimeToGo );
|
|
|
|
PlayAnimation( IdleToIronAnimName, ZoomTime, false );
|
|
}
|
|
}
|
|
|
|
/** Gets idle-to-iron anim with regard to "stance" (toggle by alt fire) */
|
|
simulated function name GetIdleToIronAnim()
|
|
{
|
|
return bUseAltFireMode ? IdleToIronSightAnim_Alt : IdleToIronSightAnim;
|
|
}
|
|
|
|
/** Gets iron-to-idle anim with regard to "stance" (toggle by alt fire) */
|
|
simulated function name GetIronToIdleAnim()
|
|
{
|
|
return bUseAltFireMode ? IronSightToIdleAnim_Alt : IronSightToIdleAnim;
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* State WeaponSingleFiring
|
|
* Fire must be released between every shot.
|
|
*
|
|
* Overridden to alternate between right and left fire before calculated muzzle positions, etc.
|
|
*********************************************************************************************/
|
|
simulated state WeaponSingleFiring
|
|
{
|
|
simulated function FireAmmunition()
|
|
{
|
|
bFireFromRightWeapon = !bFireFromRightWeapon;
|
|
Super.FireAmmunition();
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* State Reloading
|
|
* This is the default Reloading State. It's performed on both the client and the server.
|
|
*
|
|
* Overridden to reset our alternating weapon fire
|
|
*********************************************************************************************/
|
|
simulated state Reloading
|
|
{
|
|
simulated function BeginState(name PreviousStateName)
|
|
{
|
|
super.BeginState( PreviousStateName );
|
|
bFireFromRightWeapon = false;
|
|
|
|
if( bRevolver )
|
|
{
|
|
ResetCylinderLeft();
|
|
}
|
|
}
|
|
|
|
simulated function byte GetWeaponStateId()
|
|
{
|
|
local KFPerk Perk;
|
|
local bool bTacticalReload;
|
|
|
|
Perk = GetPerk();
|
|
bTacticalReload = (Perk != None && Perk.GetUsingTactialReload(self));
|
|
|
|
if( AmmoCount[0] == 1 )
|
|
{
|
|
return bTacticalReload ? WEP_ReloadDualsOneEmpty_Elite : WEP_ReloadDualsOneEmpty;
|
|
}
|
|
|
|
return super.GetWeaponStateId();
|
|
}
|
|
}
|
|
|
|
/** Returns animation to play based on reload type and status */
|
|
simulated function name GetReloadAnimName( bool bTacticalReload )
|
|
{
|
|
if( AmmoCount[0] == 1 )
|
|
{
|
|
// if one gun is empty (always the right), play our half empty reload
|
|
return bTacticalReload ? ReloadOneEmptyEliteAnim : ReloadOneEmptyAnim;
|
|
}
|
|
else
|
|
{
|
|
return super.GetReloadAnimName( bTacticalReload );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Increment Pawn's FlashCount variable.
|
|
* This is used to play weapon fire effects on remote clients.
|
|
* Call this on the server and local player.
|
|
*
|
|
* Network: Server and Local Player
|
|
*
|
|
* COMPLETELY OVERRIDDEN to set FlashCount to an even number for left fire.
|
|
* Also copies implementation of Pawn::IncrementFlashCount (usually called from super)
|
|
*/
|
|
simulated function IncrementFlashCount()
|
|
{
|
|
if( Instigator != none )
|
|
{
|
|
if( Instigator.FlashCount > 0 || bFireFromRightWeapon )
|
|
{
|
|
Instigator.FlashCount += 1;
|
|
}
|
|
else
|
|
{
|
|
Instigator.FlashCount += 2;
|
|
}
|
|
|
|
//// BEGIN PAWN.INCREMENTFLASHCOUNT
|
|
Instigator.SetFiringMode( Self, CurrentFireMode );
|
|
|
|
// This weapon has fired.
|
|
Instigator.FlashCountUpdated( Self, Instigator.FlashCount, FALSE );
|
|
//// END PAWN.INCREMENTFLASHCOUNT
|
|
}
|
|
}
|
|
|
|
/** Return true if this weapon should play the fire last animation for this shoot animation */
|
|
simulated function bool ShouldPlayFireLast(byte FireModeNum)
|
|
{
|
|
if ( bHasFireLastAnims )
|
|
{
|
|
if( bFireFromRightWeapon )
|
|
{
|
|
if( (!bAllowClientAmmoTracking && Role < ROLE_Authority && AmmoCount[GetAmmoType(FireModeNum)] <= 2)
|
|
|| ((bAllowClientAmmoTracking || Role == ROLE_Authority) && AmmoCount[GetAmmoType(FireModeNum)] == 1) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( (!bAllowClientAmmoTracking && Role < ROLE_Authority && AmmoCount[GetAmmoType(FireModeNum)] <= 1)
|
|
|| ((bAllowClientAmmoTracking || Role == ROLE_Authority) && AmmoCount[GetAmmoType(FireModeNum)] == 0) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Get name of the animation to play for PlayFireEffects
|
|
*
|
|
* Overridden to allow for left weapon anims
|
|
*/
|
|
simulated function name GetWeaponFireAnim(byte FireModeNum)
|
|
{
|
|
local bool bPlayFireLast;
|
|
|
|
bPlayFireLast = ShouldPlayFireLast(FireModeNum);
|
|
|
|
if( bFireFromRightWeapon )
|
|
{
|
|
if ( bUsingSights )
|
|
{
|
|
if( bPlayFireLast )
|
|
{
|
|
return bUseAltFireMode ? FireLastSightedAnim_Alt : FireLastSightedAnim;
|
|
}
|
|
else
|
|
{
|
|
return bUseAltFireMode ? FireSightedAnim_Alt : FireSightedAnims[Rand(LeftFireSightedAnims.Length)];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( bPlayFireLast )
|
|
{
|
|
return FireLastAnim;
|
|
}
|
|
else
|
|
{
|
|
return FireAnim;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return GetLeftWeaponFireAnim(FireModeNum, bPlayFireLast);
|
|
}
|
|
}
|
|
|
|
/** Get name of the animation to play for PlayFireEffects */
|
|
simulated function name GetLeftWeaponFireAnim(byte FireModeNum, bool bPlayFireLast)
|
|
{
|
|
// ironsights animations
|
|
if ( bUsingSights )
|
|
{
|
|
if( bPlayFireLast )
|
|
{
|
|
return bUseAltFireMode ? LeftFireLastSightedAnim_Alt : LeftFireLastSightedAnim;
|
|
}
|
|
else
|
|
{
|
|
return bUseAltFireMode ? LeftFireSightedAnim_Alt : LeftFireSightedAnims[Rand(LeftFireSightedAnims.Length)];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( bPlayFireLast )
|
|
{
|
|
return LeftFireLastAnim;
|
|
}
|
|
else
|
|
{
|
|
return LeftFireAnim;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function returns the world location for spawning the visual effects
|
|
*
|
|
* Overridden to allow for left weapon location
|
|
*/
|
|
simulated event vector GetMuzzleLoc()
|
|
{
|
|
if( bFireFromRightWeapon )
|
|
{
|
|
return super.GetMuzzleLoc();
|
|
}
|
|
else
|
|
{
|
|
return GetLeftMuzzleLoc();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function returns the world location for spawning the visual effects.
|
|
* Uses both X and Y position of LeftFireOffset.
|
|
*/
|
|
simulated event vector GetLeftMuzzleLoc()
|
|
{
|
|
local Rotator ViewRotation;
|
|
|
|
if( Instigator != none )
|
|
{
|
|
ViewRotation = Instigator.GetViewRotation();
|
|
|
|
// Add in the free-aim rotation
|
|
if ( KFPlayerController(Instigator.Controller) != None )
|
|
{
|
|
ViewRotation += KFPlayerController(Instigator.Controller).WeaponBufferRotation;
|
|
}
|
|
|
|
if( bUsingSights )
|
|
{
|
|
return Instigator.GetWeaponStartTraceLocation() + (LeftFireOffset >> ViewRotation);
|
|
}
|
|
else
|
|
{
|
|
return Instigator.GetPawnViewLocation() + (LeftFireOffset >> ViewRotation);
|
|
}
|
|
|
|
}
|
|
|
|
return Location;
|
|
}
|
|
|
|
/**
|
|
* Causes the muzzle flash to turn on and setup a time to
|
|
* turn it back off again.
|
|
*
|
|
* Overridden to cause left weapon flash
|
|
*/
|
|
simulated function CauseMuzzleFlash(byte FireModeNum)
|
|
{
|
|
if( MuzzleFlash == None || LeftMuzzleFlash == None )
|
|
{
|
|
AttachMuzzleFlash();
|
|
}
|
|
|
|
if( bFireFromRightWeapon )
|
|
{
|
|
if (MuzzleFlash != None )
|
|
{
|
|
MuzzleFlash.CauseMuzzleFlash(FireModeNum);
|
|
if ( MuzzleFlash.bAutoActivateShellEject )
|
|
{
|
|
MuzzleFlash.CauseShellEject();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( LeftMuzzleFlash != None )
|
|
{
|
|
LeftMuzzleFlash.CauseMuzzleFlash( FireModeNum );
|
|
if( LeftMuzzleFlash.bAutoActivateShellEject )
|
|
{
|
|
LeftMuzzleFlash.CauseShellEject();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove/Detach the muzzle flash components
|
|
*/
|
|
simulated function DetachMuzzleFlash()
|
|
{
|
|
super.DetachMuzzleFlash();
|
|
|
|
if (MySkelMesh != none && LeftMuzzleFlash != None)
|
|
{
|
|
LeftMuzzleFlash.DetachMuzzleFlash(MySkelMesh);
|
|
LeftMuzzleFlash = None;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adjust the FOV for the first person weapon and arms.
|
|
*/
|
|
simulated event SetFOV( float NewFOV )
|
|
{
|
|
super.SetFOV( NewFOV );
|
|
|
|
if( LeftMuzzleFlash != none )
|
|
{
|
|
LeftMuzzleFlash.SetFOV( NewFOV );
|
|
}
|
|
}
|
|
|
|
simulated function StopFireEffects(byte FireModeNum)
|
|
{
|
|
super.StopFireEffects( FireModeNum );
|
|
|
|
if (LeftMuzzleFlash != None)
|
|
{
|
|
LeftMuzzleFlash.StopMuzzleFlash();
|
|
}
|
|
}
|
|
|
|
/** Returns ID of muzzle to spawn projectile at / play effects at
|
|
* Overrides super
|
|
*/
|
|
simulated function byte GetCurrentMuzzleID()
|
|
{
|
|
return bFireFromRightWeapon ? 0 : 1;
|
|
}
|
|
|
|
/** Overridden to spawn a single and set the pickup class to single */
|
|
function SetupDroppedPickup( out DroppedPickup P, vector StartVelocity )
|
|
{
|
|
local KFWeapon NewSingle;
|
|
local KFInventoryManager KFIM;
|
|
local vector X,Y,Z;
|
|
local int NewSingleUpgradeIndex;
|
|
local int SingleSpareAmmoCount;
|
|
|
|
// For now, force the dropped single to be un-upgraded by setting this dual's upgrade index to 0.
|
|
// But remember what the upgrade index was so we can give the player a correctly upgraded single.
|
|
// SetWeaponUpgradeLevel(0) must be called before the super, because the pickup mesh is set there.
|
|
NewSingleUpgradeIndex = CurrentWeaponUpgradeIndex;
|
|
SetWeaponUpgradeLevel(0);
|
|
|
|
super.SetupDroppedPickup( P, StartVelocity );
|
|
|
|
if( Instigator != None && Instigator.InvManager != None )
|
|
{
|
|
KFIM = KFInventoryManager( Instigator.InvManager );
|
|
KFIM.bSuppressPickupMessages = true;
|
|
KFIM.bInfiniteWeight = true; // force CanCarryWeapon() to succeed
|
|
NewSingle = KFWeapon( KFIM.CreateInventory(SingleClass, true) );
|
|
KFIM.bInfiniteWeight = false;
|
|
KFIM.bSuppressPickupMessages = false;
|
|
}
|
|
|
|
if( NewSingle != none )
|
|
{
|
|
// divide ammo between make sure we don't lose a round due to truncation.
|
|
NewSingle.AmmoCount[0] = (AmmoCount[0] & 1) == 0 ? (AmmoCount[0] / 2) : (AmmoCount[0] / 2) + 1;
|
|
AmmoCount[0] /= 2;
|
|
|
|
SingleSpareAmmoCount = Min(SpareAmmoCount[0], NewSingle.SpareAmmoCapacity[0]);
|
|
|
|
NewSingle.SpareAmmoCount[0] = SingleSpareAmmoCount;
|
|
SpareAmmoCount[0] -= SingleSpareAmmoCount;
|
|
|
|
// tell client about our modification
|
|
NewSingle.ClientForceAmmoUpdate(NewSingle.AmmoCount[0],NewSingle.SpareAmmoCount[0]);
|
|
NewSingle.ClientForceSecondaryAmmoUpdate(NewSingle.AmmoCount[1]);
|
|
|
|
NewSingle.SetWeaponUpgradeLevel(NewSingleUpgradeIndex);
|
|
if(NewSingleUpgradeIndex > 0)
|
|
{
|
|
KFInventoryManager(InvManager).AddCurrentCarryBlocks(NewSingle.static.GetUpgradeWeight(NewSingleUpgradeIndex));
|
|
KFPawn(Instigator).NotifyInventoryWeightChanged();
|
|
}
|
|
|
|
NewSingle.bGivenAtStart = bGivenAtStart;
|
|
|
|
// Drop second gun on death
|
|
if( Instigator.bPlayedDeath || Instigator.Health <= 0 )
|
|
{
|
|
GetAxes( Instigator.Rotation, X,Y,Z );
|
|
NewSingle.DropFrom( P.Location + Y*20, StartVelocity*(1.f + fRand()*0.1f) );
|
|
}
|
|
else
|
|
{
|
|
Instigator.InvManager.SetCurrentWeapon( NewSingle );
|
|
}
|
|
}
|
|
|
|
P.InventoryClass = SingleClass;
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Ammo
|
|
********************************************************************************************* */
|
|
|
|
// /** Performs actual ammo reloading */
|
|
simulated function PerformReload(optional byte FireModeNum)
|
|
{
|
|
super.PerformReload(FireModeNum);
|
|
|
|
if( !bRevolver )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// reset cylinder rotation
|
|
CylinderRotInfo_L.PrevDegrees = 0;
|
|
CylinderRotInfo_L.NextDegrees = 0;
|
|
}
|
|
|
|
/** Locks the bolt bone in place to the open position (Called by animnotify)
|
|
* Completely overrides super to pick which bolt to lock back
|
|
*/
|
|
simulated function ANIMNOTIFY_LockBolt()
|
|
{
|
|
UpdateOutOfAmmoEffects(0.f);
|
|
}
|
|
|
|
/** Unlocks the bolt bone (Called by animnotify) */
|
|
simulated function ANIMNOTIFY_UnLockBolt()
|
|
{
|
|
super.ANIMNOTIFY_UnLockBolt();
|
|
EmptyMagBlendNode_L.SetBlendTarget(0, 0);
|
|
}
|
|
|
|
/** Check AmmoCount and update anim tree nodes if needed */
|
|
simulated function UpdateOutOfAmmoEffects(float BlendTime)
|
|
{
|
|
if ( WorldInfo.NetMode == NM_DedicatedServer )
|
|
return;
|
|
|
|
if( EmptyMagBlendNode != None )
|
|
{
|
|
// Differentiate Left/Right
|
|
if ( bAllowClientAmmoTracking && AmmoCount[0] <= 1 )
|
|
{
|
|
EmptyMagBlendNode.SetBlendTarget(1, 0);
|
|
if ( AmmoCount[0] == 0 )
|
|
{
|
|
EmptyMagBlendNode_L.SetBlendTarget(1,0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* @name Revolver
|
|
********************************************************************************************* */
|
|
|
|
simulated event PostInitAnimTreeRevolver( SkeletalMeshComponent SkelComp )
|
|
{
|
|
super.PostInitAnimTreeRevolver( SkelComp );
|
|
|
|
CylinderRotInfo_L.Control = SkelControlSingleBone( SkelComp.FindSkelControl('CylinderControl') );
|
|
if( CylinderRotInfo_L.Control != none )
|
|
{
|
|
CylinderRotInfo_L.Control.SetSkelControlActive( true );
|
|
}
|
|
}
|
|
|
|
simulated function ConsumeAmmoRevolver()
|
|
{
|
|
// no super
|
|
|
|
if( bFireFromRightWeapon )
|
|
{
|
|
CheckCylinderRotation( CylinderRotInfo_L );
|
|
CylinderRotInfo.State = CYLINDERSTATE_PENDING;
|
|
}
|
|
else
|
|
{
|
|
CheckCylinderRotation( CylinderRotInfo );
|
|
CylinderRotInfo_L.State = CYLINDERSTATE_PENDING;
|
|
}
|
|
}
|
|
|
|
/** Initiate cylinder rotation (rotation occurs in tickspecial in native) */
|
|
simulated function ANIMNOTIFY_RotateCylinder()
|
|
{
|
|
if( bFireFromRightWeapon )
|
|
{
|
|
super.ANIMNOTIFY_RotateCylinder();
|
|
return;
|
|
}
|
|
|
|
RotateCylinder( CylinderRotInfo_L );
|
|
}
|
|
|
|
simulated function InitializeReload()
|
|
{
|
|
// call kfweapon super so we don't rotate the other cylinder too
|
|
super(KFWeapon).InitializeReload();
|
|
CheckCylinderRotation( CylinderRotInfo_L, true );
|
|
}
|
|
|
|
simulated function ANIMNOTIFY_ResetBulletMeshesLeft()
|
|
{
|
|
ResetBulletMeshesLeft();
|
|
}
|
|
|
|
/** Resets cylinder orientation to initial state and repositions bullet meshes to line up with their pre-reset locations */
|
|
simulated function ResetCylinder()
|
|
{
|
|
local int UsedStartIdx, UsedEndIdx, UsedBullets;
|
|
|
|
// reset cylinder rotation
|
|
SetCylinderRotation( CylinderRotInfo, 0 );
|
|
ResetCylinderInfo( CylinderRotInfo );
|
|
|
|
// now, we need to make sure the used bullets are still in the correct positions
|
|
|
|
// if we fired all our ammo, it doesn't matter where the bullets are, because they're all the same (used)
|
|
if( AmmoCount[DEFAULT_FIREMODE] <= 1 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// find the range of bullets that have been used
|
|
UsedStartIdx = BulletMeshComponents.Length - 2;
|
|
UsedBullets = FCeil( float(MagazineCapacity[DEFAULT_FIREMODE] - AmmoCount[DEFAULT_FIREMODE]) / 2.f );
|
|
UsedEndIdx = UsedStartIdx - UsedBullets * 2;
|
|
|
|
RepositionUsedBullets( 0, UsedStartIdx, UsedEndIdx );
|
|
}
|
|
|
|
/** Resets cylinder orientation to initial state and repositions bullet meshes to line up with their pre-reset locations */
|
|
simulated function ResetCylinderLeft()
|
|
{
|
|
local int UsedStartIdx, UsedEndIdx, UsedBullets;
|
|
|
|
// reset cylinder rotation (skel control needs to be reset to initial state while bullet casings are not on screen)
|
|
SetCylinderRotation( CylinderRotInfo_L, 0 );
|
|
ResetCylinderInfo( CylinderRotInfo_L );
|
|
|
|
// now, we need to make sure the used bullets are still in the correct positions
|
|
|
|
// if we fired all our ammo, it doesn't matter where the bullets are, because they're all the same (used)
|
|
if( AmmoCount[DEFAULT_FIREMODE] <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// find the range of bullets that have been used
|
|
UsedStartIdx = BulletMeshComponents.Length - 1;
|
|
UsedBullets = FFloor( float(MagazineCapacity[DEFAULT_FIREMODE] - AmmoCount[DEFAULT_FIREMODE]) / 2.f );
|
|
UsedEndIdx = UsedStartIdx - UsedBullets * 2;
|
|
|
|
RepositionUsedBullets( 1, UsedStartIdx, UsedEndIdx );
|
|
}
|
|
|
|
/** Sets correct bullet mesh (used/unused) in each slot after resetting cylinder */
|
|
simulated function RepositionUsedBullets( int FirstIndex, int UsedStartIdx, int UsedEndIdx )
|
|
{
|
|
local int i;
|
|
|
|
if (BulletMeshComponents.length == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(FirstIndex >= 0 && FirstIndex < BulletMeshComponents.length)
|
|
{
|
|
// after cylinder reset, top-most bullet will be unused
|
|
BulletMeshComponents[FirstIndex].SetSkeletalMesh( UnusedBulletMeshTemplate );
|
|
}
|
|
else
|
|
{
|
|
`warn(self @ "-" @ GetFuncName() @ "- First Index is out of bounds - FirstIndex:" @ FirstIndex @ "BulletMeshComponents.Length:" @ BulletMeshComponents.length);
|
|
}
|
|
|
|
// set used bullets to used mesh
|
|
for( i = UsedStartIdx; i > UsedEndIdx; i-=2 )
|
|
{
|
|
if(i >= 0 && i < BulletMeshComponents.length)
|
|
{
|
|
BulletMeshComponents[i].SetSkeletalMesh( UsedBulletMeshTemplate );
|
|
}
|
|
}
|
|
|
|
// set the rest of the bullets to unused
|
|
for( i = UsedEndIdx; i > FirstIndex; i-=2 )
|
|
{
|
|
if (i >= 0 && i < BulletMeshComponents.length)
|
|
{
|
|
BulletMeshComponents[i].SetSkeletalMesh( UnusedBulletMeshTemplate );
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Sets all bullet casing meshes back to unused state */
|
|
simulated function ResetBulletMeshes()
|
|
{
|
|
local int i;
|
|
|
|
for( i = 0; i < BulletMeshComponents.Length; i+=2 )
|
|
{
|
|
BulletMeshComponents[i].SetSkeletalMesh( UnusedBulletMeshTemplate );
|
|
}
|
|
}
|
|
|
|
/** Sets all bullet casing meshes back to unused state */
|
|
simulated function ResetBulletMeshesLeft()
|
|
{
|
|
local int i;
|
|
|
|
for( i = 1; i < BulletMeshComponents.Length; i+=2 )
|
|
{
|
|
BulletMeshComponents[i].SetSkeletalMesh( UnusedBulletMeshTemplate );
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* State WeaponPuttingDown
|
|
* Putting down weapon in favor of a new one.
|
|
* Weapon is transitioning to the Inactive state.
|
|
*********************************************************************************************/
|
|
|
|
simulated state WeaponPuttingDown
|
|
{
|
|
simulated function EndState(Name NextStateName)
|
|
{
|
|
super.EndState( NextStateName );
|
|
CheckCylinderRotation( CylinderRotInfo_L, true );
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Trader
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/** Adds ammo to (or subtracts ammo from) the trader transaction single that gets created when selling a dual */
|
|
native simulated function AddAmmoToSingleOnSell( KFInventoryManager KFIM, int DefaultSingleAmmo, int TraderItemIndex );
|
|
|
|
/*********************************************************************************************
|
|
* @name Firing / Projectile
|
|
********************************************************************************************* */
|
|
/**
|
|
* @brief Checks if weapon should be auto-reloaded - overwritten to allow gunslinger insta switch
|
|
*
|
|
* @param FireModeNum Current fire mode
|
|
* @return auto reload or not
|
|
*/
|
|
simulated function bool ShouldAutoReload( byte FireModeNum )
|
|
{
|
|
return ShouldAutoReloadGunslinger( FireModeNum );
|
|
}
|
|
|
|
/**
|
|
* Toggle between DEFAULT and ALTFIRE
|
|
*/
|
|
simulated function AltFireMode()
|
|
{
|
|
super.AltFireMode();
|
|
PlayIdleAnim();
|
|
}
|
|
|
|
/**
|
|
* @see Weapon::StartFire
|
|
*/
|
|
simulated function StartFire(byte FireModeNum)
|
|
{
|
|
// These weapons only have a mode toggle, so if we have alt-fire
|
|
// bound (e.g. gamepad, custom bindings) then perform the toggle
|
|
if( FireModeNum == ALTFIRE_FIREMODE )
|
|
{
|
|
AltFireMode();
|
|
return;
|
|
}
|
|
|
|
Super.StartFire(FireModeNum);
|
|
}
|
|
|
|
defaultproperties
|
|
{
|
|
InventoryGroup=IG_Primary
|
|
|
|
Begin Object Name=FirstPersonMesh
|
|
AnimTreeTemplate=AnimTree'CHR_1P_Arms_ARCH.WEP_1stP_Dual_Animtree_Master'
|
|
End Object
|
|
|
|
bSkipZoomInRotation=true
|
|
|
|
BonesToLockOnEmpty_L=(LW_Bolt)
|
|
|
|
// Animations
|
|
FireAnim=Shoot_RW
|
|
LeftFireAnim=Shoot_LW
|
|
|
|
IdleSightedAnims=(Idle_IronOG)
|
|
FireSightedAnims=(Shoot_IronOG_RW)
|
|
LeftFireSightedAnims=(Shoot_IronOG_LW)
|
|
IdleToIronSightAnim=Idle_To_IronOG
|
|
IronSightToIdleAnim=IronOG_To_Idle
|
|
EquipAnimIS=Equip_IronOG
|
|
// IdleSightedAnims=(Idle_Iron)
|
|
// FireSightedAnims=(Shoot_Iron_RW)
|
|
// LeftFireSightedAnims=(Shoot_Iron_LW)
|
|
// IdleToIronSightAnim=Idle_To_Iron
|
|
// IronSightToIdleAnim=Iron_To_Idle
|
|
// EquipAnimIS=Equip_Iron
|
|
|
|
IdleSightedAnims_Alt=(Idle_Iron)
|
|
FireSightedAnim_Alt=Shoot_Iron_RW
|
|
LeftFireSightedAnim_Alt=Shoot_Iron_LW
|
|
IdleToIronSightAnim_Alt=Idle_To_Iron
|
|
IronSightToIdleAnim_Alt=Iron_To_Idle
|
|
EquipAnimISAlt=Equip_Iron
|
|
// IdleSightedAnims_Alt=(Idle_IronOG)
|
|
// FireSightedAnim_Alt=Shoot_IronOG_RW
|
|
// LeftFireSightedAnim_Alt=Shoot_IronOG_LW
|
|
// IdleToIronSightAnim_Alt=Idle_To_IronOG
|
|
// IronSightToIdleAnim_Alt=IronOG_To_Idle
|
|
// EquipAnimISAlt=Equip_IronOG
|
|
|
|
FireLastAnim=Shoot_RW_Last
|
|
LeftFireLastAnim=Shoot_LW_Last
|
|
FireLastSightedAnim=Shoot_IronOG_RW_Last
|
|
LeftFireLastSightedAnim=Shoot_IronOG_LW_Last
|
|
FireLastSightedAnim_Alt=Shoot_Iron_RW_Last
|
|
LeftFireLastSightedAnim_Alt=Shoot_Iron_LW_Last
|
|
|
|
// DEFAULT_FIREMODE
|
|
FireModeIconPaths[DEFAULT_FIREMODE]=Texture2D'ui_firemodes_tex.UI_FireModeSelect_BulletSingle'
|
|
FiringStatesArray(DEFAULT_FIREMODE)=WeaponFiring
|
|
WeaponFireTypes(DEFAULT_FIREMODE)=EWFT_InstantHit
|
|
FireInterval(DEFAULT_FIREMODE)=+1.0
|
|
InstantHitDamageTypes(DEFAULT_FIREMODE)=class'KFDT_Ballistic'
|
|
InstantHitMomentum(DEFAULT_FIREMODE)=1.0
|
|
PenetrationPower(DEFAULT_FIREMODE)=0.0
|
|
PenetrationDamageReductionCurve(DEFAULT_FIREMODE)=(Points=((InVal=0.f,OutVal=0.f),(InVal=1.f, OutVal=1.f)))
|
|
|
|
//ALTFIRE_FIREMODE
|
|
FireModeIconPaths[ALTFIRE_FIREMODE]=Texture2D'ui_firemodes_tex.UI_FireModeSelect_BulletSingle'
|
|
FiringStatesArray(ALTFIRE_FIREMODE)=WeaponFiring
|
|
WeaponFireTypes(ALTFIRE_FIREMODE)=EWFT_InstantHit
|
|
FireInterval(ALTFIRE_FIREMODE)=+1.0
|
|
InstantHitDamageTypes(ALTFIRE_FIREMODE)=class'KFDT_Ballistic'
|
|
InstantHitMomentum(ALTFIRE_FIREMODE)=1.0
|
|
PenetrationPower(ALTFIRE_FIREMODE)=0.0
|
|
PenetrationDamageReductionCurve(ALTFIRE_FIREMODE)=(Points=((InVal=0.f,OutVal=0.f),(InVal=1.f, OutVal=1.f)))
|
|
}
|