//  Weapon class for NicePack that supports:
//  1. Client-side ammo management
//      When using client-side hit detection we don't want to wait for server to send us an update on current ammunition amount.
//      So we try and keep track of it on the client instead. 
//      This is especially important for high fire rate weapons.
//      No protection against cheating implemented.
//  2. Client-side multistage reload
//      Reloading process is now handled on the client and is tightly tied to the animation. Server only receives updates on ammo amount after reload.
//      Reloading now also consists of several stages, which allows to update ammo amount at certain points of the animation and, coupled with reload cancellation, allows for partial completion of reload.
//      Stages must be manually set for each weapon by specifying animation frame at which each stage should begin. Also a name of the bone, corresponding to the weapon's magazine must be provided.
//      1) Magazine weapons
//      Magazine weapons have up to 4 reload stages:
//          - First is a 'prestage', when magazine hasn't yet been removed,
//          - Second is a main stage during which old magazine was removed, but new one wasn't yet inserted,
//          - Third is weapon charging stage that happens after magazine insertion,
//          - Fourth stage contains all the useless animation left-overs.
//      2) Shell-by-shell reload ('single') doesn't really has stages.
//      The only "feature" for this type of reload is playing the end of the reload animation in case we're reloading from 0 ammo
//      3) Auto reload
//      This describes the type of reload that is a part of a shooting animation for 1-shot weapons (and hunting shotgun).
//      It consists of 3 stages:
//          - First is unskippable part, usually the shot itself,
//          - Second is actual reloading part,
//          - Third is a trash part, that contains all the useless animation left-overs.
//      Introduced functionality allows to force reload by skipping straight to stage 2.
//      Unlike 2 previous reloads includes a stage that cannot (at least shouldn't) be interrupted by any means.
//  3. Reload cancellation
//      Initially this functionality was just a copy-paste of a1eat0r's 'Reload options' mutator.
//      Now functionality was altered to use introduced stages of reload process.
class NiceWeapon extends KFWeapon
    dependson(NicePlainData)
    abstract;
var float   lastHeadshotTime;
var float   stdFireRate;
var float   holsteredCompletition;
var bool    bLoadResourcesAsMaterial;       // Force to load all graphic resources as just materials
var bool    bUseFlashlightToToggle;
var int     MagAmmoRemainingClient;         // Tracks magazine size on client
var bool    bRoundInChamber;                // Indicates that bullet was loaded in a chamber
var float   LastMagUpdateFromClient;        // Time of the most recent magazine update, received from the client
var float   ReloadDeadLine;                 // Time, after which server should speed-up reload
var float   lastRecordedReloadRate;
var float   recordedZoomTime;
// Weapon secondary charge counter
var bool    bShowSecondaryCharge;
var int     secondaryCharge;
// HUD icons changes
var bool    bChangeClipIcon;
var bool    bChangeBulletsIcon;
var bool    bChangeSecondaryIcon;
var Texture hudClipTexture;
var Texture hudBulletsTexture;
var Texture hudSecondaryTexture;
// Laser-related variables
var         bool                        bLaserActive;               // The laser site is active
var         bool                        bAllowFreeDot;              // Allows dot from the laser to freely follow movements of the bone (the same way as it usually does during reload)
var()       class<InventoryAttachment>  LaserAttachmentClass;       // First person laser attachment class
var         Actor                       LaserAttachment;            // First person laser attachment
var         Actor                       altLaserAttachment;         // Alternative laser attachment
var()       byte                        LaserType;                  // current laser type
var         Vector                      LaserAttachmentOffset;      // relative offset from attachment bone
var         Vector                      altLaserAttachmentOffset;   // relative offset from alternative attachment bone
var         Rotator                     LaserAttachmentRotation;    // How should we rotate the bone, our laser is attached to?
var         Rotator                     altLaserAttachmentRotation; // How should we rotate the bone, our alternative laser is attached to?
var const   class<ScrnLocalLaserDot>    LaserDotClass;
var         ScrnLocalLaserDot           LaserDot;
var         ScrnLocalLaserDot           altLaserDot;
var         name                        LaserAttachmentBone;
var         name                        altLaserAttachmentBone;
// Prossible reasons for reload cancel
enum ERelCancelCause{
    CANCEL_FIRE,
    CANCEL_ALTFIRE,
    CANCEL_NADE,
    CANCEL_COOKEDNADE,
    CANCEL_SWITCH,
    CANCEL_PASSIVESWITCH,
    CANCEL_AIM,
    CANCEL_RELOAD
};
// Possible reload types for main reload (forced by 'ReloadMeNow()' function)
// Note that weapons with two fire-modes can have 'RTYPE_MAG' or 'RTYPE_SINGLE' for main reload and still use auto reload (e.g. for secondary fire of M4 203)
enum ERelType{
    RTYPE_MAG,      // Magazine-type reload (like for commando rifles or M14 EBR)
    RTYPE_SINGLE,   // Single-shell reload (like lar or shotgun)
    RTYPE_AUTO      // Means that auto reload is a main reload for this weapon (hunting shotgun and 1-shot weapons such as M79, M99, xbow, etc)
};
var ERelType reloadType;
// Possible stages of a magazine reload
enum ERelStage{
    RSTAGE_NONE,    // Weapon isn't being reloaded
    RSTAGE_PREREL,  // Magazine wasn't yet removed, reload can be safely interrupted
    RSTAGE_MAINREL, // Magazine was already removed, reload cannot be interrupted without penalties
    RSTAGE_POSTREL, // Magazine was replaced, reload can be safely interrupted, but post-reload stage (weapon charging) will have to be redone
    RSTAGE_TRASH    // Non-functioning frames of animation after inserting magazine and charging weapon
};
var bool        bServerFiredLastShot;
var bool        bGiveObsessiveBonus;
// Magazine reload-related variables
var float       reloadPreEndFrame;      // Frame number, after which magazine is removed, so interrupting reload will result in zero magazine ammo
var float       reloadEndFrame;         // Frame number, after which new magazine is inserted, but gun wasn't yet charged, so if reload is interrupted after this stage, - charging would need to be redone later
var float       reloadChargeEndFrame;   // Frame number, after which weapon's reload can be interrupted without any penalties
var float       reloadMagStartFrame;    // Frame number, from which to start animation magazine insertion; don't confuse it with a point at which magazine removal starts in a full reload animation
var float       reloadChargeStartFrame; // Frame number, from which to start animating weapon charging; don't confuse it with a point at which charging starts in a full reload animation
var bool        bMagazineOut;           // Indicates if magazine is currently removed from the weapon
var name        magazineBone;           // Bone that we need to hide when magazine is out
var bool        bHasChargePhase;        // Weapon needs to be charged at some point
var bool        bNeedToCharge;          // This flag marks the need to finish post-stage of reload (gun charging stage)
var ERelStage   currentRelStage;        // Current stage of the magazine reload
// Following variables are a result of poor initial design of this system that didn't account for the need to do reloads of dual weapons
// The whole thing will be rewritten from the ground up, but for now that's far from priority goal
// What I'm adding with these is an event system that would call a 'ReloadEvent' function when reload passes a certain point
// Only magazine reload supports these
struct EventRecord{
    var string  eventName;
    var float   eventFrame;
};
var array<EventRecord>  relEvents;
var float               lastEventCheckFrame;
// Single reload-related variables
var int             subReloadStage;     // Substage is part of animation between two consecutive shell loadings; they're numbered starting from zero
var bool            alwaysPlayAnimEnd;  // Should we always force playing end of the reload animation?
var array<float>    reloadStages;       // Array of frame numbers that indicate moments when ammo should be added
// Auto reload-related structure, enum and variables
// Auto reload activates by itself whenever appropriate animation starts to play; structure object below must be provided for each such animation
struct AutoReloadAnimDesc{
    var name    animName;           // Name of the animation for which instance of this struct is prepared
    var float   canInterruptFrame;  // Frame, starting from which reload can be interrupted
    var float   trashStartFrame;    // Frame from which starts useless part of animation
    var float   resumeFrame;        // Frame from which we must resume reload if it was previously interrupted
    var float   speedFrame;         // Frame from which we must apply reload speed bonus (so that initial, shooting part remains unaffected)
};
// Array that contains information about all possible animations that can treated as auto-reload
var array<AutoReloadAnimDesc> autoReloadsDescriptions;
enum EAutoRelStage{
    RAUTOSTAGE_NONE,            // Auto reload is inactive
    RAUTOSTAGE_UNINTERRUPTIBLE, // Auto reload is active and it's currently uninterruptible (usually that's the shooting part of animation)
    RAUTOSTAGE_INTERRUPTIBLE    // Auto reload is active and can be interrupted; ammo is reloaded at the end of this state (it will be skipped in case there's no more ammunition)
};
var bool            bAllowAutoReloadSkip;   // Indicates if switching to another weapon should allow player to skip interruptible part of auto reload completely
var int             currentAutoReload;      // Active auto reload always corresponds to a certain animation; this is index of it's corresponding data in 'autoReloadsDescriptions'
var EAutoRelStage   currentAutoReloadStage;
var bool            bAutoReload;            // Indicates that current reload is the auto reload
var bool            bAutoReloadInterrupted; // Indicates if auto reload was interrupted (and must be repeated as soon as possible)
var bool            bAutoReloadPaused;      // This is used to 'pause' auto reload in case we need to interrupt it for a moment (and then immediately continue from where we left), i.e. when throwing a grenade
var float           autoReloadPauseFrame;   // Frame at which current pause began
var bool            bAutoReloadRateApplied; // Flag that remembers whether or not we've already applied reload speed up for current auto reload (to avoid constant animation's speed updates)
var float           autoReloadSpeedModifier;
var bool            updatedDefaultReloadValues;
// Acrtive reload-related variables
// Active reload state
enum EActiveReloadState{
    ACTR_NONE,      // Activation wasn't yet attempted during current reload
    ACTR_FAIL,      // Activation failed
    ACTR_SUCCESS    // Activation succeeded
};
var bool                bCanActiveReload;   // Can we even use active reload with this weapon?
var float               activeSlowdown;     // How much should we slow down the speed of reload if player has failed?
var float               activeSpeedup;      // How much should we speedup active reload if player succeeded?
var EActiveReloadState  activeReloadState;  // Current state of active reload, only applicable during reload
var float               activeWindow;       // How long (0.0 is zero long, 1.0 is all animation) must be a time window during which you can activate active reload
replication{
    reliable if(Role < ROLE_Authority)
       ServerReduceMag, ServerSetMagSize, ServerSetSndCharge, ServerReload, ServerShiftReloadTime, ServerStopReload,
           ServerSetCharging, ServerSetLaserType;
    reliable if(Role == ROLE_Authority)
       ClientForceInterruptReload, ClientReloadMeNow, ClientSetMagSize, ClientSetSndCharge, ClientPutDown,
           ClientThrowGrenade, ClientCookGrenade, ClientTryPendingWeapon, ClientUpdateWeaponMag, ClientSetLaserType,
           ClientReloadAmmo;
    reliable if(Role == ROLE_Authority)
       holsteredCompletition;
}
static function PreloadAssets(Inventory Inv, optional bool bSkipRefCount){
    local int i;
    if(!bSkipRefCount)
       default.ReferenceCount ++;
    if(default.Mesh == none && default.MeshRef != "")
       UpdateDefaultMesh(SkeletalMesh(DynamicLoadObject(default.MeshRef, class'SkeletalMesh')));
    if(default.HudImage == none && default.HudImageRef != "")
       default.HudImage = texture(DynamicLoadObject(default.HudImageRef, class'texture'));
    if(default.SelectedHudImage == none && default.SelectedHudImageRef != "")
       default.SelectedHudImage = texture(DynamicLoadObject(default.SelectedHudImageRef, class'texture'));
    if(default.SelectSound == none && default.SelectSoundRef != "")
       default.SelectSound = sound(DynamicLoadObject(default.SelectSoundRef, class'sound'));
    for(i = 0;i < default.SkinRefs.Length;i ++)
       if(default.SkinRefs[i] != "" && (default.Skins.Length < i + 1 || default.Skins[i] == none)){
           default.Skins[i] = none;
           if(default.bLoadResourcesAsMaterial)
               default.Skins[i] = Material(DynamicLoadObject(default.SkinRefs[i], class'Material'));
           // Try to load as various types of materials
           if(default.Skins[i] == none)
               default.Skins[i] = Combiner(DynamicLoadObject(default.SkinRefs[i], class'Combiner', true));
           if(default.Skins[i] == none)
               default.Skins[i] = FinalBlend(DynamicLoadObject(default.SkinRefs[i], class'FinalBlend', true));
           if(default.Skins[i] == none)
               default.Skins[i] = Shader(DynamicLoadObject(default.SkinRefs[i], class'Shader', true));
           if(default.Skins[i] == none)
               default.Skins[i] = Texture(DynamicLoadObject(default.SkinRefs[i], class'Texture', true));
           if(default.Skins[i] == none)
               default.Skins[i] = Material(DynamicLoadObject(default.SkinRefs[i], class'Material'));
       }
    if(NiceWeapon(Inv) != none){
       Inv.LinkMesh(default.Mesh);
       NiceWeapon(Inv).HudImage = default.HudImage;
       NiceWeapon(Inv).SelectedHudImage = default.SelectedHudImage;
       NiceWeapon(Inv).SelectSound = default.SelectSound;
       for(i = 0; i < default.SkinRefs.Length;i ++)
           Inv.Skins[i] = default.Skins[i];
    }
}
static function bool UnloadAssets(){
    local int i;
    default.ReferenceCount--;
    UpdateDefaultMesh(none);
    default.HudImage = none;
    default.SelectedHudImage = none;
    default.SelectSound = none;
    for(i = 0;i < default.SkinRefs.Length; i ++)
       default.Skins[i] = none;
    return default.ReferenceCount == 0;
}
simulated function PostBeginPlay(){
    local float swapSpeedMod, reloadSpeedMod;

    if(default.recordedZoomTime < 0)
       default.recordedZoomTime = ZoomTime;
    recordedZoomTime = default.recordedZoomTime;
    // Default variables
    LastMagUpdateFromClient = 0.0;
    bNeedToCharge           = false;
    bMagazineOut            = false;
    currentRelStage         = RSTAGE_NONE;
    lastEventCheckFrame     = -1.0;
    // Fill sub reload stages
    fillSubReloadStages();
    // Auto fill reload stages for 'RTYPE_SINGLE' reload
    if(reloadType == RTYPE_SINGLE)
       UpdateSingleReloadVars();
    if(reloadType == RTYPE_AUTO)
       bHasChargePhase = false;
    if(reloadChargeStartFrame < 0.0 || reloadChargeEndFrame < 0.0)
       bHasChargePhase = false;
    if(FireModeClass[0] != none)
       stdFireRate = FireModeClass[0].default.fireRate;
    super.PostBeginPlay();
    // Hack solution - speed up reload and swap speed
    swapSpeedMod = 2.0;
    reloadSpeedMod = 1.5;
    bringUpTime /= swapSpeedMod;
	putDownTime /= swapSpeedMod;
    selectAnimRate *= swapSpeedMod;
    putDownAnimRate *= swapSpeedMod;
    quickPutDownTime /= swapSpeedMod;
    quickBringUpTime /= swapSpeedMod;
    if (!default.updatedDefaultReloadValues) {
        if(reloadType == RTYPE_AUTO) {
            if (fireModeClass[0] != none) {
                fireModeClass[0].default.FireAnimRate *= reloadSpeedMod;
            }
            if (fireModeClass[1] != none) {
                fireModeClass[1].default.FireAnimRate *= reloadSpeedMod;
            }
        } else {
            reloadRate /= reloadSpeedMod;
            reloadAnimRate *= reloadSpeedMod;
            default.reloadRate = reloadRate;
            default.reloadAnimRate = reloadAnimRate;
        }
        default.updatedDefaultReloadValues = true;
    }
}

// Allows to prevent leaving iron sights unwillingly
function bool ShouldLeaveIronsight(){
    local class<NiceVeterancyTypes> niceVet;
    niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(Instigator.PlayerReplicationInfo);
    if(niceVet != none && niceVet.static.hasSkill(NicePlayerController(Instigator.Controller), class'NiceSkillEnforcerUnstoppable'))
       return false;
    return true;
}
// Updates max value for this weapon's ammunition
simulated function UpdateWeaponAmmunition(){
    local int i;
    for(i = 0;i < NUM_FIRE_MODES;i ++){
       if(Ammo[i] == none)
           continue;
       if(NiceAmmo(Ammo[i]) != none)
           NiceAmmo(Ammo[i]).UpdateAmmoAmount();
    }
}
function UpdateWeaponMag(){
    UpdateMagCapacity(Instigator.PlayerReplicationInfo);
    MagAmmoRemaining = Min(MagAmmoRemaining, MagCapacity);
    ClientSetMagSize(MagAmmoRemaining, bRoundInChamber);
}
simulated function ClientUpdateWeaponMag(){
    local int actualMag;
    UpdateMagCapacity(Instigator.PlayerReplicationInfo);
    actualMag = MagAmmoRemainingClient;
    if(bHasChargePhase && bRoundInChamber && actualMag > 0)
       actualMag --;
    MagAmmoRemainingClient = Min(MagAmmoRemainingClient, MagCapacity);
    if(bHasChargePhase && bRoundInChamber)
       actualMag ++;
    ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds);
}
// Overloaded to properly reduce magazine's size
simulated function bool ConsumeAmmo(int Mode, float Load, optional bool bAmountNeededIsMax){
    local Inventory Inv;
    local bool bOutOfAmmo;
    local KFWeapon KFWeap;
    if(super(Weapon).ConsumeAmmo(Mode, Load, bAmountNeededIsMax)){
       if(Load > 0 && (Mode == 0 || bReduceMagAmmoOnSecondaryFire)){
           MagAmmoRemaining -= Load;
           if(MagAmmoRemaining < 0)
               MagAmmoRemaining = 0;
           if(MagAmmoRemaining <= 0)
               bRoundInChamber = false;
       }

       NetUpdateTime = Level.TimeSeconds - 1;

       if(FireMode[Mode].AmmoPerFire > 0 && InventoryGroup > 0 && !bMeleeWeapon && bConsumesPhysicalAmmo &&
           (Ammo[0] == none || FireMode[0] == none || FireMode[0].AmmoPerFire <= 0 || Ammo[0].AmmoAmount < FireMode[0].AmmoPerFire) &&
           (Ammo[1] == none || FireMode[1] == none || FireMode[1].AmmoPerFire <= 0 || Ammo[1].AmmoAmount < FireMode[1].AmmoPerFire)){
           bOutOfAmmo = true;

           for(Inv = Instigator.Inventory;Inv != none; Inv = Inv.Inventory){
               KFWeap = KFWeapon(Inv);

               if(Inv.InventoryGroup > 0 && KFWeap != none && !KFWeap.bMeleeWeapon && KFWeap.bConsumesPhysicalAmmo &&
                    ((KFWeap.Ammo[0] != none && KFWeap.FireMode[0] != none && KFWeap.FireMode[0].AmmoPerFire > 0 &&KFWeap.Ammo[0].AmmoAmount >= KFWeap.FireMode[0].AmmoPerFire) ||
                    (KFWeap.Ammo[1] != none && KFWeap.FireMode[1] != none && KFWeap.FireMode[1].AmmoPerFire > 0 && KFWeap.Ammo[1].AmmoAmount >= KFWeap.FireMode[1].AmmoPerFire))){
                   bOutOfAmmo = false;
                   break;
               }
           }

           if(bOutOfAmmo)
               PlayerController(Instigator.Controller).Speech('AUTO', 3, "");
       }
       return true;
    }
    return false;
}
// Forces update for client's magazine ammo counter
// In case we are using client-side hit-detection, client itself manages remaining ammunition in magazine, but in some cases we want server to dictate current magazine amount
// This function sets client's mag size to a given value
simulated function ClientSetMagSize(int newMag, bool bChambered){
    MagAmmoRemainingClient = newMag;
    bRoundInChamber = bChambered;
    if(MagAmmoRemainingClient > 0 && bHasChargePhase && !bRoundInChamber)
       bNeedToCharge = true;
}
// This function allows clients to change magazine size without altering total ammo amount
// It allows clients to provide time-stamps, so that older change won't override a newer one
function ServerSetMagSize(int newMag, bool bChambered, float updateTime){
    magAmmoRemaining = newMag;
    bRoundInChamber = bChambered;
    if(LastMagUpdateFromClient <= updateTime){
       LastMagUpdateFromClient = updateTime;
       if(newMag > 0)
           bServerFiredLastShot = false;
    }
}
// This function allows clients to change magazine size along with total ammo amount on the server (to update ammo counter in client-side mode)
// It allows clients to provide time-stamps, so that older change won't override a newer one
// Intended to be used for decreasing ammo count from shooting and cannot increase magazine size
simulated function ServerReduceMag(int newMag, float updateTime, int Mode){
    local int delta;
    if(Mode == 0 || !bHasSecondaryAmmo){
       delta = magAmmoRemaining - newMag;
       // Only update later changes that actually decrease magazine
       if(LastMagUpdateFromClient <= updateTime && delta > 0){
           LastMagUpdateFromClient = updateTime;
           ConsumeAmmo(Mode, delta);
       }
    }
    else
       ConsumeAmmo(Mode, 1);
}
// Forces either 'AddReloadedAmmo' or 'AddAutoReloadedAmmo' (depending on which one is appropriate) function on client
// Somewhat of a hack to allow server force-add ammo to the weapon
simulated function ClientReloadAmmo(){
    if(reloadType == RTYPE_AUTO)
       AddAutoReloadedAmmo();
    else
       AddReloadedAmmo();
    bNeedToCharge   = false;
    bMagazineOut    = false;
    if(bHasChargePhase)
       bRoundInChamber = true;
    ResetReloadVars();
}
// Adds appropriate amount of ammo during reload.
// Up to full mag for magazine reload, 1 ammo per call for single reload.
// Isn't called in case of auto reload, use 'AddAutoReloadedAmmo' for that.
simulated function AddReloadedAmmo(){
    UpdateMagCapacity(Instigator.PlayerReplicationInfo);
    if(reloadType == RTYPE_MAG){
       if(AmmoAmount(0) >= MagCapacity){
           MagAmmoRemainingClient = MagCapacity;
           if(bRoundInChamber)
               MagAmmoRemainingClient ++;
       }
       else
           MagAmmoRemainingClient = AmmoAmount(0);
    }
    else if(reloadType == RTYPE_SINGLE){
       if(AmmoAmount(0) - MagAmmoRemainingClient > 0 && MagAmmoRemainingClient < MagCapacity)
           MagAmmoRemainingClient ++;
    }
    ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds);
    if(PlayerController(Instigator.Controller) != none && KFSteamStatsAndAchievements(PlayerController(Instigator.Controller).SteamStatsAndAchievements) != none)
       KFSteamStatsAndAchievements(PlayerController(Instigator.Controller).SteamStatsAndAchievements).OnWeaponReloaded();
}
// Function that manages ammo replenishing for auto reload
simulated function AddAutoReloadedAmmo(){
    if(reloadType != RTYPE_AUTO){
       secondaryCharge = 1;
       ServerSetSndCharge(secondaryCharge);
    }
    else{
       if(AmmoAmount(0) > 0)
           MagAmmoRemainingClient = 1;
       else
           MagAmmoRemainingClient = 0;
       ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds);
    }
}
simulated function float TimeUntillReload(){
    if(reloadType == RTYPE_MAG)
       return FMax(reloadChargeEndFrame, reloadEndFrame) * GetAnimDuration(ReloadAnim);
    else if(reloadType == RTYPE_SINGLE && reloadStages.Length > 0)
       return reloadStages[0] * GetAnimDuration(ReloadAnim);
    else if(reloadType == RTYPE_AUTO && autoReloadsDescriptions.Length > 0)
       return autoReloadsDescriptions[0].trashStartFrame * GetAnimDuration(autoReloadsDescriptions[0].animName);
    return 60.0;
}
// Interrupt the reloading animation, which may reset the weapon's magAmmoRemaining;
// Don't allow Fire() and AltFire() to interrupt by shooting with empty magazine.
simulated function ClientForceInterruptReload(ERelCancelCause cause){
    local bool bActualAllowAutoReloadSkip;
    local bool bCauseFire, bCauseAltFire, bCauseNade, bCauseAim, bCauseSwitch;
    local HUDKillingFloor HUD;
    local NiceHumanPawn nicePawn;
    local NicePlayerController nicePlayer;
    local bool bDisplayInventory, bWeReallyShould;
    // Determine causes flags
    bCauseFire =        cause == CANCEL_FIRE;
    bCauseAltFire =     cause == CANCEL_ALTFIRE;
    bCauseNade =        (cause == CANCEL_NADE) || (cause == CANCEL_COOKEDNADE);
    bCauseAim =         cause == CANCEL_AIM;
    bCauseSwitch =      cause == CANCEL_SWITCH || cause == CANCEL_PASSIVESWITCH;
    // Is player looking at inventory?
    nicePlayer = NicePlayerController(Instigator.Controller);
    if(nicePlayer == none || !bIsReloading)
       return;
    HUD = HUDKillingFloor(nicePlayer.MyHUD);
    bDisplayInventory = HUD != none && HUD.bDisplayInventory;
    // General checks: is there any meaning to reset reload for provided cause?
    if(bCauseFire)
       bWeReallyShould = !bNeedToCharge && (bDisplayInventory || magAmmoRemainingClient >= GetFireMode(0).ammoPerFire);
    else if(bCauseAltFire)
       bWeReallyShould = !bNeedToCharge && AltFireCanForceInterruptReload()
           && (reloadType == RTYPE_AUTO || !bAutoReload);
    else if(bCauseNade){
       bWeReallyShould = true;
       nicePawn = NiceHumanPawn(nicePlayer.Pawn);
       if(nicePawn == none)
           bWeReallyShould = false;
       else{
           if(nicePawn.PlayerGrenade == none)
               nicePawn.PlayerGrenade = nicePawn.FindPlayerGrenade();
           if(nicePawn.PlayerGrenade == none || !nicePawn.PlayerGrenade.HasAmmo())
               bWeReallyShould = false;
       }
    }
    else if(bCauseAim)
       bWeReallyShould = magAmmoRemainingClient >= GetFireMode(0).ammoPerFire && GetReloadStage() != RSTAGE_POSTREL;
    else
       bWeReallyShould = true;
    // If this is a magazine type reload - check player's preferences
    if(reloadType == RTYPE_MAG){
       if(GetReloadStage() == RSTAGE_TRASH)
           bWeReallyShould = true;
       else if(bCauseFire || bCauseAltFire)
           bWeReallyShould = bWeReallyShould && nicePlayer.bRelCancelByFire;
       else if(bCauseSwitch)
           bWeReallyShould = bWeReallyShould && nicePlayer.bRelCancelBySwitching;
       else if(cause == CANCEL_NADE)
           bWeReallyShould = bWeReallyShould && nicePlayer.bRelCancelByNades;
       else if(bCauseAim)
           bWeReallyShould = bWeReallyShould && nicePlayer.bRelCancelByAiming;
    }
    // Allow interrupting auto reload (by pausing) to throw a grenade
    if(bAutoReload){
       if(cause == CANCEL_NADE && bWeReallyShould){
           bAutoReloadPaused = true;
           autoReloadPauseFrame = GetCurrentAnimFrame();
       }
       else if(currentAutoReloadStage == RAUTOSTAGE_UNINTERRUPTIBLE)
           bWeReallyShould = false;
    }
    // Interrupt if we really should
    if(bIsReloading && bWeReallyShould){
       HideMagazine(bMagazineOut);
       if(bAutoReload){
           bActualAllowAutoReloadSkip = bAllowAutoReloadSkip;
           if(KFGameType(Level.Game) != none)
               bActualAllowAutoReloadSkip = bAllowAutoReloadSkip
                   || KFGameType(Level.Game).WaveNum == KFGameType(Level.Game).FinalWave;
           if(bActualAllowAutoReloadSkip && bCauseSwitch && !bAutoReloadPaused){
               AddAutoReloadedAmmo();
               bAutoReloadRateApplied = false;
           }
           else if(bCauseSwitch || bCauseNade)
               bAutoReloadInterrupted = true;
       }
       ServerStopReload();
       bIsReloading = false;
       bAutoReload = false;
       lastEventCheckFrame = -1.0;
       currentAutoReloadStage = RAUTOSTAGE_NONE;
       PlayIdle();

       if(bCauseNade){
           if(cause == CANCEL_NADE && KFHumanPawn(Instigator) != none)
               ClientThrowGrenade();
           else if(cause == CANCEL_COOKEDNADE && ScrnHumanPawn(Instigator) != none)
               ClientCookGrenade();
       }
       else if(cause == CANCEL_SWITCH)
           ClientPutDown();
    }
}
// Indicates if alt. fire should also interrupt reload
simulated function bool AltFireCanForceInterruptReload(){
    if(FireModeClass[1] != none && FireModeClass[1] != class'KFMod.NoFire' && (NiceFire(FireMode[1]) == none || !NiceFire(FireMode[1]).bDisabled))
       return true;
    return false;
}
// Auxiliary functions to force certain function on client-side
simulated function bool ClientPutDown(){
    return PutDown();
}
simulated function ClientThrowGrenade(){
    if(KFHumanPawn(Instigator) != none)
       KFHumanPawn(Instigator).ThrowGrenade();
}
simulated function ClientCookGrenade(){
    if(ScrnHumanPawn(Instigator) != none)
       ScrnHumanPawn(Instigator).CookGrenade();
}
// Functions that we need to reload in order to allow reload interruption on certain actions
simulated function bool PutDown(){
    if(NicePlayerController(Instigator.Controller) != none)
       ClientForceInterruptReload(CANCEL_SWITCH);
    if(!bIsReloading)
       HideMagazine(bMagazineOut);
    TurnOffLaser();
    return super.PutDown();
}
simulated function Fire(float F){
    if(NicePlayerController(Instigator.Controller) != none)
       ClientForceInterruptReload(CANCEL_FIRE);
    super.Fire(F);
}
simulated function AltFire(float F){
    if(NicePlayerController(Instigator.Controller) != none)
       ClientForceInterruptReload(CANCEL_ALTFIRE);
    Super.AltFire(F);
    // Also request auto reload on alt. fire
    if(!bHasSecondaryAmmo && AltFireCanForceInterruptReload() && magAmmoRemainingClient <= 0)
       ServerRequestAutoReload();
    else if(bHasSecondaryAmmo && !bAutoReload && FireModeClass[1] != class'KFMod.NoFire' && secondaryCharge <= 0)
       ResumeAutoReload(autoReloadsDescriptions[currentAutoReload].resumeFrame);
}
simulated exec function ToggleIronSights(){
    if(NicePlayerController(Instigator.Controller) != none)
       ClientForceInterruptReload(CANCEL_AIM);
    Super.ToggleIronSights();
}
simulated exec function IronSightZoomIn(){
    if(NicePlayerController(Instigator.Controller) != none)
       ClientForceInterruptReload(CANCEL_AIM);
    Super.IronSightZoomIn();
}
// Function for filling-up reload stages in a single reload
simulated function FillSubReloadStages(){}
simulated function UpdateSingleReloadVars(){
    reloadPreEndFrame = 0.0;
    if(reloadStages.Length > 0)
       reloadEndFrame = reloadStages[reloadStages.Length - 1];
    else
       reloadEndFrame = 0.0;
    reloadChargeStartFrame = -1.0;
    reloadChargeEndFrame = -1.0;
    bHasChargePhase = false;
}
// Function that setups all the variable for next reload; this DOES NOT start reload animation or reload itself
simulated function SetupReloadVars(optional bool bIsActive, optional int animationIndex){
    if(Role == ROLE_Authority)
       return;
    bIsReloading = true;
    bAutoReloadRateApplied = false;
    bAutoReloadInterrupted = false;
    bAutoReloadPaused = false;
    currentAutoReloadStage = RAUTOSTAGE_UNINTERRUPTIBLE;
    autoReloadPauseFrame = 0.0;
    currentAutoReload = 0;
    activeReloadState = ACTR_NONE;
    if(bIsActive){
       bAutoReload = true;
       currentAutoReload = Clamp(animationIndex, 0, autoReloadsDescriptions.Length - 1);
    }
}
// Reset all the necessary variables after reloading
simulated function ResetReloadVars(){
    if(Role == ROLE_Authority)
       return;
    ServerStopReload();
    bIsReloading = false;
    bAutoReload = false;
    bAutoReloadInterrupted = false;
    currentAutoReloadStage = RAUTOSTAGE_NONE;
    currentAutoReload = 0;
    activeReloadState = ACTR_NONE;
    ClientTryPendingWeapon();
}
// Does what 'SetAnimFrame' does + updates 'lastEventCheckFrame' for correct event handling
// Use this instead of 'SetAnimFrame', unless you have a very specific need and know what you're doing
simulated function ScrollAnim(float newFrame){
    SetAnimFrame(newFrame);
    lastEventCheckFrame = newFrame;
}
// Starts reload at given rate from given stage.
// If specified stage is either 'RSTAGE_NONE' or 'RSTAGE_PREREL', - start reload from the beginning
simulated function PlayReloadAnimation(float rate, ERelStage stage){
    if(!HasAnim(ReloadAnim))
       return;
    PlayAnim(ReloadAnim, rate, 0.0);
    if(stage != RSTAGE_NONE && stage != RSTAGE_PREREL){
       if(stage == RSTAGE_MAINREL){
           ScrollAnim(reloadMagStartFrame);
           HideMagazine(false);
       }
       else if(stage == RSTAGE_POSTREL)
           ScrollAnim(reloadChargeStartFrame);
       else if(stage == RSTAGE_TRASH)
           ScrollAnim(reloadChargeEndFrame);
    }
}
// Resumes previously interrupted auto reload.
// Doesn't do any check for whether or not interruption took place
simulated function ResumeAutoReload(float startFrame){
    local float ReloadMulti;
    if(!HasAnim(autoReloadsDescriptions[currentAutoReload].animName))
       return;
    ReloadMulti = GetCurrentReloadMult();
    PlayAnim(autoReloadsDescriptions[currentAutoReload].animName, AutoReloadBaseRate() * ReloadMulti, 0.0);
    if(startFrame <= 0)
       startFrame = 0.0;
    ScrollAnim(startFrame);
    ServerReload((1 - startFrame) * AutoReloadBaseRate() / ReloadMulti);
    SetupReloadVars(true, currentAutoReload);
}
// Returns reload speed that weapon should have at the moment
simulated function float GetFittingReloadSpeed(){
    return default.ReloadAnimRate * GetCurrentReloadMult();
}
// Updates current reload rate
simulated function UpdateReloadRate(){
    lastRecordedReloadRate = GetFittingReloadSpeed();
    if(bIsReloading)
       ChangeReloadRate(lastRecordedReloadRate);
}
// Changes rate of current animation, allowing it to continue from the same frame
simulated function ChangeReloadRate(float newRate){
    local name SeqName;
    local float AnimFrame, AnimRate;
    GetAnimParams(0, SeqName, AnimFrame, AnimRate);
    if(AnimFrame < 0)
       AnimFrame = 0;
    if(!bAutoReload && SeqName != ReloadAnim)
       return;
    if(bAutoReload)
       PlayAnim(SeqName, newRate, 0.0);
    else
       PlayReloadAnimation(newRate, RSTAGE_NONE);
    ScrollAnim(AnimFrame);
}
// New handler for client's reload request
// Can be called directly as a command or automatically, by intercepting ReloadMeNow command (handled in 'NiceInteraction')
exec simulated function ClientReloadMeNow(){
    local float ReloadMulti;
    local NicePlayerController nicePlayer;
    local class<NiceVeterancyTypes> niceVet;
    if(reloadType != RTYPE_AUTO && bAutoReload && !IsMagazineFull())
       ClientForceInterruptReload(CANCEL_RELOAD);
    nicePlayer = NicePlayerController(Instigator.Controller);
    if(nicePlayer != none)
       niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePlayer.PlayerReplicationInfo);
    bGiveObsessiveBonus = false;
    if(bIsReloading)
       AttemptActiveReload();
    else if(niceVet.static.hasSkill(nicePlayer, class'NiceSkillSupportObsessive') && GetMagazineAmmo() >= float(MagCapacity) * class'NiceSkillSupportObsessive'.default.reloadLevel)
       bGiveObsessiveBonus = true;
    if(!AllowReload())
       return;
    if(reloadType == RTYPE_AUTO)
       ResumeAutoReload(autoReloadsDescriptions[currentAutoReload].resumeFrame);
    else{
       SetupReloadVars();
       if(bHasAimingMode && bAimingRifle && !bAutoReload){
           FireMode[1].bIsFiring = false;
           ZoomOut(false);
           ServerZoomOut(false);
       }
       subReloadStage = 0;
       ReloadMulti = GetCurrentReloadMult();
       if(bMagazineOut && reloadMagStartFrame >= 0){
           ServerReload((1 - reloadMagStartFrame) * default.ReloadRate / ReloadMulti);
           PlayReloadAnimation(default.ReloadAnimRate * ReloadMulti, RSTAGE_MAINREL);
       }
       else{
           ServerReload(default.ReloadRate / ReloadMulti);
           PlayReloadAnimation(default.ReloadAnimRate * ReloadMulti, RSTAGE_NONE);
       }
    }
}
// Tells server that reload was started and how long should it take
function ServerReload(float duration, optional bool autoReload){
    bIsReloading = true;
    Instigator.SetAnimAction(WeaponReloadAnim);
    ReloadDeadLine = Level.TimeSeconds + duration;
    if(bHasAimingMode && bAimingRifle && !autoReload)
       FireMode[1].bIsFiring = false;
    if(Level.Game.NumPlayers > 1 && KFGameType(Level.Game).bWaveInProgress && KFPlayerController(Instigator.Controller) != none &&
       Level.TimeSeconds - KFPlayerController(Instigator.Controller).LastReloadMessageTime > KFPlayerController(Instigator.Controller).ReloadMessageDelay){
       KFPlayerController(Instigator.Controller).Speech('AUTO', 2, "");
       KFPlayerController(Instigator.Controller).LastReloadMessageTime = Level.TimeSeconds;
    }
}
// Shift deadline of reload on server
function ServerShiftReloadTime(float delta){
    if(bIsReloading)
       ReloadDeadLine += delta;
}
// Forces reload to stop on server
function ServerStopReload(){
    bIsReloading = false;
}
// Reloaded to implement new reload mechanism
simulated function WeaponTick(float dt){
    local int i;
    local name SeqName;
    local float ReloadMulti;
    local float AnimFrame, AnimRate;
    local ERelStage newStage;
    if(lastRecordedReloadRate != GetFittingReloadSpeed())
       UpdateReloadRate();
    // Resume charging magazine weapon next time we can
    if(bNeedToCharge && GetReloadStage() == RSTAGE_NONE){
       GetAnimParams(0, SeqName, AnimFrame, AnimRate);
       if(SeqName == IdleAnim){
           ReloadMulti = GetCurrentReloadMult();
           if(Role == ROLE_Authority)
               ServerReload((1 - reloadChargeStartFrame) * default.ReloadRate / ReloadMulti);
           else{
               SetupReloadVars();
               PlayReloadAnimation(ReloadAnimRate * ReloadMulti, RSTAGE_POSTREL);
           }
       }
    }
    // Resume auto reload from pause next time we can
    if(bAutoReloadPaused && Role < ROLE_Authority && ClientGrenadeState == GN_NONE){
       GetAnimParams(0, SeqName, AnimFrame, AnimRate);
       if(SeqName == IdleAnim)
           ResumeAutoReload(autoReloadPauseFrame);
    }
    // Resume reloading in case auto reload was interrupted as soon as possible
    if(bAutoReloadInterrupted && Role < ROLE_Authority && ClientGrenadeState == GN_none){
       GetAnimParams(0, SeqName, AnimFrame, AnimRate);
       if(SeqName == IdleAnim)
           ResumeAutoReload(autoReloadsDescriptions[currentAutoReload].resumeFrame);
    }
    // Try and detect when animation that should trigger auto reload starts
    if(autoReloadsDescriptions.Length > 0 && currentAutoReloadStage == RAUTOSTAGE_NONE && Role < ROLE_Authority){
       GetAnimParams(0, SeqName, AnimFrame, AnimRate);
       for(i = 0;i < autoReloadsDescriptions.Length;i ++)
           if(SeqName == autoReloadsDescriptions[i].animName && AnimFrame < autoReloadsDescriptions[i].trashStartFrame){
               // Since animation is already running - all we need to do is to setup appropriate variables
               SetupReloadVars(true, i);
               break;
           }
    }
    // Random TWI's code block appears!
    if(bHasAimingMode && bForceLeaveIronsights)
       if(!shouldLeaveIronsight())
           bForceLeaveIronsights = false;
    if(bHasAimingMode){
       if(bForceLeaveIronsights){
           if(bAimingRifle){
               ZoomOut(true);
               if(Role < ROLE_Authority)
                   ServerZoomOut(false);
           }
           bForceLeaveIronsights = false;
       }
       if(ForceZoomOutTime > 0){
           if(bAimingRifle){
               if(Level.TimeSeconds - ForceZoomOutTime > 0){
                   ForceZoomOutTime = 0;
                   ZoomOut(true);
                   if(Role < ROLE_Authority)
                       ServerZoomOut(false);
               }
           }
           else
               ForceZoomOutTime = 0;
       }
    }
    // We want to be up to date on this one
    UpdateMagCapacity(Instigator.PlayerReplicationInfo);
    // Next we have 3 possibilities.
    if(Role == ROLE_Authority){
       // 1. We're on the server. Then just check if we've reached reload deadline.
       if(Level.TimeSeconds >= ReloadDeadLine)
           bIsReloading = false;
    }
    else{
       if(bAutoReload)
           // 2. It's auto reload. Handle it in it's own tick function.
           AutoReloadTick();
       else if(Role < ROLE_Authority){
           // 3. It's not. Then just handle stage swapping for magazine ('goThroughStages' function) and single reloads ('goThroughSubStages' function)
           newStage = GetReloadStage();
           if(reloadType == RTYPE_SINGLE && newStage == RSTAGE_MAINREL)
               goThroughSubStages();
           if(currentRelStage != newStage)
               goThroughStages(currentRelStage, newStage);
       }
       if(Role < ROLE_Authority){
           // Call events
           GetAnimParams(0, SeqName, AnimFrame, AnimRate);
           if(newStage == RSTAGE_NONE && !bAutoReload)
               AnimFrame = 1.0;
           if(newStage != RSTAGE_NONE || lastEventCheckFrame > 0 || bAutoReload)
               HandleReloadEvents(lastEventCheckFrame, AnimFrame);
           if(newStage == RSTAGE_NONE && !bAutoReload)
               lastEventCheckFrame = -1.0;
       }
    }
    // Some other TWI's code leftovers
    if((Level.NetMode == NM_Client) || Instigator == none || KFFriendlyAI(Instigator.Controller) == none && Instigator.PlayerReplicationInfo == none)
       return;
    // Turn it off on death / battery expenditure
    if(FlashLight != none){
       // Keep the 1P weapon client beam up to date.
       AdjustLightGraphic();
       if(FlashLight.bHasLight){
           if(Instigator.Health <= 0 || KFHumanPawn(Instigator).TorchBatteryLife <= 0 || Instigator.PendingWeapon != none ){
               KFHumanPawn(Instigator).bTorchOn = false;
               ServerSpawnLight();
           }
       }
    }
}
simulated function HandleReloadEvents(float oldFrame, float newFrame){
    local int i;
    local float currEventFrame;
    // Old reload ended and new started between checks somehow, no point in it, but try to fix that
    if(oldFrame > newFrame){
       if(oldFrame < 1.0)
           HandleReloadEvents(oldFrame, 1.0);
       if(newFrame > 0.0)
           HandleReloadEvents(0.0, newFrame);
       return;
    }
    for(i = 0;i < relEvents.Length;i ++){
       currEventFrame = relEvents[i].eventFrame;
       if(oldFrame < currEventFrame && newFrame >= currEventFrame)
           ReloadEvent(relEvents[i].eventName);
    }
    if(newFrame > lastEventCheckFrame)
       lastEventCheckFrame = newFrame;
}
simulated function ReloadEvent(string eventName){}
// This function is called each tick while auto reload is active
simulated function AutoReloadTick(){
    local bool bFinishAutoReload;
    local name SeqName;
    local float AnimFrame, AnimRate;
    bFinishAutoReload = false;
    GetAnimParams(0, SeqName, AnimFrame, AnimRate);
    // Apply reload rate to the animation's speed when it's time
    if(AnimFrame > autoReloadsDescriptions[currentAutoReload].speedFrame && !bAutoReloadRateApplied){
       ChangeReloadRate(AutoReloadBaseRate() * GetCurrentReloadMult());
       bAutoReloadRateApplied = true;
    }
    // Change state to interruptible and set reload to finish if there's no ammo
    if(AnimFrame > autoReloadsDescriptions[currentAutoReload].canInterruptFrame){
       if(autoReloadAmmo() > 0){
           currentAutoReloadStage = RAUTOSTAGE_INTERRUPTIBLE;
           ClientTryPendingWeapon();
       }
       else{
           bFinishAutoReload = true;
           PlayIdle();
       }
    }
    // Might as well end reload when we enter trash stage (or when animation is finished)
    if(SeqName != autoReloadsDescriptions[currentAutoReload].animName || AnimFrame > autoReloadsDescriptions[currentAutoReload].trashStartFrame){
       bFinishAutoReload = true;
       AddAutoReloadedAmmo();
    }
    // Finish the reload as asked
    if(bFinishAutoReload)
       ResetReloadVars();
}
simulated function float AutoReloadBaseRate(){
    if(reloadType == RTYPE_AUTO && FireModeClass[0] != none
       && (!bHasSecondaryAmmo || FireMode[1] != Class'KFMod.NoFire'))
       return FireModeClass[0].default.FireAnimRate;
    return FireModeClass[1].default.FireAnimRate;
}
// Called when current state of reload changes; only called on client side
simulated function ReloadChangedStage(ERelStage prevStage, ERelStage newStage){
    local NiceHumanPawn nicePawn;
    local class<NiceVeterancyTypes> niceVet;
    nicePawn = NiceHumanPawn(Instigator);
    if(nicePawn != none)
       niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePawn.PlayerReplicationInfo);
    // Reload was canceled, so all that is meaningless
    if(!bIsReloading)
       return;
    if(reloadType == RTYPE_MAG){
       if(newStage == RSTAGE_MAINREL){
           bNeedToCharge = false;
           ServerSetCharging(bNeedToCharge);
           bMagazineOut = true;
           MagAmmoRemainingClient = 0;
           if(bHasChargePhase && bRoundInChamber)
               MagAmmoRemainingClient ++;
           ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds);
       }
       else if(newStage == RSTAGE_POSTREL){
           bMagazineOut = false;
           HideMagazine(false);
           if(bHasChargePhase){
               if(bRoundInChamber){
                   bNeedToCharge = false;
                   PlayIdle();
               }
               else
                   bNeedToCharge = true;
           }
           ServerSetCharging(bNeedToCharge);
       }
       else if(newStage == RSTAGE_TRASH && (!bHasChargePhase || bRoundInChamber)){
           bMagazineOut = false;
           HideMagazine(false);
       }
       else if(newStage == RSTAGE_NONE)
           ResetReloadVars();
       if(prevStage == RSTAGE_MAINREL && newStage != RSTAGE_NONE)
           AddReloadedAmmo();
       if(prevStage == RSTAGE_POSTREL && newStage == RSTAGE_TRASH){
           bNeedToCharge = false;
           bRoundInChamber = true;
           ServerSetCharging(bNeedToCharge);
           ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds);
       }
       if(newStage == RSTAGE_TRASH)
           ClientTryPendingWeapon();
    }
    else if(reloadType == RTYPE_SINGLE){
       if(newStage == RSTAGE_NONE)
           ResetReloadVars();
       if(prevStage == RSTAGE_MAINREL && bIsReloading)
           goThroughSubStages(true);
    }
}
// Return current magazine (also has limited use for single reload) stage of reload
simulated function ERelStage GetReloadStage(){
    local name SeqName;
    local float AnimFrame, AnimRate;
    GetAnimParams(0, SeqName, AnimFrame, AnimRate);
    if(SeqName != ReloadAnim)
       return RSTAGE_NONE;
    if(AnimFrame < reloadPreEndFrame)
       return RSTAGE_PREREL;
    if(AnimFrame < reloadEndFrame)
       return RSTAGE_MAINREL;
    if(AnimFrame < reloadChargeEndFrame && bHasChargePhase)
       return RSTAGE_POSTREL;
    return RSTAGE_TRASH;
}
// Returns next magazine reload stage
simulated function ERelStage GetNextReloadStage(ERelStage curr){
    local byte i;
    i = curr;
    i ++;
    curr = ERelStage(i);
    if(curr == RSTAGE_POSTREL && !bHasChargePhase)
       curr = RSTAGE_TRASH;
    return curr;
}
// Function that goes between given 'prev' and 'next' stages by passing every intermediate stage and calling 'ReloadChangedStage'
simulated function GoThroughStages(ERelStage prev, ERelStage next){
    local ERelStage theEnum, limitStage;
    
    if(prev < next || next == RSTAGE_NONE){
       theEnum = prev;
       if(next == RSTAGE_NONE)
           limitStage = RSTAGE_TRASH;
       else
           limitStage = next;
       while(theEnum < limitStage){
           theEnum = GetNextReloadStage(theEnum);
           ReloadChangedStage(currentRelStage, theEnum);
           currentRelStage = theEnum;
       }
    }
    if(prev > next){
       ReloadChangedStage(prev, next);
       currentRelStage = next;
    }
}
simulated function GoThroughSubStages(optional bool bReloadEnded){
    local float AnimFrame;
    AnimFrame = GetCurrentAnimFrame();
    // Conditions: 1. Is this valid stage?
    // 2, 3. Can we even load more ammo?
    while(subReloadStage < reloadStages.Length && MagAmmoRemainingClient < MagCapacity && AmmoAmount(0) - MagAmmoRemainingClient > 0)
       if(bReloadEnded || AnimFrame > reloadStages[subReloadStage]){
           AddReloadedAmmo();
           subReloadStage ++;
       }
       else break;
    // If reload hasn't ended, we can only load one more shell, but aren't yet at animation's end - scroll animation
    // 'subReloadStage' shouldn't be zero at this point, but just in case check
    // During reload client dictates size of the magazine
    if(subReloadStage > 0 && !bReloadEnded && subReloadStage != reloadStages.Length && (MagAmmoRemainingClient >= MagCapacity || MagAmmoRemainingClient - AmmoAmount(0) >= 0)){
       if(alwaysPlayAnimEnd){
           AnimFrame = reloadStages[reloadStages.Length - 1] + AnimFrame - reloadStages[subReloadStage - 1];
           ScrollAnim(AnimFrame);    // Current animation position - previous ammo load position
       }
       else
           PlayIdle();
       subReloadStage = reloadStages.Length - 1;
    }
}
//Auzilary unction for easy initial generation of sub reload stages for single reload
simulated function GenerateReloadStages(int stagesAmount, int framesAmount, int firstLoadFrame, int loadDelta){
    local int i;
    local int frame;
    local float convFrame;
    reloadStages.Length = 0;
    frame = firstLoadFrame;
    for(i = 0;i < stagesAmount;i ++){
       // Next load time
       convFrame = float(frame) / float(framesAmount);
       reloadStages[reloadStages.Length] = convFrame;
       // Shift to the next one
       frame += loadDelta;
    }
}
simulated function HideMagazine(bool bHide){
    if(magazineBone == '')
       return;
    if(bHide)
       SetBoneScale(0, 0.0, magazineBone);
    else
       SetBoneScale(0, 1.0, magazineBone);
}
simulated function float GetCurrentAnimFrame(){
    local name SeqName;
    local float AnimFrame, AnimRate;
    GetAnimParams(0, SeqName, AnimFrame, AnimRate);
    return AnimFrame;
}
simulated function BringUp(optional Weapon PrevWeapon){
    // Change HUD icons if necessary
    if(Role < ROLE_Authority){
       if(bChangeClipIcon && hudClipTexture != none)
           HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).ClipsIcon.WidgetTexture = hudClipTexture;
       else
           HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).ClipsIcon.WidgetTexture =
               class'ScrnHUD'.default.ClipsIcon.WidgetTexture;
       if(bChangeBulletsIcon && hudBulletsTexture != none)
           HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).BulletsInClipIcon.WidgetTexture =
               hudBulletsTexture;
       else
           HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).BulletsInClipIcon.WidgetTexture =
               class'ScrnHUD'.default.BulletsInClipIcon.WidgetTexture;
       if(bChangeSecondaryIcon && hudSecondaryTexture != none)
           HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).SecondaryClipsIcon.WidgetTexture =
               hudSecondaryTexture;
       else
           HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).SecondaryClipsIcon.WidgetTexture =
               class'ScrnHUD'.default.SecondaryClipsIcon.WidgetTexture;
    }
    HideMagazine(bMagazineOut);
    super.BringUp(PrevWeapon);
    ApplyLaserState();
}
function ServerSetCharging(bool bNewNeedToCharge){
    bNeedToCharge = bNewNeedToCharge;
}
// Function that's supposed to return current amount of ammo that's used for auto reload for this weapon
simulated function int AutoReloadAmmo(){
    if(FireModeClass[1] == class'KFMod.NoFire')
       return AmmoAmount(0);
    else
       return AmmoAmount(1);
}
simulated function int GetMagazineAmmo(){
    if(Role < ROLE_Authority)
       return MagAmmoRemainingClient;
    else
       return MagAmmoRemaining;
}

simulated function bool AllowReload(){
    local int actualMagSize;
    actualMagSize = GetMagazineAmmo();
    if(bHasChargePhase && bRoundInChamber)
       actualMagSize --;
    UpdateMagCapacity(Instigator.PlayerReplicationInfo);
    if(FireMode[0].IsFiring() || FireMode[1].IsFiring() ||
       bIsReloading || IsMagazineFull() ||
       ClientState == WS_BringUp )
       return false;
    return true;
}
simulated function bool IsMagazineFull(){
    local int totalMagSize, actualMagSize;
    totalMagSize = GetMagazineAmmo();
    actualMagSize = totalMagSize;
    if(bHasChargePhase && bRoundInChamber)
       actualMagSize --;
    return (actualMagSize >= MagCapacity || totalMagSize >= AmmoAmount(0));
}
exec function ReloadMeNow(){
    local NicePlayerController nicePlayer;
    nicePlayer = NicePlayerController(Instigator.Controller);
    if(nicePlayer != none && nicePlayer.bFlagUseServerReload)
       ClientReloadMeNow();
}
simulated function float GetCurrentReloadMult(){
    local float ReloadMulti;
    local float actualActiveSpeedUp;
    local float timeDilationSpeedup;
    local NiceHumanPawn nicePawn;
    local NicePlayerController nicePlayer;
    local class<NiceVeterancyTypes> niceVet;

    nicePawn = NiceHumanPawn(Instigator);
    nicePlayer = NicePlayerController(Instigator.Controller);
    if(nicePawn != none)
       niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePawn.PlayerReplicationInfo);
    if(KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo) != none && KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill != none)
       ReloadMulti = KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill.Static.GetReloadSpeedModifier(KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo), self);
    else
       ReloadMulti = 1.0;
    if(bGiveObsessiveBonus)
       ReloadMulti *= class'NiceSkillSupportObsessive'.default.reloadBonus;
    // Active reload speedup
    actualActiveSpeedUp = activeSpeedup;
    if (nicePlayer != none && niceVet.static.hasSkill(nicePlayer, class'NiceSkillCommandoQuickerMags')) {
        actualActiveSpeedUp *= 2;
    }
    if(activeReloadState == ACTR_SUCCESS)
       ReloadMulti *= actualActiveSpeedUp;
    else if(activeReloadState == ACTR_FAIL)
           ReloadMulti *= activeSlowdown;
    else if(bCanActiveReload && reloadType == RTYPE_SINGLE && subReloadStage == 0)
       ReloadMulti *= actualActiveSpeedUp;
    if(nicePlayer != none && niceVet.static.hasSkill(nicePlayer, class'NiceSkillCommandoZEDProfessional')) {
        timeDilationSpeedup = (1.1 / Level.TimeDilation);
    }
    else {
        timeDilationSpeedup = ((1.1 / Level.TimeDilation) - 1.0) * 0.5 + 1.0;
        timeDilationSpeedup = FMax(1.0, timeDilationSpeedup);
    }
    if(nicePlayer != none && niceVet.static.hasSkill(nicePlayer, class'NiceSkillCommandoZEDProfessional'))
       ReloadMulti *= timeDilationSpeedup;
    if(bAutoReload && bAutoReloadRateApplied)
       ReloadMulti *= autoReloadSpeedModifier;
    return ReloadMulti;
}
function ServerRequestAutoReload(){
    ClientReloadMeNow();
}
simulated function AttemptActiveReload(optional bool bForce){
    local float windowStart;
    local float windowLenght;
    local name SeqName;
    local float AnimFrame, AnimRate;
    local NiceHumanPawn nicePawn;
    local class<NiceVeterancyTypes> niceVet;
    // Need this for skill check
    nicePawn = NiceHumanPawn(Instigator);
    if(nicePawn != none)
       niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePawn.PlayerReplicationInfo);
    // Does nothing if we aren't even reloading, this is auto reload, we've already succeeded/failed or we'll auto succeed anyway
    if(!bIsReloading || !bCanActiveReload || bAutoReload || activeReloadState == ACTR_SUCCESS || activeReloadState == ACTR_FAIL)
       return;
    // Find starting frame and length of the active reload window (and declare fail if single reload is still in the first sub-stage)
    windowStart = -1.0;
    if(reloadType == RTYPE_MAG){
       windowStart = reloadPreEndFrame;
       windowLenght = activeWindow;
    }
    else if(reloadType == RTYPE_SINGLE){
       // Too early!
       if(subReloadStage <= 0){
           activeReloadState = ACTR_FAIL;
           UpdateReloadRate();
           return;
       }
       windowStart = reloadStages[subReloadStage - 1];
       windowLenght = activeWindow / MagCapacity;
    }
    // Something went wrong and active reload is inapplicable
    if(windowStart < 0)
       return;
    GetAnimParams(0, SeqName, AnimFrame, AnimRate);
    if(windowStart <= AnimFrame && AnimFrame <= windowStart + windowLenght || bForce)
       activeReloadState = ACTR_SUCCESS;
    else
       activeReloadState = ACTR_FAIL;
    UpdateReloadRate();
}
// Function that's called when client tries to use flashlight on a weapon
simulated function SecondDoToggle(){}
simulated function ClientReload(){}
simulated function bool InterruptReload(){
    return false;
}
function ServerStopFire(byte Mode){
    super(BaseKFWeapon).ServerStopFire(Mode);
}
simulated function ClientTryPendingWeapon(){
    if(Instigator.PendingWeapon != none && Instigator.PendingWeapon != self)
       Instigator.Controller.ClientSwitchToBestWeapon();
}
simulated function AnimEnd(int channel){
    local name anim;
    local float frame, rate;
    GetAnimParams(0, anim, frame, rate);
    if(!FireMode[0].IsInState('FireLoop')){
       GetAnimParams(0, anim, frame, rate);
       if(ClientState == WS_ReadyToFire)
           if((FireMode[0] == none || !FireMode[0].bIsFiring) && (FireMode[1] == none || !FireMode[1].bIsFiring))
               PlayIdle();
    }
    else if(ClientState == WS_ReadyToFire){
       if(anim == FireMode[0].FireAnim && HasAnim(FireMode[0].FireEndAnim))
           PlayAnim(FireMode[0].FireEndAnim, FireMode[0].FireEndAnimRate, 0.0);
       else if (anim== FireMode[1].FireAnim && HasAnim(FireMode[1].FireEndAnim))
           PlayAnim(FireMode[1].FireEndAnim, FireMode[1].FireEndAnimRate, 0.0);
       else if ((FireMode[0] == none || !FireMode[0].bIsFiring) && (FireMode[1] == none || !FireMode[1].bIsFiring) && !bAutoReloadPaused)
           PlayIdle();
    }
}
simulated function bool StartFire(int Mode){
    if(NiceHighROFFire(FireMode[Mode]) == none || FireMode[Mode].bWaitForRelease)
       return super.StartFire(Mode);
    if(!super.StartFire(Mode))
      return false;
    if(AmmoAmount(0) <= 0)
       return false;
    AnimStopLooping();
    if(!FireMode[Mode].IsInState('FireLoop') && (AmmoAmount(0) > 0)){
       FireMode[Mode].StartFiring();
       return true;
    }
    else
       return false;
    return true;
}
simulated event OnZoomOutFinished(){
    local name anim;
    local float frame, rate;
    GetAnimParams(0, anim, frame, rate);
    if(!FireMode[0].IsInState('FireLoop'))
       super.OnZoomOutFinished();
    else if(ClientState == WS_ReadyToFire){
       // Play the regular idle anim when we're finished zooming out
       if(anim == IdleAimAnim)
           PlayIdle();
       // Switch looping fire anims if we switched to/from zoomed
       else if( FireMode[0].IsInState('FireLoop') && anim == 'Fire_Iron_Loop')
           LoopAnim('Fire_Loop', FireMode[0].FireLoopAnimRate, FireMode[0].TweenTime);
    }
}
simulated event OnZoomInFinished(){
    local name anim;
    local float frame, rate;
    GetAnimParams(0, anim, frame, rate);
    if(!FireMode[0].IsInState('FireLoop'))
       super.OnZoomInFinished();
    else if(ClientState == WS_ReadyToFire){
       // Play the iron idle anim when we're finished zooming in
       if(anim == IdleAnim)
          PlayIdle();
       // Switch looping fire anims if we switched to/from zoomed
       else if( FireMode[0].IsInState('FireLoop') && anim == 'Fire_Loop' )
           LoopAnim('Fire_Iron_Loop', FireMode[0].FireLoopAnimRate, FireMode[0].TweenTime);
    }
}
// Some functions reloaded to force update of magazine size on client's side
function GiveAmmo(int m, WeaponPickup WP, bool bJustSpawned){
    super.GiveAmmo(m, WP, bJustSpawned);
    ClientSetMagSize(MagAmmoRemaining, bRoundInChamber);
}
simulated function GiveTo(Pawn other, optional Pickup Pickup){
    local int               actualMagSize;
    local NiceWeaponPickup  niceWeapPickup;
    local NicePlainData.Data  dummyData;
    niceWeapPickup = NiceWeaponPickup(Pickup);
    if(niceWeapPickup != none)
       SetNiceData(niceWeapPickup.GetNiceData(), NiceHumanPawn(other));
    else
       SetNiceData(dummyData, NiceHumanPawn(other));
    if(Role == ROLE_Authority){
       UpdateMagCapacity(other.PlayerReplicationInfo);

       if(NiceWeaponPickup(Pickup) != none)
           actualMagSize = NiceWeaponPickup(Pickup).MagAmmoRemaining;
       if(bRoundInChamber && actualMagSize > 0)
           actualMagSize --;
       if(NiceWeaponPickup(Pickup) != none && Pickup.bDropped)
           actualMagSize = Clamp(actualMagSize, 0, MagCapacity);
       else
           actualMagSize = MagCapacity;
       MagAmmoRemaining = actualMagSize;
       if(bRoundInChamber)
           MagAmmoRemaining ++;
       super(BaseKFWeapon).GiveTo(other, Pickup);
       ClientSetMagSize(MagAmmoRemaining, bRoundInChamber);
    }
}
function NicePlainData.Data GetNiceData(){
    local NicePlainData.Data transferData;
    if(LaserType > 0)
       class'NicePlainData'.static.SetInt(transferData, "LaserType", int(LaserType));
    class'NicePlainData'.static.SetBool(transferData, "ChamberedRound", bRoundInChamber);
    class'NicePlainData'.static.SetInt(transferData, "ChargeAmount", secondaryCharge);
    return transferData;
}
function SetNiceData(NicePlainData.Data transferData, optional NiceHumanPawn newOwner){
    local int newLaserType;
    newLaserType = class'NicePlainData'.static.GetInt(transferData, "LaserType", -1);
    if(newLaserType >= 0)
       ClientSetLaserType(byte(newLaserType));
    bRoundInChamber = class'NicePlainData'.static.GetBool(transferData, "ChamberedRound", false);
    secondaryCharge = class'NicePlainData'.static.GetInt(transferData, "ChargeAmount", 1);
    ClientSetSndCharge(secondaryCharge);
}

simulated function ApplyLaserState()
{
    bLaserActive = LaserType > 0;
    if (Role < ROLE_Authority)
        ServerSetLaserType(LaserType);
    if (NiceAttachment(ThirdPersonActor) != none)
        NiceAttachment(ThirdPersonActor).SetLaserType(LaserType);

    // Instigator accessed none fix
    if (Instigator == none || !Instigator.IsLocallyControlled())
        return;
    
    if (bLaserActive)
    {
        if (LaserDot == none)
            LaserDot = Spawn(LaserDotClass, self);
        LaserDot.SetLaserType(LaserType);
        if (altLaserAttachmentBone != '')
        {
            if (altLaserDot == none)
                altLaserDot = Spawn(LaserDotClass, self);
            altLaserDot.SetLaserType(LaserType);
        }
        // spawn 1-st person laser attachment for weapon owner
        if (LaserAttachment == none)
        {
            SetBoneRotation(LaserAttachmentBone, LaserAttachmentRotation);
            LaserAttachment = Spawn(LaserAttachmentClass,,,,);
            if (LaserAttachment != none)
            {
                AttachToBone(LaserAttachment, LaserAttachmentBone);
                LaserAttachment.SetRelativeLocation(LaserAttachmentOffset);
            }
        }
        else
            LaserAttachment.bHidden = false;

        if (altLaserAttachment == none) 
        {
            if (altLaserAttachmentBone != '')
            {
                SetBoneRotation(altLaserAttachmentBone, altLaserAttachmentRotation);
                altLaserAttachment = Spawn(LaserAttachmentClass,,,,);
                if (altLaserAttachment != none)
                {
                    AttachToBone(altLaserAttachment, altLaserAttachmentBone);
                    altLaserAttachment.SetRelativeLocation(altLaserAttachmentOffset);
                }
            }
        }
        else
            altLaserAttachment.bHidden = false;

        ConstantColor'ScrnTex.Laser.LaserColor'.Color = LaserDot.GetLaserColor();
    }
    else
    {
        if (LaserAttachment != none)
            LaserAttachment.bHidden = true;
        if (altLaserAttachment != none)
            altLaserAttachment.bHidden = true;
        if (LaserDot != none)
            LaserDot.Destroy();
        if (altLaserDot != none)
            altLaserDot.Destroy();
    }
}

simulated function ToggleLaser(){
    if(!Instigator.IsLocallyControlled()) 
       return;
    // Will redo this bit later, but so far it'll have to do
    if(LaserType == 0)
       LaserType = 1;
    else if(LaserType == 1)
       LaserType = 4;
    else if(LaserType == 4)
       LaserType = 2;
    else
       LaserType = 0;
    ApplyLaserState();
}
simulated function TurnOffLaser(){
    if(!Instigator.IsLocallyControlled())
       return;
    if(Role < ROLE_Authority)
       ServerSetLaserType(0);
    bLaserActive = false;
    if(LaserAttachment != none)
       LaserAttachment.bHidden = true;
    if(altLaserAttachment != none)
       altLaserAttachment.bHidden = true;
    if(LaserDot != none)
       LaserDot.Destroy();
    if(altLaserDot != none)
       altLaserDot.Destroy();
}
function ServerSetLaserType(byte NewLaserType){
    LaserType = NewLaserType;
    bLaserActive = NewLaserType > 0; 
    if(NiceAttachment(ThirdPersonActor) != none)
       NiceAttachment(ThirdPersonActor).SetLaserType(LaserType);   
}
simulated function ClientSetLaserType(byte NewLaserType){
    LaserType = NewLaserType;
    bLaserActive = NewLaserType > 0; 
    ApplyLaserState();
}
simulated function NiceFire GetMainFire(){
    return NiceFire(FireMode[0]);
}
function ServerSetSndCharge(int newCharge){
    secondaryCharge = newCharge;
}
simulated function ClientSetSndCharge(int newCharge){
    secondaryCharge = newCharge;
}
simulated function RenderOverlays(Canvas Canvas){
    local int i;
    local Vector StartTrace, EndTrace;
    local Vector HitLocation, HitNormal;
    local Actor Other;
    local vector X,Y,Z;
    local coords C;
    local array<Actor> HitActors;
    if(Instigator == none)
       return;
    if(Instigator.Controller != none)
       Hand = Instigator.Controller.Handedness;
    if((Hand < -1.0) || (Hand > 1.0))
       return;
    for(i = 0; i < NUM_FIRE_MODES;++ i)
       if(FireMode[i] != none)
           FireMode[i].DrawMuzzleFlash(Canvas);
    SetLocation(Instigator.Location + Instigator.CalcDrawOffset(self));
    SetRotation(Instigator.GetViewRotation() + ZoomRotInterp);
    // Handle drawing the laser dots
    if(LaserDot != none){
       if(bIsReloading || bAllowFreeDot){
           C = GetBoneCoords(LaserAttachmentBone);
           X = C.XAxis;
           Y = C.YAxis;
           Z = C.ZAxis;
       }
       else 
           GetViewAxes(X, Y, Z);

       StartTrace = Instigator.Location + Instigator.EyePosition();
       EndTrace = StartTrace + 65535 * X;

       while(true){
           Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true);
           if(ROBulletWhipAttachment(Other) != none){
               HitActors[HitActors.Length] = Other;
               Other.SetCollision(false);
               StartTrace = HitLocation + X;
           }
           else{
               if(other != none && Other != Instigator && Other.Base != Instigator)
                   EndBeamEffect = HitLocation;
               else
                   EndBeamEffect = EndTrace;
               break;
           }
       }
       // restore collision
       for(i = 0; i<HitActors.Length;++ i)
           HitActors[i].SetCollision(true);

       LaserDot.SetLocation(EndBeamEffect - X*LaserDot.ProjectorPullback);

       if(Pawn(Other) != none){
           LaserDot.SetRotation(Rotator(X));
           LaserDot.SetDrawScale(LaserDot.default.DrawScale * 0.5);
       }
       else if(HitNormal == vect(0,0,0)){
           LaserDot.SetRotation(Rotator(-X));
           LaserDot.SetDrawScale(LaserDot.default.DrawScale);
       }
       else{
           LaserDot.SetRotation(Rotator(-HitNormal));
           LaserDot.SetDrawScale(LaserDot.default.DrawScale);
       }
    }
    if(altLaserDot != none){
       if(bIsReloading || bAllowFreeDot){
           C = GetBoneCoords(altLaserAttachmentBone);
           X = C.XAxis;
           Y = C.YAxis;
           Z = C.ZAxis;
       }
       else 
           GetViewAxes(X, Y, Z);

       StartTrace = Instigator.Location + Instigator.EyePosition();
       EndTrace = StartTrace + 65535 * X;

       while(true){
           Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true);
           if(ROBulletWhipAttachment(Other) != none){
               HitActors[HitActors.Length] = Other;
               Other.SetCollision(false);
               StartTrace = HitLocation + X;
           }
           else{
               if(other != none && Other != Instigator && Other.Base != Instigator)
                   EndBeamEffect = HitLocation;
               else
                   EndBeamEffect = EndTrace;
               break;
           }
       }
       // restore collision
       for(i = 0; i<HitActors.Length;++ i)
           HitActors[i].SetCollision(true);

       altLaserDot.SetLocation(EndBeamEffect - X*altLaserDot.ProjectorPullback);

       if(Pawn(Other) != none){
           altLaserDot.SetRotation(Rotator(X));
           altLaserDot.SetDrawScale(altLaserDot.default.DrawScale * 0.5);
       }
       else if(HitNormal == vect(0,0,0)){
           altLaserDot.SetRotation(Rotator(-X));
           altLaserDot.SetDrawScale(altLaserDot.default.DrawScale);
       }
       else{
           altLaserDot.SetRotation(Rotator(-HitNormal));
           altLaserDot.SetDrawScale(altLaserDot.default.DrawScale);
       }
    }
    bDrawingFirstPerson = true;
    Canvas.DrawActor(self, false, false, DisplayFOV);
    bDrawingFirstPerson = false;
}
simulated function ZoomIn(bool bAnimateTransition){
    default.ZoomTime    = default.recordedZoomTime;
    PlayerIronSightFOV  = default.PlayerIronSightFOV;
    if(class'NiceVeterancyTypes'.static.hasSkill(NicePlayerController(Instigator.Controller), class'NiceSkillSharpshooterControl')){
       default.ZoomTime *= class'NiceSkillSharpshooterControl'.default.zoomSpeedBonus;
       if(instigator != none && instigator.bIsCrouched)
           PlayerIronSightFOV *= class'NiceSkillSharpshooterControl'.default.zoomBonus;
    }
    super.ZoomIn(bAnimateTransition);
}
simulated function ZoomOut(bool bAnimateTransition){
    default.ZoomTime    = default.recordedZoomTime;
    PlayerIronSightFOV  = default.PlayerIronSightFOV;
    if(class'NiceVeterancyTypes'.static.hasSkill(NicePlayerController(Instigator.Controller), class'NiceSkillSharpshooterControl')){
       default.ZoomTime *= class'NiceSkillSharpshooterControl'.default.zoomSpeedBonus;
       PlayerIronSightFOV *= class'NiceSkillSharpshooterControl'.default.zoomBonus;
    }
    super.ZoomOut(bAnimateTransition);
}
simulated function Destroyed(){
    if(LaserDot != none)
       LaserDot.Destroy();
    if(altLaserDot != none)
       altLaserDot.Destroy();
    if(LaserAttachment != none)
       LaserAttachment.Destroy();
    if(altLaserAttachment != none)
       altLaserAttachment.Destroy();
    super(KFWeapon).Destroyed();
}


simulated function float GetAmmoMulti()
{
    if (NextAmmoCheckTime > Level.TimeSeconds)
    {
        return LastAmmoResult;
    }

    NextAmmoCheckTime = Level.TimeSeconds + 1;

    // Instigator accessed none fix
    if (FireMode[0] != none && FireMode[0].AmmoClass != none && Instigator != none && KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo) != none &&
         KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill != none )
    {
        LastAmmoResult = KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill.static.AddExtraAmmoFor(KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo), FireMode[0].AmmoClass);
    }
    else
    {
        LastAmmoResult = 1;
    }

    return LastAmmoResult;
}

defaultproperties
{
    recordedZoomTime=-1.000000
    SecondaryCharge=1
    LaserAttachmentClass=Class'ScrnLaserAttachmentFirstPerson'
    LaserDotClass=Class'ScrnLocalLaserDot'
    LaserAttachmentBone="LightBone"
    MagazineBone="Magazine"
    bHasChargePhase=True
    autoReloadSpeedModifier=1.000000
    bCanActiveReload=True
    activeSlowdown=0.850000
    activeSpeedup=1.150000
    activeWindow=0.060000
    bModeZeroCanDryFire=True
}
