//============================================================================= // 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 DamageType; CalculatedDamage = default.InstantHitDamage[DEFAULT_FIREMODE]; DamageType = class(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))) }