// Additional player info used in achievements class ScrnPlayerInfo extends Info config(ScrnBalance); // variable below are initially set by ScrnGameRules var ScrnGameRules GameRules; var PlayerController PlayerOwner; var ScrnPlayerInfo NextPlayerInfo; var byte StartWave; // wave number, when player joined the game 0 - first wave // ----- var LinkedReplicationInfo CustomReplicationInfo; // for use by mod authors to link PRI.CustomReplicationInfo const IGNORE_STAT = 0x7FFFFFFF; // used to backup ServerPerks stats struct TCustomStat { var class CustomStatClass; var int Progress; }; struct TPerkStats { var bool bSet; var int RDamageHealedStat, RWeldingPointsStat, RShotgunDamageStat, RHeadshotKillsStat, RStalkerKillsStat, RBullpupDamageStat, RMeleeDamageStat, RFlameThrowerDamageStat, RExplosivesDamageStat; var array CustomStats; }; var TPerkStats GameStartStats; // perk stats at the game start or when player just joined the game var globalconfig array ExcludeBonusStats; struct TWeapInfo { var KFWeapon Weapon; var class WeaponClass; var class DamType; var byte PickupWave; // wave number, when weapon was picked up var PlayerController PrevOwner; // player who owned this weapon before us var bool bPrevOwnerDead; // was PrevOwner dead when we picked up his weapon? var float LastDmgTime, LastKillTime; // time when last damage was made var int TotalKills, TotalHeadshots, TotalDecaps, TotalDamage; var bool bHeadshot; // Was last damage made from this weapon a headshot? var int RowHeadshots; //number of headshots in a row made from this weapon var int HeadshotsPerShot, HeadshotsPerMagazine, HeadshotsPerWave; var int KillsPerShot; //number of kills made from this weapon without releasing the trigger or firing again var int KillsPerMagazine; //number of kills made from this weapon without reloading var int KillsPerWave; //number of kills made from this weapon in wave var int DecapsPerShot, DecapsPerMagazine, DecapsPerWave; // decapitations var int DamagePerShot, DamagePerMagazine, DamagePerWave; // damages // Minimal values to trigger an event. If Value >= Trigger value, event will be called. var int TriggerRowHeadshots, TriggerHeadshotsPerShot, TriggerHeadshotsPerMagazine, TriggerKillsPerShot, TriggerKillsPerMagazine, TriggerDecapsPerShot, TriggerDecapsPerMagazine, TriggerDamagePerShot, TriggerDamagePerMagazine; }; var array WeapInfos; var bool bDied; // did player died during the way. Valid also during the next trader time. var byte Deaths; // how many times player died in the game (excluding suicides at trader time) var byte DeathsByMonster; // how many times player died in the game (excluding suicides at trader time) var Controller LastKilledBy; // enemy who killed player last time var float LastDmgTime, LastKillTime; var float LastDamage; // last damage value var class LastDamageType; var KFMonster LastDamagedMonster; // last monster, who was damaged by the player var bool bHeadshot; // was last damage made by this player a headshot? var byte RowHeadshots; //number of headshots in a row made by this player var int HeadshotsPerWave, HeadshotsPerGame; var int BodyshotsPerWave, BodyshotsPerGame; // this number doesn't include damages with bCheckForHeadShots=false var int KillsPerWave; // total kills per game are already tracked in PRI var int DecapsPerWave, DecapsPerGame; // number of decapitations in the current wave var int DamagePerWave, DamagePerGame; var int DamageReceivedPerWave, DamageReceivedPerGame; // damage received from monsters var int HealedPointsInWave; var int MedicDamage; var int MEDICXP_PER_1000DMG; // Minimal values to trigger an event. If Value >= Trigger value, event will be called. var int TriggerRowHeadshots; var KFWeapon LastFiredWeapon; var int LastWeapInfoIndex; // index in WeapInfos array of last updated record // how much dosh player donated to teammates and how much he received back. // CashFound - cash spawned on the map (wasn't dropped by a player) var int CashDonated, CashReceived, CashFound; var int CashDonatedPerWave, CashReceivedPerWave, CashFoundPerWave; //stuctures to store custom data form achievement handlers struct TCustomData { var ScrnAchHandlerBase AchHandler; var name StatName; var bool bWaveReset; // if true, stat will be automatically reset at the new wave begin var String StrVal; var int IntVal; var float FloatVal; }; var private array CustomData; var private ScrnAchHandlerBase LastSearchedAchHandler; var private name LastSearchedStatName; var private int LastFoundCustomDataIndex; // Backup data for PlayerReplicationInfo var int SteamID32; var int PRI_Score, PRI_Kills, PRI_KillAssists, PRI_Deaths, PRI_StartTime; var byte PRI_BlameCounter; var class PRI_ClientVeteranSkill; var byte PRI_TeamIndex; function ClientPerkRepLink GetRep() { return class'ScrnClientPerkRepLink'.static.FindMe(PlayerOwner); } function string PerkStatStr(out TPerkStats Stats) { return "MEDIC"$Stats.RDamageHealedStat @"SUP"$Stats.RWeldingPointsStat$"/"$Stats.RShotgunDamageStat @"SS"$Stats.RHeadshotKillsStat @"CMD"$Stats.RStalkerKillsStat$"/"$Stats.RBullpupDamageStat @"ZERK"$Stats.RMeleeDamageStat @"FB"$Stats.RFlameThrowerDamageStat @"DEMO"$Stats.RExplosivesDamageStat; } function string PerkProgressStr(out TPerkStats InitialStats) { local ClientPerkRepLink L; L = GetRep(); if ( L == none ) return ""; return "MEDIC"$(L.RDamageHealedStat - InitialStats.RDamageHealedStat) @"SUP"$(L.RWeldingPointsStat - InitialStats.RWeldingPointsStat)$"/"$(L.RShotgunDamageStat - InitialStats.RShotgunDamageStat) @"SS"$(L.RHeadshotKillsStat - InitialStats.RHeadshotKillsStat) @"CMD"$(L.RStalkerKillsStat - InitialStats.RStalkerKillsStat)$"/"$(L.RBullpupDamageStat - InitialStats.RBullpupDamageStat) @"ZERK"$(L.RMeleeDamageStat - InitialStats.RMeleeDamageStat ) @"FB"$(L.RFlameThrowerDamageStat - InitialStats.RFlameThrowerDamageStat) @"DEMO"$(L.RExplosivesDamageStat - InitialStats.RExplosivesDamageStat); } // backups only if Stats.bSet = false !!! function BackupStats(out TPerkStats Stats) { local ClientPerkRepLink L; local SRCustomProgress S; local int i; local string ClassName; if ( Stats.bSet ) return; L = GetRep(); if ( L == none ) return; Stats.bSet = true; Stats.RDamageHealedStat = L.RDamageHealedStat; Stats.RWeldingPointsStat = L.RWeldingPointsStat; Stats.RShotgunDamageStat = L.RShotgunDamageStat; Stats.RHeadshotKillsStat = L.RHeadshotKillsStat; Stats.RStalkerKillsStat = L.RStalkerKillsStat; Stats.RBullpupDamageStat = L.RBullpupDamageStat; Stats.RMeleeDamageStat = L.RMeleeDamageStat; Stats.RFlameThrowerDamageStat = L.RFlameThrowerDamageStat; Stats.RExplosivesDamageStat = L.RExplosivesDamageStat; Stats.CustomStats.length = 0; for ( S=L.CustomLink; S!=none; S=S.NextLink ) { if ( SRCustomProgressInt(S) == none && SRCustomProgressFloat(S) == none ) continue; // store only int values ClassName = GetItemName(String(S.class)); for ( i=0; i 0 ) SteamStats.AddDamageHealed(v); v = Mult * (L.RWeldingPointsStat - InitialStats.RWeldingPointsStat); if ( v > 0 ) SteamStats.AddWeldingPoints(v); v = Mult * (L.RShotgunDamageStat - InitialStats.RShotgunDamageStat); if ( v > 0 ) SteamStats.AddShotgunDamage(v); // that is fucking gay, dude! v = Mult * (L.RHeadshotKillsStat - InitialStats.RHeadshotKillsStat); while ( v-- > 0 ) SteamStats.AddHeadshotKill(false); v = Mult * (L.RStalkerKillsStat - InitialStats.RStalkerKillsStat); while ( v-- > 0 ) SteamStats.AddStalkerKill(); v = Mult * (L.RBullpupDamageStat - InitialStats.RBullpupDamageStat); if ( v > 0 ) SteamStats.AddBullpupDamage(v); v = Mult * (L.RMeleeDamageStat - InitialStats.RMeleeDamageStat); if ( v > 0 ) SteamStats.AddMeleeDamage(v); v = Mult * (L.RFlameThrowerDamageStat - InitialStats.RFlameThrowerDamageStat); if ( v > 0 ) SteamStats.AddFlameThrowerDamage(v); v = Mult * (L.RExplosivesDamageStat - InitialStats.RExplosivesDamageStat); if ( v > 0 ) SteamStats.AddExplosivesDamage(v); for ( S=L.CustomLink; S!=none; S=S.NextLink ) { if ( SRCustomProgressInt(S) == none && SRCustomProgressFloat(S) == none ) continue; // proceed only int and float values for ( i=0; i< InitialStats.CustomStats.length; ++i) { if ( InitialStats.CustomStats[i].CustomStatClass == S.Class ) { v = Mult * (S.GetProgressInt() - InitialStats.CustomStats[i].Progress); if ( v > 0 ) S.IncrementProgress(v); InitialStats.CustomStats.remove(i, 1); break; } } } // store modified stats to prevent multiple bonuses InitialStats.bSet = false; BackupStats(InitialStats); } final function bool ProgressAchievement(name AchID, int Inc) { if ( SRStatsBase(PlayerOwner.SteamStatsAndAchievements) == none ) return false; return class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID( SRStatsBase(PlayerOwner.SteamStatsAndAchievements).Rep, AchID, Inc); } final function ScrnAchievements GetAchievementsByClass(class AchClass) { local ClientPerkRepLink L; local SRCustomProgress S; L = GetRep(); if ( L == none ) return none; for( S = L.CustomLink; S != none; S = S.NextLink ) { if( ClassIsChildOf(S.Class, AchClass) ) return ScrnAchievements(S); } return none; } final function ScrnAchievements GetAchievementsByID(name AchID, out int AchIndex) { local ClientPerkRepLink L; local SRCustomProgress S; local ScrnAchievements A; local int i; L = GetRep(); if ( L == none ) return none; for( S = L.CustomLink; S != none; S = S.NextLink ) { A = ScrnAchievements(S); if( A != none ) { for ( i = 0; i < A.AchDefs.length; ++i ) { if ( A.AchDefs[i].ID == AchID ) { AchIndex = i; return A; } } } } return none; } // returns array index or -1, if record not found protected function int FindCustomData(ScrnAchHandlerBase AchHandler, name StatName, optional bool bCreate) { local int i; if ( AchHandler == LastSearchedAchHandler && StatName == LastSearchedStatName ) return LastFoundCustomDataIndex; for ( i=0; i DamType, bool bClearPerShotInfo) { local int i; local class KFDamType; KFDamType = class(DamType); if ( Weapon == none || KFDamType == none ) return -1; for ( i=0; i WC) { local TWeapInfo result; local int i; for ( i=0; i result.LastDmgTime ) result.LastDmgTime = WeapInfos[i].LastDmgTime; result.TotalKills += WeapInfos[i].TotalKills; result.TotalHeadshots += WeapInfos[i].TotalHeadshots; result.TotalDecaps += WeapInfos[i].TotalDecaps; result.TotalDamage += WeapInfos[i].TotalDamage; result.HeadshotsPerMagazine += WeapInfos[i].HeadshotsPerMagazine; result.HeadshotsPerWave += WeapInfos[i].HeadshotsPerWave; result.KillsPerMagazine += WeapInfos[i].KillsPerMagazine; result.KillsPerWave += WeapInfos[i].KillsPerWave; result.DecapsPerMagazine += WeapInfos[i].DecapsPerMagazine; result.DecapsPerWave += WeapInfos[i].DecapsPerWave; result.DamagePerMagazine += WeapInfos[i].DamagePerMagazine; result.DamagePerWave += WeapInfos[i].DamagePerWave; } } return result; } function WeaponFired(KFWeapon W, byte FireMode) { local WeaponFire WF; local class proj; if ( W == none ) return; LastFiredWeapon = W; WF = W.GetFireMode(FireMode); if ( WF == none || WF.class == Class'KFMod.NoFire' ) return; proj = WF.default.ProjectileClass; if ( proj != none ) { RegisterDamageType(W, proj.default.MyDamageType, true); if ( class(proj) != none ) RegisterDamageType(W, class(proj).default.ImpactDamageType, true); else if ( class(proj) != none ) RegisterDamageType(W, class(proj).default.ImpactDamageType, true); } if ( InstantFire(WF) != none ) RegisterDamageType(W, InstantFire(WF).DamageType, true); else if ( KFMeleeFire(WF) != none ) RegisterDamageType(W, KFMeleeFire(WF).hitDamageClass, true); } function WeaponReloaded(KFWeapon W) { local int i; if ( W == none ) return; for ( i=0; i DamType) { local int i, idx; if ( LastWeapInfoIndex >= 0 && LastWeapInfoIndex < WeapInfos.Length && WeapInfos[LastWeapInfoIndex].DamType == DamType ) return LastWeapInfoIndex; idx = -1; for ( i=0; i DamType) { local int idx; idx = FindWeaponInfoByDamType(DamType); if ( idx != -1 ) return WeapInfos[idx].Weapon; return none; } /** * This function is called each time when player made damage to the monster. P2P damages or damages * made by non-KFWeaponDamageType aren't triggered here. * * @param Damage damage delivered (including perk bonuses, resistance etc.). * @param Injured Monster that took damage. Note that Monster.Health isn't touched yet, i.e. * Damage isn't subtracted yet, but Monster.HeadHealth already has actual value. * @param DamType damage type. * @param bHeadshot is this damage acquired by a headshot? False for any shot made to already decapitaded enemy. * @param bWasDecapitated was Injured decapitaded before this shot? If true, bHeadshot will always be false. */ function MadeDamage(int Damage, KFMonster Injured, class DamType, bool bHeadshot, bool bWasDecapitated ) { local int i, m, v; local float t; local KFWeapon Weapon; local KFPlayerReplicationInfo KFPRI; if ( PlayerOwner == none ) return; KFPRI = KFPlayerReplicationInfo(PlayerOwner.PlayerReplicationInfo); LastDamage = Damage; LastDamageType = DamType; LastDamagedMonster = Injured; DamagePerWave += Damage; DamagePerGame += Damage; self.bHeadshot = bHeadshot; if ( bHeadshot ) { RowHeadshots++; HeadshotsPerWave++; HeadshotsPerGame++; if ( Injured.bDecapitated ) { DecapsPerWave++; DecapsPerGame++; } // EVENT if ( RowHeadshots >= TriggerRowHeadshots ) { m = IGNORE_STAT; // next time trigger event when reaching minimal returned value for ( i=0; i= 1000 ) { SRStatsBase(PlayerOwner.SteamStatsAndAchievements).AddDamageHealed(MedicDamage / 1000 * MEDICXP_PER_1000DMG); MedicDamage = MedicDamage % 1000; } } LastWeapInfoIndex = FindWeaponInfoByDamType(DamType); for ( i=0; i= WeapInfos[LastWeapInfoIndex].TriggerDecapsPerShot ) { m = IGNORE_STAT; // next time trigger event when reaching minimal returned value v = WeapInfos[LastWeapInfoIndex].DecapsPerShot; t = Level.TimeSeconds - WeapInfos[LastWeapInfoIndex].LastDmgTime; for ( i=0; i= WeapInfos[LastWeapInfoIndex].TriggerDecapsPerMagazine ) { m = IGNORE_STAT; // next time trigger event when reaching minimal returned value v = WeapInfos[LastWeapInfoIndex].DecapsPerMagazine; for ( i=0; i= WeapInfos[LastWeapInfoIndex].TriggerRowHeadshots ) { m = IGNORE_STAT; // next time trigger event when reaching minimal returned value v = WeapInfos[LastWeapInfoIndex].RowHeadshots; for ( i=0; i= WeapInfos[LastWeapInfoIndex].TriggerHeadshotsPerShot ) { m = IGNORE_STAT; // next time trigger event when reaching minimal returned value v = WeapInfos[LastWeapInfoIndex].HeadshotsPerShot; for ( i=0; i= WeapInfos[LastWeapInfoIndex].TriggerHeadshotsPerMagazine ) { m = IGNORE_STAT; // next time trigger event when reaching minimal returned value v = WeapInfos[LastWeapInfoIndex].HeadshotsPerMagazine; for ( i=0; i= WeapInfos[LastWeapInfoIndex].TriggerDamagePerShot ) { m = IGNORE_STAT; // next time trigger event when reaching minimal returned value v = WeapInfos[LastWeapInfoIndex].DamagePerShot; t = Level.TimeSeconds - WeapInfos[LastWeapInfoIndex].LastDmgTime; for ( i=0; i= WeapInfos[LastWeapInfoIndex].TriggerDamagePerMagazine ) { m = IGNORE_STAT; // next time trigger event when reaching minimal returned value v = WeapInfos[LastWeapInfoIndex].DamagePerMagazine; for ( i=0; i DamType) { local int i; DamageReceivedPerWave += Damage; DamageReceivedPerGame += Damage; for ( i=0; i DamType) { local int i, m, v; local float t; local KFWeapon Weapon; KillsPerWave++; LastWeapInfoIndex = FindWeaponInfoByDamType(DamType); if ( LastWeapInfoIndex != -1 ) { Weapon = WeapInfos[LastWeapInfoIndex].Weapon; WeapInfos[LastWeapInfoIndex].KillsPerShot++; WeapInfos[LastWeapInfoIndex].KillsPerMagazine++; WeapInfos[LastWeapInfoIndex].KillsPerWave++; WeapInfos[LastWeapInfoIndex].TotalKills++; // EVENT if ( WeapInfos[LastWeapInfoIndex].KillsPerShot >= WeapInfos[LastWeapInfoIndex].TriggerKillsPerShot ) { m = IGNORE_STAT; // next time trigger event when reaching minimal returned value v = WeapInfos[LastWeapInfoIndex].KillsPerShot; t = Level.TimeSeconds - WeapInfos[LastWeapInfoIndex].LastKillTime; for ( i=0; i= WeapInfos[LastWeapInfoIndex].TriggerKillsPerMagazine ) { m = IGNORE_STAT; // next time trigger event when reaching minimal returned value v = WeapInfos[LastWeapInfoIndex].KillsPerMagazine; for ( i=0; i DamType) { local int i; if ( !bDied ) { bDied = true; Deaths++; LastKilledBy = Killer; if ( KFMonster(Killer.Pawn) != none ) DeathsByMonster++; for ( i=0; i