1
0
KF2-Dev-Scripts/KFGameContent/Classes/KFWeap_Pistol_DualBladed.uc
2021-11-16 20:03:42 +03:00

691 lines
19 KiB
Ucode

//=============================================================================
// KFWeap_Pistol_DualBladed
//=============================================================================
//=============================================================================
// Killing Floor 2
// Copyright (C) 2021 Tripwire Interactive LLC
//=============================================================================
class KFWeap_Pistol_DualBladed extends KFWeap_DualBase;
/*
*
* BROUGHT FROM MELEE WEAPON TO SUPPORT PARRYING
*
*/
/** These override the base firemodes of the same ID (for readability) */
const BLOCK_FIREMODE = 1; // ALTFIRE_FIREMODE
/*********************************************************************************************
* @name Defensive Abilities
*********************************************************************************************/
struct native BlockEffectInfo
{
var class<DamageType> DmgType;
/** If != None, overrides the class default FX */
var AkEvent BlockSound;
var AkEvent ParrySound;
var ParticleSystem BlockParticleSys;
var ParticleSystem ParryParticleSys;
};
var array<BlockEffectInfo> BlockTypes;
/** Damage while blocking will be mitigated by this percentage */
var() float BlockDamageMitigation;
/** Parry damage will be mitigated by this percentage */
var() float ParryDamageMitigationPercent;
/** Hit reaction strength to bypass pawn's ParryStumbleResist */
var() byte ParryStrength;
/** If true, owning pawn moves at a slower (iron sight) walking speed */
var bool bMoveAtWalkingSpeed;
/** Time between block hit reaction anims */
var() protected float BlockHitAnimCooldownTime;
/** The last time we played a block hit reaction anim */
var transient protected float LastBlockHitAnimTime;
/** Animations played on successful block */
var array<name> MeleeBlockHitAnims;
/*********************************************************************************************
* @name Effects
********************************************************************************************* */
/** Block / Parry */
var AkBaseSoundObject BlockSound;
var AKBaseSoundObject ParrySound;
var ParticleSystem BlockParticleSystem;
var ParticleSystem ParryParticleSystem;
var name BlockEffectsSocketName;
/** Defensive stance animation names */
const MeleeBlockStartAnim = 'Brace_in';
const MeleeBlockLoopAnim = 'Brace_loop';
const MeleeBlockEndAnim = 'Brace_out';
var array<name> BonesToLockOnEmpty_Override;
simulated function NotifyAttackParried();
simulated function NotifyAttackBlocked();
// Global declarations for blocking state
simulated function BlockLoopTimer();
simulated function ParryCheckTimer();
/** Called on the server when successfully block/parry an attack */
unreliable client function ClientPlayBlockEffects(optional byte BlockTypeIndex=255)
{
local AkBaseSoundObject Sound;
local ParticleSystem PSTemplate;
GetBlockEffects(BlockTypeIndex, Sound, PSTemplate);
PlayLocalBlockEffects(Sound, PSTemplate);
}
/** Called on the server when successfully block/parry an attack */
reliable client function ClientPlayParryEffects(optional byte BlockTypeIndex=255)
{
local AkBaseSoundObject Sound;
local ParticleSystem PSTemplate;
local KFPerk InstigatorPerk;
InstigatorPerk = GetPerk();
if( InstigatorPerk != none )
{
InstigatorPerk.SetSuccessfullParry();
}
GetParryEffects(BlockTypeIndex, Sound, PSTemplate);
PlayLocalBlockEffects(Sound, PSTemplate);
}
simulated state MeleeBlocking
{
ignores ForceReload, ShouldAutoReload;
simulated function bool AllowIronSights() { return false; }
simulated function byte GetWeaponStateId()
{
return WEP_MeleeBlock;
}
simulated function BeginState(name PreviousStateName)
{
local float ParryDuration;
ParryDuration = PlayBlockStart();
// Set the duration of the window to parry incoming attacks
if ( ParryDuration > 0.f )
{
SetTimer( ParryDuration, false, nameof(ParryCheckTimer) );
}
NotifyBeginState();
}
simulated function EndState(Name NextStateName)
{
if ( Instigator.IsLocallyControlled() )
{
PlayAnimation(MeleeBlockEndAnim);
}
//SetSlowMovement(false);
NotifyEndState();
}
/** Return to active state if we're done blocking */
simulated function EndFire(byte FireModeNum)
{
Global.EndFire(FireModeNum);
// Wait until parry is finished, then check PendingFire to stop blocking
if ( !StillFiring(CurrentFireMode) && !IsTimerActive(nameof(ParryCheckTimer)) )
{
GotoState('BlockingCooldown');
}
}
/** After the parry window is finished, check PendingFire to see if we're still blocking */
simulated function ParryCheckTimer()
{
// Check PendingFire to stop blocking
if ( !StillFiring(CurrentFireMode) )
{
GotoState('BlockingCooldown');
}
}
/** Grab/Grapple attacks can be parried */
function bool IsGrappleBlocked(Pawn InstigatedBy)
{
local float FacingDot;
local vector Dir2d;
// zero Z to give us a 2d dot product
Dir2d = Normal2d(InstigatedBy.Location - Instigator.Location);
FacingDot = vector(Instigator.Rotation) dot (Dir2d);
// Cos(85)
if ( FacingDot > 0.087f )
{
if ( IsTimerActive(nameof(ParryCheckTimer)) )
{
KFPawn(InstigatedBy).NotifyAttackParried(Instigator, 255);
ClientPlayParryEffects();
NotifyAttackParried();
}
else
{
ClientPlayBlockEffects();
NotifyAttackBlocked();
}
return TRUE;
}
return FALSE;
}
/** While holding a melee weapon reduce some incoming damage */
function AdjustDamage(out int InDamage, class<DamageType> DamageType, Actor DamageCauser)
{
local float FacingDot;
local vector Dir2d;
local KFPerk InstigatorPerk;
local byte BlockTypeIndex;
// don't apply block/parry effects for teammates
if (Instigator.IsSameTeam(DamageCauser.Instigator))
{
return;
}
// zero Z to give us a 2d dot product
Dir2d = Normal2d(DamageCauser.Location - Instigator.Location);
FacingDot = vector(Instigator.Rotation) dot (Dir2d);
// Cos(85)
if ( FacingDot > 0.087f && CanBlockDamageType(DamageType, BlockTypeIndex) )
{
InstigatorPerk = GetPerk();
if ( IsTimerActive(nameof(ParryCheckTimer)) )
{
InDamage *= GetUpgradedParryDamageMitigation(CurrentWeaponUpgradeIndex);
// Notify attacking pawn for effects / animations
if ( KFPawn(DamageCauser) != None )
{
KFPawn(DamageCauser).NotifyAttackParried(Instigator, ParryStrength);
}
// @NOTE: This is now always true per discussion with AndrewL on KFII-29686. Since we always
// do the damage mitigation, we should always play the effect regardless of whether the
// zed was stumbled or knocked down. -MattF
ClientPlayParryEffects(BlockTypeIndex);
NotifyAttackParried();
if( InstigatorPerk != none )
{
InstigatorPerk.SetSuccessfullParry();
}
}
else
{
InDamage *= GetUpgradedBlockDamageMitigation(CurrentWeaponUpgradeIndex);
ClientPlayBlockEffects(BlockTypeIndex);
NotifyAttackBlocked();
if( InstigatorPerk != none )
{
InstigatorPerk.SetSuccessfullBlock();
}
}
}
}
simulated function BlockLoopTimer()
{
if( Instigator.IsLocallyControlled() )
{
PlayAnimation(MeleeBlockLoopAnim, , true);
}
}
/** State override for Block_Hit animations */
unreliable client function ClientPlayBlockEffects(optional byte BlockTypeIndex=255)
{
local int AnimIdx;
local float Duration;
local KFPerk InstigatorPerk;
Global.ClientPlayBlockEffects(BlockTypeIndex);
InstigatorPerk = GetPerk();
if( InstigatorPerk != none )
{
InstigatorPerk.SetSuccessfullBlock();
}
if( MeleeBlockHitAnims.Length > 0 && `TimeSince(LastBlockHitAnimTime) > BlockHitAnimCooldownTime && !IsTimerActive(nameof(ParryCheckTimer)) )
{
AnimIdx = Rand(MeleeBlockHitAnims.Length);
Duration = MySkelMesh.GetAnimLength(MeleeBlockHitAnims[AnimIdx]);
if ( Duration > 0 )
{
LastBlockHitAnimTime = WorldInfo.TimeSeconds;
PlayAnimation(MeleeBlockHitAnims[AnimIdx]);
SetTimer(Duration, false, nameof(BlockLoopTimer));
}
}
}
}
simulated function float PlayBlockStart()
{
local float AnimDuration;
if( Instigator.IsLocallyControlled() )
{
PlayAnimation(MeleeBlockStartAnim);
}
// set when to start playing the looping anim
AnimDuration = MySkelMesh.GetAnimLength(MeleeBlockStartAnim);
if ( AnimDuration > 0.f )
{
SetTimer(AnimDuration, false, nameof(BlockLoopTimer));
}
else
{
BlockLoopTimer();
}
// set the parry duration to the same as the block start anim
return AnimDuration;
}
/** Called on the client when successfully block/parry an attack */
simulated function PlayLocalBlockEffects(AKBaseSoundObject Sound, ParticleSystem PSTemplate)
{
local vector Loc;
local rotator Rot;
local ParticleSystemComponent PSC;
if ( Sound != None )
{
PlaySoundBase(Sound, true);
}
if ( PSTemplate != None )
{
if ( MySkelMesh.GetSocketWorldLocationAndRotation(BlockEffectsSocketName, Loc, Rot) )
{
PSC = WorldInfo.MyEmitterPool.SpawnEmitter(PSTemplate, Loc, Rot);
PSC.SetDepthPriorityGroup(SDPG_Foreground);
}
else
{
`log(self@GetFuncName()@"missing BlockEffects Socket!");
}
}
}
/** If true, this damage type can be blocked by the MeleeBlocking state */
function bool CanBlockDamageType(class<DamageType> DamageType, optional out byte out_Idx)
{
local int Idx;
// Check if this damage should be ignored completely
for (Idx = 0; Idx < BlockTypes.length; ++Idx)
{
if ( ClassIsChildOf(DamageType, BlockTypes[Idx].DmgType) )
{
out_Idx = Idx;
return true;
}
}
out_Idx = INDEX_NONE;
return false;
}
/** Returns sound and particle system overrides using index into BlockTypes array */
simulated function GetBlockEffects(byte BlockIndex, out AKBaseSoundObject outSound, out ParticleSystem outParticleSys)
{
outSound = BlockSound;
outParticleSys = BlockParticleSystem;
if ( BlockIndex != 255 )
{
if ( BlockTypes[BlockIndex].BlockSound != None )
{
outSound = BlockTypes[BlockIndex].BlockSound;
}
if ( BlockTypes[BlockIndex].BlockParticleSys != None )
{
outParticleSys = BlockTypes[BlockIndex].BlockParticleSys;
}
}
}
/** Returns sound and particle system overrides using index into BlockTypes array */
simulated function GetParryEffects(byte BlockIndex, out AKBaseSoundObject outSound, out ParticleSystem outParticleSys)
{
outSound = ParrySound;
outParticleSys = ParryParticleSystem;
if ( BlockIndex != 255 )
{
if ( BlockTypes[BlockIndex].ParrySound != None )
{
outSound = BlockTypes[BlockIndex].ParrySound;
}
if ( BlockTypes[BlockIndex].ParryParticleSys != None )
{
outParticleSys = BlockTypes[BlockIndex].ParryParticleSys;
}
}
}
/*********************************************************************************************
* State BlockingCooldown
* A short cooldown state to prevent spamming block while still allowing pendingfire to be set
*********************************************************************************************/
// Global declarations for this state
simulated function BlockCooldownTimer();
simulated state BlockingCooldown extends Active
{
ignores AllowSprinting;
/** Set cooldown duration */
simulated function BeginState( Name PreviousStateName )
{
SetTimer(0.5, false, nameof(BlockCooldownTimer));
Super.BeginState(PreviousStateName);
}
// prevent going to block/parry state
simulated function bool HasAmmo( byte FireModeNum, optional int Amount )
{
if ( FireModeNum == BLOCK_FIREMODE )
{
return false;
}
return Global.HasAmmo(FireModeNum, Amount);
}
// prevent HasAmmo (above) from causing an auto reload
simulated function bool ShouldAutoReload(byte FireModeNum)
{
if ( FireModeNum == BLOCK_FIREMODE )
{
return false;
}
return Global.ShouldAutoReload(FireModeNum);
}
simulated function BlockCooldownTimer()
{
GotoState('Active');
}
}
/*
*
* END of parrying code.
*
*/
/**
* Toggle between DEFAULT and ALTFIRE
*/
simulated function AltFireMode()
{
if (!Instigator.IsLocallyControlled())
{
return;
}
// break out of ironsights when starting to block
if (bUsingSights)
{
SetIronSights(false);
}
Super(KFWeapon).StartFire(BLOCK_FIREMODE);
}
/** Called during reload state */
simulated function bool CanOverrideMagReload(byte FireModeNum)
{
if (FireModeNum == BLOCK_FIREMODE)
{
return true;
}
return super.CanOverrideMagReload(FireModeNum);
}
/*********************************************************************************************
* Upgrades
********************************************************************************************/
static simulated function float GetUpgradedBlockDamageMitigation(int UpgradeIndex)
{
return GetUpgradedStatValue(default.BlockDamageMitigation, EWUS_BlockDmgMitigation, UpgradeIndex);
}
static simulated function float GetUpgradedParryDamageMitigation(int UpgradeIndex)
{
return GetUpgradedStatValue(default.ParryDamageMitigationPercent, EWUS_ParryDmgMitigation, UpgradeIndex);
}
simulated function int GetModifiedDamage(byte FireModeNum, optional vector RayDir)
{
if (FireModeNum == BASH_FIREMODE)
{
return GetUpgradedStatValue(InstantHitDamage[FireModeNum], EWUS_Damage2, CurrentWeaponUpgradeIndex);
}
return super.GetModifiedDamage(FireModeNum, RayDir);
}
/** 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 = AnimNodeBlendPerBone(SkeletalMeshComponent(Mesh).FindAnimNode('EmptyMagBlend'));
BuildEmptyMagNodeWeightList( EmptyMagBlendNode, BonesToLockOnEmpty_Override );
EmptyMagBlendNode.SetBlendTarget(1,0);
`Log("blending left");
EmptyMagBlendNode_L.SetBlendTarget(1,0);
}
}
}
}
/** Unlocks the bolt bone (Called by animnotify) */
simulated function ANIMNOTIFY_UnLockBolt()
{
super.ANIMNOTIFY_UnLockBolt();
EmptyMagBlendNode_L.SetBlendTarget(0, 0);
EmptyMagBlendNode = AnimNodeBlendPerBone(SkeletalMeshComponent(Mesh).FindAnimNode('EmptyMagBlend'));
BuildEmptyMagNodeWeightList( EmptyMagBlendNode, BonesToLockOnEmpty);
}
/**
This weapon only supports akimbo anims.
Overriding bUseAltFireMode that comes from gameplay options by the user
*/
simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
{
super.PostInitAnimTree(SkelComp);
bUseAltFireMode = false;
}
defaultproperties
{
// Content
PackageKey="Dual_BladedPistol"
FirstPersonMeshName="WEP_1P_Dual_BladedPistol_MESH.WEP_1stP_DualBladedPistol_Rig"
FirstPersonAnimSetNames(0)="WEP_1P_Dual_BladedPistol_ANIM.Wep_1stP_Dual_BladedPistol_ANIM"
PickupMeshName="WEP_3P_Dual_BladedPistol_MESH.Wep_Dual_BladedPistol_Pickup"
AttachmentArchetypeName="WEP_Dual_BladedPistol_ARCH.Wep_Dual_BladedPistol_3P"
MuzzleFlashTemplateName="WEP_Dual_BladedPistol_ARCH.Wep_Dual_BladedPistol_MuzzleFlash"
Begin Object Name=FirstPersonMesh
AnimTreeTemplate=AnimTree'CHR_1P_Arms_ARCH.WEP_1stP_Dual_Animtree_Master'
End Object
FireOffset=(X=30,Y=7,Z=-5)
LeftFireOffset=(X=30,Y=-7,Z=-5)
// Zooming/Position
IronSightPosition=(X=-3,Y=0,Z=0)
PlayerViewOffset=(X=-15,Y=0,Z=0)
QuickWeaponDownRotation=(Pitch=-8192,Yaw=0,Roll=0)
SingleClass=class'KFWeap_Pistol_Bladed'
// FOV
MeshFOV=96
MeshIronSightFOV=77
PlayerIronSightFOV=77
// Depth of field
DOF_FG_FocalRadius=40
DOF_FG_MaxNearBlurSize=3.5
// Ammo
MagazineCapacity[0]=12
SpareAmmoCapacity[0]=66 //72 //96
InitialSpareMags[0]=1
AmmoPickupScale[0]=0.5 //1.0
bCanBeReloaded=true
bReloadFromMagazine=true
// Recoil
maxRecoilPitch=330 //400 //250
minRecoilPitch=300 //350 //200
maxRecoilYaw=120 //100
minRecoilYaw=-120 //-100
RecoilRate=0.07
RecoilMaxYawLimit=500
RecoilMinYawLimit=65035
RecoilMaxPitchLimit=900
RecoilMinPitchLimit=65035
RecoilISMaxYawLimit=50
RecoilISMinYawLimit=65485
RecoilISMaxPitchLimit=250
RecoilISMinPitchLimit=65485
// DEFAULT_FIREMODE
FiringStatesArray(DEFAULT_FIREMODE)=WeaponSingleFiring
WeaponFireTypes(DEFAULT_FIREMODE)=EWFT_Projectile
WeaponProjectiles(DEFAULT_FIREMODE)=class'KFProj_Blade_BladedPistol'
PenetrationPower(DEFAULT_FIREMODE)=3.0
FireInterval(DEFAULT_FIREMODE)=+0.19 //+0.231
InstantHitDamage(DEFAULT_FIREMODE)=115.0
InstantHitDamageTypes(DEFAULT_FIREMODE)=class'KFDT_Slashing_BladedPistol'
Spread(DEFAULT_FIREMODE)=0.005 //0.015
FireModeIconPaths(DEFAULT_FIREMODE)=Texture2D'ui_firemodes_tex.UI_FireModeSelect_Sawblade'
// MELEE_BLOCK_FIREMODE
FiringStatesArray(BLOCK_FIREMODE)=MeleeBlocking
WeaponFireTypes(BLOCK_FIREMODE)=EWFT_Custom
FireInterval(BLOCK_FIREMODE)=1.f
AmmoCost(BLOCK_FIREMODE)=0
// BASH_FIREMODE
InstantHitDamageTypes(BASH_FIREMODE)=class'KFDT_Bludgeon_BladedPistol'
InstantHitDamage(BASH_FIREMODE)=75 //26
// Fire Effects
WeaponFireSnd(DEFAULT_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_BladedPistol.Play_WEP_BladedPistol_Fire_3P', FirstPersonCue=AkEvent'WW_WEP_BladedPistol.Play_WEP_BladedPistol_Fire_1P')
WeaponDryFireSnd(DEFAULT_FIREMODE)=AkEvent'WW_WEP_BladedPistol.Play_WEP_BladedPistol_Handling_DryFire'
WeaponFireSnd(ALTFIRE_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_BladedPistol.Play_WEP_BladedPistol_Fire_3P', FirstPersonCue=AkEvent'WW_WEP_BladedPistol.Play_WEP_BladedPistol_Fire_1P')
WeaponDryFireSnd(ALTFIRE_FIREMODE)=AkEvent'WW_WEP_BladedPistol.Play_WEP_BladedPistol_Handling_DryFire'
// Attachments
bHasIronSights=true
bHasFlashlight=true
AssociatedPerkClasses(0)=class'KFPerk_Gunslinger'
AssociatedPerkClasses(1)=class'KFPerk_Berserker'
// Inventory
InventoryGroup= IG_Primary
InventorySize=6
GroupPriority=45
bCanThrow=true
bDropOnDeath=true
WeaponSelectTexture=Texture2D'WEP_UI_Dual_BladedPistol_TEX.UI_WeaponSelect_Dual_BladedPistol'
bIsBackupWeapon=false
BonesToLockOnEmpty=(RW_FrontPivot)
BonesToLockOnEmpty_L=(LW_FrontPivot)
BonesToLockOnEmpty_Override=(RW_FrontPivot, LW_FrontPivot)
bHasFireLastAnims=true
// default MIC param names
BlockEffectsSocketName=BlockEffect
// Defensive
BlockDamageMitigation=0.60f
ParryDamageMitigationPercent=0.5
ParryStrength=4
BlockHitAnimCooldownTime=0.5f
BlockTypes.Add((DmgType=class'KFDT_Bludgeon'))
BlockTypes.Add((DmgType=class'KFDT_Slashing'))
// Block Sounds
BlockSound=AkEvent'WW_WEP_Bullet_Impacts.Play_Block_MEL_Crovel'
ParrySound=AkEvent'WW_WEP_Bullet_Impacts.Play_Parry_Metal'
BlockParticleSystem=ParticleSystem'FX_Impacts_EMIT.FX_Block_melee_01'
ParryParticleSystem=ParticleSystem'FX_Impacts_EMIT.FX_Parry_melee_01'
MeleeBlockHitAnims=(Block_Hit_V1, Block_Hit_V2, Block_Hit_V3);
// Upgrades
UpgradeFireModes(BLOCK_FIREMODE) = 0
//Upgrades
WeaponUpgrades[1]=(Stats=((Stat=EWUS_Damage0, Scale=1.15f), (Stat=EWUS_Damage1, Scale=1.15f), (Stat=EWUS_Damage2, Scale=1.15f), (Stat=EWUS_Weight, Add=1)))
WeaponUpgrades[2]=(Stats=((Stat=EWUS_Damage0, Scale=1.3f), (Stat=EWUS_Damage1, Scale=1.3f), (Stat=EWUS_Damage2, Scale=1.3f), (Stat=EWUS_Weight, Add=2)))
}