490 lines
16 KiB
Ucode
490 lines
16 KiB
Ucode
|
//=============================================================================
|
||
|
// KFWeap_Eviscerator
|
||
|
//=============================================================================
|
||
|
// A hybrid melee weapon that shoots circular saw blades
|
||
|
//=============================================================================
|
||
|
// Killing Floor 2
|
||
|
// Copyright (C) 2015 Tripwire Interactive LLC
|
||
|
// Andrew Ladenberger and John "Ramm-Jaeger" Gibson
|
||
|
//=============================================================================
|
||
|
|
||
|
class KFWeap_Eviscerator extends KFWeap_MeleeBase;
|
||
|
|
||
|
/** Anim names */
|
||
|
const IdleNoGasAnim = 'Idle_NoGas';
|
||
|
const BlockLoopNoGasAnim = 'Brace_loop_NoGas';
|
||
|
|
||
|
/** BlendNode that's active while weapon has fuel */
|
||
|
var AnimNodeAdditiveBlending AdditiveBlendNode;
|
||
|
/** BlendNode that's active when primary ammo is empty */
|
||
|
var AnimNodeBlendPerBone OutOfBladesBlendNode;
|
||
|
|
||
|
/** Sound to play while weapon has fuel */
|
||
|
var AKEvent IdleMotorSound;
|
||
|
|
||
|
/** Short time from end of FireInterval when blocking can cancel the firing state */
|
||
|
var float BlockInterruptFiringTime;
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Called immediately before gameplay begins.
|
||
|
*/
|
||
|
simulated event PreBeginPlay()
|
||
|
{
|
||
|
Super.PreBeginPlay();
|
||
|
|
||
|
// to prevent exploits the fire interrupt cannot be longer than the block intro
|
||
|
BlockInterruptFiringTime = FMin(BlockInterruptFiringTime, MySkelMesh.GetAnimLength(MeleeBlockStartAnim));
|
||
|
}
|
||
|
|
||
|
/** The SkeletalMeshComponents Animations are being instanced from AnimTreeTemplate
|
||
|
* before PostInitAnimTree. Be sure to never set the Mesh's animations directly through
|
||
|
* the package */
|
||
|
simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
|
||
|
{
|
||
|
Super.PostInitAnimTree(SkelComp);
|
||
|
|
||
|
AdditiveBlendNode = AnimNodeAdditiveBlending(SkelComp.FindAnimNode('MotorAdditiveBlend'));
|
||
|
OutOfBladesBlendNode = AnimNodeBlendPerBone(SkelComp.FindAnimNode('OutOfBladesBlend'));
|
||
|
}
|
||
|
|
||
|
/* OVERRIDE BECAUSE WE DONT NEED TO LOOK AT ALTFIRE_MODE, AS FOR MELEE WEAPONS IS THE BLOCK FIREMODE AND WILL RESULT IN
|
||
|
THE WEAPON ALWAYS GLOWING GREEN
|
||
|
*/
|
||
|
|
||
|
simulated function bool HasAnyAmmo()
|
||
|
{
|
||
|
if ( HasSpareAmmo() || HasAmmo(DEFAULT_FIREMODE) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (MedicComp != none)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if( UsesSecondaryAmmo() && (HasSpareAmmo(HEAVY_ATK_FIREMODE) || HasAmmo(HEAVY_ATK_FIREMODE) ))
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* @name Ammunition
|
||
|
********************************************************************************************* */
|
||
|
|
||
|
/**
|
||
|
* @see Weapon::StartFire
|
||
|
*/
|
||
|
simulated function StartFire(byte FireModeNum)
|
||
|
{
|
||
|
// If we're out of fuel, do a melee 'bash' instead
|
||
|
if( FireModeNum == HEAVY_ATK_FIREMODE && !HasAmmo(FireModeNum) )
|
||
|
{
|
||
|
FireModeNum = BASH_FIREMODE;
|
||
|
}
|
||
|
|
||
|
// skip MeleeBase logic
|
||
|
Super(KFWeapon).StartFire(FireModeNum);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns which ammo pool a fire mode should pull from, primary or secondary.
|
||
|
@ param FiringMode the fire mode we want to check against
|
||
|
@ return the ammo pool we should pull from for the input fire mode
|
||
|
*/
|
||
|
simulated function int GetAmmoType(byte FiringMode)
|
||
|
{
|
||
|
//UI looking for altfire
|
||
|
if( FiringMode == HEAVY_ATK_FIREMODE || FiringMode == ALTFIRE_FIREMODE)
|
||
|
{
|
||
|
return 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* @name Firing / Projectile
|
||
|
********************************************************************************************* */
|
||
|
|
||
|
/** process local player impact for clientside hit detection */
|
||
|
event RecieveClientImpact(byte FiringMode, const out ImpactInfo Impact, optional out float PenetrationValue, optional int ImpactNum)
|
||
|
{
|
||
|
if ( FiringMode == DEFAULT_FIREMODE )
|
||
|
{
|
||
|
super(KFWeapon).RecieveClientImpact(FiringMode, Impact, PenetrationValue);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
MeleeAttackHelper.ProcessMeleeHit(FiringMode, Impact);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* @section Animation
|
||
|
********************************************************************************************* */
|
||
|
|
||
|
simulated function HideBlade()
|
||
|
{
|
||
|
// @todo: remove this stub if/when we remove the notifies from the anim
|
||
|
}
|
||
|
|
||
|
simulated function UnHideBlade()
|
||
|
{
|
||
|
// @todo: remove this stub if/when we remove the notifies from the anim
|
||
|
}
|
||
|
|
||
|
/** Check if we're out of blades or fuel and toggle effects accordingly */
|
||
|
simulated function UpdateOutOfAmmoEffects(float BlendTime)
|
||
|
{
|
||
|
if ( AmmoCount[1] > 0 )
|
||
|
{
|
||
|
PlayIdleMotorSound();
|
||
|
if ( AdditiveBlendNode != None )
|
||
|
{
|
||
|
AdditiveBlendNode.SetBlendTarget(1.f, BlendTime);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
StopIdleMotorSound();
|
||
|
if ( AdditiveBlendNode != None )
|
||
|
{
|
||
|
AdditiveBlendNode.SetBlendTarget(0.f, BlendTime);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( OutOfBladesBlendNode != None )
|
||
|
{
|
||
|
OutOfBladesBlendNode.SetBlendTarget((AmmoCount[0] > 0) ? 0.f : 1.f, BlendTime);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Check to see if sound is already playing before calling SetWeaponAmbientSound */
|
||
|
simulated function PlayIdleMotorSound()
|
||
|
{
|
||
|
local KFPawn P;
|
||
|
P = KFPawn(Instigator);
|
||
|
if ( P != None && P.WeaponAmbientSound != IdleMotorSound )
|
||
|
{
|
||
|
P.SetWeaponAmbientSound(IdleMotorSound);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Check to see if sound is already stopped before calling SetWeaponAmbientSound */
|
||
|
simulated function StopIdleMotorSound()
|
||
|
{
|
||
|
local KFPawn P;
|
||
|
P = KFPawn(Instigator);
|
||
|
if ( P != None && P.WeaponAmbientSound != None )
|
||
|
{
|
||
|
P.SetWeaponAmbientSound(None);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* State Active
|
||
|
* Extended to handle IdleMotorSound
|
||
|
*********************************************************************************************/
|
||
|
|
||
|
simulated state Active
|
||
|
{
|
||
|
simulated function BeginState(name PreviousStateName)
|
||
|
{
|
||
|
Super.BeginState(PreviousStateName);
|
||
|
UpdateOutOfAmmoEffects(0.2f);
|
||
|
}
|
||
|
|
||
|
simulated function PlayIdleAnim()
|
||
|
{
|
||
|
if ( Instigator.IsFirstPerson() )
|
||
|
{
|
||
|
// If we're out of fuel play a special idle with no motor vibration
|
||
|
if ( AmmoCount[1] <= 0 )
|
||
|
{
|
||
|
PlayAnimation(IdleNoGasAnim, 0.0, true, 0.2);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Super.PlayIdleAnim();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* state Inactive
|
||
|
* Extended to handle IdleMotorSound
|
||
|
*********************************************************************************************/
|
||
|
|
||
|
auto state Inactive
|
||
|
{
|
||
|
simulated function BeginState(name PreviousStateName)
|
||
|
{
|
||
|
Super.BeginState(PreviousStateName);
|
||
|
StopIdleMotorSound();
|
||
|
}
|
||
|
|
||
|
// moved to kfweapon
|
||
|
//simulated function EndState(Name NextStateName)
|
||
|
//{
|
||
|
// Super.EndState(NextStateName);
|
||
|
// UpdateOutOfAmmoEffects(0.0f);
|
||
|
//}
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* State Reloading
|
||
|
* This is the default Reloading State. It's performed on both the client and the server.
|
||
|
*********************************************************************************************/
|
||
|
|
||
|
simulated state Reloading
|
||
|
{
|
||
|
simulated function BeginState(name PreviousStateName)
|
||
|
{
|
||
|
Super.BeginState(PreviousStateName);
|
||
|
|
||
|
// When the player starts reload force unlock the OutOfBlades bones temporarily
|
||
|
if ( OutOfBladesBlendNode != None )
|
||
|
{
|
||
|
OutOfBladesBlendNode.SetBlendTarget(0.f, 0.1f);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Called during reload state */
|
||
|
simulated function bool CanOverrideMagReload(byte FireModeNum)
|
||
|
{
|
||
|
return super.CanOverrideMagReload(FireModeNum) || FireModeNum == BLOCK_FIREMODE || FireModeNum == HEAVY_ATK_FIREMODE;
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* State MeleeBlocking
|
||
|
* This is the default Blocking State. It's performed on both the client and the server.
|
||
|
*********************************************************************************************/
|
||
|
|
||
|
simulated state MeleeBlocking
|
||
|
{
|
||
|
// Override to play BlockLoopNoGasAnim
|
||
|
simulated function BlockLoopTimer()
|
||
|
{
|
||
|
if( AmmoCount[1] <= 0 && Instigator.IsLocallyControlled() )
|
||
|
{
|
||
|
PlayAnimation(BlockLoopNoGasAnim, , true);
|
||
|
}
|
||
|
|
||
|
Super.BlockLoopTimer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* State MeleeSustained
|
||
|
* Derived to prevent Active:BeginState from playing the looping idle sound from UpdateOutOfAmmoEffects after the super changes the state to MeleeSustained
|
||
|
*********************************************************************************************/
|
||
|
|
||
|
simulated state MeleeSustained
|
||
|
{
|
||
|
// This prevents the Active:BeginState from playing the looping idle sound after the super changes the state to MeleeSustained
|
||
|
ignores UpdateOutOfAmmoEffects;
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* Trader
|
||
|
********************************************************************************************/
|
||
|
|
||
|
/** Allows weapon to calculate its own damage for display in trader
|
||
|
* While eviscerator is a melee weapon, it does more damage with its default fire projectile
|
||
|
*/
|
||
|
static simulated function float CalculateTraderWeaponStatDamage()
|
||
|
{
|
||
|
local float CalculatedDamage;
|
||
|
local class<KFDamageType> DamageType;
|
||
|
|
||
|
CalculatedDamage = default.InstantHitDamage[DEFAULT_FIREMODE];
|
||
|
|
||
|
DamageType = class<KFDamageType>(default.InstantHitDamageTypes[DEFAULT_FIREMODE]);
|
||
|
if( DamageType != none && DamageType.default.DoT_Type != DOT_None )
|
||
|
{
|
||
|
CalculatedDamage += (DamageType.default.DoT_Duration / DamageType.default.DoT_Interval) * (CalculatedDamage * DamageType.default.DoT_DamageScale);
|
||
|
}
|
||
|
|
||
|
return CalculatedDamage;
|
||
|
}
|
||
|
|
||
|
/** Returns trader filter index based on weapon type */
|
||
|
static simulated event EFilterTypeUI GetTraderFilter()
|
||
|
{
|
||
|
return FT_Projectile;
|
||
|
}
|
||
|
|
||
|
static simulated event EFilterTypeUI GetAltTraderFilter()
|
||
|
{
|
||
|
return FT_Melee;
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************
|
||
|
* State WeaponSingleFiring
|
||
|
* Handle special BlockInterruptTimer for this weapon's unusually long FireInterval
|
||
|
*********************************************************************************************/
|
||
|
|
||
|
simulated function BlockInterruptTimer();
|
||
|
|
||
|
simulated state WeaponSingleFiring
|
||
|
{
|
||
|
/** Set cooldown duration */
|
||
|
simulated function BeginState( Name PreviousStateName )
|
||
|
{
|
||
|
local float CheckBlockInterruptTime;
|
||
|
|
||
|
CheckBlockInterruptTime = FireInterval[CurrentFireMode] - BlockInterruptFiringTime;
|
||
|
if ( BlockInterruptFiringTime > 0 && CheckBlockInterruptTime > 0 )
|
||
|
{
|
||
|
SetTimer(CheckBlockInterruptTime, false, nameof(BlockInterruptTimer));
|
||
|
}
|
||
|
|
||
|
Super.BeginState(PreviousStateName);
|
||
|
}
|
||
|
|
||
|
simulated function EndState(Name NextStateName)
|
||
|
{
|
||
|
ClearTimer( nameof(BlockInterruptTimer) );
|
||
|
Super.EndState(NextStateName);
|
||
|
}
|
||
|
|
||
|
/** Check for pending fire */
|
||
|
simulated function BlockInterruptTimer()
|
||
|
{
|
||
|
if ( PendingFire(BLOCK_FIREMODE) && HasAmmo(BLOCK_FIREMODE) )
|
||
|
{
|
||
|
SendToFiringState(BLOCK_FIREMODE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Check for interrupt near end of state */
|
||
|
simulated function BeginFire(byte FireModeNum)
|
||
|
{
|
||
|
Global.BeginFire(FireModeNum);
|
||
|
|
||
|
if ( FireModeNum == BLOCK_FIREMODE && BlockInterruptFiringTime > 0 )
|
||
|
{
|
||
|
if( HasAmmo(FireModeNum) && !IsTimerActive(nameof(BlockInterruptTimer)) )
|
||
|
{
|
||
|
SendToFiringState(FireModeNum);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
defaultproperties
|
||
|
{
|
||
|
// Inventory
|
||
|
GroupPriority=100
|
||
|
InventorySize=9
|
||
|
InventoryGroup=IG_Primary
|
||
|
WeaponSelectTexture=Texture2D'ui_weaponselect_tex.UI_WeaponSelect_SawbladeShooter'
|
||
|
SecondaryAmmoTexture=Texture2D'UI_SecondaryAmmo_TEX.GasTank'
|
||
|
|
||
|
// Content
|
||
|
PackageKey="SawBlade"
|
||
|
FirstPersonMeshName="WEP_1P_SawBlade_MESH.Wep_1stP_SawBlade_Rig"
|
||
|
FirstPersonAnimSetNames(0)="WEP_1P_SawBlade_ANIM.WEP_1P_SawBlade_ANIM"
|
||
|
FirstPersonAnimTree="WEP_1P_SawBlade_ANIM.1P_Sawblade_Animtree"
|
||
|
PickupMeshName="WEP_3P_SawBlade_MESH.Wep_SawShooter_Pickup"
|
||
|
AttachmentArchetypeName="WEP_Sawblade_ARCH.Wep_Eviscerator_3P"
|
||
|
MuzzleFlashTemplateName="WEP_Sawblade_ARCH.Wep_Sawblade_MuzzleFlash"
|
||
|
|
||
|
Begin Object Name=MeleeHelper_0
|
||
|
MaxHitRange=180
|
||
|
WorldImpactEffects=KFImpactEffectInfo'FX_Impacts_ARCH.Bladed_melee_impact'
|
||
|
MeleeImpactCamShake=CameraShake'FX_CameraShake_Arch.Melee.Eviscerator'
|
||
|
End Object
|
||
|
|
||
|
// ------------------------------------------------------------------------
|
||
|
// Fire modes
|
||
|
FiringStatesArray(DEFAULT_FIREMODE)=WeaponSingleFiring
|
||
|
WeaponFireTypes(DEFAULT_FIREMODE)=EWFT_Projectile
|
||
|
WeaponProjectiles(DEFAULT_FIREMODE)=class'KFProj_Blade_Eviscerator'
|
||
|
InstantHitDamage(DEFAULT_FIREMODE)=300.0
|
||
|
InstantHitDamageTypes(DEFAULT_FIREMODE)=class'KFDT_Slashing_EvisceratorProj'
|
||
|
Spread(DEFAULT_FIREMODE)=0.02
|
||
|
PenetrationPower(DEFAULT_FIREMODE)=4.0
|
||
|
FireInterval(DEFAULT_FIREMODE)=0.95 // 63 RPM
|
||
|
FireOffset=(X=25,Y=5.0,Z=-10)
|
||
|
FireModeIconPaths(DEFAULT_FIREMODE)=Texture2D'ui_firemodes_tex.UI_FireModeSelect_Sawblade'
|
||
|
AmmoCost(DEFAULT_FIREMODE) = 1
|
||
|
BlockInterruptFiringTime=0.5
|
||
|
|
||
|
// Saw attack (uses fuel)
|
||
|
FiringStatesArray(HEAVY_ATK_FIREMODE)=MeleeSustained
|
||
|
InstantHitDamage(HEAVY_ATK_FIREMODE)=29
|
||
|
InstantHitDamageTypes(HEAVY_ATK_FIREMODE)=class'KFDT_Slashing_Eviscerator'
|
||
|
FireInterval(HEAVY_ATK_FIREMODE)=+0.12
|
||
|
AmmoCost(HEAVY_ATK_FIREMODE)=1
|
||
|
MeleeSustainedWarmupTime=0.1
|
||
|
|
||
|
// BASH_FIREMODE
|
||
|
FiringStatesArray(BASH_FIREMODE)=MeleeAttackBasic
|
||
|
WeaponFireTypes(BASH_FIREMODE)=EWFT_Custom
|
||
|
InstantHitDamage(BASH_FIREMODE)=90
|
||
|
InstantHitDamageTypes(BASH_FIREMODE)=class'KFDT_Slashing_Eviscerator'
|
||
|
|
||
|
// RELOAD
|
||
|
FiringStatesArray(RELOAD_FIREMODE)="Reloading"
|
||
|
|
||
|
// Blocking
|
||
|
ParryDamageMitigationPercent=0.3
|
||
|
BlockDamageMitigation=0.4
|
||
|
|
||
|
// Fire Effects
|
||
|
WeaponFireSnd(DEFAULT_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_SA_SawBlade.Play_WEP_SA_Sawblade_Fire_3P', FirstPersonCue=AkEvent'WW_WEP_SA_SawBlade.Play_WEP_SA_Sawblade_Fire_1P')
|
||
|
WeaponDryFireSnd(DEFAULT_FIREMODE)=AkEvent'WW_WEP_SA_SawBlade.Play_WEP_SA_Sawblade_Handling_DryFire'
|
||
|
// Altfire/MeleeSustained
|
||
|
WeaponFireSnd(HEAVY_ATK_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_SA_SawBlade.Play_WEP_SA_Sawblade_Fire_Alt_LoopStart_3P', FirstPersonCue=AkEvent'WW_WEP_SA_SawBlade.Play_WEP_SA_Sawblade_Fire_Alt_LoopStart_1P')
|
||
|
WeaponFireLoopEndSnd(HEAVY_ATK_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_SA_SawBlade.Play_WEP_SA_Sawblade_Fire_Alt_LoopEnd_3P', FirstPersonCue=AkEvent'WW_WEP_SA_SawBlade.Play_WEP_SA_Sawblade_Fire_Alt_LoopEnd_1P')
|
||
|
bLoopingFireAnim(HEAVY_ATK_FIREMODE)=true
|
||
|
bLoopingFireSnd(HEAVY_ATK_FIREMODE)=true
|
||
|
WeaponDryFireSnd(HEAVY_ATK_FIREMODE)=AkEvent'WW_WEP_SA_SawBlade.Play_WEP_SA_Sawblade_Handling_DryFire'
|
||
|
|
||
|
IdleMotorSound=AkEvent'WW_WEP_SA_SawBlade.Play_WEP_SA_Sawblade_Idle_Loop'
|
||
|
|
||
|
// Ammo
|
||
|
bCanBeReloaded=true
|
||
|
bReloadFromMagazine=true
|
||
|
MagazineCapacity[0]=5
|
||
|
SpareAmmoCapacity[0]=25
|
||
|
InitialSpareMags[0]=0
|
||
|
MagazineCapacity[1]=250 // 30 seconds of fuel
|
||
|
AmmoPickupScale[1]=0.2
|
||
|
|
||
|
// Animation
|
||
|
MeleeAttackAnims=(Atk_B)
|
||
|
bHasFireLastAnims=true
|
||
|
QuickWeaponDownRotation=(Pitch=-8192,Yaw=0,Roll=8192)
|
||
|
|
||
|
// Block Sounds
|
||
|
BlockSound=AkEvent'WW_WEP_Bullet_Impacts.Play_Block_MEL_Katana'
|
||
|
ParrySound=AkEvent'WW_WEP_Bullet_Impacts.Play_Block_MEL_Katana'
|
||
|
|
||
|
AssociatedPerkClasses(0)=class'KFPerk_Berserker'
|
||
|
|
||
|
bHasLaserSight=TRUE
|
||
|
|
||
|
ParryStrength=5
|
||
|
|
||
|
// Eviscerator uses its own anim tree with its own specified bones to lock, so leave it alone
|
||
|
BonesToLockOnEmpty.Empty()
|
||
|
|
||
|
WeaponFireWaveForm=ForceFeedbackWaveform'FX_ForceFeedback_ARCH.Gunfire.Heavy_Recoil_SingleShot'
|
||
|
|
||
|
// Weapon Upgrade stat boosts
|
||
|
//WeaponUpgrades[1]=(IncrementDamage=1.1f,IncrementWeight=1)
|
||
|
|
||
|
WeaponUpgrades[1]=(Stats=((Stat=EWUS_Damage0, Scale=1.1f), (Stat=EWUS_Damage1, Scale=1.1f), (Stat=EWUS_Damage2, Scale=1.1f), (Stat=EWUS_Weight, Add=1)))
|
||
|
}
|