467 lines
13 KiB
Ucode
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
|
|
}
|