class ScrnAchHandler extends ScrnAchHandlerBase;

var bool bOnePlayerPerPerk, bAllSamePerk;
var transient class<ScrnVeterancyTypes> SamePerk;
var transient bool bBossXbowOnly, bBossM99Only, bBossMeleeOnly, bBoss9mmOnly, bBossPrey;
var transient float BossSpawnTime, BossLastDamageTime;
var float InstantKillTime; // instant kill counts, if time between shot is less than this
var bool bPerfectWave, bPerfectGame;
var byte PlayersKilledByJason;

var float LastTeslaChainTime;
var int TeslaChainedPlayers;
var ScrnPlayerInfo TeslaChainedP1, TeslaChainedP2;

var int iDoT_Damage;

var bool bNoHeadshots, bAllCanDoHeadshots;

function PlayerDamaged(int Damage, ScrnPlayerInfo VictimSPI, KFMonster InstigatedBy, class<DamageType> DamType)
{
    bPerfectWave = false;

    if ( InstigatedBy != none && InstigatedBy.IsA('TeslaHusk') && GetItemName(string(DamType)) == "DamTypeTesla" ) {
        if ( LastTeslaChainTime == Level.TimeSeconds) {
            TeslaChainedPlayers++;
            switch ( TeslaChainedPlayers ) {
                case 2:
                    TeslaChainedP2 = VictimSPI; // remember player, but need at least 1 more to get ach
                    break;
                case 3:
                    // give ach to first 3 chained players
                    TeslaChainedP1.ProgressAchievement('TeslaChain', 1);
                    TeslaChainedP2.ProgressAchievement('TeslaChain', 1);
                    VictimSPI.ProgressAchievement('TeslaChain', 1);
                    break;
                default:
                    VictimSPI.ProgressAchievement('TeslaChain', 1);  // previously chained players already have this ach

            }
        }
        else {
            LastTeslaChainTime = Level.TimeSeconds;
            TeslaChainedP1 = VictimSPI;
            TeslaChainedP2 = none;
            TeslaChainedPlayers = 1;
        }
    }
}


function PlayerDied(ScrnPlayerInfo DeadPlayerInfo, Controller Killer, class<DamageType> DamType)
{
    bPerfectGame = false;

    if ( DeadPlayerInfo.Deaths >= GameRules.Mut.KF.FinalWave * 0.8 )
        Ach2All('Kenny', 1);

    if ( Killer != none && Killer.Pawn != none && Killer.Pawn.IsA('ZombieJason') )
        PlayersKilledByJason++;

    if ( DeadPlayerInfo.PlayerOwner.PlayerReplicationInfo.Score >= 1000
            && DeadPlayerInfo.PlayerOwner.PlayerReplicationInfo.Score > AverageScore() * 3 ) {
        Ach2Alive('LuxuryFuneral', 1, DeadPlayerInfo);
        DeadPlayerInfo.PlayerOwner.ReceiveLocalizedMessage(class'ScrnBalanceSrv.ScrnFakedAchMsg', 2);
    }

}

function int AverageScore()
{
    local ScrnPlayerInfo SPI;
    local int TotalScore, PlayerCount;

    for ( SPI=GameRules.PlayerInfo; SPI!=none; SPI=SPI.NextPlayerInfo ) {
        if ( SPI.PlayerOwner != none && SPI.PlayerOwner.PlayerReplicationInfo != none ) {
            PlayerCount++;
            TotalScore += SPI.PlayerOwner.PlayerReplicationInfo.Score;
        }
    }
    if ( PlayerCount == 0 )
        return 0; // tbs, shouldn't happen

    return TotalScore / PlayerCount;
}

function bool HasGoodWeapon(Pawn Pawn)
{
    local Inventory Inv;
    local KFWeapon Weap;

    for ( Inv = Pawn.Inventory; Inv != none; Inv = Inv.Inventory ) {
        Weap = KFWeapon(Inv);
        if ( Weap != none ) {
            if ( Weap.bIsTier3Weapon || Crossbow(Weap) != none || Weap.SellValue >= 450 )
                return true;
        }
    }
    return false;
}



function WaveStarted(byte WaveNum)
{
    local int i;
    local ScrnPlayerInfo SPI;
    local class<ScrnVeterancyTypes> Perk;
    local array< class<ScrnVeterancyTypes> > UsedPerks;
    local int TotalPlayers;

    PlayersKilledByJason = 0;

    for ( SPI=GameRules.PlayerInfo; SPI!=none; SPI=SPI.NextPlayerInfo ) {
        TotalPlayers++;
        if ( SPI.PlayerOwner.PlayerReplicationInfo.Score >= 10000 )
            SPI.ProgressAchievement('Money10k', 1);

        if ( bOnePlayerPerPerk || bAllSamePerk ) {
            if ( KFPlayerReplicationInfo(SPI.PlayerOwner.PlayerReplicationInfo) != none )
                Perk = class<ScrnVeterancyTypes>( KFPlayerReplicationInfo(SPI.PlayerOwner.PlayerReplicationInfo).ClientVeteranSkill );

            if ( Perk == none ) {
                bOnePlayerPerPerk = false; // no perk = no balance
                bAllSamePerk = false;
            }
            else {
                if ( bAllSamePerk ) {
                    if ( SamePerk == none )
                        SamePerk = Perk;
                    else if ( SamePerk.default.SamePerkAch != Perk.default.SamePerkAch )
                        bAllSamePerk = false;
                }
                for ( i = 0; i < UsedPerks.length; ++i ) {
                    if ( UsedPerks[i] == Perk ) {
                        bOnePlayerPerPerk = false;
                        break;
                    }
                }
                if ( bOnePlayerPerPerk )
                    UsedPerks[i] = Perk;
            }
        }
    }

    if ( WaveNum == 0 ) {
        // game started
        bPerfectGame = true;
    }
    else {
        bPerfectGame = bPerfectGame && TotalPlayers >= 2; // each wave must have 2+ players for perfect game
        bPerfectWave = WaveNum > 0; // can't score perfect wave in wave 1
    }
}

function WaveEnded(byte WaveNum)
{
    local ScrnPlayerInfo SPI, OnlyHealerSPI, LastAliveSPI, TopKillsSPI;
    local bool bCheckOnlyHealed;
    local int TotalPlayers, AlivePlayers;
    local int i;
    local bool bAsh;
    local int ChainsawKills, BoomstickKills, M4Kills, MinKills, MaxKills, SecondPlaceKills;

    bCheckOnlyHealed = true;
    MinKills = 0x7FFFFFFF; // max int
    for ( SPI=GameRules.PlayerInfo; SPI!=none; SPI=SPI.NextPlayerInfo ) {
        if ( SPI.PlayerOwner == none )
            continue; // player disconnected

        TotalPlayers++;
        if ( !SPI.bDied ) {
            LastAliveSPI = SPI;
            AlivePlayers++;
            if ( ScrnPlayerController(SPI.PlayerOwner) != none ) {
                if ( ScrnPlayerController(SPI.PlayerOwner).bCowboyForWave )
                    SPI.ProgressAchievement('TrueCowboy', 1);
            }
        }

        if ( bCheckOnlyHealed && SPI.HealedPointsInWave > 0 ) {
            if ( OnlyHealerSPI != none || SPI.HealedPointsInWave < 200 ) {
                OnlyHealerSPI = none;
                bCheckOnlyHealed = false;
            }
            else
                OnlyHealerSPI = SPI;
        }

        if ( SPI.DecapsPerWave >= 30 && SPI.GetAccuracyWave() >= 0.75 )
            SPI.ProgressAchievement('Accuracy', 1);
        if ( WaveNum == 9 && SPI.DecapsPerWave >= 30 && SPI.GetAccuracyWave() >= 0.10 )
            SPI.ProgressAchievement('KFG2', 1);

        if ( SPI.KillsPerWave < MinKills )
            MinKills = SPI.KillsPerWave;
        if ( SPI.KillsPerWave > MaxKills ) {
            TopKillsSPI = SPI;
            SecondPlaceKills = MaxKills;
            MaxKills = SPI.KillsPerWave;
        }
        else if ( SPI.KillsPerWave > SecondPlaceKills )
            SecondPlaceKills = SPI.KillsPerWave;

        bAsh = true;
        ChainsawKills = 0;
        BoomstickKills = 0;
        M4Kills = 0;
        for ( i=0; i<SPI.WeapInfos.length; ++i ) {
            if ( bAsh ) {
                if ( ClassIsChildOf(SPI.WeapInfos[i].DamType, Class'DamTypeDBShotgun') )
                    BoomstickKills += SPI.WeapInfos[i].KillsPerWave;
                else if ( ClassIsChildOf(SPI.WeapInfos[i].DamType, Class'DamTypeChainsaw') )
                    ChainsawKills += SPI.WeapInfos[i].KillsPerWave;
                else if ( SPI.WeapInfos[i].KillsPerWave > 0 )
                    bAsh = false;
            }
            if ( ClassIsChildOf(SPI.WeapInfos[i].DamType, Class'ScrnDamTypeM4AssaultRifle')
                || ClassIsChildOf(SPI.WeapInfos[i].DamType, Class'DamTypeM203Grenade')
                || ClassIsChildOf(SPI.WeapInfos[i].DamType, Class'ScrnDamTypeM4203M') )
            {
                M4Kills += SPI.WeapInfos[i].KillsPerWave;
            }
        }
        if ( bAsh && ChainsawKills >= 10 && BoomstickKills >= 10 && ChainsawKills + BoomstickKills >= 40 )
            SPI.ProgressAchievement('Ash', 1);
        else if ( M4Kills >= 40 )
            SPI.ProgressAchievement('M4203Kill50Zeds', 1);

    }

    if ( OnlyHealerSPI != none && TotalPlayers >= 4 )
        OnlyHealerSPI.ProgressAchievement('OnlyHealer', 1);
    if ( AlivePlayers == 1 && TotalPlayers >= 4) {
        LastAliveSPI.ProgressAchievement('ThinIcePirouette', 1);
        KFSteamStatsAndAchievements(SPI.PlayerOwner.SteamStatsAndAchievements).AddOnlySurvivorOfWave();
    }

    if ( TotalPlayers >= 3 && MinKills > max(1, MaxKills * 0.9) )
        Ach2All('NoI', 1);
    if ( TotalPlayers >= 5 && MaxKills > SecondPlaceKills * 2.5 )
        TopKillsSPI.ProgressAchievement('KillWhore', 1);

    if ( bPerfectWave )
        Ach2Alive('PerfectWave', 1);

    if ( PlayersKilledByJason >= 3 )
        Ach2Alive('Friday13', 1);
}

function GameWon(string MapName)
{
    local ScrnPlayerInfo SPI;
    local ScrnPlayerController ScrnPC;
    local TeamInfo WinnerTeam;
    local int PlayerCount;

    WinnerTeam = TeamInfo(Level.GRI.Winner);
    PlayerCount = GameRules.PlayerCountInWave();

    if ( bOnePlayerPerPerk && PlayerCount >= 6 )
        Ach2All('PerfectBalance', 1);
    if ( bNoHeadshots && PlayerCount >= 2 )
        Ach2All('TW_NoHeadshots', 1, none, WinnerTeam);
    if ( bAllCanDoHeadshots && PlayerCount >= 2 )
        Ach2All('TW_SkullCrackers', 1, none, WinnerTeam);

    if ( !GameRules.Mut.bTSCGame ) {
        // boss-related achievements
        if (GameRules.bSuperPat)
            Ach2All('KillSuperPat', 1);
        if ( bBossPrey && Level.TimeSeconds - BossSpawnTime <= 120 )
            Ach2All('PatPrey', 1);
        if (bBossXbowOnly)
            Ach2All('MerryMen', 1);
        else if (bBossM99Only)
            Ach2All('MerryMen50cal', 1);
        else if (bBossMeleeOnly)
            Ach2All('PatMelee', 1);
        else if (bBoss9mmOnly)
            Ach2All('Pat9mm', 1);

        if ( bAllSamePerk && SamePerk != none && PlayerCount >= 3 )
            Ach2All( SamePerk.default.SamePerkAch, 1, none, WinnerTeam );
    }


    for ( SPI=GameRules.PlayerInfo; SPI!=none; SPI=SPI.NextPlayerInfo ) {
        ScrnPC = ScrnPlayerController(SPI.PlayerOwner);
        if ( ScrnPC != none ) {
            if ( SPI.Deaths == 0 && SPI.StartWave <= 1 && !ScrnPC.bHadArmor )
                SPI.ProgressAchievement('BallsOfSteel', 1);
        }
        if ( SPI.StartWave == 0 && !GameRules.Mut.bTSCGame ) {
            if ( bPerfectGame )
                SPI.ProgressAchievement('PerfectGame', 1);

            if ( GameRules.Mut.KF.ShopList.Length >= 3 && Level.Game.GameReplicationInfo.ElapsedTime <= 2700 ) {
                SPI.ProgressAchievement('SpeedrunBronze', 1);
                if ( Level.Game.GameReplicationInfo.ElapsedTime <= 2400) {
                    SPI.ProgressAchievement('SpeedrunSilver', 1);
                    if ( Level.Game.GameReplicationInfo.ElapsedTime <= 1980) {
                        SPI.ProgressAchievement('SpeedrunGold', 1);
                    }
                }
            }
        }
    }
}

function BossSpawned(KFMonster EndGameBoss)
{
    if ( ZombieBoss(EndGameBoss) != none ) {
        bBossXbowOnly = true;
        bBossM99Only = true;
        bBossMeleeOnly = true;

        bBossPrey = true;
    }
    bBoss9mmOnly = true; // avaliable for other bosses as well, e.g. Doom3 bosses
    BossSpawnTime = Level.TimeSeconds;
}

function int RowHeadhots(ScrnPlayerInfo SPI, int Count)
{
    if ( Count >= 5 && SPI.LastDamage > 40 )
        SPI.ProgressAchievement('Impressive', 1); // 5 and subsequent headshots in a row (5,6,7...)
    return 5;
}

function int WRowHeadhots(ScrnPlayerInfo SPI, KFWeapon Weapon, class<KFWeaponDamageType> DamType, int Count)
{
    if ( ClassIsChildOf(DamType, class'DamTypeM14EBR') ) {
        if ( Count < 25)
            return 25; // need 25 headshots for the achievement
        // if achiemvent is earned - no need to track stat anymore, so return IGNORE_STAT
        SPI.ProgressAchievement('DotOfDoom', 1);
    }
    else if ( ClassIsChildOf(DamType, class'DamTypeSPSniper') ) {
        // Achievement has progress > 1, so need always need to return required value.
        // Function will be called on 15, 16, 17 etc. headshots in a row and there is no way to
        // reset it. But achievement can be earned only every 15 headshots (15, 30, 45 etc).
        if ( Count >= 10 && Count % 10 == 0) {
            if ( !SPI.ProgressAchievement('SteampunkSniper', 1) )
                return IGNORE_STAT; // if achievement already earned, then ProgressAchievement() returns false, meaning no need to track it anymore
        }
        return 10; // need 10 headshots for the achievement
    }
    return IGNORE_STAT;
}

function int WKillsPerShot(ScrnPlayerInfo SPI, KFWeapon Weapon, class<KFWeaponDamageType> DamType, int Count, float DeltaTime)
{
    if ( DamType.default.bIsExplosive ) {
        if ( !ClassIsChildOf(DamType, class'KFMod.DamTypePipeBomb') ) {
            if ( Count >= 10 && Count % 10 == 0) {
                if ( !SPI.ProgressAchievement('RocketBlow', 1) )
                    return IGNORE_STAT; // if achievement already earned, then ProgressAchievement() returns false, meaning no need to track it anymore
            }
            return 10;
        }
    }
    else if ( ClassIsChildOf(DamType, class'KFMod.DamTypeSPThompson')
                || ClassIsChildOf(DamType, class'KFMod.DamTypeThompsonDrum') )
    {
        if ( Count >= 5 ) { // count 5,6,7 etc.
            if ( !SPI.ProgressAchievement('OldGangster', 1) )
                return IGNORE_STAT; // if achievement already earned, then ProgressAchievement() returns false, meaning no need to track it anymore
        }
        return 5; // need 5 kills for the achievement
    }
    else if ( ClassIsChildOf(DamType, class'ScrnBalanceSrv.ScrnDamTypeHuskGun_Alt') ) {
        if ( Count < 20)
            return 20;
        SPI.ProgressAchievement('NapalmStrike', 1);
    }
    else if ( ClassIsChildOf(DamType, class'ScrnBalanceSrv.ScrnDamTypeDualies') ) {
        if ( Count < 8 )
            return 8;
        SPI.ProgressAchievement('MadCowboy', 1);
    }

    return IGNORE_STAT;
}

function int WDecapsPerShot(ScrnPlayerInfo SPI, KFWeapon Weapon, class<KFWeaponDamageType> DamType, int Count, float DeltaTime)
{
    if ( ClassIsChildOf(DamType, class'ScrnBalanceSrv.ScrnDamTypeDualies') ) {
        if ( Count < 8 )
            return 8;
        SPI.ProgressAchievement('MadCowboy', 1);
    }

    return IGNORE_STAT;
}

function int WKillsPerMagazine(ScrnPlayerInfo SPI, KFWeapon Weapon, class<KFWeaponDamageType> DamType, int Count)
{
    if ( ClassIsChildOf(DamType, class'ScrnBalanceSrv.ScrnDamTypeM4203M') ) {
        if ( Count < 15)
            return 15; // need 15 kills for the achievement
        // if achievement is earned - no need to track stat anymore, so return IGNORE_STAT
        SPI.ProgressAchievement('MedicOfDoom', 1);
    }
    else if ( ClassIsChildOf(DamType, class'KFMod.DamTypeDual44Magnum') ) {
        // DamTypeDual44Magnum is a subclass of DamTypeMagnum44Pistol, so it must be checked first
        if ( Count < 18)
            return 18;
        SPI.ProgressAchievement('Magnum12Kills', 1);
    }
    else if ( ClassIsChildOf(DamType, class'KFMod.DamTypeMagnum44Pistol') ) {
        if ( Count < 12)
            return 12;
        SPI.ProgressAchievement('Magnum12Kills', 1);
    }
    else if ( ClassIsChildOf(DamType, class'KFMod.DamTypeDualMK23Pistol') ) {
        // DamTypeDualMK23Pistol is a subclass of DamTypeMK23Pistol, so it must be checked first
        if ( Count < 24)
            return 24;
        SPI.ProgressAchievement('MK23_12Kills', 1);
    }
    else if ( ClassIsChildOf(DamType, class'KFMod.DamTypeMK23Pistol') ) {
        if ( Count < 12)
            return 12;
        SPI.ProgressAchievement('MK23_12Kills', 1);
    }

    return IGNORE_STAT;
}

function int WDecapsPerMagazine(ScrnPlayerInfo SPI, KFWeapon Weapon, class<KFWeaponDamageType> DamType, int Count)
{
    if ( ClassIsChildOf(DamType, class'KFMod.DamTypeDual44Magnum') ) {
        // DamTypeDual44Magnum is a subclass of DamTypeMagnum44Pistol, so it must be checked first
        if ( Count < 18)
            return 18;
        SPI.ProgressAchievement('Magnum12Kills', 1);
    }
    else if ( ClassIsChildOf(DamType, class'KFMod.DamTypeMagnum44Pistol') ) {
        if ( Count < 12)
            return 12;
        SPI.ProgressAchievement('Magnum12Kills', 1);
    }
    else if ( ClassIsChildOf(DamType, class'KFMod.DamTypeDualMK23Pistol') ) {
        // DamTypeDualMK23Pistol is a subclass of DamTypeMK23Pistol, so it must be checked first
        if ( Count < 24)
            return 24;
        SPI.ProgressAchievement('MK23_12Kills', 1);
    }
    else if ( ClassIsChildOf(DamType, class'KFMod.DamTypeMK23Pistol') ) {
        if ( Count < 12)
            return 12;
        SPI.ProgressAchievement('MK23_12Kills', 1);
    }

    return IGNORE_STAT;
}



function MonsterDamaged(int Damage, KFMonster Victim, ScrnPlayerInfo InstigatorInfo,
    class<KFWeaponDamageType> DamType, bool bIsHeadshot, bool bWasDecapitated)
{
    local int index, TotalDamage, df;
    local KFMonsterController MC;

    if ( Victim.Health <= 0 || Victim.IsInState('ZombieDying') )
        return; // don't track damages done to dead bodies

    MC = KFMonsterController(Victim.Controller);
    if ( MC == none )
        return; // wtf?

    bNoHeadshots = bNoHeadshots && !bIsHeadshot;
    bAllCanDoHeadshots = bAllCanDoHeadshots && DamType.default.bCheckForHeadShots;

    index = GameRules.GetMonsterIndex(Victim);

    if ( Damage >= iDoT_Damage && ClassIsChildOf(DamType, class'DamTypeFlareRevolver') ) {
        InstigatorInfo.ProgressAchievement('iDoT', 1);
    }
    else if ( DamType.default.bIsMeleeDamage ) {
        if ( Victim.bBackstabbed )
            InstigatorInfo.ProgressAchievement('MeleeHitBehind', 1);
    }
    else if ( bIsHeadshot && ClassIsChildOf(DamType, class'KFMod.DamTypeDeagle') )
        InstigatorInfo.SetCustomFloat(self, 'LastDeagleHSTime', Level.TimeSeconds);



    if ( ZombieCrawler(Victim) != none ) {
        if ( !Victim.bDamagedAPlayer && DamType.default.bIsMeleeDamage && !ClassIsChildOf(DamType, class'DamTypeCrossbuzzsaw') ) {
            if ( Damage >= Victim.Health )
                InstigatorInfo.ProgressAchievement('MeleeKillCrawlers', 1);
            if ( Victim.Physics == PHYS_Falling ) {
                if ( Damage >= Victim.Health && ClassIsChildOf(DamType, class'KFMod.DamTypeMachete') )
                    InstigatorInfo.ProgressAchievement('MacheteKillMidairCrawler', 1);
                InstigatorInfo.ProgressAchievement('MeleeKillMidairCrawlers', 1);
            }
        }
    }
    else if ( Victim.IsA('ZombieShiver') ) {
        // TeamWork: TeamWork: Grilled Shiver Brains
        // KillAss1 must be set Shiver on fire
        // KillAss2 must decapitate Shiver with commando weapon
        if ( !Victim.bDecapitated && DamType.default.bDealBurningDamage ) {
            if ( GameRules.MonsterInfos[index].DamType1 == none ) {
                GameRules.MonsterInfos[index].KillAss1 = InstigatorInfo;
                GameRules.MonsterInfos[index].DamType1 = DamType;
                GameRules.MonsterInfos[index].DamTime1 = Level.TimeSeconds;
            }
        }
        else if ( Victim.bDecapitated && !bWasDecapitated ) {
            if ( GameRules.MonsterInfos[index].KillAss1 != none && IsAssaultRifleDamage(InstigatorInfo, DamType) ) {
                GameRules.MonsterInfos[index].KillAss2 = InstigatorInfo;
                GameRules.MonsterInfos[index].DamType2 = DamType;
                GameRules.MonsterInfos[index].DamTime2 = Level.TimeSeconds;
                GameRules.MonsterInfos[index].DamageFlags2 = GameRules.DF_DECAP;
            }
            else
                GameRules.MonsterInfos[index].TW_Ach_Failed = true;
        }
    }
    else if ( ZombieStalker(Victim) != none ) {
        if ( Victim.IsA('ZombieGhost') ) {
            if ( bIsHeadshot && Victim.bDecapitated && !bWasDecapitated ) {
                if ( VSizeSquared(Victim.Location - InstigatorInfo.PlayerOwner.Pawn.Location) > 1000000 )
                    InstigatorInfo.ProgressAchievement('GhostSmell', 1);
            }
        }
    }
    else if ( ZombieBloat(Victim) != none ) {
        if ( Victim.bDecapitated && !bWasDecapitated
                && DamType.default.bIsMeleeDamage && !ClassIsChildOf(DamType, class'DamTypeCrossbuzzsaw')
                && !Victim.bDamagedAPlayer && KFPawn(InstigatorInfo.PlayerOwner.Pawn).BileCount <= 0 )
        {
            InstigatorInfo.ProgressAchievement('MeleeDecapBloats', 1);
        }
    }
    else if ( ZombieSiren(Victim) != none ) {
        // TeamWork: No Big Guns on Skinny Bitches
        // Any damage made with pistols or assault rifles counts as assistance.
        // Other damages break the achievement (if deal significant damage).
        if ( !GameRules.MonsterInfos[index].TW_Ach_Failed ) {
            if ( IsPistolDamage(InstigatorInfo, DamType) || IsAssaultRifleDamage(InstigatorInfo, DamType) ) {
                if ( GameRules.MonsterInfos[index].DamType1 == none ) {
                    GameRules.MonsterInfos[index].KillAss1 = InstigatorInfo;
                    GameRules.MonsterInfos[index].DamType1 = DamType;
                    GameRules.MonsterInfos[index].DamTime1 = Level.TimeSeconds;
                }
                else if ( GameRules.MonsterInfos[index].KillAss1 != InstigatorInfo && GameRules.MonsterInfos[index].DamType2 == none ) {
                    GameRules.MonsterInfos[index].KillAss2 = InstigatorInfo;
                    GameRules.MonsterInfos[index].DamType2 = DamType;
                    GameRules.MonsterInfos[index].DamTime2 = Level.TimeSeconds;
                }
            }
            else if ( Damage > 50 || GetTotalDamageMadeC(MC, InstigatorInfo.PlayerOwner) > 100 )
                GameRules.MonsterInfos[index].TW_Ach_Failed = true;
        }
    }
    else if ( ZombieHusk(Victim) != none ) {
        if (  Victim.bDecapitated && !bWasDecapitated && Damage < Victim.Health && Victim.IsA('TeslaHusk') ) {
            GameRules.MonsterInfos[index].KillAss1 = InstigatorInfo;
            GameRules.MonsterInfos[index].DamType1 = DamType;
            GameRules.MonsterInfos[index].DamTime1 = Level.TimeSeconds;
            GameRules.MonsterInfos[index].DamageFlags1 = DF_DECAP;
        }
        else if ( !Victim.bDecapitated && Damage < Victim.Health
                && (Damage > Victim.default.Health/1.5
                    || (Damage > 200 && !Victim.IsA('TeslaHusk')
                        && ( ClassIsChildOf(DamType, class'DamTypeCrossbow')
                            || ClassIsChildOf(DamType, class'DamTypeWinchester')
                            || ClassIsChildOf(DamType, class'DamTypeM14EBR')))) )
        {
            // track only stun damage
            if ( GameRules.MonsterInfos[index].DamType1 == none ) {
                GameRules.MonsterInfos[index].KillAss1 = InstigatorInfo;
                GameRules.MonsterInfos[index].DamType1 = DamType;
                GameRules.MonsterInfos[index].DamTime1 = Level.TimeSeconds;
                GameRules.MonsterInfos[index].DamageFlags1 = DF_STUNNED;
            }
        }
    }
    else if ( ZombieScrake(Victim) != none || Victim.IsA('ZombieJason') ) {
        if ( !Victim.bDecapitated && Damage < Victim.Health && !GameRules.MonsterInfos[index].TW_Ach_Failed ) {
            if ( ClassIsChildOf(GameRules.MonsterInfos[index].DamType1, class'KFMod.DamTypeLAW') ) {
                if ( (ClassIsChildOf(DamType, class'ScrnDamTypeHeavyBase')
                            || DamType == class'KFMod.DamTypeDBShotgun'
                            || DamType == class'KFMod.DamTypeBenelli')
                        && !ClassIsChildOf(GameRules.MonsterInfos[index].DamType2, class'ScrnDamTypeHeavyBase')
                        && GameRules.MonsterInfos[index].DamType2 != class'KFMod.DamTypeDBShotgun'
                        && GameRules.MonsterInfos[index].DamType2 != class'KFMod.DamTypeBenelli' ) {
                    GameRules.MonsterInfos[index].KillAss2 = InstigatorInfo;
                    GameRules.MonsterInfos[index].DamType2 = DamType;
                    GameRules.MonsterInfos[index].DamTime2 = Level.TimeSeconds;
                    GameRules.MonsterInfos[index].DamageFlags2 = 0;
                }
            }
            else if ( Damage >= Victim.default.Health/1.5 ) {
                if ( DamType.default.bIsMeleeDamage && ClassIsChildOf(DamType, class'KFMod.DamTypeMachete') )
                    InstigatorInfo.ProgressAchievement('MacheteStunSC', 1);

                // STUN DAMAGE
                if ( GameRules.MonsterInfos[index].DamType1 == none ) {
                    GameRules.MonsterInfos[index].KillAss1 = InstigatorInfo;
                    GameRules.MonsterInfos[index].DamType1 = DamType;
                    GameRules.MonsterInfos[index].DamTime1 = Level.TimeSeconds;
                    GameRules.MonsterInfos[index].DamageFlags1 = DF_STUNNED;
                    if ( bIsHeadshot )
                        GameRules.MonsterInfos[index].DamageFlags1 = GameRules.MonsterInfos[index].DamageFlags1 | DF_HEADSHOT;
                }
                else if ( GameRules.MonsterInfos[index].DamType1.default.bSniperWeapon ) {
                    // Instant Kill can be achieved by 3 snipers, if they all had shot in the same time
                    if ( DamType.default.bSniperWeapon && bIsHeadshot && Level.TimeSeconds < GameRules.MonsterInfos[index].FirstHitTime + InstantKillTime ) {
                        GameRules.MonsterInfos[index].KillAss2 = InstigatorInfo;
                        GameRules.MonsterInfos[index].DamType2 = DamType;
                        GameRules.MonsterInfos[index].DamageFlags2 = DF_STUNNED | DF_HEADSHOT;
                    }
                    else
                        GameRules.MonsterInfos[index].TW_Ach_Failed = true;
                }
            }
        }
    }
    else if ( ZombieFleshpound(Victim) != none ) {
        if ( Victim.bDecapitated && !bWasDecapitated && ClassIsChildOf(DamType, class'DamTypeAxe') )
            InstigatorInfo.ProgressAchievement('OldSchoolKiting', 1);
        // both TW FP achievements requires first rage shot to be from heavy sniper rifgle: Xbow, M99, HR or 2xSVD
        // Pipe achievement also requires it to be a rage shot.
        // M14 ach allows to shoot already raged FP.
        if ( !Victim.bDecapitated && Damage < Victim.Health && !GameRules.MonsterInfos[index].TW_Ach_Failed ) {
            if ( bIsHeadshot && Damage >= 600 ) {
                df = DF_HEADSHOT;
                // FP can't be stunned, but DF_STUNNED flag is used to differ heavy headshots from other damages
                if ( Damage >= 800 )
                    df = df | DF_STUNNED;

                if ( GameRules.MonsterInfos[index].DamType1 == none ) {
                    GameRules.MonsterInfos[index].KillAss1 = InstigatorInfo;
                    GameRules.MonsterInfos[index].DamType1 = DamType;
                    GameRules.MonsterInfos[index].DamTime1 = Level.TimeSeconds;
                    if ( !ZombieFleshpound(Victim).bChargingPlayer && !ZombieFleshpound(Victim).bFrustrated )
                        df = df | DF_RAGED;
                    GameRules.MonsterInfos[index].DamageFlags1 = df;
                }
                else if ( GameRules.MonsterInfos[index].KillAss1 == InstigatorInfo ) {
                    GameRules.MonsterInfos[index].DamageFlags1 = GameRules.MonsterInfos[index].DamageFlags1 | DF_STUNNED; // in case of 2xSVD
                }
                else if ( GameRules.MonsterInfos[index].KillAss2 == InstigatorInfo ) {
                    GameRules.MonsterInfos[index].DamageFlags2 = GameRules.MonsterInfos[index].DamageFlags2 | DF_STUNNED; // in case of 2xSVD
                }
                else if ( (GameRules.MonsterInfos[index].DamageFlags2 & DF_STUNNED) == 0 ) {
                    // heavy headshots always override other assistances (which don't have DF_STUNNED flag)
                    GameRules.MonsterInfos[index].KillAss2 = InstigatorInfo;
                    GameRules.MonsterInfos[index].DamType2 = DamType;
                    GameRules.MonsterInfos[index].DamTime2 = Level.TimeSeconds;
                    GameRules.MonsterInfos[index].DamageFlags2 = df;
                }
            }
            else if ( GameRules.MonsterInfos[index].DamType1 == none ) {
                // raging FP with other weapons fails TW achievements
                if ( ZombieFleshpound(Victim).bChargingPlayer || ZombieFleshpound(Victim).bFrustrated
                        || Damage + ZombieFleshpound(Victim).TwoSecondDamageTotal > ZombieFleshpound(Victim).RageDamageThreshold )
                    GameRules.MonsterInfos[index].TW_Ach_Failed = true;
            }
            else if ( DamType.Default.bSniperWeapon ) {
                if ( bIsHeadshot && GameRules.MonsterInfos[index].DamType2 == none && Damage >= 200 ) {
                    GameRules.MonsterInfos[index].KillAss2 = InstigatorInfo;
                    GameRules.MonsterInfos[index].DamType2 = DamType;
                    GameRules.MonsterInfos[index].DamTime2 = Level.TimeSeconds;
                    GameRules.MonsterInfos[index].DamageFlags2 = DF_HEADSHOT;
                }
            }
            else {
                TotalDamage = GetTotalDamageMadeC(MC, InstigatorInfo.PlayerOwner);
                if ( TotalDamage > Victim.HealthMax * 0.5 )
                    GameRules.MonsterInfos[index].TW_Ach_Failed = true; // why need teamwork, if this player just spams at FP?
                else if ( GameRules.MonsterInfos[index].DamType2 == none && TotalDamage > Victim.HealthMax * 0.1 ) {
                    GameRules.MonsterInfos[index].KillAss2 = InstigatorInfo;
                    GameRules.MonsterInfos[index].DamType2 = DamType;
                    GameRules.MonsterInfos[index].DamTime2 = Level.TimeSeconds;
                    GameRules.MonsterInfos[index].DamageFlags2 = 0;
                }
            }
        }
    }
    else if ( GameRules.BossClass == Victim.class ) {
        bBossM99Only = bBossM99Only && ClassIsChildOf(DamType, class'DamTypeM99SniperRifle');
        bBossXbowOnly = bBossXbowOnly && ClassIsChildOf(DamType, class'DamTypeCrossbow');
        bBossMeleeOnly = bBossMeleeOnly && DamType.default.bIsMeleeDamage && !ClassIsChildOf(DamType, class'DamTypeCrossbuzzsaw');
        bBoss9mmOnly = bBoss9mmOnly && ClassIsChildOf(DamType, class'DamTypeDualies');
        BossLastDamageTime = Level.TimeSeconds;
    }

    //
    if ( bBossPrey && GameRules.BossClass != Victim.class ) {
        // to get "Hunting The Prey" achievements players must follow Pat all the time without
        // focusing on other zeds. However, players are allowed to damage/kill other zeds too, if
        // players are attacking the Pat at that moment. If Pat ran away, but players are killing
        // other zeds, then ach is failed
        if ( Level.TimeSeconds > BossLastDamageTime + 5.0 )
            bBossPrey = false;
    }

}

function MonsterKilled(KFMonster Victim, ScrnPlayerInfo KillerInfo, class<KFWeaponDamageType> DamType)
{
    local int index;
    local ScrnHumanPawn KillerInfoPawn;
    local KFPlayerReplicationInfo KFPRI;
    local int PipeBombsAround;
    local int w_idx; // current KillerInfo.WeaponInfos index
    local KFMonsterController MC;
    local KFWeapon Weapon;
    local ScrnPlayerInfo AssSPI; // Ass for Assistant ;)


    KillerInfoPawn = ScrnHumanPawn(KillerInfo.PlayerOwner.Pawn);
    KFPRI = KFPlayerReplicationInfo(KillerInfo.PlayerOwner.PlayerReplicationInfo);
    Weapon = KillerInfo.FindWeaponByDamType(DamType);
    w_idx = KillerInfo.FindWeaponInfoByDamType(DamType);
    index = GameRules.GetMonsterIndex(Victim);
    MC = KFMonsterController(Victim.Controller);
    if ( MC == none )
        return; // wtf?


    KillerInfo.ProgressAchievement('Kill1000Zeds', 1); //kill counter, incs all 3 achievements

    if ( Level.TimeSeconds < KillerInfo.LastKillTime + 5.0 ) {
        if ( !DamType.default.bIsMeleeDamage && !DamType.default.bIsExplosive && !DamType.default.bDealBurningDamage
                && KillerInfo.IncCustomValue(self, 'KillsBetween5s', 1) == 30 )
            KillerInfo.ProgressAchievement('OutOfTheGum', 1);
    }
    else
        KillerInfo.SetCustomValue(self, 'KillsBetween5s', 0);

    if ( GameRules.MonsterInfos[index].PlayerKillCounter > 0 && Level.TimeSeconds < GameRules.MonsterInfos[index].PlayerKillTime + 5 )
        KillerInfo.ProgressAchievement('FastVengeance', 1);

    // big zeds
    if ( Victim.default.Health >= 1000 ) {
        if ( ClassIsChildOf(DamType, class'KFMod.DamTypeLAW') )
            KillerInfo.ProgressAchievement('BringingLAW', 1);
        else if ( ClassIsChildOf(DamType, class'KFMod.DamTypePipeBomb') ) {
            // check if somebody is blocking it
            if ( Victim.LastDamageAmount >= 1300 && GameRules.MonsterInfos[index].PlayerKillCounter == 0
                    && GameRules.MonsterInfos[index].DamageCounter < 30 * Victim.DifficultyDamageModifer()
                    && GiveAchToBlockingPlayers(Victim, MC, 'TW_PipeBlock') > 0 )
                KillerInfo.ProgressAchievement('TW_PipeBlock', 1);
        }
        if ( !Victim.bDamagedAPlayer && GameRules.MonsterInfos[index].Headshots == 0 )
            KillerInfo.ProgressAchievement('NoHeadshots', 1);
    }

    if ( DamType.default.bIsMeleeDamage ) {
        if ( Victim.bCrispified )
            KillerInfo.ProgressAchievement('CarveRoast', 1);
        if ( ClassIsChildOf(DamType, class'KFMod.DamTypeScythe')
                || ClassIsChildOf(DamType, class'ScrnBalanceSrv.ScrnDamTypeScythe') )
            KillerInfo.ProgressAchievement('GrimReaper', 1);
    }
    else if ( ClassIsChildOf(DamType, class'KFMod.DamTypeRocketImpact') ) {
        KillerInfo.ProgressAchievement('PrematureDetonation', 1);
    }

    if ( DamType.name == 'DamTypeEMP' ) {
        KillerInfo.ProgressAchievement('TeslaBomber', 1);
    }

    if ( KillerInfoPawn != none) {
        // don't give Combat Medic, if healed person is dead, healed himself, or was healed by another person later
        if ( KillerInfoPawn.IsMedic() && KillerInfoPawn.LastHealed != none && KillerInfoPawn.LastHealed !=KillerInfoPawn
                && KillerInfoPawn.LastHealed.Health > 0
                && KillerInfoPawn.LastHealed.LastHealedBy == KillerInfoPawn ) {
            // medic has 5 seconds to kill a target
            if ( (KillerInfoPawn.LastHealed.CombatMedicTarget == Victim || KillerInfoPawn.LastHealed.LastDamagedBy == Victim)
                    && level.TimeSeconds - KillerInfoPawn.LastHealed.LastHealTime < 5.0 )
                KillerInfo.ProgressAchievement('CombatMedic', 1);
        }
    }

    if ( w_idx != -1 ) {
        if ( KillerInfo.WeapInfos[w_idx].PrevOwner != none && KillerInfo.WeapInfos[w_idx].bPrevOwnerDead
                && KillerInfo.WeapInfos[w_idx].PickupWave == GameRules.Mut.KF.WaveNum )
        {
            AssSPI = GameRules.GetPlayerInfo(KillerInfo.WeapInfos[w_idx].PrevOwner);
            if ( AssSPI != none ) {
                KillerInfo.ProgressAchievement('Cookies', 1);
                if ( AssSPI.LastKilledBy == MC )
                    KillerInfo.ProgressAchievement('EyeForAnEye', 1);
            }
        }
    }

    if ( ZombieClot(Victim) == none )
        KillerInfo.SetCustomValue(self, 'RowClotKills', 0, false);

    if ( ZombieClot(Victim) != none ) {
        if ( KillerInfo.IncCustomValue(self, 'RowClotKills', 1, false) == 15 ) {
            KillerInfo.ProgressAchievement('ClotHater', 1);
            KillerInfo.SetCustomValue(self, 'RowClotKills', 0, false);
        }

        if ( Level.TimeSeconds < KillerInfo.GetCustomFloat(self, 'LastClotKillTime') + 4.0 ) {
            if ( KillerInfo.IncCustomValue(self, 'ClotKills4s', 1, true) == 25 )
                KillerInfo.ProgressAchievement('Plus25Clots', 1);
        }
        else
            KillerInfo.SetCustomValue(self, 'ClotKills4s', 0, true);
        KillerInfo.SetCustomFloat(self, 'LastClotKillTime', Level.TimeSeconds, true);
    }
    else if ( ZombieCrawler(Victim) != none ) {
        if ( Victim.LastDamageAmount < 80 && (DamType == class'DamTypeFlamethrower' || DamType == class'DamTypeBurned') )
            KillerInfo.ProgressAchievement('HFC', 1);
        else if ( IsPistolDamage(KillerInfo, DamType) ) {
            if ( GameRules.MonsterInfos[index].Headshots + GameRules.MonsterInfos[index].Bodyshots == 1 )
                KillerInfo.ProgressAchievement('Gunslingophobia', 1);
        }
        else if ( GameRules.MonsterInfos[index].bHeadshot ) {
            // Overkills
            if (  ClassIsChildOf(DamType, class'KFMod.DamTypeM99HeadShot') )
                KillerInfo.ProgressAchievement('Overkill', 1);
            else if ( Victim.LastDamageAmount > 6950 && ClassIsChildOf(DamType, class'KFMod.DamTypeHuskGunProjectileImpact') )
                KillerInfo.ProgressAchievement('Overkill1', 1);
            else if ( ClassIsChildOf(DamType, class'KFMod.DamTypeLawRocketImpact') )
                KillerInfo.ProgressAchievement('Overkill2', 1);
        }
    }
    else if ( Victim.IsA('ZombieShiver') ) {
        // Firebug (KillAss1) ignites Shiver and makes him unable to teleport
        // Then Commando (KillAss2) - decapitates
        // KillerInfo doesn't get teamwork achievement (the one who killed decapitated body)
        if ( Victim.bDecapitated && !GameRules.MonsterInfos[index].TW_Ach_Failed ) {
            if ( GameRules.MonsterInfos[index].KillAss2 != none )
                GameRules.RewardTeamwork(none, index, 'TW_Shiver'); //KillerInfo took down already decapitated body, so no TW ach for him
            else if ( DamType.default.bCheckForHeadShots && IsAssaultRifleDamage(KillerInfo, DamType) )
                GameRules.RewardTeamwork(KillerInfo, index, 'TW_Shiver'); // Decapitation shot killed the Victim
        }
    }
    else if ( ZombieStalker(Victim) != none ) {
        if ( !Victim.bDamagedAPlayer && KFPRI != none && KFPRI.ClientVeteranSkill.static.ShowStalkers(KFPRI)
                && VSizeSquared(Victim.Location - KillerInfo.PlayerOwner.Pawn.Location)
                    < 360000 * KFPRI.ClientVeteranSkill.static.GetStalkerViewDistanceMulti(KFPRI) )
            KillerInfo.ProgressAchievement('Ghostbuster', 1);
    }
    else if ( ZombieBloat(Victim) != none ) {
        PipeBombsAround = InPipeBombRange(Victim, 600);
        if ( PipeBombsAround > 0 )
            KillerInfo.ProgressAchievement('SavingResources', 1);
    }
    else if ( ZombieSiren(Victim) != none ) {
        PipeBombsAround = InPipeBombRange(Victim, 250);
        if ( PipeBombsAround > 0 )
            KillerInfo.ProgressAchievement('SavingResources', 1);
        if ( !GameRules.MonsterInfos[index].TW_Ach_Failed )
            GameRules.RewardTeamwork(KillerInfo, index, 'TW_Siren');
    }
    else if ( ZombieHusk(Victim) != none ) {
        if ( ClassIsChildOf(DamType, class'DamTypeHuskGun') || ClassIsChildOf(DamType, class'DamTypeHuskGunProjectileImpact') )
            KillerInfo.ProgressAchievement('KillHuskHuskGun', 1);
        else if ( ClassIsChildOf(DamType, class'DamTypeCrossbowHeadShot') ) {
            if ( Level.TimeSeconds - KillerInfo.GetCustomFloat(self, 'LastDeagleHSTime') < 2.0 )
                KillerInfo.ProgressAchievement('KFG1', 1);
        }

        if ( !Victim.bDamagedAPlayer ) {
            // give Fast Shot achievement only if player killed Husk alone
            if ( GameRules.MonsterInfos[index].KillAss1 == KillerInfo )
                KillerInfo.ProgressAchievement('FastShot', 1);
            else if ( DamType.default.bIsPowerWeapon && GameRules.MonsterInfos[index].KillAss1 != none
                        && (GameRules.MonsterInfos[index].DamageFlags1 & DF_STUNNED) > 0 )
            {
                // finishing with shotgun after the stun
                GameRules.RewardTeamwork(KillerInfo, index, 'TW_Husk_Stun');
            }
        }
        else if ( KillerInfoPawn.LastDamagedBy == Victim && (DamType.default.bIsPowerWeapon || DamType.default.bIsMeleeDamage)
                  && Victim.IsA('TeslaHusk') )
        {
            KillerInfo.ProgressAchievement('NikolaTesla', 1);
        }
    }
    else if ( ZombieScrake(Victim) != none || Victim.IsA('ZombieJason') ) {
        if ( Victim.IsA('ZombieJason') && ClassIsChildOf(DamType, class'DamTypeMachete') )
            KillerInfo.ProgressAchievement('ComeatMe', 1);
        //scrake unnader
        if ( !Victim.bDamagedAPlayer && GameRules.MonsterInfos[index].DamType2 == class'KFMod.DamTypeFrag'
                && GameRules.MonsterInfos[index].DamageFlags2 == (DF_RAGED | DF_STUPID) )
            KillerInfo.ProgressAchievement('ScrakeUnnader', 1);

        if ( DamType.default.bSniperWeapon || ClassIsChildOf(DamType, class'KFMod.DamTypeCrossbuzzsawHeadShot') ) {
            KillerInfo.ProgressAchievement('Snipe250SC', 1);
            // Teamwork: InstantKill
            if ( GameRules.MonsterInfos[index].KillAss1 != KillerInfo
                    && GameRules.MonsterInfos[index].DamType1 != none && GameRules.MonsterInfos[index].DamType1.default.bSniperWeapon
                    && (GameRules.MonsterInfos[index].DamageFlags1 & DF_STUNNED) > 0
                    && Level.TimeSeconds - GameRules.MonsterInfos[index].FirstHitTime < InstantKillTime ) {
                GameRules.RewardTeamwork(KillerInfo, index, 'TW_SC_Instant');
            }
        }
        else if ( DamType.default.bIsPowerWeapon ) {
            if ( (ClassIsChildOf(DamType, class'ScrnDamTypeHeavyBase')
                        || DamType == class'KFMod.DamTypeDBShotgun'
                        || DamType == class'KFMod.DamTypeBenelli')
                    && GameRules.MonsterInfos[index].DamType1 != none && GameRules.MonsterInfos[index].DamType1.default.bIsExplosive
                    && ClassIsChildOf(GameRules.MonsterInfos[index].DamType1, class'KFMod.DamTypeLAW')
                    && (GameRules.MonsterInfos[index].DamageFlags1 & DF_STUNNED) > 0 )
            {
                // TeamWork: LAW Stun + HSg finish
                GameRules.RewardTeamwork(KillerInfo, index, 'TW_SC_LAWHSG');
            }
        }
        else if ( DamType.default.bIsMeleeDamage ) {
            if ( GameRules.MonsterInfos[index].BodyShots == 0 && !ClassIsChildOf(DamType, class'KFMod.DamTypeCrossbuzzsaw') )
                KillerInfo.ProgressAchievement('MeleeGod', 1);
            if ( ClassIsChildOf(DamType, class'KFMod.DamTypeChainsaw') )
                KillerInfo.ProgressAchievement('BitterIrony', 1);
            if ( !Victim.bDamagedAPlayer && GameRules.MonsterInfos[index].bWasBackstabbed )
                CheckAchBackstabAttract(Victim, KillerInfo);
        }
        else if ( ClassIsChildOf(DamType, class'KFMod.DamTypeHuskGunProjectileImpact')
                || ClassIsChildOf(DamType, class'KFMod.DamTypeHuskGun') )
            KillerInfo.ProgressAchievement('HuskGunSC', 1);
        else if ( !Victim.bDamagedAPlayer && (ClassIsChildOf(DamType, class'ScrnDamTypeDualDeagle')
                || ClassIsChildOf(DamType, class'ScrnDamTypeDual44Magnum')) )
           KillerInfo.ProgressAchievement('GunslingerSC', 1);
    }
    else if ( ZombieFleshpound(Victim) != none ) {
        if ( DamType.default.bIsExplosive )
            KillerInfo.ProgressAchievement('Kill100FPExplosives', 1);
        else if ( DamType.default.bIsPowerWeapon) {
            if ( DamType == class'KFMod.DamTypeDBShotgun' || DamType == class'KFMod.DamTypeBenelli' )
                KillerInfo.ProgressAchievement('GetOffMyLawn', 1);
        }
        else if ( DamType.default.bIsMeleeDamage) {
            // if Victim is already decapitated, then OldSchoolKiting  is granted on decapitation
            if ( !Victim.bDecapitated && ClassIsChildOf(DamType, class'DamTypeAxe') )
                KillerInfo.ProgressAchievement('OldSchoolKiting', 1);
        }

        if ( (MC.KillAssistants.Length == 0 ||
                    (MC.KillAssistants.Length == 1 && MC.KillAssistants[0].PC == KillerInfo.PlayerOwner))
                && GameRules.AlivePlayerCount() >= 6 )
        {
            KillerInfo.ProgressAchievement('Unassisted', 1);
        }
        else {
            if ( GameRules.MonsterInfos[index].DamType1 != none ) {
                if ( DamType == class'KFMod.DamTypePipeBomb'
                        && (GameRules.MonsterInfos[index].DamageFlags1 & DF_RAGED) != 0 )
                    GameRules.RewardTeamwork(KillerInfo, index, 'TW_FP_Pipe');
                else if ( DamType.default.bSniperWeapon ) {
                    // ensure that second assistant used sniper headshots too
                    if ( (GameRules.MonsterInfos[index].DamageFlags2 & DF_HEADSHOT) == 0 )
                        GameRules.MonsterInfos[index].KillAss2 = none;
                    GameRules.RewardTeamwork(KillerInfo, index, 'TW_FP_Snipe');
                }
            }
        }
    }
    else if ( Victim.IsA('FemaleFP') ) {
        if ( DamType.default.bIsExplosive )
            KillerInfo.ProgressAchievement('Kill100FPExplosives', 1);
        else if ( DamType.default.bIsPowerWeapon) {
            if ( DamType == class'KFMod.DamTypeDBShotgun' || DamType == class'KFMod.DamTypeBenelli' )
                KillerInfo.ProgressAchievement('GetOffMyLawn', 1);
        }
    }
    else if ( Victim.IsA('ZombieBrute') ) {
        if ( DamType.default.bIsExplosive )
            KillerInfo.ProgressAchievement('BruteExplosive', 1);

        if ( !Victim.bDamagedAPlayer ) {
            if ( ClassIsChildOf(DamType, class'KFMod.DamTypeM14EBR') )
                KillerInfo.ProgressAchievement('BruteM14', 1);
            else if ( ClassIsChildOf(DamType, class'KFMod.DamTypeM99HeadShot') || ClassIsChildOf(DamType, class'KFMod.DamTypeCrossbowHeadShot') )
                KillerInfo.ProgressAchievement('BruteXbow', 1);
            else if ( ClassIsChildOf(DamType, class'KFMod.DamTypeSCARMK17AssaultRifle') )
                KillerInfo.ProgressAchievement('BruteSCAR', 1);
        }
    }
}


/**
 * Gives achievement for all players who are blocking the Monster.
 * Player is blocking the Monster if:
 * a) he is 1 meter (50uu) or closer to the Monster's collision cylinder
 * b) he is Monster's current or previous enemy OR he is extremely close to the Monster and can see it
 * c) he is in line of sight of the Monster
 *
 * @param Monster enemy, who is supposed to be blocked
 * @param MC Monster's controller
 * @param AchID achievement ID to progress
 * @return number of players, who gained (or supposed to gain) achievement progress
 */
function int GiveAchToBlockingPlayers(KFMonster Monster, KFMonsterController MC, name AchID)
{
    local KFHumanPawn Pawn;
    local ScrnPlayerInfo SPI;
    local int c;

    if ( Monster == none || MC == none )
        return 0;

    foreach Monster.VisibleCollidingActors( class'KFHumanPawn', Pawn, Monster.CollisionRadius + 50, Monster.Location ) {
        if ( ((Pawn == MC.Enemy || Pawn == MC.VisibleEnemy  || Pawn == MC.OldEnemy)
                || (vsize(Pawn.Location - Monster.Location) < Monster.CollisionRadius + Pawn.CollisionRadius + 10
                    && Pawn.Controller.CanSee(Monster)))
                && mc.CanSee(Pawn) )
        {
            c++;
            SPI = GameRules.GetPlayerInfo(PlayerController(Pawn.Controller));
            if ( SPI != none )
                SPI.ProgressAchievement(AchID, 1);
        }
    }
    return c;
}

function private CheckAchBackstabAttract(KFMonster Monster, ScrnPlayerInfo KillerSPI)
{
    local KFPawn KFP;
    local ScrnPlayerInfo SPI;
    local int i;
    local KFMonsterController MC;
    local bool bGiveAch;
    local float Pawn2MonsterDistanceSquared, Killer2PawnDistanceSquared;

    MC = KFMonsterController(Monster.Controller);

    for ( i = 0; i < MC.KillAssistants.Length; ++i ) {
        if ( MC.KillAssistants[i].PC != none && MC.KillAssistants[i].PC != KillerSPI.PlayerOwner ) {
            KFP = KFPawn(MC.KillAssistants[i].PC.Pawn);
            // attractive pawn and killed must be on opposite sides of monsters, so distance between
            // monster and player must be smaller than distance between 2 players
            Pawn2MonsterDistanceSquared = VSizeSquared(Monster.Location - KFP.Location);
            Killer2PawnDistanceSquared = VSizeSquared(KillerSPI.PlayerOwner.Pawn.Location - KFP.Location);
            // attractive person and monster must be within 5m range and see each other
            // only God knows where Scrake is looking during stun animation, so don't use CanSee() here
            if ( KFP != none && Pawn2MonsterDistanceSquared < 62500 && Pawn2MonsterDistanceSquared < Killer2PawnDistanceSquared )
                    // && KFP.Controller.CanSee(Monster) && (MC.LineOfSightTo(KFP) || KillerSPI.PlayerOwner.LineOfSightTo(KF)) )
            {
                bGiveAch = true;
                SPI = GameRules.GetPlayerInfo(PlayerController(KFP.Controller));
                if ( SPI != none )
                    SPI.ProgressAchievement('TW_BackstabSC', 1);
            }
        }
    }
    if ( bGiveAch )
        KillerSPI.ProgressAchievement('TW_BackstabSC', 1);
}

function PickedCash(int CashAmount, ScrnPlayerInfo ReceiverInfo, ScrnPlayerInfo DonatorInfo, bool bDroppedCash)
{
    if ( bDroppedCash && DonatorInfo != none && DonatorInfo.PlayerOwner != none ) {
        if ( CashAmount == 1 && DonatorInfo.PlayerOwner.PlayerReplicationInfo.Score >=
                max(500, 10 * (ReceiverInfo.PlayerOwner.PlayerReplicationInfo.Score + ReceiverInfo.CashDonated)) )
            DonatorInfo.ProgressAchievement('SpareChange', 1);
        if ( DonatorInfo.CashDonated - DonatorInfo.CashReceived - DonatorInfo.CashFound >= 2000 )
            DonatorInfo.ProgressAchievement('MilkingCow', 1);
    }
}

function PickedWeapon(ScrnPlayerInfo SPI, KFWeaponPickup WeaponPickup)
{
    if ( MachetePickup(WeaponPickup ) != none ) {
        if ( SPI.IncCustomValue(none, 'MacheteWalker', 1) == 422 )
            SPI.ProgressAchievement('MacheteWalker', 1);
    }
}

defaultproperties
{
     bOnePlayerPerPerk=True
     bAllSamePerk=True
     InstantKillTime=0.700000
     iDoT_Damage=300
     bNoHeadshots=True
     bAllCanDoHeadshots=True
}
