//==============================================================================
//  NicePack / NiceMonster
//==============================================================================
//  New base class for zeds that makes it easier to implement various changes
//  and bugfixes.
//  Functionality:
//      - Variable zed stun time and unstun at any moment;
//      - Temperature system for zeds, that allows ignition be 'accumulated'
//          through several shots, rather that instantenious +
//          supports freezing mechanic;
//      - Increased complexity of some mechanics, like supporting
//          float-valued level of headshots instead of simple
//          true/false-switch;
//      - Fixed decapitation visuals.
//==============================================================================
//  'Nice pack' source
//  Do whatever the fuck you want with it
//  Author: dkanus
//  E-mail: dkanus@gmail.com
//==============================================================================
class NiceMonster extends KFMonster
    hidecategories(AnimTweaks,DeRes,Force,Gib,Karma,Udamage,UnrealPawn)
    abstract;
#exec OBJ LOAD FILE=KF_EnemyGlobalSndTwo.uax
#exec OBJ LOAD FILE=KFZED_FX_T.utx

var bool initializationComplete;
//==============================================================================
//==============================================================================
//  >   Affliction system
//  This class, like the vanilla one, supports 3 types of afflictions:
//      - Stun: fixes zed in place for a period of time or until
//          zed-specific unstun conditions are met
//      - Flinch: prevents zed from attacking for a short periodof time,
//          defined by a variable named 'StunTime' for some stupid-fuck-reason
//      - Mini-flinch: forced anim-action that can cancel other actions of zeds
// Describes possible affliction reactions of zeds to damage
enum EPainReaction{
    PREACTION_NONE,
    PREACTION_MINIFLINCH,
    PREACTION_FLINCH,
    PREACTION_STUN
};
//==============================================================================
//  >>  Stun rules can be overwritten for each zed independently,
//  but the default behaviour is to check two conditions:
//  1.  Check a stun score of the attack against a certain threshold.
//  2.  Check if zed wasn't already stunned too many times
//      (by default amount of stuns is unlimited,
//       but can be configured by changing a variable's value)
//  Defines if zed is currently stunned
var bool    bIsStunned;
// By how much to change stun duration for the zed?
var float   stunDurationMultiplier;
//  Stun score (defined as ratio of the default health), required to stun a zed;
//  always expected to be positive.
var float   stunThreshold;
//  How many times this zed can be stunned;
//  negative values mean there's no limit
var int     remainingStuns;
//  Standart stun duration;
//  automatically aquired from the length of stun animation
var float   stunDuration;
//  Stores the time left until the end of the current stun
var float   stunCountDown;
//  When the last stun occured;
//  doesn't update on extending active stuns (restuns)
//  Set to negative value by default
var float   lastStunTime;
//  Variables that define animation frames between which we can loop animation
//  and a frame, from which we can insert idle animation
//  (in case there isn't enough time for another loop)
var float   stunLoopStart, stunLoopEnd, idleInsertFrame;
//  Internal variables that define the state of animatin used to depict stun
//  before the current tick
var bool    bWasIdleStun;       // Were we using idleanimation for stun?
var float   prevStunAnimFrame;  // At which tick we were?
//==============================================================================
//  >>  Another default stun system allows to stun zeds by repeatedly dealing
//  head damage to them fast enough:
//      1. Dealing head damage increases accumulated damage
//      2. If head damage was received for a certain time period, -
//          accumulated damage starts to decrease
//      3. If accumulated damage is high enough, passes 'IsStunPossible' check
//          - normal duration stun activates
//  How much more time will this zed spend concussed?
var float   concussionCountdown;
//  Accumulated head damage
var float   accHeadDamage;
//  Rate (per second) at which accumulated head damage diminishes
var float   headDamageRecoveryRate;
//  Time that needs to pass after last head damage for recovery to kick in
var float   headRecoveryTime;
//  Count down variable for above mentioned time
var float   headRecoveryCountDown;

//==============================================================================
//==============================================================================
//  >   Temperature system
//  Manages temperature (heat level) of the zed by either changing it because
//  of elemental damage or bringing it back to zero (normal heat) with time.
//  Allows for two afflictions to occure:
//      1. Zed being set on fire, which might cause a change in behaviour
//      2. Zed being frozen, fixing it in place
//  Having this flag set to 'true' massively decreases damage
//  taken from fire sources
var bool    bFireImmune;
//  Can this zed be set on fire?
var bool    bCanBurn;
//  Is zed currently burning?
var bool    bOnFire;
//  Tracks whether or not zed's behaviour was changed to the burning one
var bool    bBurningBehavior;
//  Having positive fuel values means that zed's heat will increase while it
//  burining and will decrease otherwise;
//  Both variables are automatically setup for zed upon spawning
var float   flameFuel, initFlameFuel;
//  Defines how much fuel (relative to scaled max health) zed will have
var float   fuelRatio;
//  Current heat level of the zed:
//      - Getting it high enough results in zed burning
//      - Getting it low enough results in zed being frozen
var float   heat;
//  Rate at which heat restores itself to default (zero) value
var float   heatDissipationRate;
//  Affects how often heat value updates and how often fire/cold DoT ticks
var float   heatTicksPerSecond;
//  Tracks last time heat tick occured
var float   lastHeatTick;
var float   scorchmarkDamage;
var float   MIN_HEAT, MAX_HEAT;

//==============================================================================
//==============================================================================
//  >   Speed system
//  Manages speed modifications from weapon attacks and other effects.
//
//  This variable stores with how much stopping power zed was affected.
//  Is a value from 0.0 to 1.0 and `1 - stoppingEffect` acts as speed multiplier
//  for the zed
var float   stoppingEffect;
//  How much stopping effect would be recovred in a second
var float   stoppingRecoveryRate;
//  Maximum `stoppingEffect` value zed can accumulate
var float   maxStoppingEffect;
//  Minimal stopping threshold for the zed (supposed to be subtracted from
//  incoming effects' strenght).
var float   minStoppingThreshold;

//==============================================================================
//==============================================================================
//  >   Miscellaneous variables
//==============================================================================
//  >>  Head-damage related
//  'true' means any headshot ill destroy head completely
var bool    bWeakHead;
//  Head radius scale for client-side hitdetection
var float   clientHeadshotScale;
//  Stores maximum head health zed can have
var float   headHealthMax;
//==============================================================================
//  >>  Auxiliary variables for general purposes
var ScrnGameRules   scrnRules;
var float           lastTookDamageTime;
//  'CuteDecapFX' already performed decapitation on thiszed
var bool            bCuteDecapDone;
//  Don't run decap tick (makes sure that head is visually destroyed)
//  for this zed
var bool            bSkipDecapTick;
//==============================================================================
//  >>  Auxiliary variables for perks/skills
//  Time left before (medic's) madness wears off
var float   madnessCountDown;
//  Should this zed be decapitated in a melee-like manner?
var bool bMeleeDecapitated;
//  More precise momentum replication
var float TearOffMomentumX, TearOffMomentumY, TearOffMomentumZ;
var const material FrozenMaterial;
var bool bFrozenZed, bFrozenZedClient;
var Rotator frozenRotation;
var array<name> frozenAnimations;
var array<float> frozenAnimFrames;
var Pawn frostInstigator;
var float lastFrostDamage;
var class<NiceWeaponDamageType> frostDamageClass;
var float iceCrustStrenght;
var bool isStatue;
var class<Emitter> ShatteredIce;
//==============================================================================
//  >>  Replacement variables to store current melee damage
var class<NiceWeaponDamageType> niceZombieDamType;
replication{
    reliable if(Role < ROLE_Authority)
       ServerDropFaster;
    reliable if(Role == ROLE_Authority)
       bMeleeDecapitated, TearOffMomentumX, TearOffMomentumY, TearOffMomentumZ,
       bFrozenZed, frozenRotation;
}
simulated function PostBeginPlay(){
    local GameRules rules;
    local Vector AttachPos;
    Super.PostBeginPlay();
    //  Auto-fill of some values
    stunDuration    = GetAnimDuration('KnockDown');
    HeadHealthMax   = HeadHealth;
    InitFlameFuel   = FuelRatio * HealthMax;
    FlameFuel       = InitFlameFuel;
    if(Role == ROLE_Authority){ //  auto-fill ScrnRules
       rules = Level.Game.GameRulesModifiers;
       while(rules != none){
           if(ScrnGameRules(rules) != none){
               scrnRules = ScrnGameRules(rules);
               break;
           }
           rules = rules.NextGameRules;
       }
    }
    //  Fool-protection: in case we (someone) forgets to set both variables
    clientHeadshotScale = FMax(clientHeadshotScale, OnlineHeadshotScale);
    //  Add zed-collisions on client too
    if(Role < ROLE_Authority){
       if(bUseExtendedCollision && MyExtCollision == none){
           MyExtCollision = Spawn(Class'ExtendedZCollision', Self);
           MyExtCollision.SetCollisionSize(colRadius, colHeight);
           MyExtCollision.bHardAttach = true;
           AttachPos = Location + (ColOffset >> Rotation);
           MyExtCollision.SetLocation(AttachPos);
           MyExtCollision.SetPhysics(PHYS_none);
           MyExtCollision.SetBase(Self);
           SavedExtCollision = MyExtCollision.bCollideActors;
       }
    }
    initializationComplete = true;
}

//==============================================================================
//==============================================================================
//  >   Dismemberment-related functions
//  Created to move some repeated code from 'HideBone' function
simulated function
AttachSeveredLimb(  out SeveredAppendageAttachment replacement,
                   class<SeveredAppendageAttachment> replacementClass,
                   float replacementScale,
                   name attachmentPoint,
                   name boneName){
    local coords boneCoords;
    local class<DismembermentJet> emitterClass;
    //  Leave if replacement limb has already spawned
    if(replacement != none) return;
    //  Decide on the right emitter class
    if(boneName == headBone){
       if(bNoBrainBitEmitter)
           emitterClass = NeckSpurtNoGibEmitterClass;
       else
           emitterClass = NeckSpurtEmitterClass;
    }
    else
       emitterClass = LimbSpurtEmitterClass;
    //  Spawn & attach replacement
    replacement = Spawn(replacementClass, self);
    replacement.SetDrawScale(replacementScale);
    boneCoords = GetBoneCoords(attachmentPoint);
    AttachEmitterEffect(    emitterClass, attachmentPoint,
                           boneCoords.Origin, rot(0,0,0));
    AttachToBone(replacement, attachmentPoint);
}
simulated function HideBone(name boneName){
    switch(boneName){
       case LeftThighBone:
           SetBoneScale(0, 0.0, boneName);
           AttachSeveredLimb(  SeveredLeftLeg, SeveredLegAttachClass,
                               SeveredLegAttachScale, 'lleg', boneName);
           break;
       case RightThighBone:
           SetBoneScale(1, 0.0, boneName);
           AttachSeveredLimb(  SeveredRightLeg, SeveredLegAttachClass,
                               SeveredLegAttachScale, 'rleg', boneName);
           break;
       case RightFArmBone:
           SetBoneScale(2, 0.0, boneName);
           AttachSeveredLimb(  SeveredRightArm, SeveredArmAttachClass,
                               SeveredArmAttachScale, 'rarm', boneName);
           break;
       case LeftFArmBone:
           SetBoneScale(3, 0.0, boneName);
           AttachSeveredLimb(  SeveredLeftArm, SeveredArmAttachClass,
                               SeveredArmAttachScale, 'larm', boneName);
           break;
       case HeadBone:
           if(SeveredHead == none)
               SetBoneScale(4, 0.0, boneName);
           AttachSeveredLimb(  SeveredHead, SeveredHeadAttachClass,
                               SeveredHeadAttachScale, 'neck', boneName);
           break;
       case 'spine':
           SetBoneScale(5, 0.0, boneName);
    }
}
simulated function CuteDecapFX(){
    local int leftRight;
    if(!bCuteDecapDone){
       LeftRight = 1;
       if(rand(10) > 5)
           LeftRight = -1;
       NeckRot.Yaw     = - Clamp(rand(24000), 14000, 24000);
       NeckRot.Roll    = leftRight * clamp(rand(8000), 2000, 8000);
       NeckRot.Pitch   = leftRight * clamp(rand(12000), 2000, 12000);
       RemoveHead();
    }
    bCuteDecapDone = true;
    SetBoneRotation('neck', NeckRot);
}
simulated function DecapFX( Vector DecapLocation,
                           Rotator DecapRotation,
                           bool bSpawnDetachedHead,
                           optional bool bNoBrainBits){
    if(SeveredHead != none)
       return;
    super.DecapFX(  DecapLocation, DecapRotation,
                   bSpawnDetachedHead, bNoBrainBits);
}
//==============================================================================
//==============================================================================
//  >   Routins that should be executed every tick
//  (and ones closely related to them), moved out from the 'Tick' function
//  to make it more comprehensible.
//==============================================================================
//  >>  Handles medic's madness count down
simulated function MadnessTick(float deltaTime){
    if(madnessCountDown > 0)
       madnessCountDown -= DeltaTime;
    if(madnessCountDown < 0.0)
       madnessCountDown = 0.0;
}
//==============================================================================
//  >>  Makes sure zed loses it's head upon decapitation
simulated function DecapTick(float deltaTime){
    local Coords boneCoords;
    if(Role == ROLE_Authority) return;
    if(bDecapitated && SeveredHead == none && !bSkipDecapTick){
       boneCoords = GetBoneCoords(HeadBone);
       if(bMeleeDecapitated)
           DecapFX(boneCoords.Origin, rot(0, 0, 0), true);
       else
           DecapFX(boneCoords.Origin, rot(0, 0, 0), false);
    }
}
//==============================================================================
//  >>  Makes zeds REALLY avoid fear spots
simulated function FearTick(float deltaTime){
    local Vector fearCenter;
    local bool fstFearAffects, sndFearAffects;
    if(Role < ROLE_Authority || controller == none || bShotAnim) return;
    //  What spots saffect this zed and if affected at all
    if( controller.fearSpots[0] != none
       && Controller.fearSpots[0].RelevantTo(self))
       fstFearAffects = true;
    if( controller.fearSpots[1] != none
       && Controller.fearSpots[1].RelevantTo(self))
       sndFearAffects = true;
    if(!fstFearAffects && !sndFearAffects) return;
    //  Calculate fear center
    if(fstFearAffects)
       fearCenter = controller.fearSpots[0].Location;
    if(sndFearAffects)
       fearCenter = controller.fearSpots[1].Location;
    if(fstFearAffects && sndFearAffects)
       fearCenter *= 0.5 * fearCenter;
    //  Accelerate zed in the right direction
    acceleration = acceleration * 0.25
       + 0.75 * accelRate * Normal(Location - fearCenter);
}
//==============================================================================
//  >>  Decrease active stopping power on the zed as the time goes by
simulated function StoppingPowerTick(float deltaTime) {
    if (stoppingEffect <= 0.0 || concussionCountdown > 0.0) {
        return;
    }
    stoppingEffect -= deltaTime * stoppingRecoveryRate;
    stoppingEffect = FMax(0, stoppingEffect);
}
//==============================================================================
//  >>  Set of functions to handle animation changes during stun
simulated function CalcRemainigStunStructure(   name        seqName,
                                               float       oFrame,
                                               float       oRate,
                                               out int     stunLoopsLeft,
                                               out float   stunLeftover){
    local float loopDuration, temp;
    loopDuration = (stunLoopEnd - stunLoopStart) * stunDuration;
    if(seqName == IdleRestAnim){
       stunLoopsLeft = 0;
       stunLeftover = StunCountDown - (1 - idleInsertFrame) * stunDuration;
    }
    else if(prevStunAnimFrame < stunLoopEnd && loopDuration > 0.0){
       temp = StunCountDown - (1 - oFrame) * stunDuration;
       if(temp <= 0){
           stunLoopsLeft = 0;
           stunLeftover = 0.0;
       }
       else{
           stunLoopsLeft = Ceil(temp / loopDuration) - 1;
           stunLeftover = temp - stunLoopsLeft * loopDuration;
       }
    }
    else{
       stunLoopsLeft = 0;
       stunLeftover = StunCountDown - (1 - oFrame) * stunDuration;
    }
    if(stunLeftover < 0.0)
       stunLeftover = 0.0;
}
simulated function UpdateStunAnim(  name    seqName,
                                   float   oFrame,
                                   float   oRate,
                                   int     stunLoopsLeft,
                                   float   stunLeftover){
    local bool bIdleFramePassed, bLoopEndFramePassed, bNotIdle;
    if(!bIsStunned){
       bWasIdleStun = (seqName == IdleRestAnim);
       prevStunAnimFrame = oFrame;
       return;
    }
    bNotIdle            = (seqName != IdleRestAnim);
    bIdleFramePassed    = oFrame >= idleInsertFrame
       && prevStunAnimFrame < idleInsertFrame;
    bLoopEndFramePassed = oFrame >= stunLoopEnd
       && prevStunAnimFrame < stunLoopEnd;
    //  Hit on idle flag
    //  (and have no stun loops left, while there's enough leftovers left)
    if( bNotIdle && bIdleFramePassed
       && stunLoopsLeft <= 0 && stunLeftover > 0.2){
       PlayAnim(IdleRestAnim,, 0.1);
       bWasIdleStun = true;
       prevStunAnimFrame = -1.0;
    }
    //  Hit on loopEnd flag detected and there's loops left
    else if(bNotIdle && bLoopEndFramePassed && stunLoopsLeft > 0){
       SetAnimFrame(stunLoopStart);
       bWasIdleStun = false;
       prevStunAnimFrame = stunLoopStart;
    }
    else{
       bWasIdleStun = !bNotIdle;
       prevStunAnimFrame = oFrame;
    }
}
simulated function StunTick(float deltaTime){
    local name  seqName;
    local float oFrame, oRate;
    // How many full loops we can play and leftovers after them
    local int   stunLoopsLeft;
    local float stunLeftover;
    //// Handle stun count down
    if(bIsStunned)
       GetAnimParams(0, seqName, oFrame, oRate);
    StunCountDown -= DeltaTime;
    if(StunCountDown < 0.0)
       StunCountDown = 0.0;
    if(bIsStunned && StunCountDown <= 0)
       Unstun();
    // Animation update
    // Compute stun loops left and their leftovers
    CalcRemainigStunStructure(  seqName, oFrame, oRate,
                               stunLoopsLeft, stunLeftover);
    // Make decisions based on current state of stun
    UpdateStunAnim( seqName, oFrame, oRate,
                   stunLoopsLeft, stunLeftover);
}
//==============================================================================
//  >>  Set of functions to handle stun from head damage accumulation
function AccumulateHeadDamage( float addDamage,
                               bool bIsHeadshot,
                               NicePlayerController nicePlayer){
    if(bIsHeadshot){
       AccHeadDamage += addDamage * 0.5;
       HeadRecoveryCountDown = HeadRecoveryTime;
       if(AccHeadDamage > (default.HeadHealth / 1.5)
           && (concussionCountdown > 0.0 && IsStunPossible()))
           DoStun(nicePlayer.pawn,,,, 1.0);
    }
    else if(HeadRecoveryCountDown > 0.0)
       HeadRecoveryCountDown = FMin(   HeadRecoveryCountDown,
                                       HeadRecoveryTime / 2);
}
function HeadDamageRecoveryTick(float delta){
    concussionCountdown -= delta;
    concussionCountdown = FMax(0.0, concussionCountdown);
    headRecoveryCountDown -= delta;
    headRecoveryCountDown = FMax(0.0, headRecoveryCountDown);
    if(headRecoveryCountDown <= 0.0)
       accHeadDamage -= delta * HeadDamageRecoveryRate;
    accHeadDamage = FMax(accHeadDamage, 0.0);
}
//==============================================================================
//  >>  Function that calls actual 'HeatTick' when needed
simulated function FakeHeatTick(float deltaTime){
    local int i;
    local name seqName;
    local float oFrame;
    local float oRate;
    local NiceMonsterController niceZedController;
    if (bOnFire) {
        // 100 of the scorchmark damage should restore every 1/10 of a second
        scorchmarkDamage += deltaTime * 10 * 100;
        scorchmarkDamage = FMin(scorchmarkDamage, 100);
    }
    if(lastHeatTick + (1 / HeatTicksPerSecond) < Level.TimeSeconds){
           if(bOnFire && !bBurningBehavior)
               SetBurningBehavior();
           if(!bOnFire && bBurningBehavior)
               UnSetBurningBehavior();
           HeatTick();
           lastHeatTick = Level.TimeSeconds;
    }
    if(bFrozenZedClient != bFrozenZed){
       bFrozenZedClient = bFrozenZed;
       if(bFrozenZed){
           frozenAnimations.length = 0;
           frozenAnimFrames.length = 0;
           while(IsAnimating(i)){
               GetAnimParams(i, seqName, oFrame, oRate);
               frozenAnimations[i] = seqName;
               frozenAnimFrames[i] = oFrame;
               i ++;
           }
       }
    }
    if(bFrozenZed){
       if(Role < Role_AUTHORITY){
           GetAnimParams(0, seqName, oFrame, oRate);
           for(i = 0;i < frozenAnimations.length;i ++){
               PlayAnim(frozenAnimations[i],,, i);
               if(frozenAnimFrames.length > i)
                   SetAnimFrame(frozenAnimFrames[i], i);
           }
       }
       else
           StopAnimating();
       StopMovement();
       SetRotation(frozenRotation);
       niceZedController = NiceMonsterController(controller);
       if(niceZedController != none && !controller.IsInState('Freeze')){
           controller.GotoState('Freeze');
           niceZedController.bUseFreezeHack = true;
           niceZedController.focus = none;
           niceZedController.focalPoint = location + 512 * vector(rotation);
       }
    }
}
//==============================================================================
//  >>  Ticks from TWI's code
//  Updates zed's speed if it's not relevant;
//  code, specific to standalone game and listen servers was cut out

// Kill zed if it has been bleeding long enough
simulated function BleedOutTick(float deltaTick)
{
   if (Role < ROLE_Authority || !bDecapitated)
      return;
   if (BleedOutTime <= 0 || Level.TimeSeconds < BleedOutTime)
      return;
   if (LastDamagedBy != none)
      Died(LastDamagedBy.Controller, class'DamTypeBleedOut', Location);
   // else we can say we killed ourselves, none -> controller (self)
   // P.S. we really need to use Suicide here D:
   else
      Died(controller, class'DamTypeBleedOut', Location);
   BleedOutTime = 0;
}

// FX-stuff TWI did in the tick, unchanged
simulated function TWIFXTick(float deltaTime){
    if(Level.netMode == NM_DedicatedServer) return;
    TickFX(DeltaTime);
    if(bBurnified && !bBurnApplied){
       if(!bGibbed)
           StartBurnFX();
    }
    else if(!bBurnified && bBurnApplied)
       StopBurnFX();
    if( bAshen && Level.netMode == NM_Client
       && !class'GameInfo'.static.UseLowGore()){
       ZombieCrispUp();
       bAshen = False;
    }
}
simulated function TWIDECAPTick(float deltaTime){
    if(!DECAP) return;
    if(Level.TimeSeconds <= (DecapTime + 2.0) || Controller == none) return;
    DECAP = false;
    MonsterController(Controller).ExecuteWhatToDoNext();
}
simulated function BileTick(float deltaTime){
    if(BileCount <= 0 || NextBileTime >= level.TimeSeconds) return;
    BileCount --;
    NextBileTime += BileFrequency;
    TakeBileDamage();
}
//  TWI's code, separeted into shorter functions and segments
simulated function TWITick(float deltaTime){
    //  If we've flagged this character to be destroyed next tick, handle that
    if(bDestroyNextTick && TimeSetDestroyNextTickTime < Level.TimeSeconds)
       Destroy();
    //  Reset AnimAction
    if(bResetAnimAct && ResetAnimActTime < Level.TimeSeconds){
       AnimAction = '';
       bResetAnimAct = False;
    }
    //  Update look target
    if(Controller != none)
       LookTarget = Controller.Enemy;
    // Some more ticks
    BleedOutTick(deltaTime);
    TWIFXTick(deltaTime);
    TWIDECAPTick(deltaTime);
    BileTick(deltaTime);
}
//==============================================================================
//  >>  Actual tick function, that is much shorter and manageble now
simulated function Tick(float deltaTime){
    //  NicePack-specific ticks
    MadnessTick(deltaTime);
    DecapTick(deltaTime);
    FearTick(deltaTime);
    StunTick(deltaTime);
    StoppingPowerTick(deltaTime);
    HeadDamageRecoveryTick(deltaTime);
    FakeHeatTick(deltaTime);
    UpdateGroundSpeed();
    //  TWI's tick
    TWITick(deltaTime);
}
simulated function bool IsFinisher( int damage,
                                   class<NiceWeaponDamageType> niceDmg,
                                   NicePlayerController nicePlayer,
                                   optional bool isHeadshot){
    local bool hasTrashCleaner, isReaperActive;
    if(nicePlayer == none) return false;
    hasTrashCleaner = class'NiceVeterancyTypes'.static.hasSkill(nicePlayer,
                                       class'NiceSkillCommandoTrashCleaner');
    isReaperActive = false;
    if(nicePlayer.abilityManager != none){
       isReaperActive = nicePlayer.abilityManager.IsAbilityActive(
           class'NiceSkillSharpshooterReaperA'.default.abilityID);
    }
    if(     (niceDmg == none || !niceDmg.default.bFinisher)
       &&  (!hasTrashCleaner || default.health >= 500)
       &&  (!isReaperActive || !isHeadshot)    )
       return false;
    return  (isHeadshot && damage >= headHealth)
           || (!isHeadshot && damage >= health);
}
//  Checks current zed for head-shot
//  Returns result as 'float' value from 0.0 to 1.0,
//  where 1.0 means perfect head-shot and 0.0 means a miss
simulated function float IsHeadshotClient(  Vector Loc,
                                           Vector Ray,
                                           optional float additionalScale){
    local Coords C;
    local Vector HeadLoc;
    local float distance;
    local Vector HeadToLineOrig;
    //  Let A be such a dot on the bullet trajectory line,
    //  that vector between A and a head is a normal to the line and, therefore,
    //  the shortest distance
    local Vector AToLineOrig;
    local Vector lineDir;
    if(HeadBone == '')
       return 0.0;
    C = GetBoneCoords(HeadBone);
    if(additionalScale == 0.0)
       additionalScale = 1.0;
    HeadLoc = C.Origin + headHeight * headScale * C.XAxis;
    HeadToLineOrig = Loc - HeadLoc;
    lineDir = Normal(Ray);
    //  If we project 'HeadToLineOrig' onto the line,
    //  - line origin ('Loc') will go to itself and Head center ('HeadLoc')
    //  to the point A
    //  So we'll get a vector between A and head center
    AToLineOrig = (HeadToLineOrig Dot lineDir) * lineDir;
    distance = VSize(HeadToLineOrig - AToLineOrig);
    if(distance < headRadius * headScale * additionalScale)
       return 1.0 - (distance / (headRadius * headScale * additionalScale));
    return 0.0;
}
//  Calculates distance from `location` to this zed's head.
simulated function float GetDistanceToHead(Vector location) {
    local Coords headBoneCoords;
    local Vector headLocation;

    if(headBone == '') {
       return 0.0;
    }
    headBoneCoords = GetBoneCoords(headBone);
    headLocation =
      headBoneCoords.Origin + headHeight * headScale * headBoneCoords.XAxis;
    return VSize(location - headLocation);
}
//  In case of a future modifications:
//  check if it's a player doing damage before relying on it
function ModDamage( out int damage,
                   Pawn instigatedBy,
                   Vector hitLocation,
                   Vector momentum,
                   class<NiceWeaponDamageType> damageType,
                   float headshotLevel,
                   KFPlayerReplicationInfo KFPRI,
                   optional float lockonTime){
    local NicePlayerController  nicePlayer;
    local bool                  hasGiantSlayer;
    local int                   bonusDamageStacks;
    if(KFPRI == none || KFPRI.ClientVeteranSkill == none) return;
    //  Add perked damage
    damage = KFPRI.ClientVeteranSkill.Static.AddDamage( KFPRI, self,
                                                        KFPawn(instigatedBy),
                                                        damage, damageType);
    // Skill bonuses
    if(instigatedBy == none)
        return;
    nicePlayer = NicePlayerController(instigatedBy.controller);
    if(nicePlayer == none)
        return;
    hasGiantSlayer = class'NiceVeterancyTypes'.static.hasSkill(nicePlayer,
                                            class'NiceSkillCommandoGiantSlayer');
    if(!hasGiantSlayer)
        return;
    bonusDamageStacks =
        int(health / class'NiceSkillCommandoGiantSlayer'.default.healthStep);
    damage *= 1.0f + bonusDamageStacks *
        class'NiceSkillCommandoGiantSlayer'.default.bonusDamageMult;
}
function ModRegularDamage(  out int damage,
                           Pawn instigatedBy,
                           Vector hitLocation,
                           Vector momentum,
                           class<NiceWeaponDamageType> damageType,
                           float headshotLevel,
                           KFPlayerReplicationInfo KFPRI,
                           optional float lockonTime){
    local bool                          hasOverkillSkill;
    local NicePlayerController          nicePlayer;
    local class<NiceVeterancyTypes>     niceVet;
    if(KFPRI == none) return;
    if(instigatedBy != none)
       nicePlayer = NicePlayerController(instigatedBy.Controller);
    niceVet     = class<NiceVeterancyTypes>(KFPRI.ClientVeteranSkill);
    //  Add perked damage
    if(niceVet != none)
       damage = niceVet.static.AddRegDamage(   KFPRI, self,
                                               KFPawn(instigatedBy), damage,
                                               damageType);
    //  Skills bonuses
    if(nicePlayer == none) return;
    hasOverkillSkill = class'NiceVeterancyTypes'.static.
       hasSkill(nicePlayer, class'NiceSkillSharpshooterZEDOverkill');
    if(headshotLevel > 0.0 && nicePlayer.isZedTimeActive() && hasOverkillSkill)
       damage *= class'NiceSkillSharpshooterZEDOverkill'.default.damageBonus;
}
function ModFireDamage( out int damage,
                       Pawn instigatedBy,
                       Vector hitLocation,
                       Vector momentum,
                       class<NiceWeaponDamageType> damageType,
                       float headshotLevel,
                       KFPlayerReplicationInfo KFPRI,
                       optional float lockonTime){
    local class<NiceVeterancyTypes>     niceVet;
    if(KFPRI == none) return;
    niceVet = class<NiceVeterancyTypes>(KFPRI.ClientVeteranSkill);
    //  Add perked damage
    if(niceVet != none)
       damage = niceVet.static.AddFireDamage(  KFPRI, self,
                                               KFPawn(instigatedBy), damage,
                                               damageType);
    //  Cut fire damage against fire-immune zeds
    if(bFireImmune)
       damage /= 10;
}

function ModHeadDamage( out int damage,
                       Pawn instigatedBy,
                       Vector hitLocation,
                       Vector momentum,
                       class<NiceWeaponDamageType> dmgType,
                       float headshotLevel,
                       KFPlayerReplicationInfo KFPRI,
                       optional float lockonTime)
{
   local bool                          shouldCountHS;
   local NicePlayerController          nicePlayer;
   local class<NiceVeterancyTypes>     niceVet;

   if (instigatedBy != none)
      nicePlayer = NicePlayerController(instigatedBy.Controller);
   shouldCountHS = (lockonTime >= dmgType.default.lockonTime)
                   && (headshotLevel > dmgType.default.prReqMultiplier);

   // Weapon damage bonus
   if (dmgType != none && shouldCountHS)
      damage *= dmgType.default.HeadShotDamageMult;

   // Perk damage bonus
   if (KFPRI != none)
   {
      niceVet = class<NiceVeterancyTypes>(KFPRI.ClientVeteranSkill);
      if (niceVet != none)
         damage *= niceVet.static.GetNiceHeadShotDamMulti(KFPRI, self, dmgType);
   }
}

//  This function must record damage actual value in 'damage' variable and
//  return value that will decide stun/flinch
function int ModBodyDamage( out int damage,
                           Pawn instigatedBy,
                           Vector hitLocation,
                           Vector momentum,
                           class<NiceWeaponDamageType> damageType,
                           float headshotLevel,
                           KFPlayerReplicationInfo KFPRI,
                           optional float lockonTime){
    local bool                          bHasMessy;
    local bool                          bIsHeadShot;
    local int                           painDamage;
    local NicePlayerController          nicePlayer;
    painDamage = damage;
    bIsHeadShot = (headshotLevel > 0.0);
    if (instigatedBy != none)
       nicePlayer = NicePlayerController(instigatedBy.Controller);
    if (bFrozenZed && damageType != none && damageType.default.bIsExplosive) {
        damage *= 1.5;
    }
    // On damaging critical spot (so far only head) - do body destruction
    if (bIsHeadShot && damageType != none)
       damage *= damageType.default.bodyDestructionMult;
    // Skill bonuses
    if (nicePlayer == none)
       return painDamage;
    bHasMessy = class'NiceVeterancyTypes'.static.
       someoneHasSkill(nicePlayer, class'NiceSkillSharpshooterDieAlready');
    return painDamage;
}
// Do effects, based on fire damage dealt to monster
function FireDamageEffects(out int damage,
                           Pawn instigatedBy,
                           Vector hitLocation,
                           Vector momentum,
                           class<NiceWeaponDamageType> damageType,
                           float headshotLevel,
                           KFPlayerReplicationInfo KFPRI){
    local float heatDelta, bonusFireDamage;
    damage = FMax(0.0, damage);
    iceCrustStrenght = FMax(0.0, iceCrustStrenght);
    if(bFrozenZed){
       damage *= 10;
       if(iceCrustStrenght <= damage){
           damage -= iceCrustStrenght;
           if(iceCrustStrenght >= 0){
               iceCrustStrenght = 0;
               UnFreeze();
           }
       }
       else{
           iceCrustStrenght -= damage;
           damage = 0;
       }
       damage /= 10;
    }
    if(damage <= 0) {
        return;
    }
    //  Turn up the heat!
    //  (we can only make it twice as hot with that damage,
    //  but set limit at least at 50, as if we've dealt at least 25 heat damage)
    heatDelta = (damage * HeatIncScale())
       * FMin(1.0, FMax(0.0, (2 - Abs(heat) / FMax(25, Abs(damage) ))));
    if (concussionCountdown > 0) {
        heat += heatDelta * 2;
    } else {
        heat += heatDelta;
    }
    CapHeat();
    // Change damage type if new one was stronger
    if(!bOnFire || damage * HeatIncScale() > lastBurnDamage){
       fireDamageClass = damageType;
       burnInstigator = instigatedBy;
    }
    // Double heat damage at the cost of the fuel, if zed is already on fire
    if (bOnFire) {
        bonusFireDamage = FMax(FMin(damage, flameFuel) * 0.25, 0);
        flameFuel -= bonusFireDamage;
        damage += bonusFireDamage;
    }
    // Set on fire, if necessary
    if(heat > GetIgnitionPoint() && !bOnFire && bCanBurn){
       bBurnified = true;
       bOnFire = true;
       burnInstigator = instigatedBy;
       fireDamageClass = damageType;
       lastHeatTick = Level.TimeSeconds;
    }
}
function FrostEffects(  Pawn instigatedBy,
                       Vector hitLocation,
                       Vector momentum,
                       class<NiceWeaponDamageType> damageType,
                       float headshotLevel,
                       KFPlayerReplicationInfo KFPRI){
    local float freezePower;
    local float heatChange;
    if(damageType == none) return;
    freezePower = damageType.default.freezePower;
    heatChange = freezePower * (100.0 / default.health);
    heat -= heatChange;
    heat = FMax(-freezePower, heat);
    CapHeat();
    if(heat <= 0){
       bBurnified = false;
       UnSetBurningBehavior();
       RemoveFlamingEffects();
       StopBurnFX();
       bOnFire = false;
       //Log("Pre: strenght="$iceCrustStrenght@"/"@(freezePower * GetIceCrustScale()));
       iceCrustStrenght += freezePower * GetIceCrustScale();
       iceCrustStrenght = FMin(iceCrustStrenght, 150.0);
       //Log("Crusts status: freezePower="$freezePower$", scale="$GetIceCrustScale()$", strenght="$iceCrustStrenght);
    }
    if(!bFrozenZed || freezePower * 0.05 > lastFrostDamage){
       frostDamageClass = damageType;
       frostInstigator = instigatedBy;
    }
    if(!bFrozenZed && iceCrustStrenght >= GetFreezingPoint())
       Freeze();
}
function BileDamageEffect(   int damage,
                           Pawn instigatedBy,
                           class<damageType> damageType){
    if(class<DamTypeVomit>(damageType) != none){
       BileCount = 7;
       BileInstigator = instigatedBy;
       LastBileDamagedByType=class<DamTypeVomit>(damageType);
       if(NextBileTime < Level.TimeSeconds )
           NextBileTime = Level.TimeSeconds + BileFrequency;
    }
}

function float GetDecapDamageModifier(  class<NiceWeaponDamageType> damageType,
                                       NicePlayerController nicePlayer,
                                       KFPlayerReplicationInfo KFPRI)
{
   local float                     damageMod;
   local bool                      shouldDoGoodDecap;
   local bool                      hasTrashCleaner;
   local bool                      isPerkedPickup;
   local class<NiceWeaponPickup>   pickupClass;
   local class<NiceVeterancyTypes> niceVet;

   // KFPRI accessed none fix
   if (KFPRI != none)
      niceVet = class<NiceVeterancyTypes>(KFPRI.ClientVeteranSkill);
   isPerkedPickup = false;
   if (niceVet != none)
   {
      pickupClass = niceVet.static.GetPickupFromDamageType(damageType);
      if (pickupClass != none)
         isPerkedPickup = niceVet.static.IsPerkedPickup(pickupClass);
   }
   shouldDoGoodDecap = false;
   shouldDoGoodDecap = (damageType.default.decapType == DB_DROP);
   shouldDoGoodDecap = shouldDoGoodDecap ||
       (damageType.default.decapType == DB_PERKED &&  isPerkedPickup);
   if (shouldDoGoodDecap)
      damageMod = damageType.default.goodDecapMod;
   else
      damageMod = damageType.default.badDecapMod;
   if (nicePlayer != none)
      hasTrashCleaner = class'NiceVeterancyTypes'.static.
           hasSkill(nicePlayer, class'NiceSkillCommandoTrashCleaner');
   if (hasTrashCleaner)
   {
      damageMod = FMin(damageMod, class'NiceSkillCommandoTrashCleaner'.default.decapitationMultiLimit);
   }
   return damageMod;
}

function DealDecapDamage(   int damage,
                           Pawn instigatedBy,
                           Vector hitLocation,
                           Vector momentum,
                           class<NiceWeaponDamageType> damageType,
                           float headshotLevel,
                           KFPlayerReplicationInfo KFPRI,
                           optional float lockonTime){
    local int                   decapDmg;
    local NicePlayerController  nicePlayer;
    if(instigatedBy != none)
       nicePlayer = NicePlayerController(instigatedBy.Controller);
    RemoveHead();
    if(damageType == none){
       ModDamage(  decapDmg, instigatedBy, hitLocation, momentum, damageType,
                   headshotLevel, KFPRI, lockonTime);
       ModHeadDamage(  decapDmg, instigatedBy, hitLocation, momentum,
                       damageType, headshotLevel, KFPRI, lockonTime);
    }
    else
    {
       decapDmg = Ceil(HealthMax * GetDecapDamageModifier(  damageType,
                                                      nicePlayer, KFPRI));
    }
    DealBodyDamage( decapDmg, instigatedBy, hitLocation, momentum, damageType,
                   headshotLevel, KFPRI, lockonTime);
    if(class'NiceVeterancyTypes'.static.
       hasSkill(nicePlayer, class'NiceSkillSharpshooterDieAlready'))
       ServerDropFaster(NiceHumanPawn(nicePlayer.pawn));
}
function DealHeadDamage(   int damage,
                           Pawn instigatedBy,
                           Vector hitLocation,
                           Vector momentum,
                           class<NiceWeaponDamageType> damageType,
                           float headshotLevel,
                           KFPlayerReplicationInfo KFPRI,
                           optional float lockonTime){
    local NicePlayerController          nicePlayer;
    local KFSteamStatsAndAchievements   KFStatsAndAchievements;
    if(instigatedBy != none)
       nicePlayer = NicePlayerController(instigatedBy.Controller);
    //  Sound effects
    PlaySound(  Sound'KF_EnemyGlobalSndTwo.Impact_Skull', SLOT_none,
               2.0, true, 500);
    //  Actual damage effects
    // Skull injury killed a zed
    if(HeadHealth <= 0) return;
    // Additional weakpoint damage to burning zeds from non-flame weapons
    if (bOnFire) {
        damage += scorchmarkDamage * (1 - damageType.default.heatPart);
        scorchmarkDamage *= damageType.default.heatPart;
    }
    HeadHealth -= damage;
    if(nicePlayer != none && IsFinisher(damage, damageType, nicePlayer, true))
       HeadHealth -= damage;
    //  Remove head for the weak creatures
    if(bWeakHead && damage > 0 && HeadHealth > 0)
       HeadHealth = 0;
    if(HeadHealth <= 0 || damage > Health)
       DealDecapDamage(damage, instigatedBy, hitLocation, momentum, damageType,
                       headshotLevel, KFPRI, lockonTime);
    //  Head damage accumulation
    AccumulateHeadDamage(damage, headshotLevel > 0.0, nicePlayer);
    //  Award head-shot for achievements and stats
    if(nicePlayer == none || damageType == none || !bDecapitated) return;
    KFStatsAndAchievements =
       KFSteamStatsAndAchievements(nicePlayer.SteamStatsAndAchievements);
    damageType.static.ScoredNiceHeadshot(  KFStatsAndAchievements, self.class,
                                           scrnRules.HardcoreLevel);
}
function Vector RecalculateMomentum(Vector momentum,
                                   Pawn instigatedBy,
                                   class<NiceWeaponDamageType> damageType){
    local bool bApplyMomentum;
    if(Physics == PHYS_none)
       SetMovementPhysics();
    if(Physics == PHYS_Walking && damageType.default.bExtraMomentumZ)
       momentum.Z = FMax(momentum.Z, 0.4 * VSize(momentum));
    if(instigatedBy == self)
       momentum *= 0.6;
    momentum = momentum / mass;
    bApplyMomentum = ShouldApplyMomentum(damageType);
    if(Health > 0 && !bApplyMomentum)
       momentum = vect(0 ,0, 0);
    return momentum;
}
function ManageDeath(   Vector hitLocation,
                       Vector momentum,
                       Pawn instigatedBy,
                       class<NiceWeaponDamageType> damageType,
                       float headshotLevel){
    local bool          bWorldOrSafeCaused;
    local Controller    killer;
    if(damageType == none) return;
    bWorldOrSafeCaused = damageType.default.bCausedByWorld
       && (instigatedBy == none || instigatedBy == self);
    if(bWorldOrSafeCaused && LastHitBy != none)
       killer = LastHitBy;
    else if(instigatedBy != none)
       killer = instigatedBy.GetKillerController();
    if(killer == none && damageType.Default.bDelayedDamage)
       killer = DelayedDamageInstigatorController;
    if(bPhysicsAnimUpdate)
       SetTearOffMomemtum(momentum);
    if(bFrozenZed){
       bHidden = true;
       Spawn(ShatteredIce,,, location);
    }
    Died(killer, damageType, hitLocation);
    if(headshotLevel > 0.0 && KFGameType(Level.Game) != none)
       KFGameType(Level.Game).DramaticEvent(0.03);
}
function DealBodyDamage(int damage,
                       Pawn instigatedBy,
                       Vector hitLocation,
                       Vector momentum,
                       class<NiceWeaponDamageType> damageType,
                       float headshotLevel,
                       KFPlayerReplicationInfo KFPRI,
                       optional float lockonTime){
    local int                   actualDamage;
    local bool                  delayedDamage;
    local NicePlayerController  nicePlayer;
    if(Health <= 0 || damageType == none || Role < ROLE_Authority) return;
    // Find correct instigator and it's controller
    delayedDamage = damageType.default.bDelayedDamage
       && DelayedDamageInstigatorController != none;
    if((instigatedBy == none || instigatedBy.Controller == none)
       && delayedDamage)
       instigatedBy = DelayedDamageInstigatorController.Pawn;
    if(instigatedBy != none)
       nicePlayer = NicePlayerController(instigatedBy.Controller);
    // Apply game rules to damage
    actualDamage = Level.Game.ReduceDamage( damage, self, instigatedBy,
                                           hitLocation, Momentum, damageType);
    // Reduce health
    Health -= actualDamage;
    if(IsFinisher(damage, damageType, nicePlayer))
    {
       Health -= actualDamage;
    }
    // Update location
    if(hitLocation == vect(0,0,0))
       hitLocation = Location;
    // Update physics/momentum
    momentum = RecalculateMomentum(momentum, instigatedBy, damageType);
    // Generate effects
    PlayHit(actualDamage, instigatedBy, hitLocation, damageType, Momentum);
    // Add momentum to survivors / manage death
    if(Health > 0){
       AddVelocity(momentum);
       if(controller != none)
           controller.NotifyTakeHit(   instigatedBy, hitLocation, actualDamage,
                                       damageType, Momentum);
       if(instigatedBy != none && instigatedBy != self)
           LastHitBy = instigatedBy.controller;
    }
    else
       ManageDeath(hitLocation, momentum, instigatedBy,
                   damageType, headshotLevel);
    MakeNoise(1.0);
}

function Died( Controller killer,
               class<DamageType> damageType,
               vector HitLocation)
{
   local bool                       bHasManiac;
   local NiceHumanPawn              nicePawn;
   local class<NiceVeterancyTypes>  niceVet;

   if (killer != none || (Controller != none && killer != Controller))
   {
      // Maniac stuff
      bHasManiac = class'NiceVeterancyTypes'.static.
         HasSkill(NicePlayerController(killer), class'NiceSkillDemoManiac');
      nicePawn = NiceHumanPawn(killer.pawn);
      if (bHasManiac && nicePawn != none)
         nicePawn.maniacTimeout = 
            class'NiceSkillDemoManiac'.default.reloadBoostTime;
      // Enforcer's invincibility
      if (nicePawn != none)
      {
         niceVet = class'NiceVeterancyTypes'.static.
            GetVeterancy(nicePawn.playerReplicationInfo);
      }
      if (  niceVet != none
         && niceVet == class'NiceVetEnforcer'
         && nicePawn.invincibilityTimer > 0)
      {
         nicePawn.invincibilityTimer += default.healthMax / 500.0;
      }
   }
   
   super.Died(killer, damageType, HitLocation);
}

simulated function SetTearOffMomemtum(vector NewMomentum){
    TearOffMomentum     = NewMomentum;
    TearOffMomentumX    = NewMomentum.X;
    TearOffMomentumY    = NewMomentum.Y;
    TearOffMomentumZ    = NewMomentum.Z;
}
simulated function vector GetTearOffMomemtum(){
    TearOffMomentum.X   = TearOffMomentumX;
    TearOffMomentum.Y   = TearOffMomentumY;
    TearOffMomentum.Z   = TearOffMomentumZ;
    return TearOffMomentum;
}
function bool ShouldApplyMomentum(class<NiceWeaponDamageType> damageType){
    if(damageType!=class'DamTypeFrag' && damageType!=class'DamTypePipeBomb'
       /*&& damageType!=class'DamTypeM79Grenade'
       && damageType!=class'DamTypeM32Grenade'
       && damageType!=class'DamTypeM203Grenade'
       && damageType!=class'DamTypeDwarfAxe'
       && damageType!=class'DamTypeSPGrenade'
       && damageType!=class'DamTypeSealSquealExplosion'
       && damageType!=class'DamTypeSeekerSixRocket'
       && damageType!=class'NiceDamTypeM41AGrenade'
       && damageType!=class'NiceDamTypeRocket' NICETODO: sort this shit out*/
       && !ClassIsChildOf(damageType, class'NiceDamageTypeVetDemolitions'))
       return false;
    return true;
}
function EPainReaction GetRightPainReaction(int painDamage,
                                           Pawn instigatedBy,
                                           Vector hitLocation,
                                           Vector momentum,
                                           class<NiceWeaponDamageType> dmgType,
                                           float headshotLevel,
                                           KFPlayerReplicationInfo KFPRI){
    local int                           stunScore, flinchScore;
    local bool                          bStunPass, bFlinchPass, bMiniFlinshPass;
    local class<NiceVeterancyTypes>     niceVet;
    if (bOnFire) {
        return PREACTION_NONE;
    }
    if(KFPRI != none)
       niceVet = class<NiceVeterancyTypes>(KFPRI.ClientVeteranSkill);
    stunScore   = painDamage;
    flinchScore = painDamage;
    if(dmgType != none){
       stunScore *= dmgType.default.stunMultiplier;
       flinchScore *= dmgType.default.flinchMultiplier;
    }
    if(niceVet != none){
       flinchScore = niceVet.static.
           AddFlinchScore( KFPRI, self, KFPawn(instigatedBy),
                           flinchScore, dmgType);
       stunScore = niceVet.static.
           AddStunScore(   KFPRI, self, KFPawn(instigatedBy),
                           stunScore, dmgType);
    }
    bStunPass = CheckStun(  stunScore, instigatedBy, hitLocation, momentum,
                           dmgType, headshotLevel, KFPRI);
    bFlinchPass = CheckFlinch(  flinchScore, instigatedBy, hitLocation,
                               momentum, dmgType, headshotLevel, KFPRI);
    bMiniFlinshPass = CheckMiniFlinch(  flinchScore, instigatedBy,
                                       hitLocation, momentum, dmgType,
                                       headshotLevel, KFPRI);
    if(bStunPass) return PREACTION_STUN;
    else if(bFlinchPass) return PREACTION_FLINCH;
    else if(bMiniFlinshPass) return PREACTION_MINIFLINCH;
    return PREACTION_NONE;
}
function DoRightPainReaction( int painDamage,
                                           Pawn instigatedBy,
                                           Vector hitLocation,
                                           Vector momentum,
                                           class<NiceWeaponDamageType> dmgType,
                                           float headshotLevel,
                                           KFPlayerReplicationInfo KFPRI){
    local EPainReaction painReaction;
    painReaction = GetRightPainReaction(painDamage, instigatedBy,
                                       hitLocation, momentum, dmgType,
                                       headshotLevel, KFPRI);
    switch(painReaction){
       case PREACTION_STUN:
           DoStun(         instigatedBy, hitLocation, momentum, dmgType,
                           headshotLevel, KFPRI);
           break;
       case PREACTION_FLINCH:
           DoFlinch(       instigatedBy, hitLocation, momentum, dmgType,
                           headshotLevel, KFPRI);
           break;
       case PREACTION_MINIFLINCH:
           DoMiniFlinch(   instigatedBy, hitLocation, momentum, dmgType,
                           headshotLevel, KFPRI);
           break;
    }
    if(Level.TimeSeconds - LastPainTime > 0.1)
       LastPainTime = Level.TimeSeconds;
}
// Only called when stun is confirmed, so no need to re-check
function float GetstunDurationMult( Pawn instigatedBy,
                                   Vector hitLocation,
                                   Vector momentum,
                                   class<NiceWeaponDamageType> damageType,
                                   float headshotLevel,
                                   KFPlayerReplicationInfo KFPRI){
   local class<NiceVeterancyTypes>     niceVet;
   //  Default out
   if(KFPRI == none) return stunDurationMultiplier;
   niceVet = class<NiceVeterancyTypes>(KFPRI.ClientVeteranSkill);
   if(niceVet == none) return stunDurationMultiplier;
   if(damageType == none) return stunDurationMultiplier;
   //  Perk's bonuses out
   return stunDurationMultiplier * damageType.default.stunLengthMultiplier *
      niceVet.static.stunDurationMult( KFPRI, self, KFPawn(instigatedBy),
                                       damageType);
}
function bool IsStunPossible(){
    return (remainingStuns != 0 || bIsStunned);
}
function bool CheckStun(int stunScore,
                       Pawn instigatedBy,
                       Vector hitLocation,
                       Vector momentum,
                       class<NiceWeaponDamageType> damageType,
                       float headshotLevel,
                       KFPlayerReplicationInfo KFPRI){
    if(bFrozenZed) return false;
    if(stunScore > float(default.Health) * stunThreshold && IsStunPossible())
       return true;
    return false;
}
function bool CheckMiniFlinch(  int flinchScore,
                               Pawn instigatedBy,
                               Vector hitLocation,
                               Vector momentum,
                               class<NiceWeaponDamageType> damageType,
                               float headshotLevel,
                               KFPlayerReplicationInfo KFPRI){
    local bool bOnCooldown;
    if(bFrozenZed) return false;
    if(instigatedBy == none || damageType == none) return false;
    if(flinchScore < 5 || Health <= 0 || StunsRemaining == 0) return false;
    bOnCooldown = Level.TimeSeconds - LastPainAnim < MinTimeBetweenPainAnims;
    if(!bOnCooldown && HitCanInterruptAction())
       return true;
    return false;
}
function bool CheckFlinch(  int flinchScore,
                           Pawn instigatedBy,
                           Vector hitLocation,
                           Vector momentum,
                           class<NiceWeaponDamageType> damageType,
                           float headshotLevel,
                           KFPlayerReplicationInfo KFPRI){
    local bool  shouldFlinch;
    local bool  bCanMiniFlinch;
    local Vector X, Y, Z, Dir;
    //  We must be able to perform at least a mini-flinch for a flinch to work
    bCanMiniFlinch = CheckMiniFlinch(   flinchScore, instigatedBy,
                                       hitLocation, momentum, damageType,
                                       headshotLevel, KFPRI);
    if(!bCanMiniFlinch) return false;
    GetAxes(Rotation, X, Y, Z);
    hitLocation.Z = Location.Z;
    //  Actual flinch check
    shouldFlinch = false;
    //  1. Check direction
    if(VSize(Location - hitLocation) < 1.0)
       shouldFlinch = true;
    else{
       Dir = -Normal(Location - hitLocation);
       shouldFlinch = Dir dot X > 0.7;
    }
    //  2. Can we still flinch? ('StunsRemaining' is amount of flinches
    //  remaining, cause stupid naming); note that negative value of
    //  'StunsRemaining' means infinite flinches
    shouldFlinch = shouldFlinch && (StunsRemaining != 0);
    //  3. Do we have high enough 'flinchScore'?
    if(ClassIsChildOf(damageType, class'NiceDamageTypeVetBerserker'))
       shouldFlinch = shouldFlinch && flinchScore >= (0.1 * default.Health);
    else
       shouldFlinch = shouldFlinch && flinchScore >= (0.5 * default.Health);
    return shouldFlinch;
}
function StopMovement(){
    if(physics == PHYS_Falling)
       SetPhysics(PHYS_Walking);
    if(health > 0){
       acceleration.X  = 0;
       acceleration.Y  = 0;
       velocity.X      = 0;
       velocity.Y      = 0;
    }
}
// Do the stun; no check, no conditions, just stun
function DoStun(optional Pawn instigatedBy,
               optional Vector hitLocation,
               optional Vector momentum,
               optional class<NiceWeaponDamageType> damageType,
               optional float headshotLevel,
               optional KFPlayerReplicationInfo KFPRI){
    local int                   i;
    local float                 stunDurationMult;
    local NicePack              niceMut;
    local NiceMonsterController niceController;
    niceMut         = class'NicePack'.static.Myself(Level);
    niceController  = NiceMonsterController(controller);
    if(niceMut == none || niceController == none) return;
    //  Freeze zed and stop it from rotating
    StopMovement();
    niceController.GoToState('Freeze');
    niceController.bUseFreezeHack = true;
    niceController.focus = none; 
    niceController.focalPoint = location + 512 * vector(rotation);
    //  Reduce this value only if player was the one to make a flinch/stun and
    //  zed isn't currently stunned
    if(remainingStuns > 0 && !bIsStunned && KFHumanPawn(InstigatedBy) != none)
       remainingStuns --;
    if(bIsStunned)
       LastStunTime = Level.TimeSeconds;
    else
       SetAnimAction('KnockDown');
    //  Stunned flags
    bSTUNNED = true;
    bShotAnim = true;
    bIsStunned = true;
    stunDurationMult = GetStunDurationMult( instigatedBy, hitLocation, momentum,
                                           damageType, headshotLevel, KFPRI);
    stunCountDown = FMax(stunCountDown, stunDuration * stunDurationMult);
    // Tell clients about a stun
    for(i = 0;i < niceMut.playersList.Length;i ++)
       if(niceMut.playersList[i] != none)
           niceMut.playersList[i].ClientSetZedStun(self, true, stunCountDown);
}
simulated function Unstun(){
    local int i;
    local NicePack niceMut;
    if(Health <= 0.0) return;
    bSTUNNED            = false;
    bIsStunned          = false;
    bShotAnim           = false;
    bWaitForAnim        = false;
    bWasIdleStun        = false;
    prevStunAnimFrame   = 0.0;
    SetAnimFrame(1.0);
    if(Role < Role_AUTHORITY) return;
    if(Controller != none)
       Controller.GoToState('ZombieHunt');
    // Tell clients about a unstun
    niceMut = class'NicePack'.static.Myself(Level);
    if(niceMut == none)
       return;
    for(i = 0;i < niceMut.playersList.Length;i ++)
       if(niceMut.playersList[i] != none)
           niceMut.playersList[i].ClientSetZedStun(self, false, 0.0);
}
simulated function StunRefreshClient(bool bEnableStun){
    local name  seqName;
    local bool  leftLoop;
    local float oFrame, oRate;
    if(bEnableStun){
       leftLoop = prevStunAnimFrame >= FMax(stunLoopEnd, idleInsertFrame);
       //  If we've already left the loop
       //  or were in the idle => restart animation
       if(leftLoop || bWasIdleStun)
           PlayAnim('KnockDown',, 0.1);
       // Other than that - just register stun
       bIsStunned = true;
    }
    else{
       GetAnimParams(0, seqName, oFrame, oRate);
       if(seqName == 'KnockDown' || seqName == IdleRestAnim)
           SetAnimFrame(1.0);
       bIsStunned = false;
    }
}
// Do the flinch; no check, no conditions, just stun
function DoFlinch(  optional Pawn instigatedBy,
                   optional Vector hitLocation,
                   optional Vector momentum,
                   optional class<NiceWeaponDamageType> damageType,
                   optional float headshotLevel,
                   optional KFPlayerReplicationInfo KFPRI){
    SetAnimAction(HitAnims[Rand(3)]);
    LastPainAnim = Level.TimeSeconds;
    bSTUNNED = true;
    SetTimer(StunTime, false);
    // Reduce this value only if play was the one to make a flinch/stun
    if(StunsRemaining > 0 && KFHumanPawn(InstigatedBy) != none)
       StunsRemaining --;
    PainSoundEffect(instigatedBy, hitLocation, momentum, damageType,
                   headshotLevel, KFPRI);
}
// Do the mini-flinch; no check, no conditions, just stun
function DoMiniFlinch(  optional Pawn instigatedBy,
                       optional Vector hitLocation,
                       optional Vector momentum,
                       optional class<NiceWeaponDamageType> damageType,
                       optional float headshotLevel,
                       optional KFPlayerReplicationInfo KFPRI){
    local Vector X,Y,Z, Dir;
    GetAxes(Rotation, X, Y, Z);
    hitLocation.Z = Location.Z;
    Dir = -Normal(Location - hitLocation);
    if(Dir dot X > 0.7 || VSize(Location - hitLocation) < 1.0)
       SetAnimAction(KFHitFront);
    else if(Dir Dot X < -0.7)
       SetAnimAction(KFHitBack);
    else if(Dir Dot Y > 0)
       SetAnimAction(KFHitRight);
    else
       SetAnimAction(KFHitLeft);
    LastPainAnim = Level.TimeSeconds;
    PainSoundEffect(instigatedBy, hitLocation, momentum, damageType,
                   headshotLevel, KFPRI);
}
// Plays sound effect for flinch and updates last sound time
function PainSoundEffect(   Pawn instigatedBy,
                           Vector hitLocation,
                           Vector momentum,
                           class<NiceWeaponDamageType> damageType,
                           float headshotLevel,
                           KFPlayerReplicationInfo KFPRI){
    local PlayerController Hearer;
    if(damageType.default.bDirectDamage)
       Hearer = PlayerController(instigatedBy.Controller);
    if(Hearer != none)
       Hearer.bAcuteHearing = true;
    if(Level.TimeSeconds - LastPainSound < MinTimeBetweenPainSounds){
       LastPainSound = Level.TimeSeconds;
       if(class<NiceDamTypeFire>(damageType) == none)
           PlaySound(HitSound[0], SLOT_Pain, 1.25,, 400);
    }
    if(Hearer != none)
       Hearer.bAcuteHearing = false;
}
function UpdateLastDamageVars(  Pawn instigatedBy,
                               int damage,
                               class<NiceWeaponDamageType> damageType,
                               Vector hitLocation,
                               Vector momentum){
    lastTookDamageTime  = Level.TimeSeconds;
    lastDamageAmount    = damage;
    lastDamagedBy       = instigatedBy;
    lastDamagedByType   = damageType;
    hitMomentum         = VSize(momentum);
    lasthitLocation     = hitLocation;
    lastMomentum        = momentum;
}
//  Breaks damage into different elemental components and
//  applies damage mods to them
function ExtractElementalDamage(out int regDamage,
                               out int heatDamage,
                               int damage,
                               Pawn instigatedBy,
                               Vector hitLocation,
                               Vector momentum,
                               class<NiceWeaponDamageType> damageType,
                               KFPlayerReplicationInfo KFPRI,
                               float headshotLevel,
                               float lockonTime){
    ModDamage(  damage, instigatedBy, hitLocation, momentum, damageType,
               headshotLevel, KFPRI, lockonTime);
    // Divide damage into different components (so far only regular and fire)
    if(damageType != none){
       RegDamage = damage * (1 - damageType.default.heatPart);
       HeatDamage = damage * damageType.default.heatPart;
    }
    else{
       RegDamage = damage;
       HeatDamage = 0.0;
    }
    // Mod different component's damages
    ModRegularDamage(   RegDamage, instigatedBy, hitLocation, momentum,
                       damageType, headshotLevel, KFPRI, lockonTime);
    ModFireDamage(  HeatDamage, instigatedBy, hitLocation, momentum,
                   damageType, headshotLevel, KFPRI, lockonTime);
}
//  Extracts damage to different body components and applies damage mods to them
function ExtractPartsDamage(out int bodyDamage,
                           out int headDamage,
                           out int painDamage,
                           int damage,
                           Pawn instigatedBy,
                           Vector hitLocation,
                           Vector momentum,
                           class<NiceWeaponDamageType> damageType,
                           KFPlayerReplicationInfo KFPRI,
                           float headshotLevel,
                           float lockonTime){
    bodyDamage = damage;
    headDamage = 0.0;
    //  Mod head health on head-shots only
    if(headshotLevel > 0.0 && HeadHealth > 0){
       headDamage = bodyDamage;
       ModHeadDamage(  headDamage, instigatedBy, hitLocation, momentum,
                       damageType, headshotLevel, KFPRI, lockonTime);
    }
    //  Make sure whole body damage is always at least the
    //  highest single component damage
    bodyDamage = Max(bodyDamage, headDamage);
    //  Always mod body health
    painDamage = ModBodyDamage( bodyDamage, instigatedBy, hitLocation, momentum,
                               damageType, headshotLevel, KFPRI, lockonTime);
    //  Limit pain damage by a head damage
    painDamage = Max(painDamage, headDamage);
}
function AddKillAssistant(Pawn assistant, float damage){
    local KFMonsterController myController;
    myController = KFMonsterController(Controller);
    if(assistant == none || myController == none) return;
    if(!assistant.IsPlayerPawn()) return;
    KFMonsterController(Controller).
       AddKillAssistant(assistant.controller, FMin(health, damage));
}
function DealPartsDamage(   int bodyDamage,
                           int headDamage,
                           Pawn instigatedBy,
                           Vector hitLocation,
                           Vector momentum,
                           class<NiceWeaponDamageType> damageType,
                           KFPlayerReplicationInfo KFPRI,
                           float headshotLevel,
                           float lockonTime){
    if(headDamage > 0 && headshotLevel > 0.0 && !bDecapitated)
       DealHeadDamage(headDamage, instigatedBy, hitLocation, momentum,
           damageType, headshotLevel, KFPRI, lockonTime);
    if(bodyDamage > 0)
       DealBodyDamage(bodyDamage, instigatedBy, hitLocation, momentum,
           damageType, headshotLevel, KFPRI, lockonTime);
}
simulated event SetAnimAction(name NewAction){
    if(bFrozenZed)
       return;
    super.SetAnimAction(NewAction);
}
function Freeze(){
    SetOverlayMaterial(FrozenMaterial, 999, true);
    AnimAction = '';
    bShotAnim = true;
    bWaitForAnim = true;
    StopMovement();
    Disable('AnimEnd');
    StopAnimating();

    if(Controller != none){
       Controller.FocalPoint = Location + 512*vector(Rotation);
       Controller.Enemy = none;
       Controller.Focus = none;
       if(!Controller.IsInState('Freeze'))
           Controller.GoToState('Freeze');
       KFMonsterController(Controller).bUseFreezeHack = true;
    }
    bFrozenZed = true;
    frozenRotation = rotation;
}
function UnFreeze(){
    if(controller == none || Health <= 0) return;
    SetOverlayMaterial(none, 0.1, true);
    bShotAnim = false;
    bWaitForAnim = false;
    Enable('AnimEnd');
    AnimEnd(0);
    AnimEnd(1);
    controller.GotoState('ZombieHunt');
    GroundSpeed = GetOriginalGroundSpeed();
    bFrozenZed = false;
}
function TakeDamageClient( int damage,
                           Pawn instigatedBy,
                           Vector hitLocation,
                           Vector momentum,
                           class<NiceWeaponDamageType> damageType,
                           float headshotLevel,
                           float lockonTime){
    local KFPlayerReplicationInfo KFPRI;
    //  Elemental damage components
    local int   regDamage;
    local int   heatDamage;
    //  Body part damage components
    local int   headDamage;
    local int   bodyDamage;
    local int   painDamage;
    if(instigatedBy != none)
       KFPRI = KFPlayerReplicationInfo(instigatedBy.PlayerReplicationInfo);
    if(headHealth <= 0)
       headshotLevel = 0.0;
    //  Handle special weapon effects
    HandleStoppingPower(damageType, headshotLevel);
    //  Handle elemental damage components
    ExtractElementalDamage(regDamage, heatDamage, damage,
                           instigatedBy, hitLocation, momentum,
                           damageType, KFPRI, headshotLevel, lockonTime);
    FireDamageEffects(HeatDamage, instigatedBy, hitLocation,
                           momentum, damageType, headshotLevel, KFPRI);
    FrostEffects(          instigatedBy, hitLocation, momentum,
                           damageType, headshotLevel, KFPRI);
    //  Handle body parts damage components
    ExtractPartsDamage(    bodyDamage, headDamage, painDamage,
                           RegDamage + HeatDamage, instigatedBy,
                           hitLocation, momentum, damageType, KFPRI,
                           headshotLevel, lockonTime);
    DoRightPainReaction(   painDamage, instigatedBy, hitLocation, momentum,
                           damageType, headshotLevel, KFPRI);
    DealPartsDamage( bodyDamage, headDamage,
                     instigatedBy, hitLocation, momentum, damageType,
                     KFPRI, headshotLevel, lockonTime);
    AddKillAssistant(instigatedBy, bodyDamage);
    //  Rewrite values of last deal damage, instigator, etc.
    UpdateLastDamageVars(   instigatedBy, bodyDamage, damageType,
                           hitLocation, momentum);
    //  Reset flags: NICETODO: remove this fucking bullshit
    //  like why the fuck is it being done HERE? Makes no fucking sense
    bBackstabbed = false;
}
function HandleStoppingPower(
    class<NiceWeaponDamageType> damageType,
    float headshotLevel
) {
    local float strength;
    local float actualMinStoppingThreshold, actualMaxStoppingEffect;

    strength = damageType.default.stoppingPower;
    if (headshotLevel > 0.0) {
        strength *= 2;
    }
    if (concussionCountdown > 0) {
        actualMinStoppingThreshold = FMax(0.0, minStoppingThreshold - 0.2);
        actualMaxStoppingEffect = FMin(0.9, maxStoppingEffect + 0.2);
    }
    else {
        actualMinStoppingThreshold = minStoppingThreshold;
        actualMaxStoppingEffect = maxStoppingEffect;
    }
    strength = FMax(0, strength - actualMinStoppingThreshold);
    stoppingEffect = FMin(actualMaxStoppingEffect, stoppingEffect + strength);
}
function TakeDamage(int damage,
                   Pawn instigatedBy,
                   Vector hitLocation,
                   Vector momentum,
                   class<damageType> damageType,
                   optional int HitIndex){
    local bool                          isHeadDamage;
    local bool                          isInstigatorMad;
    local class<KFWeaponDamageType>     kfDmgType;
    local class<NiceWeaponDamageType>   niceDmgType;
    //  Figure out what damage type to use
    kfDmgType = class<KFWeaponDamageType>(damageType);
    niceDmgType = class<NiceWeaponDamageType>(damageType);
    if(niceDmgType == none){
       if(kfDmgType != none && kfDmgType.default.bDealBurningDamage)
           niceDmgType = class'NiceEnviromentalDamageFire';
       else
           niceDmgType = class'NiceEnviromentalDamage';
    }
    // Increase damage from mad zeds, 'cause they aren't kidding
    if(NiceMonster(instigatedBy) != none)
       isInstigatorMad = NiceMonster(instigatedBy).madnessCountDown > 0.0;
    if(isInstigatorMad)
       damage *= damageToMonsterScale;
    if(class<DamTypeVomit>(damageType) != none)
       BileDamageEffect(damage, instigatedBy, damageType);
    if(kfDmgType != none){
       if(!bDecapitated && kfDmgType.default.bCheckForHeadShots)
           isHeadDamage = IsHeadShot(hitLocation, normal(momentum), 1.0);
    }
    if(isHeadDamage)
       TakeDamageClient(   damage, instigatedBy, hitLocation, momentum,
                           niceDmgType, 1.0, 0.0);
    else
       TakeDamageClient(   damage, instigatedBy, hitLocation, momentum,
                           niceDmgType, 0.0, 0.0);
    if(isInstigatorMad){
       madnessCountDown =
           FMax(   madnessCountDown,
                   class'NiceSkillMedicZEDFrenzy'.default.madnessTime * 0.25);
       if(KFMonsterController(Controller) != none)
           KFMonsterController(Controller).FindNewEnemy();
    }
}
function TakeFireDamage(int damage, Pawn instigator){
    local Vector    DummyHitLoc, DummyMomentum;
    super(Skaarj).TakeDamage(   damage, instigator, dummyHitLoc,
                               dummyMomentum, fireDamageClass);
    lastBurnDamage = damage;
    //  Melt em' :)
    if(FlameFuel <= 0)
       ZombieCrispUp();
}
function TakeFrostDamage(int damage, Pawn instigator){
    local Vector    dummyHitLoc, dummyMomentum;
    if(damage > health)
       damage = health - 1;
    if(damage > 0)
       super(Skaarj).TakeDamage(   damage, instigator, dummyHitLoc,
                                   dummyMomentum, frostDamageClass);
    lastFrostDamage = damage;
}
simulated function ZombieCrispUp(){
    bAshen = true;
    bCrispified = true;
    if(Level.netMode == NM_DedicatedServer) return;
    if(class'GameInfo'.static.UseLowGore()) return;
    Skins[0]=Combiner'PatchTex.Common.BurnSkinEmbers_cmb';
    Skins[1]=Combiner'PatchTex.Common.BurnSkinEmbers_cmb';
    Skins[2]=Combiner'PatchTex.Common.BurnSkinEmbers_cmb';
    Skins[3]=Combiner'PatchTex.Common.BurnSkinEmbers_cmb';
}
simulated function HeatTick(){
    local float iceDamage;
    local float heatRelativeLevel, heatMultiplier;

    heatRelativeLevel = FMin(heat, flameFuel) / initFlameFuel;
    if (heatRelativeLevel < 0.05) {
        heatMultiplier = 1.5;
    } else {
        heatMultiplier = 1.1;
    }
    //  Update heat value
    if(!bOnFire || flameFuel <= 0)
           heat *= heatDissipationRate;
    else {
        if(flameFuel < heat) {
            heat = flameFuel * heatMultiplier + (heat - flameFuel) * heatDissipationRate;
        } else {
            heat = heat * heatMultiplier;
        }
    }
    flameFuel = FMax(0, flameFuel - heat);
    CapHeat();
    if(Abs(heat) < 1)
       heat = 0.0;
    //  Update on-fire status
    if(bOnFire){
        if(heat > 0) {
            TakeFireDamage(heat, burnInstigator);
        }
        else {
            bBurnified = false;
            UnSetBurningBehavior();
            RemoveFlamingEffects();
            StopBurnFX();
            bOnFire = false;
        }
    }
    //  Update frozen status (always deal frost damage)
    iceCrustStrenght = FMax(iceCrustStrenght, heat);
    if(heat >= -10 && health > 1)
       iceCrustStrenght -= 5 * (heat + 10);
    iceCrustStrenght = FMax(0.0, iceCrustStrenght);
    if(bFrozenZed){
       iceDamage = -heat * 0.25;
       if(iceDamage > 10)
           TakeFrostDamage(iceDamage + rand(5), frostInstigator);
       if(iceCrustStrenght <= 0){
           UnFreeze();
           heat = 0;
       }
    }
}
simulated function SetBurningBehavior(){
    bBurningBehavior = true;
    if(default.Health >= 1000) {
        return;
    }
    MovementAnims[0] = BurningWalkFAnims[Rand(3)];
    WalkAnims[0]     = BurningWalkFAnims[Rand(3)];
    MovementAnims[1] = BurningWalkAnims[0];
    WalkAnims[1]     = BurningWalkAnims[0];
    MovementAnims[2] = BurningWalkAnims[1];
    WalkAnims[2]     = BurningWalkAnims[1];
    MovementAnims[3] = BurningWalkAnims[2];
    WalkAnims[3]     = BurningWalkAnims[2];
}
simulated function UnSetBurningBehavior(){
    local int i;
    bBurningBehavior = false;
    if(Role == Role_Authority){
       Intelligence = default.Intelligence;
       if(!bZapped){
           AirSpeed = default.AirSpeed;
           WaterSpeed = default.WaterSpeed;
       }
    }
    if(bCrispified)
       bAshen = True;
    for(i = 0; i < 4; i++){
       MovementAnims[i]  = default.MovementAnims[i];
       WalkAnims[i]      = default.WalkAnims[i];
    }
}
simulated function SetGroundSpeed(float newGroundSpeed) {
    if (initializationComplete) {
        UpdateGroundSpeed();
    } else {
        // Let `KFMonster`'s code setup original speed for this zed.
        // This is a hack to be removed later, when we'll be untangling
        // initialization code
        super.SetGroundSpeed(newGroundSpeed);
    }
}
simulated function UpdateGroundSpeed() {
    if (TryNonRelevantSpeedup()) {
        return;
    }
    groundSpeed = GetOriginalGroundSpeed();
    groundSpeed *= (1.0 - stoppingEffect);
    if (bDecapitated) {
        groundSpeed *= 0.8;
    }
    if (bCrispified) {
        groundSpeed *= 1.25;
    }
}
simulated function ModDamageFromZed(
    out int damage,
    class<DamageType> damageType
) {
    if (bCrispified) {
        damage *= 2;
    }
}
// If this function returns `true`, then we shouldn't touch speed further,
// because a speed hack was used for the zed
simulated function bool TryNonRelevantSpeedup(){
    if(level.netMode == NM_Client || !CanSpeedAdjust()) {
        return false;
    }
    if(level.timeSeconds - lastReplicateTime > 0.5) {
        groundSpeed = default.groundSpeed * (300.0 / default.groundSpeed);
        return true;
    }
    lastSeenOrRelevantTime = level.timeSeconds;
    return false;
}
simulated function ServerDropFaster(NiceHumanPawn nicePawn){
    if(nicePawn == none) return;
    if(Health > 0)
       BleedOutTime = Level.TimeSeconds
         + class'NiceSkillSharpshooterDieAlready'.default.bleedOutTime[nicePawn.calibrationScore - 1];
}
simulated function RemoveHead(){
    local int i;
    local class<KFWeaponDamageType> kfDmgType;
    Intelligence    = BRAINS_Retarded;
    bDecapitated    = true;
    DECAP           = true;
    DecapTime       = Level.TimeSeconds;
    kfDmgType = class<KFWeaponDamageType>(lastDamagedByType);
    if(kfDmgType != none && kfDmgType.default.bIsMeleeDamage)
       bMeleeDecapitated = true;
    SetAnimAction('HitF');
    UpdateGroundSpeed();
    //  No more raspy breathin'...cuz he has no throat or mouth :S
    AmbientSound = MiscSound;
    if(Health > 0)
       BleedOutTime = Level.TimeSeconds + BleedOutDuration;
    if(MeleeAnims[1] == 'Claw3')
       MeleeAnims[1] = 'Claw1';
    if(MeleeAnims[2] == 'Claw3')
       MeleeAnims[2] = 'Claw2';
    //  Plug in headless anims if we have them
    for(i = 0;i < 4;i ++)
       if(HeadlessWalkAnims[i] != '' && HasAnim(HeadlessWalkAnims[i])){
           MovementAnims[i] = HeadlessWalkAnims[i];
           WalkAnims[i]     = HeadlessWalkAnims[i];
       }
    PlaySound(DecapitationSound, SLOT_Misc, 1.30, true, 525);
    if(NiceMonsterController(Controller) != none)
       NiceMonsterController(Controller).FindNewEnemy();
}
function bool FlipOverWithIntsigator(Pawn InstigatedBy){
    return FlipOver();
}
//  Calculates bone which bone we've hit
//  as well as updates hit location and hit normal;
//  extracted from TWI code without much changes
function CalculateHitBone(  out Vector hitLocation,
                           out Vector hitNormal,
                           out name hitBone,
                           Pawn instigatedBy,
                           class<damageType> damageType){
    local Vector    hitRay;
    local Vector    instigatorEyes;
    local float     hitBoneDist;
    //  We have 'hitLocation' that designates where we approximately hit zed
    //  (it's collision cylinder).
    //  We want to know which bone instigator hit,
    //  so here we compute direction in wich bullet flew.
    hitRay = vect(0,0,0);
    if(instigatedBy != none){
       instigatorEyes = instigatedBy.location
           + vect(0,0,1) * instigatedBy.EyeHeight;
       hitRay = hitLocation - instigatorEyes;
       hitRay = Normal(hitLocation);
    }
    //  Now we have a vector that gives us bullet's
    //  trajectory direction ('hitRay')
    //  and a point it passes through ('hitRay'),
    //  so we can use magic funtion to find out which bone that bullet hit
    if(damageType.default.bLocationalHit)
       CalcHitLoc(hitLocation, hitRay, hitBone, hitBoneDist);
    else{
       //  ...or just ignore all that and chose whatever
       hitLocation = location;
       hitBone = fireRootBone;
    }
    //  Now get let's come up with some 'hitNormal'
    if(instigatedBy != none){
       hitNormal =     Normal(instigatedBy.location - hitLocation);
       hitNormal +=    vect(0, 0, 2.8);
       hitNormal +=    VRand() * 0.2;
       hitNormal =     Normal(hitNormal);
    }
    else
       hitNormal = Normal(Vect(0, 0, 1) + VRand() * 0.2 + vect(0, 0, 2.8));
}
function SplatterBlood( Pawn instigatedBy,
                       Vector hitLocation,
                       class<damageType> damageType,
                       Vector momentum){
    local bool      bNotRecentHit;
    local bool      bBloodDisabled;
    local rotator   splatRot;
    //  Is this hit recent?
    //  Even if recent - randomly count ome hits as non-recent
    bNotRecentHit = Level.TimeSeconds - LastPainTime >= 0.2;
    bNotRecentHit = bNotRecentHit || FRand() > 0.8;
    //  Is blood allowed?
    bBloodDisabled = class'GameInfo'.static.NoBlood();
    bBloodDisabled = bBloodDisabled || class'GameInfo'.static.UseLowGore();
    //  Generate some blood
    if(damageType.default.bCausesBlood && !bBloodDisabled && bNotRecentHit){
       //  Get correct-looking rotatin for our blood splat,
       //  if possible for momentum
       if(momentum != vect(0,0,0))
           splatRot = rotator(Normal(momentum));
       else{
           if(instigatedBy != none)
               splatRot = rotator(Normal(Location - instigatedBy.Location));
           else
               splatRot = rotator(Normal(Location - hitLocation));
       }
       Spawn(ProjectileBloodSplatClass, instigatedBy,, hitLocation, splatRot);
    }
}
//  Overloaded to suck less ass,
//  for example containing only visual side of effects.
//  Removed zapped effect, since zeds can't be zapped in NicePack.
simulated function PlayHit( float damage,
                       Pawn instigatedBy,
                       Vector hitLocation,
                       class<damageType> damageType,
                       Vector momentum,
                       optional int HitIdx){
    local Vector    hitNormal;
    local name      hitBone;
    // Call the modified version of the original Pawn playhit
    OldPlayHit(damage, instigatedBy, hitLocation, damageType, momentum);
    if(damage <= 0) return;
    CalculateHitBone(hitLocation, hitNormal, hitBone, instigatedBy, damageType);
    SplatterBlood(instigatedBy, hitLocation, damageType, momentum);
    DoDamageFX(hitBone, damage, damageType, Rotator(hitNormal));
    if(damageType.default.DamageOverlayMaterial != none && damage > 0)
       SetOverlayMaterial( damageType.default.damageOverlayMaterial,
                           damageType.default.damageOverlayTime, false);
}
//  I've gotta come clean - I'm not sure what this one does exactly
function SpawnPVolumeExitActor(){
    local bool  bPVCanExitActor;
    if(PhysicsVolume != none){
       bPVCanExitActor = PhysicsVolume.bDestructive;
       bPVCanExitActor = bPVCanExitActor && PhysicsVolume.bDestructive;
       bPVCanExitActor = bPVCanExitActor && PhysicsVolume.ExitActor != none;
    }
    if(health <= 0 && bPVCanExitActor)
       Spawn(PhysicsVolume.ExitActor);
}
function OldSpawnEffect(Vector hitLocation,
                       Vector hitNormal,
                       Vector momentum,
                       class<Effects> effectClass){
    local Vector bloodOffset;
    if(effectClass == none) return;
    bloodOffset     = 0.2 * collisionRadius * hitNormal;
    bloodOffset.Z   = 0.5 * bloodOffset.Z;
    if(momentum.Z > 0)
       momentum.Z *= 0.5;
    Spawn(effectClass, self,, hitLocation + bloodOffset, rotator(momentum));
}
function OldSpawnEmitter(   Vector hitLocation,
                           Vector hitNormal,
                           Pawn instigatedBy,
                           class<Emitter> emitterClass){
    local Vector emitterOffset;
    local Vector instigatorEyes;
    if(emitterClass == none) return;
    emitterOffset   = hitNormal - hitNormal * CollisionRadius;
    instigatorEyes  = instigatedBy.location
       + vect(0,0,1) * instigatedBy.EyeHeight;
    if(instigatedBy != none)
       hitNormal = Normal(instigatorEyes - hitLocation);
    Spawn(emitterClass,,, hitLocation + emitterOffset, Rotator(hitNormal));
}
// Now only visual part of effects
function OldPlayHit(float damage,
                   Pawn instigatedBy,
                   Vector hitLocation,
                   class<damageType> damageType,
                   Vector momentum,
                   optional int HitIndex){
    local bool              bLowDetails;
    local bool              bShouldPlayEffect;
    local Vector            hitNormal;
    local class<Effects>    desiredEffect;
    local class<Emitter>    desiredEmitter;
    if(damageType == none || damage <= 0) return;
    SpawnPVolumeExitActor();
    //  Comment in 'DamageType' says that 'DamageThreshold' is
    //  how much damage much occur before playing effects.
    bShouldPlayEffect = damage > damageType.default.damageThreshold;
    bShouldPlayEffect = bShouldPlayEffect && EffectIsRelevant(location, true);
    if(!bShouldPlayEffect) return;
    hitNormal = Normal(hitLocation - Location);
    bLowDetails = Level.bDropDetail || Level.detailMode == DM_Low;
    desiredEffect = damageType.static.GetPawnDamageEffect(  hitLocation, damage,
                                                           momentum,
                                                           self, bLowDetails);
    desiredEmitter = damageType.Static.GetPawnDamageEmitter(    hitLocation,
                                                               damage,
                                                               momentum, self,
                                                               bLowDetails);
    OldSpawnEffect(hitLocation, hitNormal, momentum, desiredEffect);
    OldSpawnEmitter(hitLocation, hitNormal, instigatedBy, desiredEmitter);
}
simulated function PlayTakeHit( Vector hitLocation,
                               int damage,
                               class<damageType> damageType){}
function float GetIgnitionPoint(){
    return 10;
}
function float GetFreezingPoint(){
    return 100.0;
}
function float GetIceCrustScale(){
    return 25000 / (default.health * default.health);
}
function float HeatIncScale(){
    return 100.0 / default.health;
}
function CapHeat(){
    heat = FMin(heat, MAX_HEAT);
    heat = FMax(heat, MIN_HEAT);
}
function bool TryMeleeReachTarget(out Vector hitLocation){
    local Actor     hitActor;
    local Vector    hitNormal;
    //  See if a trace would hit a pawn
    //  (have to turn off hit point collision so trace doesn't hit the
    //  HumanPawn's bullet whiz cylinder)
    bBlockHitPointTraces = false;
    hitActor = Trace(   hitLocation, hitNormal, controller.target.location,
                       location + EyePosition(), true);
    bBlockHitPointTraces = true;
    if(Pawn(hitActor) != none) return true;
    //  If the trace wouldn't hit a pawn, do the old thing of just checking if
    //  there is something blocking the trace
    bBlockHitPointTraces = false;
    hitActor = Trace(   hitLocation, hitNormal, controller.target.location,
                       location, false);
    bBlockHitPointTraces = true;
    return (hitActor == none);  //  Nothing in the way means no problems
}
function MeleeGoreDeadPlayer(   KFHumanPawn kfHumanPawn,
                               Vector hitLocation,
                               Vector pushDir){
    local float dummy;
    local name  tearBone;
    if(kfHumanPawn == none || class'GameInfo'.static.UseLowGore()) return;
    Spawn(  class'KFMod.FeedingSpray', self,,
           kfHumanPawn.location, rotator(pushDir));
    kfHumanPawn.SpawnGibs(rotator(pushDir), 1);
    tearBone = kfHumanPawn.GetClosestBone(hitLocation, velocity, dummy);
    kfHumanPawn.HideBone(tearBone);
}
function bool MeleeDamageTarget(int hitDamage, Vector pushDir){
    local bool          bTargetIsDoor;
    local bool          bInMeleeRange, bCanMeleeReach;
    local float         meleeDistance, distanceFromTarget;
    local Vector        hitLocation;
    local KFHumanPawn   kfHumanPawn;
    if(Level.netMode == NM_Client) return false;
    if(controller == none || controller.target == none) return false;
    ModDamageFromZed(hitDamage, niceZombieDamType);
    //  Melee for doors
    kfHumanPawn = KFHumanPawn(controller.target);
    bTargetIsDoor = controller.target.IsA('KFDoorMover');
    if(bTargetIsDoor){
       controller.target.TakeDamage(   hitDamage, self, hitLocation,
                                       pushDir, niceZombieDamType);
       return true;
    }
    //  Check if still in melee range
    meleeDistance = meleeRange * 1.4;
    meleeDistance += controller.target.collisionRadius + collisionRadius;
    distanceFromTarget = VSize(controller.target.location - location);
    bInMeleeRange = distanceFromTarget <= meleeDistance;
    bCanMeleeReach = TryMeleeReachTarget(hitLocation);
    if(!bInMeleeRange || !bCanMeleeReach || bSTUNNED) return false;
    //  Melee for non-human actors
    if(kfHumanPawn == none){
       controller.target.TakeDamage(   hitDamage, self, hitLocation,
                                       pushDir, niceZombieDamType);
       return true;
    }
    //  Melee for human pawns
    kfHumanPawn.TakeDamage( hitDamage, instigator,
                           hitLocation, pushDir, niceZombieDamType);
    if(kfHumanPawn != none && kfHumanPawn.Health <= 0){
       MeleeGoreDeadPlayer(kfHumanPawn, hitLocation, pushDir);
       // Give us some health back
       if(health <= (1.0 - feedThreshold) * healthMax)
           health += feedThreshold * healthMax * health / healthMax;
    }
    return true;
}


state ZombieDying
{
   ignores AnimEnd, Trigger, Bump, HitWall, HeadVolumeChange,
       PhysicsVolumeChange, Falling, BreathTimer, Died, RangedAttack;

   // #1 disable zed collisions on death
   simulated function BeginState()
   {
      super.BeginState();
      DisableCollisions();
   }

    simulated function Landed(vector HitNormal){
       SetCollision(false, false, false);
       if(!bDestroyNextTick)
           Disable('Tick');
    }

    simulated function TakeDamageClient(int damage,
                                       Pawn InstigatedBy,
                                       Vector hitLocation,
                                       Vector momentum,
                                       class<NiceWeaponDamageType> damageType,
                                       optional float headshotLevel,
                                       optional float lockonTime){
       local Vector shotDir;
       local Vector pushLinVel, pushAngVel;
       if(bFrozenBody || bRubbery || damage <= 0) return;

       if(headshotLevel > 0.0)
           RemoveHead();
       PlayHit(damage, InstigatedBy, hitLocation, damageType, momentum);

       //  Can't shoot corpses during de-res
       if(Physics != PHYS_KarmaRagdoll || bDeRes) return;

       //  Throw the body if its a rocket explosion or shock combo
       if(momentum == vect(0,0,0))
           momentum = hitLocation - instigatedBy.Location;
       shotDir = Normal(momentum);
       if(damageType.default.bThrowRagdoll){
           pushLinVel = (RagDeathVel * shotDir) +  vect(0, 0, 250);
           pushAngVel = Normal(shotDir Cross vect(0, 0, 1)) * -18000;
           KSetSkelVel(pushLinVel, pushAngVel);
       }
       else if(damageType.default.bRagdollBullet){
           if(FRand() < 0.65){
               if(velocity.Z <= 0)
                   pushLinVel = vect(0,0,40);
               pushAngVel = Normal(shotDir Cross vect(0, 0, 1)) * (-8000);
               pushAngVel.X *= 0.5;
               pushAngVel.Y *= 0.5;
               pushAngVel.Z *= 4;
               KSetSkelVel(pushLinVel, pushAngVel);
           }
           pushLinVel = RagShootStrength * shotDir;
           KAddImpulse(pushLinVel, hitLocation);
           if((LifeSpan > 0) && (LifeSpan < DeResTime + 2))
               LifeSpan += 0.2;
       }
       else{
           pushLinVel = RagShootStrength * shotDir;
           KAddImpulse(pushLinVel, hitLocation);
       }
    }
}


// #2 disable zed collisions on death
simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
{
   super.PlayDying(DamageType, HitLoc);
   DisableCollisions();
}


// disables all collisions
simulated function DisableCollisions()
{
   bBlockActors = false;
   bBlockPlayers = false;
   bBlockProjectiles = false;
   bProjTarget = false;
   bBlockZeroExtentTraces = false;
   bBlockNonZeroExtentTraces = false;
   bBlockHitPointTraces = false;
}


// Setters for extra collision cylinders
simulated function ToggleAuxCollision(bool newbCollision)
{
   if (MyExtCollision == none)
   {
      log(">> NiceMonster -> ToggleAuxCollision(" $ newbCollision $ ") -> MyExtCollision was none!!!");
      return;
   }

   if (!newbCollision)
   {
      SavedExtCollision = MyExtCollision.bCollideActors;
      MyExtCollision.SetCollision(false);
   }
   else
   {
      MyExtCollision.SetCollision(SavedExtCollision);
   }
}


defaultproperties
{
    stunDurationMultiplier=0.5
    StunThreshold=0.666000
    remainingStuns=-1
    lastStunTime=-1.000000
    headDamageRecoveryRate=100.000000
    headRecoveryTime=1.000000
    bCanBurn=True
    fuelRatio=0.750000
    heatDissipationRate=0.666000
    heatTicksPerSecond=3.000000
    clientHeadshotScale=1.000000
    FrozenMaterial=Texture'HTec_A.Overlay.IceOverlay'
    ShatteredIce=class'NiceIceChunkEmitter'
    niceZombieDamType=class'NiceZedMeleeDamageType'
    ZappedSpeedMod=0.300000
    DamageToMonsterScale=5.000000
    RagdollLifeSpan=120.000000
    ControllerClass=class'NiceMonsterController'
    stoppingEffect=0.0
    stoppingRecoveryRate=0.025
    maxStoppingEffect=0.25
    minStoppingThreshold=0.0
    MIN_HEAT = -150.0
    MAX_HEAT = 200.0
    Begin Object Class=KarmaParamsSkel Name=KarmaParamsSkelN
        KConvulseSpacing=(Max=2.200000)
        KLinearDamping=0.150000
        KAngularDamping=0.050000
        KBuoyancy=1.000000
        KStartEnabled=True
        KVelDropBelowThreshold=50.000000
        bHighDetailOnly=False
        KFriction=1.300000
        KRestitution=0.200000
        KImpactThreshold=85.000000
    End Object
    KParams=KarmaParamsSkelN
}
