class ScrnHumanPawn extends SRHumanPawn;

var float HealthRestoreRate; // how fast player can be healed (hp/s)
var float HealthToGiveRemainder; // pawn receives integer amount of hp. remainder should be moved to next healing tick
var byte ClientHealthToGive; //required for client replication


var const class<ScrnVestPickup> NoVestClass;            // dummy class that indicates player has no armor
var const class<ScrnVestPickup> StandardVestClass;      // standard KF armor (combat armor)
var const class<ScrnVestPickup> LightVestClass;     // Warning! LightVestClass must have no weight (weight=0)
var private class<ScrnVestPickup> CurrentVestClass;     // Equipped shield class

var private transient class<KFVeterancyTypes> PrevPerkClass;
var private transient int PrevPerkLevel;

var bool bCowboyMode;

var transient ScrnHumanPawn   LastHealedBy; // last player who healed me
var transient ScrnHumanPawn   LastHealed; // last player, who was healed by me
var transient KFMonster       CombatMedicTarget; // "LastHealedBy" must kill this monster to earn an ach
var transient int             HealthBeforeHealing;
var transient float           LastDamageTime;
var transient float           LastExplosionTime;
var transient float           LastExplosionDistance; // distance between player and explosions's epicenter

// Seems like bonus ammo is fixed in v1051 and not needed anymore
// var class<Ammunition> BonusAmmoClass;
// var int BonusAmmoAmount;

var ScrnGameRules GameRules;
var bool bCheckHorzineArmorAch;

// used in AssessThreatTo()
var protected transient KFMonsterController LastThreatMonster;
var protected transient float LastThreatTime, LastThreat;

var localized string strNoSpawnCashToss;


struct SWeaponFlashlight {
    var class<KFWeapon> WeaponClass;
    var int TorchBatteryLife, MaxBatteryLife;
};
var array<SWeaponFlashlight> WeaponFlashlights;

var Sound HeadshotSound;

var transient int HealthBeforeDeath; // in cases when death is not caused by health drop down to 0
var transient float NextEnemyBaseDamageMsg; //reserved for TSC

var() int HealthBonus;

var KFPlayerReplicationInfo KFPRI;
var class<ScrnVeterancyTypes> ScrnPerk;

var transient bool bAmIBaron; // :trollface:

// spectator info
var bool bViewTarget; // somebody spectating me
var class<KFWeapon> SpecWeapon;
var byte AmmoStatus;
var byte SpecWeight, SpecMagAmmo, SpecMags, SpecSecAmmo, SpecNades;

var     transient Frag          PlayerGrenade;

var bool bTraderSpeedBoost;
var float TraderSpeedBoost;
var byte MacheteBoost; // that's one of the most retarded things I've done
var float MacheteResetTime;

var transient KFMeleeGun QuickMeleeWeapon;
var transient KFWeapon WeaponToFixClientState;
var transient bool bQuickMeleeInProgress;

var localized string BlameStrM99;
var localized string BlameStrRPG;

replication
{
    reliable if( bNetOwner && Role == ROLE_Authority )
        ClientSetInventoryGroup;

    reliable if( bNetOwner && Role == ROLE_Authority )
        QuickMeleeWeapon, MacheteBoost;

    reliable if( Role == ROLE_Authority )
        ClientSetVestClass; //send it to all clients, cuz they need to know max health and max shield

    reliable if( bNetDirty && Role == ROLE_Authority )
        ClientHealthToGive, HealthBonus; // all clients  need to know it to properly display it on the hud

    reliable if( bNetDirty && Role == ROLE_Authority )
        SpecWeapon, AmmoStatus;
    reliable if( bNetDirty && bViewTarget && Role == ROLE_Authority )
        SpecWeight, SpecMagAmmo, SpecMags, SpecSecAmmo, SpecNades;

    // seem like that there is no need to replicate bCowboyMode, because it is used only on local player,
    // which can set it himself
    // reliable if( !bNetOwner && bNetDirty && Role == ROLE_Authority )
        // bCowboyMode; // net owner can check cowboy mode himself

    reliable if(Role < ROLE_Authority)
        ServerBuyShield, ServerSellShield;

    reliable if(Role < ROLE_Authority)
        ServerReload, ServerFire;
}

simulated function PostBeginPlay()
{
    super.PostBeginPlay();

    FindGameRules();
    ReplaceRequiredEquipment();

    if ( SoundGroupClass == none )
        SoundGroupClass = Class'KFMod.KFMaleSoundGroup';

    HealthMax += HealthBonus;
    Health += HealthBonus;

    bTraderSpeedBoost = class'ScrnBalance'.default.Mut.bTraderSpeedBoost;
}

function ReplaceRequiredEquipment()
{
    local int i;
    local KFLevelRules_Story StoryRules;
    local ScrnBalance Mut;

    Mut = class'ScrnBalance'.static.Myself(Level);

    if ( KFStoryGameInfo(Level.Game) != none ) {
        StoryRules = KFStoryGameInfo(Level.Game).StoryRules;
        for ( i = 0; i < StoryRules.RequiredPlayerEquipment.Length; ++i ) {
            if ( StoryRules.RequiredPlayerEquipment[i] == Class'KFMod.Knife' )
                StoryRules.RequiredPlayerEquipment[i] = class'ScrnBalanceSrv.ScrnKnife';
            else if ( StoryRules.RequiredPlayerEquipment[i] == Class'KFMod.Single' )
                StoryRules.RequiredPlayerEquipment[i] = class'ScrnBalanceSrv.ScrnSingle';
            else if ( Mut.bReplaceNades && StoryRules.RequiredPlayerEquipment[i] == Class'KFMod.Frag' )
                StoryRules.RequiredPlayerEquipment[i] = class'ScrnBalanceSrv.ScrnFrag';
            else if ( StoryRules.RequiredPlayerEquipment[i] == Class'KFMod.Syringe' )
                StoryRules.RequiredPlayerEquipment[i] = class'ScrnBalanceSrv.ScrnSyringe';
        }
    }
    else if ( Mut.bNoRequiredEquipment ) {
        for ( i=0; i<16; ++i )
            RequiredEquipment[i] = "";
    }
    else {
        RequiredEquipment[0] = String(class'ScrnBalanceSrv.ScrnKnife');
        RequiredEquipment[1] = String(class'ScrnBalanceSrv.ScrnSingle');
        if ( Mut.bReplaceNades ) {
            RequiredEquipment[2] = String(class'ScrnBalanceSrv.ScrnFrag');
        }
        RequiredEquipment[3] = String(class'ScrnBalanceSrv.ScrnSyringe');
    }
}


function RecalcWeight()
{
    local Inventory Inv;
    local KFWeapon Weap;
    local int c;

    CurrentWeight = CurrentVestClass.default.Weight;

    for ( Inv = Inventory; Inv != none && ++c < 1000; Inv = Inv.Inventory ) {
        Weap = KFWeapon(Inv);
        if (Weap == none)
            continue;

        if ( ScrnFrag(Weap) != none )
            Weap.Weight = Weap.default.Weight;

        CurrentWeight +=  Weap.Weight;
    }
}


// Changed MaxCarryWeight to default.MaxCarryWeight, so support with 15/24 weight will move with same speed as other perk 15/15
// Support with 24/24 weight now will move slower
// Other code strings are just copy-pasted
simulated function ModifyVelocity(float DeltaTime, vector OldVelocity)
{
    local float WeightMod, HealthMod, MovementMod;
    local float EncumbrancePercentage;
    local Inventory Inv;
    local KF_StoryInventoryItem StoryInv;
    local int c;

    super(KFPawn).ModifyVelocity(DeltaTime, OldVelocity);

    if ( Controller != none )
    {
        // Calculate encumbrance, but cap it to the maxcarryweight so when we use dev weapon cheats we don't move mega slow
        EncumbrancePercentage = (FMin(CurrentWeight, MaxCarryWeight) / default.MaxCarryWeight); //changed MaxCarryWeight to default.MaxCarryWeight
        // Calculate the weight modifier to speed
        WeightMod = (1.0 - (EncumbrancePercentage * WeightSpeedModifier));
        // Calculate the health modifier to speed
        HealthMod = ((Health/HealthMax) * HealthSpeedModifier) + (1.0 - HealthSpeedModifier);

        // Apply all the modifiers
        GroundSpeed = default.GroundSpeed * HealthMod;
        GroundSpeed *= WeightMod;
        GroundSpeed += InventorySpeedModifier;

        if ( KFPRI != none && KFPRI.ClientVeteranSkill != none )
            MovementMod = KFPRI.ClientVeteranSkill.static.GetMovementSpeedModifier(KFPRI, KFGameReplicationInfo(Level.GRI));
        else
            MovementMod = 1.0;
        GroundSpeed *= MovementMod;
        AccelRate = default.AccelRate * MovementMod;


        /* Give the pawn's inventory items a chance to modify his movement speed */
        for( Inv=Inventory; Inv!=None && ++c < 1000; Inv=Inv.Inventory )
        {
            GroundSpeed *= Inv.GetMovementModifierFor(self);

            StoryInv = KF_StoryInventoryItem(Inv);
            if(StoryInv != none && StoryInv.bUseForcedGroundSpeed)
            {
                GroundSpeed = StoryInv.ForcedGroundSpeed;
                return;
            }
        }

        if ( bTraderSpeedBoost && !KFGameReplicationInfo(Level.GRI).bWaveInProgress )
            GroundSpeed *= TraderSpeedBoost;

        GroundSpeed += MacheteBoost;
    }


}


function PossessedBy(Controller C)
{
    Super.PossessedBy(C);

    KFPRI = KFPlayerReplicationInfo(PlayerReplicationInfo);
    ScrnPerk = class<ScrnVeterancyTypes>(KFPRI.ClientVeteranSkill);
    if ( PlayerController(C) != none )
        bAmIBaron = PlayerController(C).GetPlayerIDHash() == "76561198006289592";
}

function UnPossessed()
{
    super.UnPossessed();

    KFPRI = none;
    ScrnPerk = none;
}


simulated function ClientSetInventoryGroup( class<Inventory> NewInventoryClass, byte NewInventoryGroup )
{
    local Inventory Inv;
    local int c;

    if ( Role < ROLE_Authority ) {
        //need to change default value to, because it is using in some static functions
        NewInventoryClass.default.InventoryGroup = NewInventoryGroup;
        //search the client inventory for the specified class and change its group
        for ( Inv=Inventory; Inv!=None && ++c < 1000 ; Inv=Inv.Inventory ) {
            if ( Inv.class == NewInventoryClass ) {
                Inv.InventoryGroup = NewInventoryGroup;
                return;
            }
        }
    }
}

// handle pickup
// function HandlePickup(Pickup pick)
// {
    // super.HandlePickup()
// }




function bool AddInventory( inventory NewItem )
{
    local KFWeapon weap;
    local bool GroupChanged;
    // local KFAmmunition ammo;

    weap = KFWeapon(NewItem);
    if( weap != none ) {
        //log("AddInventory:" @ weap, 'ScrnBalance');
        if ( Dualies(weap) != none ) {
            if ( (DualDeagle(weap) != none || Dual44Magnum(weap) != none || DualMK23Pistol(weap) != none)
                  && weap.InventoryGroup != 4 ) { //skip special weapons like Laser-44
                // Move dual pistols to slot 3 for Gunslinger perk, so he can easier to cycle them
                if ( KFPRI != none && ClassIsChildOf(KFPRI.ClientVeteranSkill, class'ScrnBalanceSrv.ScrnVetGunslinger') )
                    weap.InventoryGroup = 3;
                else
                    weap.InventoryGroup = 2; //leave dual pistols in slot 2 for other perks
                GroupChanged = true;
            }
        }
        else if ( weap.class == class'Single' ) {
            weap.bKFNeverThrow = false;
        }

        weap.bIsTier3Weapon = true; // hack to set weap.Tier3WeaponGiver for all weapons
    }

    //replicate changes on the client side
    if ( GroupChanged )
        ClientSetInventoryGroup(NewItem.class, NewItem.InventoryGroup);

    if ( super.AddInventory(NewItem) ) {
        if ( weap != none ) {
            if ( weap.bTorchEnabled )
                AddToFlashlightArray(weap.class); // v6.22 - each weapon has own flashlight
            CheckQuickMeleeWeapon(KFMeleeGun(weap));
            if ( class'ScrnBalance'.default.Mut.SrvTourneyMode == 0 && Machete(weap) != none ) {
                if ( MacheteBoost < 250 && VSizeSquared(Velocity) > 10000 ) {
                    if ( MacheteBoost < 50 )
                        MacheteBoost += 3;
                    else if ( MacheteBoost < 100 )
                        MacheteBoost += 2;
                    else
                        MacheteBoost++;
                }
                MacheteResetTime = Level.TimeSeconds + 5.0;
            }
        }
        return true;
    }
    return false;
}

function DeleteInventory( inventory Item )
{
    super.DeleteInventory(Item);
    if ( Item == QuickMeleeWeapon ) {
        QuickMeleeWeapon = none;
        SetBestQuickMeleeWeapon();
        // for machete-walking
        if ( QuickMeleeWeapon != none )
            PendingWeapon = QuickMeleeWeapon;
    }
}

// todo: add support for modded guns
function bool ValidQuickMeleeWeapon(KFMeleeGun W)
{
    if ( W == none )
        return false;

    return ScrnKnife(W) != none || ScrnMachete(W) != none;
}

function bool CheckQuickMeleeWeapon(KFMeleeGun W)
{
    if ( !ValidQuickMeleeWeapon(W) )
        return false;

    if ( QuickMeleeWeapon == none || class<KFMeleeFire>(QuickMeleeWeapon.FireModeClass[1]).default.MeleeDamage <
            class<KFMeleeFire>(W.FireModeClass[1]).default.MeleeDamage )
    {
        QuickMeleeWeapon = W;
        return true;
    }
    return false;
}

function SetBestQuickMeleeWeapon()
{
    local inventory inv;
    local KFMeleeGun W;
    local int c;

    for ( inv = Inventory; inv != none && ++c < 1000; inv = inv.Inventory) {
        W = KFMeleeGun(inv);
        if ( W != none )
            CheckQuickMeleeWeapon(W);
    }
}

// The player wants to switch to weapon group number F.
// Merged code from Pawn and KFHumanPawn + added ability to switch perked weapon first and empty guns last
simulated function SwitchWeapon(byte F)
{
    local Inventory Inv;
    local Weapon W;
    local bool bPerkedFirst;
    local array<Weapon> SortedGroupInv; // perked -> non-perked -> empy
    local int NonPerkedIndex, EmptyIndex, i;
    local class<KFWeaponPickup> WP;

    if ( (Level.Pauser!=None) || (Inventory == None) )
        return;
    if ( PendingWeapon != None && PendingWeapon.bForceSwitch )
        return;
    if ( bQuickMeleeInProgress )
        return;

    bPerkedFirst = ScrnPerk != none && ScrnPlayerController(Controller) != none && ScrnPlayerController(Controller).bPrioritizePerkedWeapons;

    // sort group inventory
    for ( Inv = Inventory; Inv != none && ++i < 1000; Inv = Inv.Inventory ) {
        W = Weapon(Inv);
        if ( W != none && W.InventoryGroup == F && AllowHoldWeapon(W) ) {
            WP = class<KFWeaponPickup>(W.PickupClass);
            if ( !W.HasAmmo() && (KFWeapon(W) == none || (KFWeapon(W).bConsumesPhysicalAmmo && !KFWeapon(W).bMeleeWeapon)) ) {
                // weapon has no ammo
                SortedGroupInv[SortedGroupInv.length] = W;
            }
            else if ( bPerkedFirst && (PipeBombExplosive(W) != none || Knife(W) != none
                    || WP == none
                    || (WP.default.CorrespondingPerkIndex != ScrnPerk.default.PerkIndex
                        && !ScrnPerk.static.OverridePerkIndex(WP))) )
            {
                // non-perked weapon, has ammo
                SortedGroupInv.insert(EmptyIndex, 1);
                SortedGroupInv[EmptyIndex] = W;
                EmptyIndex++;
            }
            else {
                // perked weapon, has ammo
                if ( Boomstick(W) != none && ScrnPlayerController(Controller) != none && ScrnPlayerController(Controller).bPrioritizeBoomstick ) {
                    SortedGroupInv.insert(0, 1);
                    SortedGroupInv[0] = W;
                }
                else {
                    SortedGroupInv.insert(NonPerkedIndex, 1);
                    SortedGroupInv[NonPerkedIndex] = W;
                }
                NonPerkedIndex++;
                EmptyIndex++;
            }
        }
    }

    if ( SortedGroupInv.length == 0 )
        return; // no weapons in current category

    if ( Weapon == none || Weapon.InventoryGroup != F || SortedGroupInv.length == 1 )
        W = SortedGroupInv[0];
    else {
        for ( i=0; i<SortedGroupInv.length; ++i ) {
            if ( Weapon == SortedGroupInv[i] ) {
                i++; // switch to next weapon
                break;
            }
        }
        if ( i < SortedGroupInv.length )
            W = SortedGroupInv[i];
        else
            W = SortedGroupInv[0];
    }

    if ( W != none && W != Weapon ) {
        PendingWeapon = W;
        if ( Weapon != None )
            Weapon.PutDown();
        else
            ChangedWeapon();
    }
}

simulated function ChangedWeapon()
{
    super(KFPawn).ChangedWeapon();

    if (Role < ROLE_Authority) {
        ApplyWeaponStats(Weapon);
    }

    if ( IsLocallyControlled() && ScrnPlayerController(Controller) != none )
        ScrnPlayerController(Controller).LoadGunSkinFromConfig();
}

function ServerChangedWeapon(Weapon OldWeapon, Weapon NewWeapon)
{
    local ScrnPlayerController PC;
    local int i;

    PC = ScrnPlayerController(Controller);
    if ( NewWeapon != none && PC != none) {
        // set skinned attachment class
        for ( i=0; i<PC.WeaponSettings.length; ++i ) {
            if ( NewWeapon.class == PC.WeaponSettings[i].Weapon ) {
                if ( PC.WeaponSettings[i].SkinnedWeapon != none )
                    NewWeapon.AttachmentClass = PC.WeaponSettings[i].SkinnedWeapon.default.AttachmentClass;
                break;
            }
        }
    }

    super(KFPawn).ServerChangedWeapon(OldWeapon,NewWeapon);
    ApplyWeaponStats(NewWeapon);
    ApplyWeaponFlashlight(false);
}

function UpdateSpecInfo()
{
    local KFWeapon Weap;

    SpecWeight = CurrentWeight;
    Weap = KFWeapon(Weapon);
    if ( Weap != none ) {
        SpecWeapon = Weap.class;
        SpecMagAmmo = Weap.MagAmmoRemaining;
        if (  Weap.MagCapacity <= 1 ) {
            SpecMags = Weap.AmmoAmount(0);
        }
        else if ( Weap.bHoldToReload )
            SpecMags = Max(Weap.AmmoAmount(0)-Weap.MagAmmoRemaining,0); // Single rounds reload, just show the true ammo count.
        else if ( Weap.MagCapacity <= 1 )
            SpecMags = Weap.AmmoAmount(0);
        else if ( Weap.AmmoAmount(0) <= Weap.MagAmmoRemaining )
            SpecMags = 0;
        else
            SpecMags = ceil(float(Weap.AmmoAmount(0) - Weap.MagAmmoRemaining)/Weap.MagCapacity);

        if ( Weap.bHasSecondaryAmmo )
            SpecSecAmmo = Weap.AmmoAmount(1);
        else
            SpecSecAmmo = 0;
    }
    else
        SpecWeapon = none;

    if ( FindPlayerGrenade() != none )
        SpecNades = PlayerGrenade.AmmoAmount(0);
    else
        SpecNades = 0;
}

simulated function ApplyWeaponStats(Weapon NewWeapon)
{
    local KFWeapon Weap;
    //local KFShotgunFire SgFire;

    BaseMeleeIncrease = default.BaseMeleeIncrease;
    InventorySpeedModifier = 0;

    Weap = KFWeapon(NewWeapon);

    // check cowboy mode
    if ( Role == ROLE_Authority || IsLocallyControlled() ) {
        bCowboyMode = ScrnPerk != none && Weap != none && ScrnPerk.static.CheckCowboyMode(KFPRI, Weap.class);

        if ( Weap != none ) {
            if ( Weap.class == class'Dualies' || ClassIsChildOf(Weap.class, class'ScrnDualies') ) {
                // Machine Pistols for Cowboy!
                Weap.GetFireMode(0).bWaitForRelease = !bCowboyMode;
                Weap.GetFireMode(0).bNowWaiting = Weap.GetFireMode(0).bWaitForRelease;
            }
        }

        if ( Role == ROLE_Authority ) {
            if ( ScrnPlayerController(Controller) != none )
                ScrnPlayerController(Controller).bCowboyForWave = bCowboyMode && ScrnPlayerController(Controller).bCowboyForWave;
            UpdateSpecInfo();
        }
    }

    SetAmmoStatus();
    if ( KFPRI != none && Weap != none ) {
        Weap.bIsTier3Weapon = Weap.default.bIsTier3Weapon; // restore default value from the hack in AddInventory()

        if ( Weap.bSpeedMeUp ) {
            if ( KFPRI.ClientVeteranSkill != none )
                BaseMeleeIncrease += KFPRI.ClientVeteranSkill.Static.GetMeleeMovementSpeedModifier(KFPRI);
            InventorySpeedModifier = (default.GroundSpeed * BaseMeleeIncrease) - (Weap.Weight * 2);
        }

        if ( ScrnPerk != none ) {
            InventorySpeedModifier +=
                default.GroundSpeed * ScrnPerk.static.GetWeaponMovementSpeedBonus(KFPRI, NewWeapon);
        }
        // ScrN Armor can slow down players (or even boost) -- PooSH
        InventorySpeedModifier -= default.GroundSpeed * CurrentVestClass.default.SpeedModifier;
    }


    //fix spawn offsets for projectile fire modes
    //exclude healing nades, cuz they detonate on instigator too
    // if ( Weap != none && ScrnM79M(Weap) == none && ScrnM4203MMedicGun(Weap) == none) {
        // SgFire = KFShotgunFire(NewWeapon.GetFireMode(0));
        // if ( SgFire != none && SgFire.ProjSpawnOffset.X > 15 ) {
            // SgFire.ProjSpawnOffset.X = 15;
        // }
        // SgFire = KFShotgunFire(NewWeapon.GetFireMode(1));
        // if ( SgFire != none && SgFire.ProjSpawnOffset.X > 15 ) {
            // SgFire.ProjSpawnOffset.X = 15;
        // }
    // }
}

function CheckPerkAchievements()
{
    local ScrnPlayerController ScrnPlayer;
    local ClientPerkRepLink StatRep;
    local int i;

    if ( Role < ROLE_Authority )
        return; // ROLE_AutonomousProxy can execute non-simulated functions too

    ScrnPlayer = ScrnPlayerController(Controller);
    if ( KFPRI == none || KFPRI.ClientVeteranSkill == none || ScrnPlayer == none )
        return;

    if ( KFPRI.ClientVeteranSkill != ScrnPlayer.InitialPerkClass ) {
        if ( KFGameType(Level.Game).WaveNum == 0 && ScrnPlayer.InitialPerkClass == none ) {
            ScrnPlayer.InitialPerkClass = KFPRI.ClientVeteranSkill;
            ScrnPlayer.bChangedPerkDuringGame = false;
        }
        else {
            ScrnPlayer.bChangedPerkDuringGame = true;
        }
    }

    StatRep = SRStatsBase(ScrnPlayer.SteamStatsAndAchievements).Rep;
    if ( StatRep == none )
        return;

    if ( KFPRI.ClientVeteranSkillLevel < 6 )
        return;

    if ( KFPRI.ClientVeteranSkillLevel >= 26 )
        class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(StatRep, 'PerkOrange', 1);
    else if ( KFPRI.ClientVeteranSkillLevel >= 21 )
        class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(StatRep, 'PerkPurple', 1);
    else if ( KFPRI.ClientVeteranSkillLevel >= 16 )
        class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(StatRep, 'PerkBlue', 1);
    else if ( KFPRI.ClientVeteranSkillLevel >= 11 )
        class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(StatRep, 'PerkGreen', 1);

    // Mr. Perky
    for ( i = 0; i < StatRep.CachePerks.length; ++i ) {
        if ( StatRep.CachePerks[i].CurrentLevel < 6 )
            return;
    }
    class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(StatRep, 'MrPerky', 1);
}

simulated function VeterancyChanged()
{
    local Inventory Inv, NextInv;
    local int c;

    MaxCarryWeight = Default.MaxCarryWeight;

    if ( KFPRI == none )
        return;

    if ( KFPRI != none && KFPRI.ClientVeteranSkill != none )
        MaxCarryWeight += KFPRI.ClientVeteranSkill.Static.AddCarryMaxWeight(KFPRI);

    if ( CurrentWeight > MaxCarryWeight )   {
        // drop extra weight
        for ( Inv = Inventory; Inv != none && CurrentWeight > MaxCarryWeight && ++c < 1000; Inv = NextInv ) {
            NextInv = Inv.Inventory; // save next link before deleting this one
            if ( KFWeapon(Inv) != none && !KFWeapon(Inv).bKFNeverThrow ) {
                Inv.Velocity = Velocity;
                Inv.DropFrom(Location + VRand() * 10);
            }
        }
    }

    ScrnPerk = class<ScrnVeterancyTypes>(KFPRI.ClientVeteranSkill);
    if ( ScrnPerk != none )
        HealthMax = (default.HealthMax + HealthBonus) * ScrnPerk.static.HealthMaxMult(KFPRI, self);
    ApplyWeaponStats(Weapon);
    RecalcAmmo();
    CheckPerkAchievements();
    PrevPerkClass = KFPRI.ClientVeteranSkill;
    PrevPerkLevel = KFPRI.ClientVeteranSkillLevel;
}

// recalculates ammo bonuses
function RecalcAmmo()
{
    local Inventory CurInv;
    local Ammunition MyAmmo;
    local float ClipPrice, FullRefillPrice;
    local int ClipSize;
    local int c;

    for ( CurInv = Inventory; CurInv != none && ++c < 1000; CurInv = CurInv.Inventory ) {
        if ( KFAmmunition(CurInv) != none ) {
            // just call this function to apply perk bonuses
            CalcAmmoCost(self, Class<Ammunition>(CurInv.class), MyAmmo, ClipPrice, FullRefillPrice, ClipSize);
        }
    }
}

function DestroyMyPipebombs(optional int CountToLeave)
{
    local PipeBombProjectile P;

    foreach DynamicActors(Class'PipeBombProjectile',P)
    {
        if( !P.bHidden && P.Instigator==self )
        {
            if ( CountToLeave > 0 )
                CountToLeave--;
            else
                P.bEnemyDetected = true; // blow me up
        }
    }
}


// ===================================== SHIELD =====================================
simulated function class<ScrnVestPickup> GetVestClass()
{
    if ( CurrentVestClass == NoVestClass ) {
        if ( CanUseVestClass(StandardVestClass) )
            return StandardVestClass;
        else
            return LightVestClass; //this should be always wearable
    }
    return CurrentVestClass;
}

simulated function class<ScrnVestPickup> GetCurrentVestClass()
{
    return CurrentVestClass;
}

simulated function bool CanUseVestClass(class<ScrnVestPickup> NewClass)
{
    if ( NewClass == none )
        return false;

    // always allow to use LightVestClass, no matter of crap modders can do in the future
    if ( CurrentVestClass == NewClass || NewClass == LightVestClass || NewClass == NoVestClass )
        return true;

    return ( CurrentWeight + NewClass.default.Weight - CurrentVestClass.default.Weight < MaxCarryWeight + 0.0001 );
}

function bool SetVestClass(class<ScrnVestPickup> NewClass)
{
    //log("SetVestClass("$NewClass$"), CurrentVestClass = " $ CurrentVestClass, 'ScrnBalance');
    if ( !CanUseVestClass(NewClass) )
        return false;

    // always update ShieldStrengthMax, even if NewClass is already set
    ShieldStrengthMax = NewClass.default.ShieldCapacity;
    if ( ShieldStrength > ShieldStrengthMax )
        ShieldStrength = ShieldStrengthMax;

    if ( NewClass != CurrentVestClass ) {
        CurrentWeight += NewClass.default.Weight - CurrentVestClass.default.Weight;
        CurrentVestClass = NewClass;
        ApplyWeaponStats(Weapon); // update armor speed modifier
        ClientSetVestClass(NewClass);
    }

    return true;
}

simulated function ClientSetVestClass(class<ScrnVestPickup> NewVestClass)
{
    if ( Role < ROLE_Authority ) {
        CurrentVestClass = NewVestClass;
        ShieldStrengthMax = CurrentVestClass.default.ShieldCapacity;
        ApplyWeaponStats(Weapon);
    }
}



simulated function SetShieldWeight()
{
    if ( int(ShieldStrength) <= 0 )
            SetVestClass(NoVestClass);
}


function float GetShieldStrengthMax()
{
    if ( CurrentVestClass == NoVestClass ) {
        if ( CanUseVestClass(StandardVestClass) )
            return StandardVestClass.default.ShieldCapacity;
        else
            return LightVestClass.default.ShieldCapacity;
    }
    return ShieldStrengthMax;
}

function int CanUseShield(int ShieldAmount)
{
    ShieldStrength = Max(ShieldStrength,0);
    if ( ShieldStrength < GetShieldStrengthMax() )
    {
        return (Min(GetShieldStrengthMax(), ShieldStrength + ShieldAmount) - ShieldStrength);
    }
    return 0;
}

function bool AddShieldStrength(int AmountToAdd)
{
    if ( AmountToAdd == 0 )
        return false;

    // this was added to make compatible with current KF shield system
    if ( AmountToAdd > LightVestClass.default.ShieldCapacity && ShieldStrengthMax < StandardVestClass.default.ShieldCapacity )
        SetVestClass(StandardVestClass);
    // if can't wear combat armor, try light armor
    if ( CurrentVestClass == NoVestClass )
        SetVestClass(LightVestClass);

    if ( AmountToAdd > 0 && ShieldStrength >= ShieldStrengthMax )
        return false;

    ShieldStrength = clamp(ShieldStrength + AmountToAdd, 0, ShieldStrengthMax);
    SetShieldWeight();
    if ( ShieldStrength >= 26 ) {
        bCowboyMode = false;
        if ( ScrnPlayerController(Controller) != none )
            ScrnPlayerController(Controller).bHadArmor = true;
    }

    return true;
}


function int ShieldAbsorb( int damage )
{
    local int AbsorbedValue, OldShieldStrength, OriginalDamage;
    local float dmg;

    if ( ShieldStrength == 0 || damage <= 0 )
        return damage;

    OldShieldStrength = ShieldStrength;
    OriginalDamage = damage;

    // I don't get Tripwire's armor protection formula (possibly just a chain of bugs that somehow work together),
    // so I wrote my own one, which is quite simple:
    // Shield > 50%  = 67% protection
    // Shield 26-50% = 50% protection
    // Shield <= 25% = 33% protection
    // (c) PooSH

    dmg = damage;
    if ( KFPRI != none && KFPRI.ClientVeteranSkill != none ) {
        if ( KFHumanPawn(LastDamagedBy) != none )
            dmg = fmax(1.0, dmg * fmax(0.6, KFPRI.ClientVeteranSkill.static.GetBodyArmorDamageModifier(KFPRI))); // cap 40% better armor against Human Damage
        else
            dmg = fmax(1.0, dmg * KFPRI.ClientVeteranSkill.static.GetBodyArmorDamageModifier(KFPRI)); // do at least 1 damage point after perk bonus
    }

    if ( dmg < 3 ) {
        // special case for tiny damages
        AbsorbedValue = rand(2);
        ShieldStrength -= AbsorbedValue;
        dmg -= AbsorbedValue;
    }
    else {
        if ( ShieldStrength > 50 ) {
            AbsorbedValue = min(0.67 * dmg, ShieldStrength - 50);
            ShieldStrength -= AbsorbedValue;
            dmg -= AbsorbedValue;
        }
        // don't put "else" here - after lowering the shield this can be executed too
        if ( ShieldStrength > 25 && ShieldStrength <= 50 ) {
            AbsorbedValue = min(0.50 * dmg, ShieldStrength - 25);
            ShieldStrength -= AbsorbedValue;
            dmg -= AbsorbedValue;
        }
        // don't put "else" here - after lowering the shield this can be executed too
        if ( ShieldStrength > 0 && ShieldStrength <= 25 ) {
            AbsorbedValue = clamp(0.33 * dmg, 1, ShieldStrength);
            ShieldStrength -= AbsorbedValue;
            dmg -= AbsorbedValue;
        }
    }
    damage = dmg;

    if ( bCheckHorzineArmorAch && OldShieldStrength > 100 && damage < Health && OriginalDamage > clamp(Health, 50, 80)
                && PlayerController(Controller) != none
                && SRStatsBase(PlayerController(Controller).SteamStatsAndAchievements) != none )
            bCheckHorzineArmorAch = class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(
                SRStatsBase(PlayerController(Controller).SteamStatsAndAchievements).Rep, 'HorzineArmor', 1);

    // just to be sure
    if ( ShieldStrength < 0 )
        ShieldStrength = 0;
     if ( damage < 0 )
        damage = 0;

    SetShieldWeight(); //recalculate shield's weight
    //test cowboy mode on loosing armor
    // v7.28 - don't do it because this function isn't executed on client side
    // if ( !bCowboyMode && ShieldStrength < 26 && OldShieldStrength >= 26
            // && ScrnPerk != none )
        // bCowboyMode = ScrnPerk.static.CheckCowboyMode(KFPlayerReplicationInfo(PlayerReplicationInfo), Weapon.class);

    return damage;
}



simulated function CalcVestCost(class<ScrnVestPickup> VestClass, out int Cost, out int AmountToBuy, out float Price1p)
{
    if ( VestClass == none || VestClass == NoVestClass )
        VestClass = GetVestClass();

    AmountToBuy = VestClass.default.ShieldCapacity;
    Price1p = float(VestClass.default.Cost) / VestClass.default.ShieldCapacity;
    if ( KFPlayerReplicationInfo(PlayerReplicationInfo).ClientVeteranSkill != none)
        Price1p *= KFPlayerReplicationInfo(PlayerReplicationInfo).ClientVeteranSkill.static.GetCostScaling(KFPlayerReplicationInfo(PlayerReplicationInfo), VestClass);;
    Cost = ceil(AmountToBuy * Price1p);

    // No Refunds, if vest classes differs
    if ( VestClass == CurrentVestClass) {
        if ( ShieldStrength >= AmountToBuy ) {
            Cost = 0;
            AmountToBuy = 0;
        }
        else if ( ShieldStrength > 0 ) {
            // price of repairs
            Cost -= ShieldStrength * Price1p;
            AmountToBuy -= ShieldStrength;
        }
    }
}

function ServerBuyShield(class<ScrnVestPickup> VestClass)
{
    local float Price1p;
    local int Cost, AmountToBuy;

    if ( VestClass == none || VestClass == NoVestClass || !CanUseVestClass(VestClass) )
        return;

    CalcVestCost(VestClass, Cost, AmountToBuy, Price1p);

    // log("ServerBuyShield: Current Vest = " $ GetItemName(String(CurrentVestClass)) $ ", " $ int(ShieldStrength) $"%."
        // @ "Vest to Buy = " $ GetItemName(String(VestClass))
        // @ "Amount to Buy = " $ AmountToBuy $ " * " $ Price1p $ " = $" $ Cost, 'ScrnBalance');

    if ( CanBuyNow() && AmountToBuy > 0 ) {
        if ( PlayerReplicationInfo.Score >= Cost ) {
            if ( SetVestClass(VestClass) && AddShieldStrength(AmountToBuy) )
                PlayerReplicationInfo.Score -= Cost;
        }
        else if ( VestClass == CurrentVestClass && ShieldStrength > 0 ) {
            //repair shield for money players has, if not enough to buy a full shield
            AmountToBuy = PlayerReplicationInfo.Score / Price1p;
            if ( AmountToBuy > 0 && AddShieldStrength(AmountToBuy) ) {
                PlayerReplicationInfo.Score -= ceil(AmountToBuy * Price1p);
                if ( PlayerReplicationInfo.Score < 0 )
                    PlayerReplicationInfo.Score = 0;
                UsedStartCash(ceil(AmountToBuy * Price1p));
            }
        }
    }
    SetShieldWeight();
    SetTraderUpdate();
}

function ServerSellShield()
{
    //Marcus warns you : NO REFUNDS ;)
    SetVestClass(NoVestClass);
    SetTraderUpdate();
}


function ServerBuyKevlar()
{
    if ( CurrentVestClass == NoVestClass ) {
        SetVestClass(GetVestClass());
    }
    ServerBuyShield(CurrentVestClass);
}


function AddDefaultInventory()
{
    local int s;

    super.AddDefaultInventory();

    if ( class'ScrnCustomPRI'.static.FindMe(PlayerReplicationInfo).GetSteamID32() == 15238764 ) {
        // give Bullpup to [REB]Mike1, because after 1000+ hours of using it he definitely deserves it :)
        CreateInventoryVeterancy(string(class'ScrnBullpup'), 0);
        Weapon(FindInventoryType(class'ScrnBullpup')).AddAmmo(500, 0);
    }

    // make sure players have at least knife, when admin screwed up the config
    if ( Inventory == none && KFSPGameType(Level.Game) == none ) {
        CreateInventory("ScrnBalanceSrv.ScrnKnife");
        if ( Inventory != none ) {
            Inventory.OwnerEvent('LoadOut');
            Controller.ClientSwitchToBestWeapon();
        }
    }

    // update vest class, if ShieldStrength is set directly, not via AddShieldStrength()
    if ( ShieldStrength > 0 ) {
        s = ShieldStrength;
        ShieldStrength = 0;
        AddShieldStrength(s);
    }
}
// ===================================== AMMO =====================================
/**
 * Calculates ammo price form given ammo class
 * @param P                 pawn, which wants to buy ammo
 * @param AClass            ammo class to buy. P must have this ammo in his inventory!
 * @param MyAmmo            [out] Ammunition object in the P inventory
 * @param ClipPrice         [out] cost per magazine/clip
 * @param FullRefillPrice   [out] how much to fully refill ammo?
 * @param ClipSize          [out] how much ammo in one magazine (including perk bonuses)
 * @return false, if calculation can't be made (e.g. no such ammo in P inventory)
 * @author PooSH, 2012
 */
static function bool CalcAmmoCost(Pawn P, Class<Ammunition> AClass,
        out Ammunition MyAmmo,
        out float ClipPrice, out float FullRefillPrice,
        out int ClipSize)
{
    local KFPlayerReplicationInfo KFPRI;
    local class<KFVeterancyTypes> Perk;
    local Inventory Inv;
    local int c;
    local KFWeapon KW;
    local class<KFWeaponPickup> KWPickupClass;
    local bool bSecondaryAmmo; // is this a secondary ammo?

    // reset output arguments to be sure caller won't use old values, if function returns false
    MyAmmo = none;
    ClipPrice = 0;
    FullRefillPrice = 0;
    ClipSize = 0;

    if ( P == none || AClass == none )
        return false;

    KFPRI = KFPlayerReplicationInfo(P.PlayerReplicationInfo);
    if ( KFPRI != none )
        Perk = KFPRI.ClientVeteranSkill;

    for ( Inv = P.Inventory; Inv != none && ++c < 1000 && (MyAmmo == none || KW == none); Inv = Inv.Inventory ) {
        if ( Inv.Class == AClass )
            MyAmmo = Ammunition(Inv);
        else if ( KW == None && KFWeapon(Inv) != None && (KFWeapon(Inv).AmmoClass[0] == AClass || KFWeapon(Inv).AmmoClass[1] == AClass) )
            KW = KFWeapon(Inv);
    }

    if ( KW == none || MyAmmo == none )
        return false;

    KWPickupClass = class<KFWeaponPickup>(KW.PickupClass);
    if ( KWPickupClass == none )
        return false;
    bSecondaryAmmo = KW.bHasSecondaryAmmo
                        && AClass != KW.FireModeClass[0].default.AmmoClass
                        && AClass == KW.FireModeClass[1].default.AmmoClass;

    MyAmmo.MaxAmmo = MyAmmo.default.MaxAmmo;
    if ( bSecondaryAmmo && Perk != none)
        MyAmmo.MaxAmmo = MyAmmo.default.MaxAmmo * Perk.static.AddExtraAmmoFor(KFPRI, AClass);
    else
        MyAmmo.MaxAmmo  = MyAmmo.default.MaxAmmo * KW.GetAmmoMulti(); // allow weapons to hande ammo bonuses
    if ( MyAmmo.AmmoAmount > MyAmmo.MaxAmmo )
        MyAmmo.AmmoAmount = MyAmmo.MaxAmmo;

    // Adding SecondaryAmmoCost variable to KFWeaponPickup would make things much easier,
    // but Tripwire doesn't look for easy ways ;)
    // If weapon has secondary ammo and this isn't secondary ammo, then this is primary ammo,
    // and primary ammo's cost is stored inside PrimaryWeaponPickup class
    if ( KW.bHasSecondaryAmmo && !bSecondaryAmmo && KWPickupClass.default.PrimaryWeaponPickup != none ) {
        ClipPrice = KWPickupClass.default.PrimaryWeaponPickup.default.AmmoCost;
        if( KWPickupClass.default.PrimaryWeaponPickup.default.BuyClipSize > 0 )
            ClipSize = KWPickupClass.default.PrimaryWeaponPickup.default.BuyClipSize;
        else
            ClipSize = KW.default.MagCapacity;
    }
    else {
        ClipPrice = KWPickupClass.default.AmmoCost;
        if( KWPickupClass.default.BuyClipSize > 0 )
            ClipSize = KWPickupClass.default.BuyClipSize;
        else if ( bSecondaryAmmo )
            ClipSize = 1; // Secondary Mags always MUST have a Mag Capacity of 1
        else
            ClipSize = KW.default.MagCapacity;
    }

    if ( Perk != none )
        ClipPrice *= Perk.static.GetAmmoCostScaling(KFPRI, KW.PickupClass);

    if ( !bSecondaryAmmo && Perk != none )
        ClipSize *= Perk.static.GetMagCapacityMod(KFPRI, KW);

    FullRefillPrice = ceil(float(MyAmmo.MaxAmmo-MyAmmo.AmmoAmount) * ClipPrice/ClipSize);

    return true;
}

function UsedStartCash(int UseAmount)
{
    if ( UseAmount != 0 && ScrnPlayerController(Controller) != none )
        ScrnPlayerController(Controller).StartCash = Max(ScrnPlayerController(Controller).StartCash - UseAmount, 0);
}

function bool ServerBuyAmmo( Class<Ammunition> AClass, bool bOnlyClip )
{
    local Ammunition MyAmmo;
    local float ClipPrice, FullRefillPrice;
    local int ClipSize;
    local int AmmoToAdd;
    local float Price;

    if ( !CanBuyNow() || !CalcAmmoCost(self, AClass, MyAmmo, ClipPrice, FullRefillPrice, ClipSize)
            || MyAmmo.AmmoAmount >= MyAmmo.MaxAmmo ) {
        //SetTraderUpdate();
        return false;
    }

    if ( bOnlyClip ) {
        AmmoToAdd = ClipSize;
        Price = ClipPrice;
        if ( AmmoToAdd + MyAmmo.AmmoAmount > MyAmmo.MaxAmmo ) {
            AmmoToAdd = MyAmmo.MaxAmmo - MyAmmo.AmmoAmount;
            Price = ceil(float(AmmoToAdd) * ClipPrice/ClipSize);
        }
    }
    else {
        AmmoToAdd = MyAmmo.MaxAmmo - MyAmmo.AmmoAmount;
        Price = FullRefillPrice;
    }

    if ( PlayerReplicationInfo.Score < Price ) {
        // Not enough CASH (so buy the amount you CAN afford).
        AmmoToAdd *= (PlayerReplicationInfo.Score/Price);
        Price = ceil(float(AmmoToAdd) * ClipPrice/ClipSize);

        if ( AmmoToAdd > 0 ) {
            PlayerReplicationInfo.Score = Max(PlayerReplicationInfo.Score - Price, 0);
            UsedStartCash(Price);
            MyAmmo.AddAmmo(AmmoToAdd);
        }
    }
    else {
        PlayerReplicationInfo.Score = int(PlayerReplicationInfo.Score-Price);
        UsedStartCash(Price);
        MyAmmo.AddAmmo(AmmoToAdd);
    }
    SetTraderUpdate();
    return true;
}

// ===================================== WEAPONS =====================================
//fixed an exploit when player buy perked dualies with discount,
//then change the perk and sell 2-nd pistol as an off-perk weapon for a full price
// (c) PooSH, 2012
function ServerSellWeapon( Class<Weapon> WClass )
{
    local Inventory I;
    local int c;
    local KFWeapon W, SinglePistol;
    local float SellValue;
    local int AmmoCount;

    if ( !CanBuyNow() || Class<KFWeapon>(WClass) == none || Class<KFWeaponPickup>(WClass.Default.PickupClass)==none
        || Class<KFWeapon>(WClass).Default.bKFNeverThrow )
    {
        SetTraderUpdate();
        Return;
    }

    I = Inventory;
    while ( I != none && I.Class != WClass && ++c < 1000 )
        I = I.Inventory;

    if ( I == none || I.Class != WClass )
        return; //no instances of specified class found in inventory

    W = KFWeapon(I);
    //Changed from "!= -1" to ">= 0" to reject negative value possibility (c) PooSH
    if ( W != none && W.SellValue >= 0 ) {
        SellValue = W.SellValue;
    }
    else {
        SellValue = (class<KFWeaponPickup>(WClass.default.PickupClass).default.Cost * 0.75);

        if ( KFPlayerReplicationInfo(PlayerReplicationInfo).ClientVeteranSkill != none )
            SellValue *= KFPlayerReplicationInfo(PlayerReplicationInfo).ClientVeteranSkill.static.GetCostScaling(KFPlayerReplicationInfo(PlayerReplicationInfo), WClass.Default.PickupClass);
    }

    if ( W != none ) {
        if ( PipeBombExplosive(W) != none ) {
            //give 75% of all pipes, not 2 (even if there is only 1 left)
            // calc price per ammo and multiply by ammo count
            SellValue /= W.default.FireModeClass[0].default.AmmoClass.default.InitialAmount;
            SellValue *= W.AmmoAmount(0);

        }
        else if ( W.class==Class'Dualies' ) {
            SinglePistol = Spawn(class'Single', self);
            SellValue *= 2; //cuz we can't sell 9mm
        }
        else if ( W.class==Class'DualDeagle' )
            SinglePistol = Spawn(class'Deagle', self);
        else if ( W.class==Class'Dual44Magnum' )
            SinglePistol = Spawn(class'Magnum44Pistol', self);
        else if ( W.class==Class'DualMK23Pistol' )
            SinglePistol = Spawn(class'MK23Pistol', self);
        else if ( W.class==Class'DualFlareRevolver' )
            SinglePistol = Spawn(class'FlareRevolver', self);
        else if( Weapon(I).DemoReplacement!=None )
            SinglePistol = KFWeapon(Spawn(W.DemoReplacement, self));

        if( SinglePistol!=None )
        {
            AmmoCount = W.AmmoAmount(0);

            SellValue /= 2;
            //fixed an exploit when player buys perked dualies with discount,
            //then changes the perk and sells 2-nd pistol as an off-perk weapon for a full SellValue
            // (c) PooSH, 2012
            SinglePistol.SellValue = SellValue;

            SinglePistol.GiveTo(self);
            //restore ammo count to it previous value
            SinglePistol.ConsumeAmmo(0, SinglePistol.AmmoAmount(0) - min(AmmoCount, SinglePistol.MaxAmmo(0)));
        }
    }

    if ( I==Weapon || I==PendingWeapon )
    {
        ClientCurrentWeaponSold();
    }

    PlayerReplicationInfo.Score += int(SellValue);

    I.Destroy();

    SetTraderUpdate();

    if ( KFGameType(Level.Game)!=none )
        KFGameType(Level.Game).WeaponDestroyed(WClass);
}

// Searches for a weapon in the player's invenotry. If finds - sets outputs and returns true
final function bool HasWeaponClassToSell( class<KFWeapon> Weap, out float SellValue, out float Weight )
{
    local Inventory I;
    local int c;
    local KFWeapon W;

    for ( I=Inventory; I!=None && ++c < 1000; I=I.Inventory ) {
        W = KFWeapon(I);
        if (W != none && I.Class == Weap ) {
            SellValue = W.SellValue;
            Weight = W.Weight;
            return true;
        }
    }
    return false;
}

// fixed exploit then player buys perked dualies, drops them, changes perk and picks them
// with the full sell price
// (c) PooSH, 2012
function ServerBuyWeapon( Class<Weapon> WClass, float ItemWeight )
{
    local float Price,Weight, SellValue, SingleSellValue, SingleWeight;
    local Inventory I;
    local int c;
    local KFWeapon KFW;
    local Class<KFWeaponPickup> WP;
    local ScrnBalance Mut;

    if( !CanBuyNow() || Class<KFWeapon>(WClass)==None || HasWeaponClass(WClass) )
        Return;

    WP = Class<KFWeaponPickup>(WClass.Default.PickupClass);
    if ( WP == none )
        return;

    // Validate if allowed to buy that weapon.
    if( PerkLink==None )
        PerkLink = FindStats();
    if( PerkLink!=None && !PerkLink.CanBuyPickup(WP) )
        return;

    Mut = class'ScrnBalance'.default.Mut;

    Price = WP.Default.Cost;
    if ( ScrnPerk != none ) {
        Price *= ScrnPerk.static.GetCostScaling(KFPRI, WP);
        if  (Mut.bBuyPerkedWeaponsOnly
                && WP.default.CorrespondingPerkIndex != 7
                && WP.default.CorrespondingPerkIndex != ScrnPerk.default.PerkIndex
                && !ScrnPerk.static.OverridePerkIndex(WP) )
            return;
    }
    SellValue = Price * 0.75;
    Weight = Class<KFWeapon>(WClass).Default.Weight;

    if ( (WClass==class'Magnum44Pistol' || WClass==class'ScrnMagnum44Pistol'
                || WClass==class'Dual44Magnum' || WClass==class'ScrnDual44Magnum')
            && HasWeaponClass(class'ScrnDual44MagnumLaser') )
        return;
    else if ( (WClass==class'MK23Pistol' || WClass==class'ScrnMK23Pistol'
                || WClass==class'DualMK23Pistol' || WClass==class'ScrnDualMK23Pistol')
            && HasWeaponClass(class'ScrnDualMK23Laser') )
        return;
    else if( WClass==class'DualDeagle' || WClass==class'Dual44Magnum'
            || WClass==class'DualMK23Pistol' || WClass==class'DualFlareRevolver'
            || WClass.Default.DemoReplacement!=None )
    {
        if ( (WClass==class'DualDeagle' && HasWeaponClassToSell(class'Deagle', SingleSellValue, SingleWeight))
             || (WClass==class'GoldenDualDeagle' && HasWeaponClassToSell(class'GoldenDeagle', SingleSellValue, SingleWeight))
             || (WClass==class'Dual44Magnum' && HasWeaponClassToSell(class'Magnum44Pistol', SingleSellValue, SingleWeight))
             || (WClass==class'DualMK23Pistol' && HasWeaponClassToSell(class'MK23Pistol', SingleSellValue, SingleWeight))
             || (WClass==class'DualFlareRevolver' && HasWeaponClassToSell(class'FlareRevolver', SingleSellValue, SingleWeight))
             || (WClass.Default.DemoReplacement!=None && HasWeaponClassToSell(class<KFWeapon>(WClass.Default.DemoReplacement), SingleSellValue, SingleWeight)) )
        {
            Weight -= SingleWeight;
            Price*=0.5;
            //if one gun is perked, but other isn't - give lowest sell value to fix exploits
            SellValue =  fmin(SellValue, SingleSellValue*2);
        }
    }
    else if( WClass==class'Single' || WClass==class'Deagle' || WClass==class'GoldenDeagle' || WClass==class'Magnum44Pistol'
        || WClass==class'MK23Pistol' || WClass==class'FlareRevolver' )
    {
        if ( (WClass==class'Deagle' && HasWeaponClass(class'DualDeagle'))
                || (WClass==class'GoldenDeagle' && HasWeaponClass(class'GoldenDualDeagle'))
                || (WClass==class'Magnum44Pistol' && HasWeaponClass(class'Dual44Magnum'))
                || (WClass==class'Dualies' && HasWeaponClass(class'Single'))
                || (WClass==class'DualMK23Pistol' && HasWeaponClass(class'MK23Pistol'))
                || (WClass==class'DualFlareRevolver' && HasWeaponClass(class'FlareRevolver'))
            )
            return; // Has the dual weapon.
    }
    else // Check for custom dual weapon mode
    {
        for ( I=Inventory; I!=None && ++c < 1000; I=I.Inventory )
            if( Weapon(I)!=None && Weapon(I).DemoReplacement==WClass )
                return;
    }

    Price = int(Price); // Truncuate price.

    if ( (Weight>0 && !CanCarry(Weight)) || PlayerReplicationInfo.Score<Price )
        Return;

    I = Spawn(WClass, self);
    if ( I != none )
    {
        KFW = KFWeapon(I);

        if ( KFW != none ) {
            Mut.KF.WeaponSpawned(KFW);
            KFW.UpdateMagCapacity(PlayerReplicationInfo);
            KFW.FillToInitialAmmo();
            KFW.SellValue = SellValue;
            CheckBlame(KFW);
        }

        I.GiveTo(self);
        PlayerReplicationInfo.Score -= Price;
        UsedStartCash(Price);
        ClientForceChangeWeapon(I);
    }
    else ClientMessage("Error: Weapon failed to spawn.");

    SetTraderUpdate();
}

// blames the player for buying particular guns
function CheckBlame(KFWeapon GunToBlameFor)
{
    local string BlameStr;
    local ScrnBalance Mut;

    Mut = class'ScrnBalance'.default.Mut;

    if ( M99SniperRifle(GunToBlameFor) != none )
        BlameStr = BlameStrM99;
    else if ( Mut.ScrnGT != none && Mut.ScrnGT.bUseEndGameBoss && Mut.ScrnGT.WaveNum >= Mut.ScrnGT.FinalWave ) {
        // buying guns for boss wave
        if ( GunToBlameFor.IsA('RPG') )
            BlameStr = BlameStrRPG;
    }

    if ( BlameStr != "" )
        Mut.BlamePlayer(ScrnPlayerController(Controller), BlameStr);
}

// allows to adjust player's health
function bool GiveHealth(int HealAmount, int HealMax)
{
    if (ScrnPerk != none )
        HealthMax = (default.HealthMax + HealthBonus) * ScrnPerk.static.HealthMaxMult(KFPRI, self);

    if( BurnDown > 0 ) {
        if( BurnDown > 1 )
            BurnDown *= 0.5;
        LastBurnDamage *= 0.5;
    }

    // Don't let them heal more than the max health
    if( HealAmount + HealthToGive + Health  > HealthMax ) {
        healAmount = HealthMax - (Health + HealthToGive);

        if( healAmount == 0 )
            return false;
    }

    if( Health < HealMax ) {
        HealthToGive+=HealAmount;
        ClientHealthToGive = HealthToGive;
        lastHealTime = level.timeSeconds;
        return true;
    }
    return false;
}

// returns true, if player is using medic perk
simulated function bool IsMedic()
{
    if ( KFPRI != none && KFPRI.ClientVeteranSkill != none )
        return KFPRI.ClientVeteranSkill.static.GetHealPotency(KFPRI) > 1.01;

    return false;
}

function TakeHealing(ScrnHumanPawn Healer, int HealAmount, float HealPotency, optional KFWeapon MedicGun)
{
    local ScrnPlayerInfo SPI;

    if ( HealthToGive <= 0 || HealthBeforeHealing == 0 || Health < HealthBeforeHealing )
        HealthBeforeHealing = Health;

    if ( GiveHealth(HealAmount, HealthMax) ) {
        HealthRestoreRate = fmax(default.HealthRestoreRate * HealPotency, 1);

        if ( LastHealedBy != Healer ) {
            LastHealedBy = Healer;
            HealthBeforeHealing = Health;
        }

        if ( Healer != none ) {
            Healer.LastHealed = self;
            if ( PlayerController(Healer.Controller) != none &&  GameRules != none) {
                SPI = GameRules.GetPlayerInfo(PlayerController(Healer.Controller));
                if ( SPI != none )
                    SPI.Healed(HealAmount, self, MedicGun);
            }
            if ( KFMonster(LastDamagedBy) != none && Healer.IsMedic() ) {
                CombatMedicTarget = KFMonster(LastDamagedBy);
            }
        }
    }
    ClientHealthToGive = HealthToGive;
}


//overrided to add HealthRestoreRate
simulated function AddHealth()
{
    local int TempHeal;
    local float TempHealReal;

    if( (level.TimeSeconds - lastHealTime) >= 0.1 ) {
        if( Health < HealthMax ) {
            TempHealReal = HealthRestoreRate * (level.TimeSeconds - lastHealTime) + HealthToGiveRemainder;
            TempHeal = int(TempHealReal);

            if( TempHeal > 0 ) {
                HealthToGiveRemainder = TempHealReal - TempHeal; // move remainder to the next healing tick
                HealthToGive -= TempHeal;

                // don't leave last hp for the next tick
                if ( HealthToGive < 1.0 ) {
                    if ( HealthToGive > 0 )
                        TempHeal++;
                    HealthToGive = 0;
                    HealthToGiveRemainder = 0;
                }
                Health += TempHeal;
                if ( Health >= HealthMax) {
                    Health = HealthMax;
                    HealthToGive = 0;
                    HealthToGiveRemainder = 0;
                }
                lastHealTime = level.TimeSeconds;
                ClientHealthToGive = HealthToGive;
            }
        }
        else {
            lastHealTime = level.timeSeconds;
            // if we are all healed, there's no more healing gonna be
            HealthToGive = 0;
            ClientHealthToGive = 0;
            HealthToGiveRemainder = 0;
        }
    }
}

function Timer()
{
    if ( BurnDown > 0 && BurnInstigator != self && KFPawn(BurnInstigator) != none )
        LastBurnDamage *= 1.8; // prevent lowering player-to-player burn damage in KFHumanPawn.Timer()

    super.Timer();
    // tick down health if it's greater than max
    if ( Health > HealthMax ) {
        if ( Health > 250 )
            Health = 250;
        else if (Health > 200)
            Health-=5;
        else
            Health-=2;
        if (Health < HealthMax)
            Health = HealthMax;
    }
    SetAmmoStatus();
    ApplyWeaponFlashlight(true);
}

function SetAmmoStatus()
{
    if ( KFWeapon(Weapon) == none ) {
        SpecWeapon = none;
        AmmoStatus = 0;
    }
    else {
        SpecWeapon = KFWeapon(Weapon).class;
        if (Weapon.MaxAmmo(0) <= 0)
            AmmoStatus = 0;
        else
            AmmoStatus = clamp(255 * Weapon.AmmoAmount(0) / Weapon.MaxAmmo(0), 1, 255);
    }
}

simulated function Tick(float DeltaTime)
{
    if ( PlayerReplicationInfo == none )
        KFPRI = none;
    else if ( KFPRI == none )
        KFPRI = KFPlayerReplicationInfo(PlayerReplicationInfo);

    if( HealthToGive < 0 ) {
        HealthToGive = 0;
        ClientHealthToGive = 0;
    }

    if ( WeaponToFixClientState != none ) {
        // make sure that we don't hide current weapon (in case when QuickMelee didn't worked)
        if ( WeaponToFixClientState != Weapon && WeaponToFixClientState.ClientState == WS_ReadyToFire ) {
            WeaponToFixClientState.ClientState = WS_Hidden;
            WeaponToFixClientState.ClientGrenadeState = GN_None;
            WeaponToFixClientState.SetTimer(0, false);
        }
        WeaponToFixClientState = none;
    }

    AlphaAmount = 255; // hack to avoid KFHumanPawn of updating KFPRI.ThreeSecondScore
    super.Tick(DeltaTime);

    bCowboyMode = bCowboyMode && ShieldStrength < 26;

    if ( Role == ROLE_Authority ) {
        if ( MacheteBoost > 0 && Level.TimeSeconds > MacheteResetTime ) {
            MacheteBoost = 0;
            ModifyVelocity(0, Velocity);
        }
    }

    if ( KFPRI != none && ( PrevPerkClass != KFPRI.ClientVeteranSkill || PrevPerkLevel != KFPRI.ClientVeteranSkillLevel) )
        VeterancyChanged();

    if ( bViewTarget )
        UpdateSpecInfo();
}



// each weapon has own light battery
function ApplyWeaponFlashlight(bool bDrainBattery)
{
    local int i;
    local KFWeapon CurWeap;

    CurWeap = KFWeapon(Weapon);
    for ( i=0; i<WeaponFlashlights.Length; ++i ) {
        if ( CurWeap != none && WeaponFlashlights[i].WeaponClass == CurWeap.class ) {
            if ( bDrainBattery ) {
                if ( CurWeap.FlashLight != none && CurWeap.FlashLight.bHasLight )
                    WeaponFlashlights[i].TorchBatteryLife -= 10;
                else
                    WeaponFlashlights[i].TorchBatteryLife += 20;
            }
            WeaponFlashlights[i].TorchBatteryLife = clamp(WeaponFlashlights[i].TorchBatteryLife, 0, WeaponFlashlights[i].MaxBatteryLife);
            TorchBatteryLife = WeaponFlashlights[i].TorchBatteryLife;
        }
        else if (bDrainBattery && WeaponFlashlights[i].TorchBatteryLife < WeaponFlashlights[i].MaxBatteryLife)
            WeaponFlashlights[i].TorchBatteryLife += 20;
    }
}

// looks for a weapon class in WeaponFlashlights array.
// Creates a new record, if weapon class not found.
// Returns array index of a given weapon.
function int AddToFlashlightArray(class<KFWeapon> WeaponClass)
{
    local int i;

    for ( i=0; i<WeaponFlashlights.Length; ++i )
        if ( WeaponFlashlights[i].WeaponClass == WeaponClass )
            return i;

    WeaponFlashlights.insert(i, 1);
    WeaponFlashlights[i].WeaponClass = WeaponClass;
    WeaponFlashlights[i].TorchBatteryLife = default.TorchBatteryLife;
    WeaponFlashlights[i].MaxBatteryLife = default.TorchBatteryLife;
    return i;
}


simulated function Frag FindPlayerGrenade()
{
    local inventory inv;
    local int c;

    if ( PlayerGrenade == none ) {
        for ( inv = Inventory; inv != none && ++c < 1000 && PlayerGrenade == none ; inv = inv.Inventory)
            PlayerGrenade = Frag(inv);
    }

    return PlayerGrenade;
}

function CookGrenade()
{
    local ScrnFrag aFrag;

    if (SecondaryItem != none)
        return;

    if ( ScrnPerk == none || !ScrnPerk.static.CanCookNade(KFPRI, Weapon) )
        return;

    aFrag = ScrnFrag(FindPlayerGrenade());
    if ( aFrag == none )
        return;

    if ( aFrag.HasAmmo() && !bThrowingNade
            && !aFrag.bCooking && !aFrag.bThrowingCooked
            && Level.TimeSeconds - aFrag.CookExplodeTimer > 0.1 )
    {
        if ( KFWeapon(Weapon) == none || Weapon.GetFireMode(0).NextFireTime - Level.TimeSeconds > 0.1
                || (KFWeapon(Weapon).bIsReloading && !KFWeapon(Weapon).InterruptReload()) )
            return;

        aFrag.CookNade();

        KFWeapon(Weapon).ClientGrenadeState = GN_TempDown;
        Weapon.PutDown();
    }
}

function ThrowCookedGrenade()
{
    local ScrnFrag aFrag;

    aFrag = ScrnFrag(SecondaryItem);
    if ( aFrag != none && aFrag.bCooking && !aFrag.bThrowingCooked )
        aFrag.ClientThrowCooked();
}

function ThrowGrenade()
{
    local KFWeapon KFW;

    if ( bThrowingNade || SecondaryItem != none )
        return;

    KFW = KFWeapon(Weapon);
    if ( KFW == none || KFW.GetFireMode(0).NextFireTime - Level.TimeSeconds > 0.1
            || (KFW.bIsReloading && !KFW.InterruptReload()) )
        return;


    if ( PlayerGrenade == none )
        PlayerGrenade = FindPlayerGrenade();
    if ( PlayerGrenade != none && PlayerGrenade.HasAmmo() ) {
        KFW.ClientGrenadeState = GN_TempDown;
        KFW.PutDown();
    }
}

function QuickMelee()
{
    local KFWeapon KFW;

    KFW = KFWeapon(Weapon);
    if ( KFW == none ) {
        Controller.bAltFire = 0; // avoid accidental firing with the current weapon
        return;
    }

    if ( Weapon == QuickMeleeWeapon && QuickMeleeWeapon.IsInState('QuickMelee') ) {
        QuickMeleeWeapon.GotoState('');
        return;
    }

    if ( bThrowingNade
            || KFW.GetFireMode(0).NextFireTime - Level.TimeSeconds > 0.1
            || KFW.GetFireMode(1).NextFireTime - Level.TimeSeconds > 0.1
            // || KFW.ClientState != WS_ReadyToFire
            || (KFW.bIsReloading && !KFW.InterruptReload()) )
    {
        Controller.bAltFire = 0; // avoid accidental firing with the current weapon
        return;
    }

    if ( Weapon == QuickMeleeWeapon ) {
        AltFire(); // already equipped quick melee gun - simply do alt fire
    }
    else if ( SecondaryItem == none && QuickMeleeWeapon != none ) {
        bQuickMeleeInProgress = true;
        SecondaryItem = QuickMeleeWeapon;
        KFW.SetTimer(0, false);
        KFW.ClientGrenadeState = GN_TempDown;
        KFW.PutDown();
    }
}

function QuickMeleeFinished()
{
    bQuickMeleeInProgress = false;
    SecondaryItem = none;
}

function WeaponDown()
{
    local KFWeapon W;
    local byte Mode;

    W = KFWeapon(Weapon);
    if ( SecondaryItem == QuickMeleeWeapon && QuickMeleeWeapon != none ) {
        // quick melee
        //copied from weapon's putdown timer
        W.SetTimer(0, false);
        W.ClientState = WS_Hidden;
        W.ClientGrenadeState = GN_None;
        if( W.FlashLight!=none )
                W.Tacshine.Destroy();

        QuickMeleeWeapon.GotoState('QuickMelee');
        PendingWeapon = QuickMeleeWeapon;
        ChangedWeapon(); // sets Weapon=PendingWeapon and calls BringUp

         //copied from weapon's putdown timer
        for( Mode = 0; Mode < W.NUM_FIRE_MODES; Mode++ )
            W.GetFireMode(Mode).DestroyEffects();

        // after calling this function W.ClientSet will be set back to WS_ReadyToFire in KFWeapon.Timer()
        // That's why QuickMeleeWeapon must reset it to WS_Hidden again
        WeaponToFixClientState = W;
    }
    else if (SecondaryItem == none && PlayerGrenade != none ) {
        //Can't throw nade when secondary item is in use (e.g. for nade cooking)
        SecondaryItem = PlayerGrenade;
        PlayerGrenade.StartThrow();
    }
}

simulated function ThrowGrenadeFinished()
{
    local ScrnFrag aFrag;

    aFrag = ScrnFrag(SecondaryItem);
    if (aFrag != none) {
        aFrag.bCooking = false;
        aFrag.bThrowingCooked = false;
    }
    SecondaryItem = none;
    if ( Weapon != none ) {
        if ( KFWeapon(Weapon) != none )
            KFWeapon(Weapon).ClientGrenadeState = GN_BringUp;
        Weapon.BringUp();
    }
    bThrowingNade = false;
}

// disable automatic reloading
simulated function Fire( optional float F )
{
    local KFWeapon W;
    local ScrnPlayerController PC;

    if ( Weapon == none )
        return;

    PC = ScrnPlayerController(Controller);
    W = KFWeapon(Weapon);
    if ( PC != none && PC.bManualReload && W != none && !W.bMeleeWeapon && W.bConsumesPhysicalAmmo
            && !W.bIsReloading && !W.bHoldToReload
            && W.MagCapacity > 1 && W.MagAmmoRemaining < W.GetFireMode(0).AmmoPerFire ) {
        if ( W.AmmoAmount(0) == 0 )
            PC.ReceiveLocalizedMessage(class'ScrnBalanceSrv.ScrnPlayerWarningMessage',1);
        else
            PC.ReceiveLocalizedMessage(class'ScrnBalanceSrv.ScrnPlayerWarningMessage',0);
        W.PlayOwnedSound(W.GetFireMode(0).NoAmmoSound, SLOT_None,2.0,,,,false); //play weapon's no ammo sound
        W.GetFireMode(0).ModeDoFire(); //force weapon's mode do fire
        return;
    }

    Weapon.Fire(F);

    if ( W != none && W.FireModeClass[0] != class'KFMod.NoFire' )
        ServerFire(0);
}

simulated function AltFire( optional float F )
{
    local KFWeapon W;
    local ScrnPlayerController PC;

    if ( Weapon == none )
        return;


    PC = ScrnPlayerController(Controller);
    W = KFWeapon(Weapon);
    if ( PC != none && PC.bManualReload && W != none && !W.bMeleeWeapon && W.bConsumesPhysicalAmmo
            && W.bReduceMagAmmoOnSecondaryFire && KFMedicGun(W) == none
            && !W.bIsReloading && !W.bHoldToReload
            && W.MagCapacity > 2 && W.MagAmmoRemaining < W.GetFireMode(1).AmmoPerFire ) {
        if ( W.AmmoAmount(0) == 0 )
            PC.ReceiveLocalizedMessage(class'ScrnBalanceSrv.ScrnPlayerWarningMessage',1);
        else
            PC.ReceiveLocalizedMessage(class'ScrnBalanceSrv.ScrnPlayerWarningMessage',0);
        W.PlayOwnedSound(W.GetFireMode(0).NoAmmoSound, SLOT_None,2.0,,,,false);
        return;
    }

    Weapon.AltFire(F);

    if ( W != none  && W.FireModeClass[1] != class'KFMod.NoFire')
        ServerFire(1);
}

// simulated function StopFiring()
// {
    // super.StopFiring();
    // ServerStopFiring();
// }


// made p2p damage more like p2m, not like in RO  -- PooSH
function ProcessLocationalDamage(int Damage, Pawn instigatedBy, Vector hitlocation, Vector momentum, class<DamageType> damageType, array<int> PointsHit )
{
    local int actualDamage, i;
    local int HighestDamagePoint, HighestDamageAmount;
    local bool bHeadShot;
    local class<KFWeaponDamageType> KFDamType;
    local float KFHeadshotMult;
    local KFPlayerReplicationInfo InstigatorPRI;

    if ( instigatedBy != none  )
        InstigatorPRI = KFPlayerReplicationInfo( instigatedBy.PlayerReplicationInfo );

    // If someone else has killed this player , return
    if( bDeleteMe || PointsHit.Length < 1 || Health <= 0 )
        return;

    // Don't process locational damage if we're not going to damage a friendly anyway
    if( TeamGame(Level.Game)!=None && TeamGame(Level.Game).FriendlyFireScale==0 && instigatedBy!=None && instigatedBy!=Self
            && instigatedBy.GetTeamNum()==GetTeamNum() )
        Return;

    KFHeadshotMult = 1.0;
    KFDamType = class<KFWeaponDamageType>(damageType);

    // do only highest damage
    for(i=0; i<PointsHit.Length; i++)  {
        actualDamage = Damage;

        // use KFWeaponDamageType.HeadShotDamageMult instead of Hitpoints.DamageMultiplier
        // -- PooSH
        if ( Hitpoints[PointsHit[i]].HitPointType == PHP_Head ) {
            bHeadShot = KFDamType != none && KFDamType.default.bCheckForHeadShots;

            if ( bHeadShot ) {
                if ( KFDamType.default.bIsPowerWeapon )
                    KFHeadshotMult = 1.0;
                else
                    KFHeadshotMult = 2.0;
                KFHeadshotMult = fmax(KFHeadshotMult, KFDamType.default.HeadShotDamageMult);
                if ( !KFDamType.default.bIsMeleeDamage && InstigatorPRI != none && InstigatorPRI.ClientVeteranSkill != none )
                    KFHeadshotMult *= fmin(2.0, InstigatorPRI.ClientVeteranSkill.Static.GetHeadShotDamMulti(InstigatorPRI, KFPawn(instigatedBy), DamageType));
            }

            actualDamage *= KFHeadshotMult;
        }
        else
            actualDamage *= Hitpoints[PointsHit[i]].DamageMultiplier;

        //actualDamage = Level.Game.ReduceDamage(actualDamage, self, instigatedBy, HitLocation, Momentum, DamageType);

        if( actualDamage > HighestDamageAmount )
        {
            HighestDamageAmount = actualDamage;
            HighestDamagePoint = PointsHit[i];
            if ( bHeadShot )
                break; // headshot does max damage - no need to look for more
        }
    }

    if( HighestDamageAmount > 0 ) {
        if( bHeadShot ) {
            // Play a sound when someone gets a headshot - KFTODO: Replace this with a better bullet hitting a helmet sound
            PlaySound(HeadshotSound, SLOT_None,2.0,true,500);
        }
        TakeDamage(HighestDamageAmount, instigatedBy, hitlocation, momentum, damageType, HighestDamagePoint);
    }
}

// this is used by explosive projectiles to scale damage
function float GetExposureTo(vector TestLocation)
{
    local float PercentExposed;

    if( FastTrace(GetBoneCoords(HeadBone).Origin,TestLocation))
        PercentExposed += 0.5;

    if( FastTrace(GetBoneCoords(RootBone).Origin,TestLocation))
        PercentExposed += 0.5;

    LastExplosionTime = Level.TimeSeconds;
    LastExplosionDistance = VSize( TestLocation - Location ) - CollisionRadius;

    return PercentExposed;
}

simulated function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class<DamageType> damageType, optional int HitIndex)
{
    local int OldHealth, OriginalDamage;
    local class<KFWeaponDamageType> KFDamType;
    local KFPlayerReplicationInfo InstigatorPRI;

    if ( Damage <= 0 )
        return; // just in case
    // copy-pasted from KFHumanPawn to check for nones
    if( Controller!=None && Controller.bGodMode )
        return;

    KFDamType = class<KFWeaponDamageType>(damageType);
    if ( InstigatedBy == none ) {
        // Player received non-zombie KF damage from unknown source.
        // Let's assume that it is friendly damage, e.g. from just disconnected/crashed/cheating teammate
        // and ignore it.
        if ( KFDamType != none && class<DamTypeZombieAttack>(KFDamType) == none )
            return;
    }
    else {
        if ( KFMonster(InstigatedBy) != none )
        {
            KFMonster(InstigatedBy).bDamagedAPlayer = true;
        }
        else if( KFHumanPawn(InstigatedBy) != none ) {
            InstigatorPRI = KFPlayerReplicationInfo(InstigatedBy.PlayerReplicationInfo);
             // Don't allow momentum from a player shooting a player
            Momentum = vect(0,0,0);
            // no damage from spectators (i.e. fire missile -> spectate -> missile hits player
            if ( InstigatorPRI != none && InstigatorPRI.bOnlySpectator )
                return;
        }
    }

    OriginalDamage = Damage;
    OldHealth = Health;
    // copied from KFPawn to adjust player-to-player damage -- PooSH
    LastHitDamType = damageType;
    LastDamagedBy = instigatedBy;
    LastDamageTime = Level.TimeSeconds;

    if ( KFDamType != none && KFHumanPawn(InstigatedBy) != none ) {
        // HDMG
        if ( InstigatorPRI != none && InstigatorPRI.ClientVeteranSkill != none )
            Damage = InstigatorPRI.ClientVeteranSkill.Static.AddDamage(InstigatorPRI, none, KFHumanPawn(instigatedBy), Damage, DamageType);
        if ( LastExplosionTime == Level.TimeSeconds && LastExplosionDistance > 50 )
            Damage *= (1.0 - lerp( LastExplosionDistance / 200, 0.0, 1.00, true ));

        if ( KFDamType.default.bDealBurningDamage ) {
            if ( class<DamTypeMAC10MPInc>(KFDamType) == none )
                Damage *= 1.50; // Increase burn damage 1.5 times, except MAC10.
        }
        else if ( KFDamType.default.bIsExplosive ) {
            if ( KFDamType != class'DamTypeFrag' )
                Damage *= 0.70;
        }
        else if ( KFDamType.default.bIsMeleeDamage ) {
            if ( ClassIsChildOf(KFDamType, class'DamTypeCrossbuzzsaw') )
                Damage *= 0.80;
        }
    }

    super(xPawn).TakeDamage(Damage, instigatedBy, hitLocation, momentum, damageType);

    if( class<DamTypeVomit>(DamageType)!=none ) {
        BileCount=7;
        BileInstigator = instigatedBy;
        LastBileDamagedByType=class<DamTypeVomit>(DamageType);
        if(NextBileTime< Level.TimeSeconds )
            NextBileTime = Level.TimeSeconds+BileFrequency;

        if ( Level.Game != none && Level.Game.GameDifficulty >= 4.0 && KFPlayerController(Controller) != none &&
             !KFPlayerController(Controller).bVomittedOn)
        {
            KFPlayerController(Controller).bVomittedOn = true;
            KFPlayerController(Controller).VomittedOnTime = Level.TimeSeconds;

            if ( Controller.TimerRate == 0.0 )
                Controller.SetTimer(10.0, false);
        }
    }
    else if ( class<SirenScreamDamage>(DamageType) != none ) {
        if ( Level.Game != none && Level.Game.GameDifficulty >= 4.0 && KFPlayerController(Controller) != none &&
             !KFPlayerController(Controller).bScreamedAt)
        {
            KFPlayerController(Controller).bScreamedAt = true;
            KFPlayerController(Controller).ScreamTime = Level.TimeSeconds;

            if ( Controller.TimerRate == 0.0 )
                Controller.SetTimer(10.0, false);
        }
    }

    //Bloody Overlays
    if ( Health <= 50 )
        SetOverlayMaterial(InjuredOverlay,0, true);


    // SERVER-SIDE ONLY
    if ( Role < ROLE_Authority )
        return;

    // pawn didn't took any damage, probably due to resistance or disabled friendly fire
    // do not lower HealthToGive in such cases
    if ( Health >= OldHealth )
        return;

    HealthToGive -= 5;
    ClientHealthToGive = HealthToGive;

    if ( KFDamType != none ) {
        if ( KFDamType.default.bDealBurningDamage ) {
            // Do burn damage if the damage was significant enough
            if( Damage > 2 ) {
                // If we are already burning, and this damage is more than our current burn amount, add more burn time
                if( BurnDown == 0 || OriginalDamage > LastBurnDamage ) {
                    if ( InstigatedBy != self && KFPawn(InstigatedBy) != none )
                        BurnDown = 8;
                    else
                        BurnDown = 5;
                    BurnInstigator = InstigatedBy;
                    LastBurnDamage = OriginalDamage;
                    bBurnified  = true;
                }
            }
        }
    }

    // added null check for Instigator  -- PooSH
    if ( Level.Game.NumPlayers > 1 && Instigator != none && PlayerController(Instigator.Controller) != none
        && Health < 25 && Level.TimeSeconds - LastDyingMessageTime > DyingMessageDelay )
    {
        // Tell everyone we're dying
        PlayerController(Instigator.Controller).Speech('AUTO', 6, "");

        LastDyingMessageTime = Level.TimeSeconds;
    }

    // ScrN stuff
    if ( Health > 0 && HealthBeforeHealing > 0 && (level.TimeSeconds - LastHealTime) < 1.0 ) {
        if (  (HealthBeforeHealing - Damage) <= 0
                && LastHealedBy != none && LastHealedBy != self
                && ScrnPlayerController(LastHealedBy.Controller) != none
                && SRStatsBase(ScrnPlayerController(LastHealedBy.Controller).SteamStatsAndAchievements) != none )
        {
            class'ScrnAchievements'.static.ProgressAchievementByID(SRStatsBase(ScrnPlayerController(LastHealedBy.Controller).SteamStatsAndAchievements).Rep, 'TouchOfSavior', 1);
            HealthBeforeHealing = 0; //don't give this achievement anymore until next healing will be received
        }
        else if ( Health < HealthBeforeHealing )
            HealthBeforeHealing = Health;
    }
    // ScrN 9.15: allow weapon handle damage
    if ( Weapon != none )
        Weapon.TakeDamage(Damage, InstigatedBy, Hitlocation, Momentum, damageType, HitIndex);
}


// store bonus ammo in pawn's class
function TossWeapon(Vector TossVel)
{
    // local int a;
    // local class<Ammunition> ac;
    // local class<KFWeaponPickup> wpc;
    local KFWeapon w;
    // local KFWeaponPickup wp;
    // local Actor other;

    w = KFWeapon(Weapon);
    if ( w != none ) {
        w.bIsTier2Weapon = false; // This hack is needed because KFWeaponPickup.DroppedBy isn't set for tier 2 weapons.
        w.bIsTier3Weapon = w.default.bIsTier3Weapon; // restore default value from the hack in AddInventory()
    }


    // if ( w != none && w.FireModeClass[0] != none && w.FireModeClass[0] != class'KFMod.NoFire' ) {
        // wpc = class<KFWeaponPickup>(w.PickupClass);
        // ac =  w.FireModeClass[0].default.AmmoClass;
        // if ( ac != none )
            // a = max(0, w.AmmoAmount(0) - ac.default.MaxAmmo); // bonus ammo
    // }

    super.TossWeapon(TossVel);

    // if ( Health > 0 && a > 0 ) {
        // foreach DynamicActors(wpc, other) {
            // wp = KFWeaponPickup(other);
            // if ( wp != none && wp.Inventory == w ) {
                // wp.AmmoAmount[0] = ac.default.MaxAmmo; // remove bonus ammo from pickup (it will be lost anyway)
                // BonusAmmoClass = ac;
                // BonusAmmoAmount = a;
                // return;
            // }
        // }
    // }
}

exec function SwitchToLastWeapon()
{
    if ( bQuickMeleeInProgress )
        return;
    super.SwitchToLastWeapon();
}


/**
    Monster threat assessment functionality
    Tripwire's new AssessThreatTo() is a bull crap. No, it is even worse:
    it is a bull crap eaten by Bloat and vomited back again.
    a)  No randomization at all. If players are camping the spot, then all zeds spawned at once
        place will attack the same player all the time.
    b)  EnemyThreatChanged() always returns false. That means zed always will [try to] attack the
        same player, no matter how far it is and is he doing damage to zed or not.
    c)  SetEnemy() always returns false in story game mode


    Changes by PooSH:
    1)  KFGameType.bUseZEDThreatAssessment set to true, i.e. in regular game new AssessThreatTo()
        function will be used too.
    2)  Distance between monster and player will always be in place.
    3)  AssessThreatTo will always return value > 0. Because zeds should not ignore players.
    4)  Added randomization - zeds can choose different targets in the same circumstances.
    5)  Blood smell. Wounded players will attract zeds slightly more than their healthy teammates.

    * @param    Monster         Monster's controller, for which we are calculating the threat level
    * @param    CheckDistance   Not used!
    * @return   threat level between 0 and 100, where 100 is the max threat level.
    *
    * @author   PooSH
*/
function  float AssessThreatTo(KFMonsterController  Monster, optional bool CheckDistance)
{
    local float DistancePart, RandomPart, TacticalPart;
    local float DistanceSquared; // squared distance is calculated faster
    local bool bAttacker, bSeeMe; // this player attacks zed
    local int i;

    if( bHidden || bDeleteMe || Health <= 0
            || Monster == none || KFMonster(Monster.Pawn) == none)
    {
        return -1.f;
    }

    if ( Level.TimeSeconds - SpawnTime < 10 )
        return 0.01; // very minor chance to attack players who just spawned

    // Gorefasts love Baron :D
    // https://www.youtube.com/watch?v=vytEYKpFAwk
    if ( bAmIBaron && ZombieGorefast(Monster.Pawn) != none && TSCGameReplicationInfo(Level.GRI) == none ) {
        if ( class'ScrnCustomPRI'.static.FindMe(PlayerReplicationInfo).BlameCounter >= 5 )
            return 100.f;
    }

    if ( LastThreatMonster == Monster && Level.TimeSeconds - LastThreatTime < 2.0 )
        return LastThreat; // keep threat level for next 2 seconds


    DistanceSquared = VSizeSquared(Monster.Pawn.Location - Location);
    bSeeMe = DistanceSquared < 1562500 && Monster.LineOfSightTo(self); // check line of sight only withing 25 meters
    // v7.52: if monster is on different floor (5+ meters), then give additional 20 meters of distance
    if ( abs(Monster.Pawn.Location.Z - Location.Z) > 250 && !bSeeMe )
        DistanceSquared += 1000000;
    // v7.52: check KillAssistants instead of LastDamagedBy
    for ( i=0; i<Monster.KillAssistants.length; ++i ) {
        if ( Monster.KillAssistants[i].PC == Controller ) {
            bAttacker = Monster.KillAssistants[i].Damage > 100;
            break;
        }
    }

    DistancePart = 50.0;
    RandomPart = 100.0 - DistancePart;
    // let zeds smell blood within 15m radius - wounded players attract more zeds
    if ( Health < 80 && DistanceSquared < 562500.0 )
        TacticalPart += RandomPart * 0.30 * (1.0 - Health/100.0);
    // more chance to attack the same enemy multiple times
    if ( Monster.Enemy == self || Monster.Target == self ) {
        if ( bAttacker )
            TacticalPart += RandomPart * 0.80;
        else if ( bSeeMe )
            TacticalPart += RandomPart * 0.70;
        else
            TacticalPart += RandomPart * 0.60;
    }
    else if ( bAttacker )
        TacticalPart += RandomPart * 0.50; // more chance to focus on the player, who are attacking the monster
    else if ( bSeeMe )
        TacticalPart += RandomPart * 0.30; // zed can see player
    RandomPart -= TacticalPart;


    // If target is closer than 1 meter, max DistancePart value will be used,
    // otherwise DistancePart is lowering by 10% per meter
    // 1 meter = 50 ups (2500 squared)
    if ( DistanceSquared > 2500.0 )
        DistancePart /= 1.0 + DistanceSquared / 250000.0;
    RandomPart *= frand();

    // save threat level for this tick
    LastThreatMonster = Monster;
    LastThreatTime = Level.TimeSeconds;
    LastThreat = DistancePart + TacticalPart + RandomPart;
    // less chance to attach jsut-spawned players
    if ( Level.TimeSeconds - SpawnTime < 30 )
        LastThreat *= lerp( (Level.TimeSeconds - SpawnTime) / 30.0, 1.0, 0.01 );

    if ( KFStoryGameInfo(Level.Game) != none )
        LastThreat *= InventoryThreatModifier();
    return LastThreat;
}

// used in story game mode to attract zeds when holding some mission items
function float InventoryThreatModifier()
{
    local float ThreatRating;
    local Inventory CurInv;
    local int c;
    local KF_StoryInventoryItem StoryInv;

    ThreatRating = 1.0;

    /* Factor in story Items which adjust your desirability to ZEDs */
    for ( CurInv = Inventory; CurInv != none && ++c < 1000; CurInv = CurInv.Inventory )  {
        StoryInv = KF_StoryInventoryItem(CurInv);
        if(StoryInv != none)
            ThreatRating *= StoryInv.AIThreatModifier ;
    }

    return ThreatRating;
}

// C&P to check for SoundGroupClass - in cases when player joins with a custom skin, with isn't
// supported by the server
function Sound GetSound(xPawnSoundGroup.ESoundType soundType)
{
    local int SurfaceTypeID;
    local actor A;
    local vector HL,HN,Start,End;
    local material FloorMat;

    // added this in case when player joins using a custom skin with a custom SoundGroupClass,
    // which not present on the server
    if ( SoundGroupClass == none )
        SoundGroupClass = Class'KFMod.KFMaleSoundGroup';

    if( soundType == EST_Land || soundType == EST_Jump )
    {
        if ( (Base!=None) && (!Base.IsA('LevelInfo')) && (Base.SurfaceType!=0) )
        {
            SurfaceTypeID = Base.SurfaceType;
        }
        else
        {
            Start = Location - Vect(0,0,1)*CollisionHeight;
            End = Start - Vect(0,0,16);
            A = Trace(hl,hn,End,Start,false,,FloorMat);
            if (FloorMat !=None)
                SurfaceTypeID = FloorMat.SurfaceType;
        }
    }

    return SoundGroupClass.static.GetSound(soundType, SurfaceTypeID);
}


simulated event SetAnimAction(name NewAction)
{
    local KFWeapon W;


    super.SetAnimAction(NewAction);

    W = KFWeapon(Weapon);
    if ( W != none && InStr(Caps(String(AnimAction)), "RELOAD") != -1 ) {
        ServerReload();
    }
}

function ServerReload()
{
    if ( KFWeapon(Weapon) != none ) {
        GameRules.WeaponReloaded(PlayerController(Controller), KFWeapon(Weapon));
    }
}

function ServerFire(byte FireMode)
{
    if ( KFWeapon(Weapon) != none ) {
        GameRules.WeaponFire(PlayerController(Controller), KFWeapon(Weapon), FireMode);
    }
}


// function ServerStopFiring()
// {
        // GameRules.WeaponStoppedFire(PlayerController(Controller), Weapon);
// }

static function DropAllWeapons(Pawn P)
{
    local Inventory Inv, NextInv;
    local KFWeapon Weap;
    local int c;
    local rotator r;
    local Vector X,Y,Z, TossVel;

    if ( P != none && (P.DrivenVehicle == None || P.DrivenVehicle.bAllowWeaponToss) )
    {
        r = P.Rotation;
        r.pitch = 0;
        for ( Inv = P.Inventory; Inv != none && ++c < 1000; Inv = NextInv ) {
            NextInv = Inv.Inventory;
            Weap = KFWeapon(Inv);
            if (Weap != none && Weap.bCanThrow && !Weap.bKFNeverThrow /* && Weap.SellValue > 0 */ ) {
                //PlayerController(P.Controller).ClientMessage("Dropping " $ GetItemName(String(Weap.class)) $ "...");
                r.yaw += 4096 + 8192.0 * frand(); // 45 +/- 22.5 degrees
                P.GetAxes(r,X,Y,Z);
                TossVel = Vector(r);
                Weap.Velocity = TossVel * ((P.Velocity Dot TossVel) + 200) + Vect(0,0,200);
                Weap.DropFrom(P.Location + 0.8 * P.CollisionRadius * X - 0.5 * P.CollisionRadius * Y);
                NextInv = P.Inventory; // start from beginning again
            }
        }
        if ( P.Weapon == None && P.Controller != None )
            P.Controller.SwitchToBestWeapon();
    }
}


function FindGameRules()
{
    local GameRules G;

    if ( GameRules != none )
        return;

    for ( G=Level.Game.GameRulesModifiers; GameRules == none && G!=None; G=G.NextGameRules ) {
        GameRules = ScrnGameRules(G);
    }
}

// overrided to allow tossing amount below 50
exec function TossCash( int Amount )
{
    local Vector X,Y,Z;
    local ScrnCashPickup CashPickup;
    local Vector TossVel;
    local Actor A;
    local int StartCash;

    // To fix cash tossing exploit.
    if( Level.TimeSeconds < CashTossTimer || (Level.TimeSeconds < LongTossCashTimer && LongTossCashCount>=20) )
        return;

    PlayerReplicationInfo.Score = int(PlayerReplicationInfo.Score); // why it is defined as float in a first place?
    if ( PlayerReplicationInfo.Score <= 0 )
        return;

    if ( ScrnPlayerController(Controller) != none )
        StartCash = ScrnPlayerController(Controller).StartCash;
    if ( Amount <= 0 )
        Amount = 50;
    if ( Amount > PlayerReplicationInfo.Score )
        Amount = PlayerReplicationInfo.Score;

    // don't use bNoStartCashToss in story mode
    if ( class'ScrnBalance'.default.Mut.bNoStartCashToss && KF_StoryGRI(Level.GRI) == none ) {
        if ( PlayerReplicationInfo.Score <= ScrnPlayerController(Controller).StartCash ) {
            PlayerController(Controller).ClientMessage(strNoSpawnCashToss);
            CashTossTimer = Level.TimeSeconds+1.0;
            return;
        }
        Amount = Min(Amount, PlayerReplicationInfo.Score - ScrnPlayerController(Controller).StartCash);
    }

    // copied from KFPawn to override dosh class
    GetAxes(Rotation,X,Y,Z);
    TossVel = Vector(GetViewRotation());
    TossVel = TossVel * ((Velocity Dot TossVel) + 500) + Vect(0,0,200);
    CashPickup = Spawn(class'ScrnCashPickup',,, Location + 0.8 * CollisionRadius * X - 0.5 * CollisionRadius * Y);
    if(CashPickup != none) {
        CashPickup.CashAmount = Amount;
        CashPickup.bDroppedCash = true;
        CashPickup.RespawnTime = 0;   // Dropped cash doesnt respawn. For obvious reasons.
        CashPickup.Velocity = TossVel;
        CashPickup.DroppedBy = Controller;
        CashPickup.InitDroppedPickupFor(None);
        PlayerReplicationInfo.Score -= Amount;

        if ( Level.Game.NumPlayers > 1 && Level.TimeSeconds - LastDropCashMessageTime > DropCashMessageDelay )
            PlayerController(Controller).Speech('AUTO', 4, "");
        // Hack to get Slot machines to accept dosh that's thrown inside their collision cylinder.
        ForEach CashPickup.TouchingActors(class 'Actor', A) {
            if( A.IsA('KF_Slot_Machine') )
                A.Touch(Cashpickup);
        }
    }
    // end of copy

    CashTossTimer = Level.TimeSeconds+0.1f;
    if( LongTossCashTimer<Level.TimeSeconds ){
        LongTossCashTimer = Level.TimeSeconds+5.f;
        LongTossCashCount = 0;
    }
    else
        ++LongTossCashCount;
}

simulated function DoHitCamEffects(vector HitDirection, float JarrScale, float BlurDuration, float JarDurationScale )
{
    if( KFPC!=none && Viewport(KFPC.Player)!=None )
        Super(KFHumanPawn_Story).DoHitCamEffects(HitDirection,JarrScale, BlurDuration, JarDurationScale);
}


// fixes critical bug:
// Assertion failed: inst->KPhysRootIndex != INDEX_NONE && inst->KPhysLastIndex != INDEX_NONE [File:.\KSkeletal.cpp] [Line: 595]
simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
{
    if ( DamageType.default.bSkeletize )
        DamageType.default.bSkeletize = false;

    super.PlayDying(DamageType, HitLoc);
}


simulated function Died(Controller Killer, class<DamageType> damageType, vector HitLocation)
{
    if ( ScrnPlayerController(Controller) != none && !ScrnPlayerController(Controller).bDestroying ) {
        HealthBeforeDeath = Health;
    }
    super.Died(Killer, damageType, HitLocation);
}

function PlayerChangedTeam()
{
    if ( ScrngameType(Level.Game) != none && !ScrngameType(Level.Game).ShouldKillOnTeamChange(self) )
        return;

    Died( None, class'DamageType', Location );
}

simulated function Setup(xUtil.PlayerRecord rec, optional bool bLoadNow)
{
    local ScrnPlayerController PC;
    local string CN;

    PC = ScrnPlayerController(Controller);
    if ( PC != none && IsLocallyControlled() ) {
        // check this only on player side, because it stores
        // RedCharacter & BlueCharacter in the config
        CN = rec.DefaultName;
        if ( !PC.ValidateCharacter(CN) ) {
            // character invalid, change it valid one, which was set up in ValidateCharacter()
            rec = class'xUtil'.static.FindPlayerRecord(CN);
            PC.ChangeCharacter(CN);
        }
    }

    super.Setup(rec, bLoadNow);
}

// disable shopping at enemy trader
function bool CanBuyNow()
{
    local ShopVolume Shop, MyShop, EnemyShop;
    local TSCGameReplicationInfo TSCGRI;
    local bool bAtEnemyShop;

    if( KFGameType(Level.Game)==None || KFGameType(Level.Game).bWaveInProgress
            || PlayerReplicationInfo==None || PlayerReplicationInfo.Team==None )
        return False;

    TSCGRI = TSCGameReplicationInfo(Level.GRI);
    if ( TSCGRI == none ) {
        MyShop = KFGameReplicationInfo(Level.GRI).CurrentShop;
        EnemyShop = none;
    }
    else if ( PlayerReplicationInfo.Team.TeamIndex == 0 ) {
        MyShop = TSCGRI.CurrentShop;
        EnemyShop = TSCGRI.BlueShop;
    }
    else {
        MyShop = TSCGRI.BlueShop;
        EnemyShop = TSCGRI.CurrentShop;
    }

    foreach TouchingActors(Class'ShopVolume',Shop) {
        if ( Shop == MyShop || (Shop.bAlwaysEnabled && Shop != EnemyShop) ) {
            ScrnPlayerController(Controller).bShoppedThisWave = true;
            return True;
        }
        bAtEnemyShop = bAtEnemyShop || Shop == EnemyShop;
    }

    if ( bAtEnemyShop ) {
        PlayerController(Controller).ReceiveLocalizedMessage(class'TSCMessages', 300);
        // throw player outside of enemy shop
        if ( !EnemyShop.bTelsInit )
            EnemyShop.InitTeleports();
        if ( EnemyShop.TelList.Length > 0 )
            EnemyShop.TelList[EnemyShop.TelList.Length-1].Accept(self, EnemyShop);
    }

    Return False;
}

event Bump(actor Other)
{
    local ZombieCrawler crawler;

    super.Bump(Other);

    if ( class'ScrnBalance'.default.Mut.bBeta ) {
        // push crawlers away
        crawler = ZombieCrawler(Other);
        if ( crawler != none && crawler.health > 0 && !crawler.bPouncing //&& !crawler.bShotAnim
                && (LastDamagedBy != crawler || Level.TimeSeconds - LastDamageTime > 0.2) )
        {
            crawler.Velocity = GroundSpeed * 0.5 * normal(Velocity);
        }
    }
}

defaultproperties
{
     HealthRestoreRate=7.000000
     NoVestClass=Class'ScrnBalanceSrv.ScrnNoVestPickup'
     StandardVestClass=Class'ScrnBalanceSrv.ScrnCombatVestPickup'
     LightVestClass=Class'ScrnBalanceSrv.ScrnLightVestPickup'
     CurrentVestClass=Class'ScrnBalanceSrv.ScrnNoVestPickup'
     bCheckHorzineArmorAch=True
     strNoSpawnCashToss="Can not drop starting cash"
     HeadShotSound=Sound'ProjectileSounds.Bullets.impact_metal09'
     TraderSpeedBoost=1.500000
     BlameStrM99="%p blamed for using a Noobgun"
     BlameStrRPG="%p blamed for buying an RPG on Boss wave"
     ShieldStrengthMax=0.000000
     MaxFallSpeed=750.000000
}
