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

467 lines
13 KiB
Ucode

class KFShotgunFire extends BaseProjectileFire;
var float LastClickTime;
var() Name EmptyAnim;
var() float EmptyAnimRate;
var() float MaxAccuracyBonus,CrouchedAccuracyBonus; // Lower number = higher bonus . 1.0 = no bonus , 0.1 = 90% bonus.
var() float EffectiveRange;
var() vector KickMomentum;
var() float LowGravKickMomentumScale; // How much to scale up kick momentum in low grav so guys fly around hilariously :)
var() bool bFiringDoesntAffectMovement;
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() name FireAimedAnim;
var() name FireLoopAimedAnim;
var KFWeapon KFWeap; // To avoid casting, store the owning KFWeapon
// 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 KFShotgunFire 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);
}
}
// Begin code from FlakFire
function InitEffects()
{
Super.InitEffects();
if ( FlashEmitter != None )
Weapon.AttachToBone(FlashEmitter, 'tip');
}
// End code from FlakFire
function DoFireEffect()
{
local Vector StartProj, StartTrace, X,Y,Z;
local Rotator R, Aim;
local Vector HitLocation, HitNormal;
local Actor Other;
local int p;
local int SpawnCount;
local float theta;
Instigator.MakeNoise(1.0);
Weapon.GetViewAxes(X,Y,Z);
StartTrace = Instigator.Location + Instigator.EyePosition();// + X*Instigator.CollisionRadius;
StartProj = StartTrace + X*ProjSpawnOffset.X;
if ( !Weapon.WeaponCentered() && !KFWeap.bAimingRifle )
StartProj = StartProj + Weapon.Hand * Y*ProjSpawnOffset.Y + Z*ProjSpawnOffset.Z;
// check if projectile would spawn through a wall and adjust start location accordingly
Other = Weapon.Trace(HitLocation, HitNormal, StartProj, StartTrace, false);
// Collision attachment debugging
/* if( Other.IsA('ROCollisionAttachment'))
{
log(self$"'s trace hit "$Other.Base$" Collision attachment");
}*/
if (Other != None)
{
StartProj = HitLocation;
}
Aim = AdjustAim(StartProj, AimError);
SpawnCount = Max(1, ProjPerFire * int(Load));
switch (SpreadStyle)
{
case SS_Random:
X = Vector(Aim);
for (p = 0; p < SpawnCount; p++)
{
R.Yaw = Spread * (FRand()-0.5);
R.Pitch = Spread * (FRand()-0.5);
R.Roll = Spread * (FRand()-0.5);
SpawnProjectile(StartProj, Rotator(X >> R));
}
break;
case SS_Line:
for (p = 0; p < SpawnCount; p++)
{
theta = Spread*PI/32768*(p - float(SpawnCount-1)/2.0);
X.X = Cos(theta);
X.Y = Sin(theta);
X.Z = 0.0;
SpawnProjectile(StartProj, Rotator(X >> Aim));
}
break;
default:
SpawnProjectile(StartProj, Aim);
}
if (Instigator != none )
{
if( Instigator.Physics != PHYS_Falling )
{
Instigator.AddVelocity(KickMomentum >> Instigator.GetViewRotation());
}
// Really boost the momentum for low grav
else if( Instigator.Physics == PHYS_Falling
&& Instigator.PhysicsVolume.Gravity.Z > class'PhysicsVolume'.default.Gravity.Z)
{
Instigator.AddVelocity((KickMomentum * LowGravKickMomentumScale) >> Instigator.GetViewRotation());
}
}
}
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;
}
event ModeDoFire()
{
local float Rec;
if (!AllowFire())
return;
Spread = Default.Spread;
Rec = GetFireSpeed();
FireRate = default.FireRate/Rec;
FireAnimRate = default.FireAnimRate*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);
}
if( !bFiringDoesntAffectMovement )
{
if (FireRate > 0.25)
{
Instigator.Velocity.x *= 0.1;
Instigator.Velocity.y *= 0.1;
}
else
{
Instigator.Velocity.x *= 0.5;
Instigator.Velocity.y *= 0.5;
}
}
Super.ModeDoFire();
// client
if (Instigator.IsLocallyControlled())
{
HandleRecoil(Rec);
}
}
// Handle setting new recoil
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 )
{
NewRecoilRotation.Pitch = RandRange( maxVerticalRecoilAngle * 0.5, maxVerticalRecoilAngle );
NewRecoilRotation.Yaw = RandRange( maxHorizontalRecoilAngle * 0.5, maxHorizontalRecoilAngle );
if( Rand( 2 ) == 1 )
NewRecoilRotation.Yaw *= -1;
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* 3 * 0.5);
NewRecoilRotation.Yaw += (AdjustedSpeed* 3 * 0.5);
}
else
{
//log("Velocity = "$VSize(Weapon.Owner.Velocity)$" scale = "$(VSize(Weapon.Owner.Velocity)* RecoilVelocityScale));
NewRecoilRotation.Pitch += (VSize(Weapon.Owner.Velocity)* 3);
NewRecoilRotation.Yaw += (VSize(Weapon.Owner.Velocity)* 3);
}
NewRecoilRotation.Pitch += (Instigator.HealthMax / Instigator.Health * 5);
NewRecoilRotation.Yaw += (Instigator.HealthMax / Instigator.Health * 5);
NewRecoilRotation *= Rec;
KFPC.SetRecoil(NewRecoilRotation,RecoilRate * (default.FireRate/FireRate));
}
}
}
// Overridden to support interrupting the reload
simulated function bool AllowFire()
{
if( KFWeapon(Weapon).bIsReloading && KFWeapon(Weapon).MagAmmoRemaining < 2)
return false;
if(KFPawn(Instigator).SecondaryItem!=none)
return false;
if( KFPawn(Instigator).bThrowingNade )
return false;
if( Level.TimeSeconds - LastClickTime>FireRate )
{
LastClickTime = Level.TimeSeconds;
}
if( KFWeaponShotgun(Weapon).MagAmmoRemaining<1 )
{
return false;
}
return super(WeaponFire).AllowFire();
}
// TODO: Maybe provide more control? So there's a 'if you're desparate, you MIGHT
// do something at this range, but here's the recommended dist
// Currently, bots won't fire past this recommendation no matter what
// We'll see if desperation long-range fire is needed
//TODO: Also, check the effectiverange for all subclasses to make sure it is
// appropriate
function float MaxRange()
{
if (Instigator.Region.Zone.bDistanceFog)
return FClamp(Instigator.Region.Zone.DistanceFogEnd, 1500, EffectiveRange);
else return 1500;
}
// Accuracy update based on pawn velocity
simulated function AccuracyUpdate(float Velocity)
{
local float MovementScale, ShakeScaler;
if (KFWeapon(Weapon).bSteadyAim)
return;
if (Pawn(Weapon.Owner).bIsCrouched)
Velocity *= CrouchedAccuracyBonus;
Spread = (default.Spread + (Velocity * 10 ));
// Add up to 20% more shake depending on how fast the player is moving
MovementScale= Velocity/Instigator.default.GroundSpeed;
ShakeScaler = 1.0 + (0.2 * MovementScale);
ShakeRotMag.x = (default.ShakeRotMag.x * ShakeScaler);
ShakeRotMag.y = (default.ShakeRotMag.y * ShakeScaler);
ShakeRotMag.z = (default.ShakeRotMag.z * ShakeScaler);
}
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++;
}
defaultproperties
{
// Begin code from FlakFire
AmmoClass=none
AmmoPerFire=1
FireAnim=Fire
FireAnimRate=1.0
FireEndAnim=None
ProjectileClass=class'Old2k4.FlakChunk'
ProjPerFire=9
ProjSpawnOffset=(X=25,Y=5,Z=-6)
SpreadStyle=SS_Random
//FlashEmitterClass=class'XEffects.FlakMuzFlash1st'
//FireSound=Sound'WeaponSounds.FlakCannon.FlakCannonFire'
FireForce="FlakCannonFire" // jdf
FireRate=0.8947
BotRefireRate=0.7
// End code from FlakFire
EffectiveRange=700.000000
Spread=2250
CrouchedAccuracyBonus = 0.6
RecoilRate=0.05
bRandomPitchFireSound=true
RandomPitchAdjustAmt=0.05
LowGravKickMomentumScale=10.0
}