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
}
