NicePack/sources/Weapons/NiceFire.uc
Anton Tarasenko e1769e8048 Change enforcer's heavy rifles' balance via burst fire parameters
This patch dramatically reduces recoil on all guns and lets XMV850
accumulate way more damage that previously possible.
2024-11-27 03:06:31 +07:00

760 lines
31 KiB
Ucode

class NiceFire extends KFFire
abstract;
var bool bDoSpam;
var name FireIncompleteAnim;
var name FireIncompleteAimedAnim;
var name FireIncompleteLoopAnim;
var name FireIncompleteLoopAimedAnim;
var float zedTimeFireSpeedUp;
// Projectile-related variables
var() bool bProjectileFire;
var() bool bCanFireIncomplete;
var() int ProjPerFire;
var() Vector ProjSpawnOffset;
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() float ProjectileSpeed; // How fast projectile should move
var() bool bDisabled;
var() bool bSemiMustBurst;
var() int MaxBurstLength;
var() int burstShotsMade;
var bool bResetRecoil; // Set this flag to 'true' to disable recoil for the next shot; flag will be automatically reset to 'false' after that
var float aimingSpreadReductionCoefficient;
var bool zoomOutOnShot;
var bool bShouldBounce;
var bool bCausePain;
var bool bBetterBurst;
var class<NiceBullet> bulletClass;
var class<NiceWeaponDamageType> explosionDamageType;
var int explosionDamage;
var float explosionRadius;
var float explosionExponent;
var float explosionMomentum;
var float fuseTime;
var bool explodeOnFuse;
var bool explodeOnPawnHit;
var bool explodeOnWallHit;
var bool projAffectedByScream;
var bool bShouldStick;
var int resetTicks;
var float niceNextFireTime;
var float minimalSpreadScale;
var float activeSpreadScale;
var float spreadGainedPerShot;
var float spreadLostPerSecond;
struct FireModeContext{
var bool bHipfire;
var NiceWeapon sourceWeapon;
var NiceHumanPawn Instigator;
var bool bIsBursting;
var int burstLength;
var float continiousBonus;
var float lockonTime;
var NiceMonster lockonZed;
};
var FireModeContext currentContext;
// Secondary fire effect, - allows to spawn additional projectiles along with main hitscan/projectile
struct ShotType{
var bool bShouldBounce; // Bullets should bounce off the walls
var int damage;
var int projPerFire;
var float spread;
var float projSpeed;
var float momentum;
var ESpreadStyle spreadStyle;
var class<NiceWeaponDamageType> shotDamageType;
var class<NiceWeaponDamageType> explosionDamageType;
var class<NiceBullet> bulletClass;
var int explosionDamage;
var float explosionRadius;
var float explosionExponent;
var float explosionMomentum;
var float fuseTime;
var bool bCausePain;
var bool explodeOnFuse;
var bool explodeOnPawnHit;
var bool explodeOnWallHit;
var bool projAffectedByScream;
var bool bShouldStick;
};
// All the available shot types; shot type at index zero is auto-filled from the usual kf's parameters
var array<ShotType> fireShots;
// Index of the shot we're currently generating
var int currentShot;
// Variables for managing damage boosts for fast firing
var float contBonus;
var bool contBonusReset; // does bonus reset after reaching the top value? 'false' means it'll keep being maxed out until player releases fire button
var int maxBonusContLenght;
var int currentContLenght;
var float period;
static function PreloadAssets(LevelInfo LevelInfo, optional KFFire Spawned){
local int i;
if(default.FireSound == none && default.FireSoundRef != "")
default.FireSound = sound(DynamicLoadObject(default.FireSoundRef, class'Sound', true));
if(default.StereoFireSound == none){
if(default.StereoFireSoundRef != "")
default.StereoFireSound = sound(DynamicLoadObject(default.StereoFireSoundRef, class'Sound', true));
else
default.StereoFireSound = default.FireSound;
}
if(default.NoAmmoSound == none && 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;
}
if(default.bulletClass != none)
default.bulletClass.static.PreloadAssets();
for(i = 0;i < default.fireShots.Length;i ++)
if(default.fireShots[i].bulletClass != none)
default.fireShots[i].bulletClass.static.PreloadAssets();
}
static function bool UnloadAssets(){
default.FireSound = none;
default.StereoFireSound = none;
default.NoAmmoSound = none;
if(default.bulletClass != none)
default.bulletClass.static.UnloadAssets();
return true;
}
simulated function PostBeginPlay(){
local ShotType defaultShotType;
currentContext.continiousBonus = 1.0;
currentContext.burstLength = 1;
// Build index-zero shot type
defaultShotType.damage = DamageMax;
defaultShotType.projPerFire = ProjPerFire;
defaultShotType.spread = Spread;
defaultShotType.projSpeed = projectileSpeed;
defaultShotType.momentum = momentum;
defaultShotType.spreadStyle = spreadStyle;
defaultShotType.bShouldBounce = bShouldBounce;
defaultShotType.shotDamageType = class<NiceWeaponDamageType>(DamageType);
defaultShotType.explosionDamageType = explosionDamageType;
defaultShotType.bulletClass = bulletClass;
defaultShotType.explosionDamage = explosionDamage;
defaultShotType.explosionRadius = explosionRadius;
defaultShotType.explosionExponent = explosionExponent;
defaultShotType.explosionMomentum = explosionMomentum;
defaultShotType.fuseTime = fuseTime;
defaultShotType.explodeOnFuse = explodeOnFuse;
defaultShotType.explodeOnPawnHit = explodeOnPawnHit;
defaultShotType.explodeOnWallHit = explodeOnWallHit;
defaultShotType.projAffectedByScream = projAffectedByScream;
defaultShotType.bCausePain = bCausePain;
defaultShotType.bShouldStick = bShouldStick;
fireShots[0] = defaultShotType;
super.PostBeginPlay();
}
simulated function int GetBurstLength(){
local NicePlayerController nicePlayer;
if(Instigator != none)
nicePlayer = NicePlayerController(Instigator.Controller);
if(nicePlayer != none && class'NiceVeterancyTypes'.static.hasSkill(nicePlayer, class'NiceSkillCommandoExplosivePower')) {
return currentContext.burstLength + 1;
}
return currentContext.burstLength;
}
simulated function ModeTick(float delta){
local float headLevel;
local NiceMonster currTarget;
if (spreadLostPerSecond > 0 && activeSpreadScale > minimalSpreadScale
&& niceNextFireTime + fireRate <= Level.TimeSeconds) {
activeSpreadScale -= spreadLostPerSecond * delta;
activeSpreadScale = FMax(minimalSpreadScale, activeSpreadScale);
}
if(currentContext.Instigator == none)
currentContext.Instigator = NiceHumanPawn(Instigator);
if(currentContext.sourceWeapon == none)
currentContext.sourceWeapon = NiceWeapon(Weapon);
if(burstShotsMade >= GetBurstLength() && currentContext.bIsBursting){
SetTimer(0, false);
currentContext.bIsBursting = false;
}
// Lock-on update
if(Instigator.Role < Role_AUTHORITY){
period += delta;
headLevel = TraceZed(currTarget);
if(headLevel <= 0.0 || currTarget == none){
currentContext.lockonTime = 0.0;
currentContext.lockonZed = none;
}
else{
if(currTarget == currentContext.lockonZed)
currentContext.lockonTime += delta ;
else
currentContext.lockonTime = 0.0;
currentContext.lockonZed = currTarget;
}
if(period > 0.1 && currentContext.lockonTime > 0.0)
period = 0.0;
}
// Reset 'FireCount'
if(Instigator.Controller.bFire == 0 && Instigator.Controller.bAltFire == 0)
FireCount = 0;
super.ModeTick(delta);
}
simulated function Timer(){
if(!AllowFire())
burstShotsMade = GetBurstLength();
if(currentContext.bIsBursting && burstShotsMade < GetBurstLength())
ModeDoFire();
}
simulated function bool AllowFire(){
local int magAmmo;
local bool allowZeroShot;
local bool bRegularFire;
local KFPawn kfPwn;
bRegularFire = !currentContext.sourceWeapon.bHasSecondaryAmmo || ThisModeNum == 0;
//if(niceNextFireTime > Level.TimeSeconds)
// return false;
if(currentContext.sourceWeapon == none || Instigator == none)
return false;
if(currentContext.bIsBursting && burstShotsMade >= GetBurstLength())
return false;
if(currentContext.sourceWeapon.bHasChargePhase && !currentContext.sourceWeapon.bRoundInChamber && bRegularFire)
return false;
if(currentContext.sourceWeapon.secondaryCharge < default.AmmoPerFire && !bCanFireIncomplete && !bRegularFire)
return false;
// Check reloading
magAmmo = currentContext.sourceWeapon.GetMagazineAmmo();
if(currentContext.sourceWeapon != none)
allowZeroShot = (Instigator.Role == Role_AUTHORITY && !currentContext.sourceWeapon.bServerFiredLastShot);
if(currentContext.sourceWeapon.bIsReloading || (magAmmo < 1 && !allowZeroShot && bRegularFire)
|| (magAmmo < default.AmmoPerFire && !bCanFireIncomplete && !allowZeroShot && bRegularFire))
return false;
// Check pawn actions
kfPwn = KFPawn(Instigator);
if(kfPwn == none || kfPwn.SecondaryItem != none || kfPwn.bThrowingNade)
return false;
return super(WeaponFire).AllowFire();
}
simulated function DoBurst(optional bool bSkipFirstShot){
local NicePlayerController nicePlayer;
local class<NiceVeterancyTypes> niceVet;
if(NextFireTime > Level.TimeSeconds || currentContext.bIsBursting)
return;
nicePlayer = NicePlayerController(Instigator.Controller);
if(nicePlayer != none)
niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePlayer.PlayerReplicationInfo);
currentContext.bIsBursting = true;
burstShotsMade = 0;
FireCount = 0;
if(!bSkipFirstShot)
ModeDoFire();
SetTimer(FireRate / GetBurstLength(), true);
}
event ModeDoFire(){
local float Rec;
local int magAmmo;
local bool bForceBurst;
if(bDisabled || Instigator == none || Instigator.Controller == none || currentContext.sourceWeapon == none)
return;
// Update how much we can fire
magAmmo = currentContext.sourceWeapon.GetMagazineAmmo();
if(bCanFireIncomplete)
AmmoPerFire = min(default.AmmoPerFire, magAmmo);
else
AmmoPerFire = default.AmmoPerFire;
UpdateFireSpeed();
// Should we be allowed to fire?
if(!AllowFire())
return;
// Bursting
bForceBurst = bSemiMustBurst && bWaitForRelease;
if(!currentContext.bIsBursting && bForceBurst)
DoBurst(true);
if(currentContext.bIsBursting)
burstShotsMade ++;
// If we made it this far with zero AmmoPerFire, - it's a last shot that can be incomplete and was enforced, so make it shoot something
if(AmmoPerFire <= 0 && bCanFireIncomplete)
AmmoPerFire = 1;
if(Level.TimeSeconds > niceNextFireTime + FireRate){
currentContLenght = 1;
currentContext.continiousBonus = 1.0;
}
else{
currentContLenght ++;
if(currentContLenght > maxBonusContLenght)
{
if(contBonusReset)
currentContext.continiousBonus = 1.0;
}
else
currentContext.continiousBonus *= contBonus;
}
MDFEffects(AmmoPerFire);
if(Instigator.Role == Role_AUTHORITY)
MDFEffectsServer(AmmoPerFire);
else{
// Compute recoil
Rec = 1.0;
if(KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo) != none && KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill != none)
KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill.static.ModifyRecoilSpread(KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo), self, Rec);
if(currentContext.bIsBursting)
Rec = Rec / GetBurstLength();
MDFEffectsClient(AmmoPerFire, Rec);
}
}
// Fire effects that should affect both client and server
simulated function MDFEffects(float newAmmoPerFire){
local NicePlayerController nicePlayer;
if(instigator != none)
nicePlayer = NicePlayerController(instigator.controller);
if(Weapon.Owner != none && !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;
}
}
if(MaxHoldTime > 0.0)
HoldTime = FMin(HoldTime, MaxHoldTime);
Weapon.IncrementFlashCount(ThisModeNum);
niceNextFireTime = UpdateNextFireTime(niceNextFireTime);
NextFireTime = niceNextFireTime;
AmmoPerFire = newAmmoPerFire;
Load = AmmoPerFire;
HoldTime = 0;
if(currentContext.sourceWeapon != none && (zoomOutOnShot || currentContext.sourceWeapon.reloadType == RTYPE_AUTO))
currentContext.sourceWeapon.ZoomOut(false);
if(Instigator.PendingWeapon != Weapon && Instigator.PendingWeapon != none){
bIsFiring = false;
Weapon.PutDown();
}
for(currentShot = 0;currentShot < fireShots.Length;currentShot ++)
DoFireEffect();
}
// Fire effects that should only affect shooting client.
simulated function MDFEffectsClient(float newAmmoPerFire, float Rec){
local NicePlayerController nicePlayer;
if(Instigator != none)
nicePlayer = NicePlayerController(Instigator.Controller);
if(nicePlayer == none || !nicePlayer.IsZedTimeActive() || !class'NiceVeterancyTypes'.static.hasSkill(nicePlayer, class'NiceSkillSharpshooterZEDHundredGauntlets'))
ReduceAmmoClient();
InitEffects();
ShakeView();
PlayFiring();
FlashMuzzleFlash();
StartMuzzleSmoke();
currentContext.lockonTime = 0.0;
currentContext.lockonZed = none;
if(bDoClientRagdollShotFX && Weapon.Level.NetMode == NM_Client)
DoClientOnlyFireEffect();
HandleRecoil(Rec);
}
// Fire effects that should only be done on server
simulated function MDFEffectsServer(float newAmmoPerFire){
local NicePlayerController nicePlayer;
if(Instigator != none)
nicePlayer = NicePlayerController(Instigator.Controller);
HoldTime = 0;
if((Instigator == none) || (Instigator.Controller == none))
return;
Instigator.DeactivateSpawnProtection();
ServerPlayFiring();
if(currentContext.sourceWeapon.MagAmmoRemaining <= 0)
currentContext.sourceWeapon.bServerFiredLastShot = true;
}
simulated function ReduceAmmoClient(){
if(currentContext.sourceWeapon.MagAmmoRemainingClient > 0 && (ThisModeNum == 0 || !currentContext.sourceWeapon.bHasSecondaryAmmo)){
currentContext.sourceWeapon.MagAmmoRemainingClient -= Load;
if(currentContext.sourceWeapon.MagAmmoRemainingClient <= 0){
currentContext.sourceWeapon.MagAmmoRemainingClient = 0;
currentContext.sourceWeapon.bRoundInChamber = false;
}
// Force server's magazine size
currentContext.sourceWeapon.ServerReduceMag(currentContext.sourceWeapon.MagAmmoRemainingClient, Level.TimeSeconds, ThisModeNum);
}
else if(ThisModeNum == 1){
currentContext.sourceWeapon.secondaryCharge -= Load;
currentContext.sourceWeapon.ServerReduceMag(currentContext.sourceWeapon.MagAmmoRemainingClient, Level.TimeSeconds, ThisModeNum);
currentContext.sourceWeapon.ServerSetSndCharge(currentContext.sourceWeapon.secondaryCharge);
}
}
function name GetCorrectAnim(bool bLoop, bool bAimed){
local bool bIncomplete;
bIncomplete = Load < default.AmmoPerFire;
if(bLoop){
if(bAimed){
if(bIncomplete && Weapon.HasAnim(FireIncompleteLoopAimedAnim))
return FireIncompleteLoopAimedAnim;
else
return FireLoopAimedAnim;
}
else{
if(bIncomplete && Weapon.HasAnim(FireIncompleteLoopAnim))
return FireIncompleteLoopAnim;
else
return FireLoopAnim;
}
}
else{
if(bAimed){
if(bIncomplete && Weapon.HasAnim(FireIncompleteAimedAnim))
return FireIncompleteAimedAnim;
else
return FireAimedAnim;
}
else{
if(bIncomplete && Weapon.HasAnim(FireIncompleteAnim))
return FireIncompleteAnim;
else
return FireAnim;
}
}
return FireAnim;
}
function PlayFiring(){
local float RandPitch;
if(Weapon.Mesh != none){
if(FireCount > 0){
if(KFWeap.bAimingRifle){
if(Weapon.HasAnim(FireLoopAimedAnim))
Weapon.PlayAnim(GetCorrectAnim(true, true), FireLoopAnimRate, 0.0);
else if(Weapon.HasAnim(FireAimedAnim))
Weapon.PlayAnim(GetCorrectAnim(false, true), FireAnimRate, TweenTime);
else
Weapon.PlayAnim(GetCorrectAnim(false, false), FireAnimRate, TweenTime);
}
else{
if(Weapon.HasAnim(FireLoopAnim))
Weapon.PlayAnim(GetCorrectAnim(true, false), FireLoopAnimRate, 0.0);
else
Weapon.PlayAnim(GetCorrectAnim(false, false), FireAnimRate, TweenTime);
}
}
else{
if(KFWeap.bAimingRifle){
if(Weapon.HasAnim(FireAimedAnim))
Weapon.PlayAnim(GetCorrectAnim(false, true), FireAnimRate, TweenTime);
else
Weapon.PlayAnim(GetCorrectAnim(false, false), FireAnimRate, TweenTime);
}
else
Weapon.PlayAnim(GetCorrectAnim(false, false), 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);
if(!currentContext.bIsBursting)
FireCount ++;
}
// Handle setting new recoil
simulated function HandleRecoil(float Rec)
{
local int stationarySeconds;
local rotator NewRecoilRotation;
local NicePlayerController nicePlayer;
local NiceHumanPawn nicePawn;
local vector AdjustedVelocity;
local float AdjustedSpeed;
local KFWeapon KFW;
if(Instigator != none)
{
nicePlayer = NicePlayerController(Instigator.Controller);
nicePawn = NiceHumanPawn(Instigator);
}
if(nicePlayer == none || nicePawn == none)
return;
if(bResetRecoil || nicePlayer.IsZedTimeActive() && class'NiceVeterancyTypes'.static.hasSkill(nicePlayer, class'NiceSkillEnforcerZEDBarrage'))
{
Rec = 0.0;
bResetRecoil = false;
}
KFW= KFWeapon(Weapon);
if (KFW.bAimingRifle)
{
Rec *= aimingSpreadReductionCoefficient;
}
if (nicePawn.stationaryTime > 0.0 && class'NiceVeterancyTypes'.static.hasSkill(nicePlayer, class'NiceSkillHeavyStablePosition'))
{
stationarySeconds = Ceil(2 * nicePawn.stationaryTime) - 1;
Rec *= FMax(0.0, 1.0 - (stationarySeconds * class'NiceSkillHeavyStablePosition'.default.recoilDampeningBonus));
}
if (!nicePlayer.bFreeCamera)
{
if (Weapon.GetFireMode(ThisModeNum).bIsFiring || currentContext.bIsBursting)
{
NewRecoilRotation.Pitch = RandRange(maxVerticalRecoilAngle * 0.5, maxVerticalRecoilAngle);
NewRecoilRotation.Yaw = RandRange(maxHorizontalRecoilAngle * 0.5, maxHorizontalRecoilAngle);
if (!bRecoilRightOnly && 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);
// Reduce the falling recoil in low grav
NewRecoilRotation.Pitch += (AdjustedSpeed * RecoilVelocityScale * 0.5);
NewRecoilRotation.Yaw += (AdjustedSpeed * RecoilVelocityScale * 0.5);
}
else
{
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;
if (default.FireRate <= 0)
nicePlayer.SetRecoil(NewRecoilRotation, RecoilRate);
else
nicePlayer.SetRecoil(NewRecoilRotation, RecoilRate * (FireRate / default.FireRate));
}
}
}
function DoFireEffect(){
local bool bIsShotgunBullet, bForceComplexTraj;
local bool bPinpoint;
local Vector StartProj, StartTrace, X,Y,Z;
local Rotator R, Aim;
local Vector HitLocation, HitNormal;
local Actor other;
local int p;
local float activeSpread;
local ESpreadStyle activeSpreadStyle;
local int SpawnCount;
local float theta;
local NicePlayerController nicePlayer;
nicePlayer = NicePlayerController(Instigator.Controller);
Instigator.MakeNoise(1.0);
Weapon.GetViewAxes(X, Y, Z);
StartTrace = Instigator.Location + Instigator.EyePosition();
StartProj = StartTrace + X * ProjSpawnOffset.X;
if(!Weapon.WeaponCentered() && !KFWeap.bAimingRifle)
StartProj = StartProj + Weapon.Hand * Y * ProjSpawnOffset.Y + Z * ProjSpawnOffset.Z;
other = Weapon.Trace(HitLocation, HitNormal, StartProj, StartTrace, false);
activeSpread = fireShots[currentShot].spread * activeSpreadScale;
if(class'NiceVeterancyTypes'.static.hasSkill(nicePlayer, class'NiceSkillEnforcerBombard')){
bPinpoint = true;
activeSpread *= class'NiceSkillEnforcerBombard'.default.spreadMult;
}
bIsShotgunBullet = ClassIsChildOf(fireShots[currentShot].bulletClass, class'NiceShotgunPellet');
if( bIsShotgunBullet && weapon.class != class'NiceSpas' && weapon.class != class'NiceNailGun'
&& class'NiceVeterancyTypes'.static.hasSkill(nicePlayer, class'NiceSkillSupportSlugs') )
activeSpread = 0.0;
if(bIsShotgunBullet && activeSpread <= 0.0 && !bPinpoint)
bForceComplexTraj = true;
if(other != none)
StartProj = HitLocation;
Aim = AdjustAim(StartProj, AimError);
SpawnCount = Max(0, fireShots[currentShot].projPerFire * int(Load));
if(class'NiceVeterancyTypes'.static.hasSkill(nicePlayer, class'NiceSkillSupportZEDBulletStorm') && nicePlayer.IsZedTimeActive())
SpawnCount *= class'NiceSkillSupportZEDBulletStorm'.default.projCountMult;
activeSpreadStyle = fireShots[currentShot].spreadStyle;
DoTraceHack(StartProj, Aim);
currentContext.bHipfire = false;
if(currentContext.sourceWeapon != none && !currentContext.sourceWeapon.bAimingRifle
&& !currentContext.sourceWeapon.bZoomingIn && !currentContext.sourceWeapon.bZoomingOut
&& !currentContext.sourceWeapon.bFastZoomOut)
currentContext.bHipfire = true;
switch(activeSpreadStyle){
case SS_Random:
X = Vector(Aim);
for(p = 0; p < SpawnCount;p ++){
if(p > 0 || !bPinpoint){
R.Yaw = activeSpread * (FRand()-0.5);
R.Pitch = activeSpread * (FRand()-0.5);
R.Roll = activeSpread * (FRand()-0.5);
}
class'NiceProjectileSpawner'.static.MakeProjectile(StartProj, Rotator(X >> R), fireShots[currentShot], currentContext, bForceComplexTraj);
}
break;
case SS_Line:
X = Vector(Aim);
for(p = 0; p < SpawnCount;p ++){
if(p > 0 || !bPinpoint){
theta = activeSpread * PI/32768 * (p - float(SpawnCount-1)/2.0);
X.X = cos(theta);
X.Y = sin(theta);
X.Z = 0.0;
}
class'NiceProjectileSpawner'.static.MakeProjectile(StartProj, Rotator(X >> Aim), fireShots[currentShot], currentContext, bForceComplexTraj);
}
break;
case SS_None:
default:
for(p = 0; p < SpawnCount;p ++)
class'NiceProjectileSpawner'.static.MakeProjectile(StartProj, Aim, fireShots[currentShot], currentContext, bForceComplexTraj);
}
if(Instigator != none && Instigator.Role == Role_AUTHORITY){
if(Instigator.Physics != PHYS_Falling)
Instigator.AddVelocity(KickMomentum >> Instigator.GetViewRotation());
else if(Instigator.Physics == PHYS_Falling && Instigator.PhysicsVolume.Gravity.Z > class'PhysicsVolume'.default.Gravity.Z)
Instigator.AddVelocity((KickMomentum * LowGravKickMomentumScale) >> Instigator.GetViewRotation());
}
if (activeSpreadScale < 1.0) {
if (KFWeap.bAimingRifle) {
activeSpreadScale += spreadGainedPerShot * 0.5;
}
else {
activeSpreadScale += spreadGainedPerShot;
}
activeSpreadScale = FMin(1.0, activeSpreadScale);
}
}
simulated function float TraceZed(out NiceMonster tracedZed, optional out Vector hitLoc, optional out Vector hitNorm,
optional float hsMultiplier){
local Vector Start, End, hitLocation, HitNormal;
local Rotator aim;
local Actor tracedActor;
if(hsMultiplier <= 0.0)
hsMultiplier = 1.0;
MaxRange();
Start = Instigator.Location + Instigator.EyePosition();
aim = AdjustAim(Start, AimError);
End = Start + TraceRange * Vector(aim);
tracedActor = Instigator.Trace(hitLocation, hitNormal, End, Start);
hitLoc = hitLocation;
hitNorm = hitNormal;
tracedZed = NiceMonster(tracedActor);
if(tracedZed == none && ExtendedZCollision(tracedActor) != none && tracedActor.owner != none)
tracedZed = NiceMonster(tracedActor.owner);
if(tracedZed != none)
return tracedZed.IsHeadshotClient(hitLocation, Vector(aim), tracedZed.clientHeadshotScale * hsMultiplier);
return 0;
}
simulated function TraceWall(out Actor tracedActor, optional out Vector hitLoc, optional out Vector hitNorm){
local Vector Start, End, hitLocation, hitNormal;
local Rotator aim;
MaxRange();
Start = Instigator.Location + Instigator.EyePosition();
aim = AdjustAim(Start, AimError);
End = Start + TraceRange * Vector(Aim);
tracedActor = Instigator.Trace(hitLocation, hitNormal, End, Start);
hitLoc = hitLocation;
hitNorm = hitNormal;
if(KFPawn(tracedActor) != none && ExtendedZCollision(tracedActor) != none)
tracedActor = none;
}
simulated function NiceReplicationInfo GetNiceRI(){
if(Instigator != none && NicePlayerController(Instigator.Controller) != none)
return NicePlayerController(Instigator.Controller).NiceRI;
return none;
}
function DoTraceHack(Vector Start, Rotator Dir){
local Actor other;
local array<int> HitPoints;
local Vector X, End, HitLocation, HitNormal;
X = Vector(Dir);
End = Start + TraceRange * X;
other = Instigator.HitPointTrace(HitLocation, HitNormal, End, HitPoints, Start,, 1);
if(Trigger(other) != none)
other.TakeDamage(35, Instigator, HitLocation, Momentum * X, DamageType);
}
// All weapons should have 100% accuracy anyway
simulated function AccuracyUpdate(float Velocity){}
// This function is called when 'FireRate', 'FireAnimRate' or 'ReloadAnimRate' need to be updated
simulated function UpdateFireSpeed(){
local float fireSpeedMod;
local NicePlayerController nicePlayer;
fireSpeedMod = GetFireSpeed();
if (instigator != none) {
nicePlayer = NicePlayerController(instigator.controller);
}
if(NiceSingle(Weapon) != none || NiceDualies(Weapon) != none)
fireSpeedMod /= (Level.TimeDilation / 1.1);
fireSpeedMod *= 1.0 + 1.1 * (zedTimeFireSpeedUp - 1.0) * (1.1 - Level.TimeDilation);
if(nicePlayer != none && nicePlayer.IsZedTimeActive() && class'NiceVeterancyTypes'.static.HasSkill(nicePlayer, class'NiceSkillEnforcerZEDBarrage')) {
fireSpeedMod *= 2.0;
}
FireRate = default.FireRate / fireSpeedMod;
FireAnimRate = default.FireAnimRate * fireSpeedMod;
ReloadAnimRate = default.ReloadAnimRate * fireSpeedMod;
}
// This function is called when next fire time needs to be updated
simulated function float UpdateNextFireTime(float fireTimeVar) {
local float usedFireRate;
local float burstSlowDown;
local NiceHumanPawn nicePawn;
local class<NiceVeterancyTypes> niceVet;
nicePawn = NiceHumanPawn(Instigator);
if(nicePawn != none)
niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePawn.PlayerReplicationInfo);
fireTimeVar = FMax(fireTimeVar, Level.TimeSeconds);
usedFireRate = fireRate;
if (maxBonusContLenght > 1) {
if (currentContLenght <= maxBonusContLenght) {
usedFireRate *= 0.8 + 0.2 * (maxBonusContLenght - currentContLenght);
} else {
usedFireRate *= 0.8;
}
}
if(bFireOnRelease){
if(bIsFiring)
fireTimeVar += MaxHoldTime + usedFireRate;
else
fireTimeVar = Level.TimeSeconds + usedFireRate;
}
else{
if(currentContext.bIsBursting && GetBurstLength() > 1){
if( niceVet != none
&& (bBetterBurst ||
niceVet.static.hasSkill(NicePlayerController(nicePawn.Controller), class'NiceSkillCommandoExplosivePower'))
)
burstSlowDown = 1.0;
else
burstSlowDown = 1.3;
fireTimeVar += usedFireRate * burstSlowDown;
}
else
fireTimeVar += usedFireRate;
fireTimeVar = FMax(fireTimeVar, Level.TimeSeconds);
}
return fireTimeVar;
}
defaultproperties
{
zedTimeFireSpeedUp=1.000000
ProjPerFire=1
ProjectileSpeed=1524.000000
MaxBurstLength=3
bulletClass=class'NiceBullet'
contBonus=1.200000
contBonusReset=True
maxBonusContLenght=1
AmmoPerFire=1
minimalSpreadScale=1
activeSpreadScale=1
spreadGainedPerShot=0
spreadLostPerSecond=0
RecoilVelocityScale = 0
aimingSpreadReductionCoefficient = 0.5
}