rott/kf_sources/KFMod/Classes/KFFire.uc
2026-07-14 20:27:09 +07:00

604 lines
18 KiB
Ucode

class KFFire extends InstantFire
abstract;
var() name FireAimedAnim;
var() name FireEndAimedAnim;
var() name FireLoopAimedAnim;
var float LastClickTime;
var float LastFireTime;
var() name EmptyAnim;
var() float EmptyAnimRate;
var() Name EmptyFireAnim;
var() float EmptyFireAnimRate;
var bool Empty;
var () bool bFiringDoesntAffectMovement;
var int UpKick;
var(Recoil) float RecoilRate; // Time in seconds each recoil should take to be applied. Must be less than the fire rate or the full recoil wont be applied
var(Recoil) int maxVerticalRecoilAngle; // max vertical angle a weapon muzzle can climb from recoil
var(Recoil) int maxHorizontalRecoilAngle; // max horizontal angle a weapon muzzle can move from recoil
var(Recoil) float RecoilVelocityScale; // How much to scale the recoil by based on how fast the player is moving
var(Recoil) bool bRecoilRightOnly; // Only recoil the weapon's yaw to the right, not just randomly right and left
var KFWeapon KFWeap; // To avoid casting, store the owning KFWeapon
var() bool bDoClientRagdollShotFX; // Do traces on clients when the player is in slomo shooting a dieing enemy so there will be blood puffs
var() class<Emitter> ShellEjectClass; // class of the shell eject emitter
var() Emitter ShellEjectEmitter; // The shell eject emitter
var() name ShellEjectBoneName; // name of the shell eject bone
// Accuracy vars
var() float MaxSpread; // The maximum spread this weapon will ever have (like when firing in long bursts, etc)
var int NumShotsInBurst; // How many shots fired recently
var() bool bAccuracyBonusForSemiAuto; // Give an accuracy bonus for semi auto fire
// Steroe Fire Sound support
var() sound StereoFireSound; // A stereo version of the fire sound
var() bool bRandomPitchFireSound; // Fire sound randomly change pitch (use this instead of lots of multiple sounds to save memory)
var() float RandomPitchAdjustAmt; // How much to randomly adjust the pitch for firing sounds
var string FireSoundRef;
var string StereoFireSoundRef;
var string NoAmmoSoundRef;
static function PreloadAssets(LevelInfo LevelInfo, optional KFFire Spawned)
{
if ( default.FireSoundRef != "" )
{
default.FireSound = sound(DynamicLoadObject(default.FireSoundRef, class'Sound', true));
}
if ( LevelInfo.bLowSoundDetail || (default.StereoFireSoundRef == "" && default.StereoFireSound == none) )
{
default.StereoFireSound = default.FireSound;
}
else
{
default.StereoFireSound = sound(DynamicLoadObject(default.StereoFireSoundRef, class'Sound', true));
}
if ( default.NoAmmoSoundRef != "" )
{
default.NoAmmoSound = sound(DynamicLoadObject(default.NoAmmoSoundRef, class'Sound', true));
}
if ( Spawned != none )
{
Spawned.FireSound = default.FireSound;
Spawned.StereoFireSound = default.StereoFireSound;
Spawned.NoAmmoSound = default.NoAmmoSound;
}
}
static function bool UnloadAssets()
{
default.FireSound = none;
default.StereoFireSound = none;
default.NoAmmoSound = none;
return true;
}
simulated function PostBeginPlay()
{
if ( FireSound == none )
{
PreloadAssets(Level, self);
}
super.PostBeginPlay();
if( KFWeapon(Owner) != none )
{
KFWeap = KFWeapon(Owner);
}
}
simulated function DestroyEffects()
{
super.DestroyEffects();
if (ShellEjectEmitter != None)
ShellEjectEmitter.Destroy();
}
simulated function InitEffects()
{
super.InitEffects();
// don't even spawn on server
if ( (Level.NetMode == NM_DedicatedServer) || (AIController(Instigator.Controller) != None) )
return;
if ( (ShellEjectClass != None) && ((ShellEjectEmitter == None) || ShellEjectEmitter.bDeleteMe) )
{
ShellEjectEmitter = Weapon.Spawn(ShellEjectClass);
Weapon.AttachToBone(ShellEjectEmitter, ShellEjectBoneName);
}
if ( FlashEmitter != None )
Weapon.AttachToBone(FlashEmitter, KFWeapon(Weapon).FlashBoneName);
}
function DrawMuzzleFlash(Canvas Canvas)
{
super.DrawMuzzleFlash(Canvas);
// Draw smoke first
if (ShellEjectEmitter != None )
{
Canvas.DrawActor( ShellEjectEmitter, false, false, Weapon.DisplayFOV );
}
}
function FlashMuzzleFlash()
{
super.FlashMuzzleFlash();
if (ShellEjectEmitter != None)
{
//ShellEjectEmitter.SpawnParticle(1);//Trigger(Weapon, Instigator);
ShellEjectEmitter.Trigger(Weapon, Instigator);
}
}
function float GetFireSpeed()
{
if ( KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo) != none && KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill != none )
{
return KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill.Static.GetFireSpeedMod(KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo), Weapon);
}
return 1;
}
function PlayFiring()
{
local float RandPitch;
if ( Weapon.Mesh != None )
{
if ( FireCount > 0 )
{
if( KFWeap.bAimingRifle )
{
if ( Weapon.HasAnim(FireLoopAimedAnim) )
{
Weapon.PlayAnim(FireLoopAimedAnim, FireLoopAnimRate, 0.0);
}
else if( Weapon.HasAnim(FireAimedAnim) )
{
Weapon.PlayAnim(FireAimedAnim, FireAnimRate, TweenTime);
}
else
{
Weapon.PlayAnim(FireAnim, FireAnimRate, TweenTime);
}
}
else
{
if ( Weapon.HasAnim(FireLoopAnim) )
{
Weapon.PlayAnim(FireLoopAnim, FireLoopAnimRate, 0.0);
}
else
{
Weapon.PlayAnim(FireAnim, FireAnimRate, TweenTime);
}
}
}
else
{
if( KFWeap.bAimingRifle )
{
if( Weapon.HasAnim(FireAimedAnim) )
{
Weapon.PlayAnim(FireAimedAnim, FireAnimRate, TweenTime);
}
else
{
Weapon.PlayAnim(FireAnim, FireAnimRate, TweenTime);
}
}
else
{
Weapon.PlayAnim(FireAnim, FireAnimRate, TweenTime);
}
}
}
if( Weapon.Instigator != none && Weapon.Instigator.IsLocallyControlled() &&
Weapon.Instigator.IsFirstPerson() && StereoFireSound != none )
{
if( bRandomPitchFireSound )
{
RandPitch = FRand() * RandomPitchAdjustAmt;
if( FRand() < 0.5 )
{
RandPitch *= -1.0;
}
}
Weapon.PlayOwnedSound(StereoFireSound,SLOT_Interact,TransientSoundVolume * 0.85,,TransientSoundRadius,(1.0 + RandPitch),false);
}
else
{
if( bRandomPitchFireSound )
{
RandPitch = FRand() * RandomPitchAdjustAmt;
if( FRand() < 0.5 )
{
RandPitch *= -1.0;
}
}
Weapon.PlayOwnedSound(FireSound,SLOT_Interact,TransientSoundVolume,,TransientSoundRadius,(1.0 + RandPitch),false);
}
ClientPlayForceFeedback(FireForce); // jdf
FireCount++;
}
function PlayFireEnd()
{
if ( Weapon.Mesh != none )
{
if( KFWeap.bAimingRifle )
{
if( Weapon.HasAnim(FireEndAimedAnim) )
{
Weapon.PlayAnim(FireEndAimedAnim, FireEndAnimRate, TweenTime);
}
else if(Weapon.HasAnim(FireEndAnim))
{
Weapon.PlayAnim(FireEndAnim, FireEndAnimRate, TweenTime);
}
}
else if(Weapon.HasAnim(FireEndAnim))
{
Weapon.PlayAnim(FireEndAnim, FireEndAnimRate, TweenTime);
}
}
}
simulated function bool AllowFire()
{
if(KFWeapon(Weapon).bIsReloading)
return false;
if(KFPawn(Instigator).SecondaryItem!=none)
return false;
if(KFPawn(Instigator).bThrowingNade)
return false;
if(KFWeapon(Weapon).MagAmmoRemaining < 1)
{
if( Level.TimeSeconds - LastClickTime>FireRate )
{
LastClickTime = Level.TimeSeconds;
}
if( AIController(Instigator.Controller)!=None )
KFWeapon(Weapon).ReloadMeNow();
return false;
}
return Super.AllowFire();
}
function StartBerserk();
function StopBerserk();
// Calculate modifications to spread
simulated function float GetSpread()
{
local float NewSpread;
local float AccuracyMod;
AccuracyMod = 1.0;
// Spread bonus for firing aiming
if( KFWeap.bAimingRifle )
{
AccuracyMod *= 0.5;
}
// Small spread bonus for firing crouched
if( Instigator != none && Instigator.bIsCrouched )
{
AccuracyMod *= 0.85;
}
// Small spread bonus for firing in semi auto mode
if( bAccuracyBonusForSemiAuto && bWaitForRelease )
{
AccuracyMod *= 0.85;
}
NumShotsInBurst += 1;
if ( Level.TimeSeconds - LastFireTime > 0.5 )
{
NewSpread = Default.Spread;
NumShotsInBurst=0;
}
else
{
// Decrease accuracy up to MaxSpread by the number of recent shots up to a max of six
NewSpread = FMin(Default.Spread + (NumShotsInBurst * (MaxSpread/6.0)),MaxSpread);
}
NewSpread *= AccuracyMod;
return NewSpread;
}
event ModeDoFire()
{
local float Rec;
if (!AllowFire())
return;
if( Instigator==None || Instigator.Controller==none )
return;
Spread = GetSpread();
Rec = GetFireSpeed();
FireRate = default.FireRate/Rec;
FireAnimRate = default.FireAnimRate*Rec;
ReloadAnimRate = default.ReloadAnimRate*Rec;
Rec = 1;
if ( KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo) != none && KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill != none )
{
Spread *= KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill.Static.ModifyRecoilSpread(KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo), self, Rec);
}
LastFireTime = Level.TimeSeconds;
if (Weapon.Owner != none && AllowFire() && !bFiringDoesntAffectMovement && Weapon.Owner.Physics != PHYS_Falling )
{
if (FireRate > 0.25)
{
Weapon.Owner.Velocity.x *= 0.1;
Weapon.Owner.Velocity.y *= 0.1;
}
else
{
Weapon.Owner.Velocity.x *= 0.5;
Weapon.Owner.Velocity.y *= 0.5;
}
}
Super.ModeDoFire();
// client
if (Instigator.IsLocallyControlled())
{
if( bDoClientRagdollShotFX && Weapon.Level.NetMode == NM_Client )
{
DoClientOnlyFireEffect();
}
HandleRecoil(Rec);
}
}
// do a fire effect on clients only, used for causing blood puffs, etc on ragdolls during slomo deaths
simulated function DoClientOnlyFireEffect()
{
local Vector StartTrace;
local Rotator R, Aim;
// the to-hit trace always starts right in front of the eye
StartTrace = Instigator.Location + Instigator.EyePosition();
Aim = AdjustAim(StartTrace, AimError);
R = rotator(vector(Aim) + VRand()*FRand()*Spread);
DoClientOnlyEffectTrace(StartTrace, R);
}
// Do a effect trace on clients only, used for causing blood puffs, etc on ragdolls during slomo deaths
simulated function DoClientOnlyEffectTrace(Vector Start, Rotator Dir)
{
local Vector X,Y,Z, End, HitLocation, HitNormal, ArcEnd;
local Actor Other;
MaxRange();
Weapon.GetViewAxes(X, Y, Z);
if ( Weapon.WeaponCentered() )
ArcEnd = (Instigator.Location + Weapon.EffectOffset.X * X + 1.5 * Weapon.EffectOffset.Z * Z);
else ArcEnd = (Instigator.Location + Instigator.CalcDrawOffset(Weapon) + Weapon.EffectOffset.X * X + Weapon.Hand * Weapon.EffectOffset.Y * Y +
Weapon.EffectOffset.Z * Z);
X = Vector(Dir);
End = Start + TraceRange * X;
Other = Weapon.Trace(HitLocation, HitNormal, End, Start, true);
if ( Other != None && (Other != Instigator) && Pawn(Other) != none && Pawn(Other).bTearOff )
{
Other.TakeDamage(DamageMax, Instigator, HitLocation, Momentum*X, DamageType);
}
}
// Handle setting the recoil amount
simulated function HandleRecoil(float Rec)
{
local rotator NewRecoilRotation;
local KFPlayerController KFPC;
local KFPawn KFPwn;
local vector AdjustedVelocity;
local float AdjustedSpeed;
if( Instigator != none )
{
KFPC = KFPlayerController(Instigator.Controller);
KFPwn = KFPawn(Instigator);
}
if( KFPC == none || KFPwn == none )
return;
if( !KFPC.bFreeCamera )
{
if( Weapon.GetFireMode(0).bIsFiring || (DeagleAltFire(Weapon.GetFireMode(1))!=none
&& DeagleAltFire(Weapon.GetFireMode(1)).bIsFiring) )
{
NewRecoilRotation.Pitch = RandRange( maxVerticalRecoilAngle * 0.5, maxVerticalRecoilAngle );
NewRecoilRotation.Yaw = RandRange( maxHorizontalRecoilAngle * 0.5, maxHorizontalRecoilAngle );
if( !bRecoilRightOnly )
{
if( Rand( 2 ) == 1 )
NewRecoilRotation.Yaw *= -1;
}
if( RecoilVelocityScale > 0 )
{
if( Weapon.Owner != none && Weapon.Owner.Physics == PHYS_Falling &&
Weapon.Owner.PhysicsVolume.Gravity.Z > class'PhysicsVolume'.default.Gravity.Z )
{
AdjustedVelocity = Weapon.Owner.Velocity;
// Ignore Z velocity in low grav so we don't get massive recoil
AdjustedVelocity.Z = 0;
AdjustedSpeed = VSize(AdjustedVelocity);
//log("AdjustedSpeed = "$AdjustedSpeed$" scale = "$(AdjustedSpeed* RecoilVelocityScale * 0.5));
// Reduce the falling recoil in low grav
NewRecoilRotation.Pitch += (AdjustedSpeed* RecoilVelocityScale * 0.5);
NewRecoilRotation.Yaw += (AdjustedSpeed* RecoilVelocityScale * 0.5);
}
else
{
//log("Velocity = "$VSize(Weapon.Owner.Velocity)$" scale = "$(VSize(Weapon.Owner.Velocity)* RecoilVelocityScale));
NewRecoilRotation.Pitch += (VSize(Weapon.Owner.Velocity)* RecoilVelocityScale);
NewRecoilRotation.Yaw += (VSize(Weapon.Owner.Velocity)* RecoilVelocityScale);
}
}
NewRecoilRotation.Pitch += (Instigator.HealthMax / Instigator.Health * 5);
NewRecoilRotation.Yaw += (Instigator.HealthMax / Instigator.Health * 5);
NewRecoilRotation *= Rec;
KFPC.SetRecoil(NewRecoilRotation,RecoilRate / (default.FireRate/FireRate));
}
}
}
function float MaxRange()
{
if (Instigator.Region.Zone.bDistanceFog)
TraceRange = FClamp(Instigator.Region.Zone.DistanceFogEnd, 8000, default.TraceRange);
else TraceRange = default.TraceRange;
return TraceRange;
}
function DoTrace(Vector Start, Rotator Dir)
{
local Vector X,Y,Z, End, HitLocation, HitNormal, ArcEnd;
local Actor Other;
local KFWeaponAttachment WeapAttach;
local array<int> HitPoints;
local KFPawn HitPawn;
MaxRange();
Weapon.GetViewAxes(X, Y, Z);
if ( Weapon.WeaponCentered() )
ArcEnd = (Instigator.Location + Weapon.EffectOffset.X * X + 1.5 * Weapon.EffectOffset.Z * Z);
else ArcEnd = (Instigator.Location + Instigator.CalcDrawOffset(Weapon) + Weapon.EffectOffset.X * X + Weapon.Hand * Weapon.EffectOffset.Y * Y +
Weapon.EffectOffset.Z * Z);
X = Vector(Dir);
End = Start + TraceRange * X;
Other = Instigator.HitPointTrace(HitLocation, HitNormal, End, HitPoints, Start,, 1);
if ( Other != None && Other != Instigator && Other.Base != Instigator )
{
WeapAttach = KFWeaponAttachment(Weapon.ThirdPersonActor);
if ( !Other.bWorldGeometry )
{
// Update hit effect except for pawns
if ( !Other.IsA('Pawn') && !Other.IsA('HitScanBlockingVolume') &&
!Other.IsA('ExtendedZCollision') )
{
if( WeapAttach!=None )
{
WeapAttach.UpdateHit(Other, HitLocation, HitNormal);
}
}
HitPawn = KFPawn(Other);
if ( HitPawn != none )
{
// Hit detection debugging
/*log("PreLaunchTrace hit "$HitPawn.PlayerReplicationInfo.PlayerName);
HitPawn.HitStart = Start;
HitPawn.HitEnd = End;*/
if(!HitPawn.bDeleteMe)
HitPawn.ProcessLocationalDamage(DamageMax, Instigator, HitLocation, Momentum*X,DamageType,HitPoints);
// Hit detection debugging
/*if( Level.NetMode == NM_Standalone)
HitPawn.DrawBoneLocation();*/
}
else
{
Other.TakeDamage(DamageMax, Instigator, HitLocation, Momentum*X,DamageType);
}
}
else
{
HitLocation = HitLocation + 2.0 * HitNormal;
if ( WeapAttach != None )
{
WeapAttach.UpdateHit(Other,HitLocation,HitNormal);
}
}
}
else
{
HitLocation = End;
HitNormal = Normal(Start - End);
}
}
// Accuracy update based on pawn velocity
simulated function AccuracyUpdate(float Velocity)
{
if (KFWeapon(Weapon).bSteadyAim)
return;
if (Pawn(Weapon.Owner).bIsCrouched)
Velocity *= 0.6;
AimError = ((default.AimError * 0.75) + (Velocity * 4 )); //2
Spread = ((default.Spread * 0.75) + (Velocity * 0.0010 )); //.0005
}
defaultproperties
{
EmptyAnim="Empty"
EmptyAnimRate=1.000000
EmptyFireAnim="EmptyFire"
EmptyFireAnimRate=1.000000
RecoilRate=0.09
bDoClientRagdollShotFX=true
MaxSpread=0.12
RecoilVelocityScale=3.0
bRandomPitchFireSound=true
RandomPitchAdjustAmt=0.05
}