rott/kf_sources/ScrnBalanceSrv/Classes/ScrnPlayerInfo.uc
2026-07-14 20:27:09 +07:00

1067 lines
39 KiB
Ucode

// 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<SRCustomProgress> CustomStatClass;
var int Progress;
};
struct TPerkStats {
var bool bSet;
var int RDamageHealedStat,
RWeldingPointsStat, RShotgunDamageStat,
RHeadshotKillsStat,
RStalkerKillsStat, RBullpupDamageStat,
RMeleeDamageStat,
RFlameThrowerDamageStat,
RExplosivesDamageStat;
var array<TCustomStat> CustomStats;
};
var TPerkStats GameStartStats; // perk stats at the game start or when player just joined the game
var globalconfig array<string> ExcludeBonusStats;
struct TWeapInfo {
var KFWeapon Weapon;
var class<KFWeapon> WeaponClass;
var class<KFWeaponDamageType> 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<TWeapInfo> 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<KFWeaponDamageType> 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<TCustomData> 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<KFVeterancyTypes> 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<ExcludeBonusStats.length; ++i ) {
if ( ClassName ~= ExcludeBonusStats[i] ) {
i = -1;
break;
}
}
if ( i == -1 )
continue;
i = Stats.CustomStats.length;
Stats.CustomStats.insert(i, 1);
Stats.CustomStats[i].CustomStatClass = S.class;
Stats.CustomStats[i].Progress = S.GetProgressInt();
}
//PlayerOwner.ClientMessage(class'ScrnPlayerController'.static.ConsoleColorString("Initial Perks Stats: " $ PerkStatStr(Stats), 255, 1, 200));
}
// add bonus values to all stats by multiplying gained progress with Mult, e.g.:
// RDamageHealedStat += (RDamageHealedStat - InitialStats.RDamageHealedStat) * Mult;
// InitialStats.bSet must be true
// New stats will be written into InitialStats
function BonusStats(out TPerkStats InitialStats, float Mult)
{
local ClientPerkRepLink L;
local SRCustomProgress S;
local SRStatsBase SteamStats;
local int i, v;
if ( Mult <= 0 )
return;
if ( !InitialStats.bSet ) {
PlayerOwner.ClientMessage(class'ScrnPlayerController'.static.ConsoleColorString("Unable to give end game bonus! No stat backup found", 255, 1, 1));
return;
}
SteamStats = SRStatsBase(PlayerOwner.SteamStatsAndAchievements);
if ( SteamStats == none )
return;
L = SteamStats.Rep;
if ( L == none )
return;
PlayerOwner.ClientMessage(class'ScrnPlayerController'.static.ConsoleColorString("Stat Bonus (x"$Mult$"): " $ PerkProgressStr(InitialStats), 255, 1, 200));
v = Mult * (L.RDamageHealedStat - InitialStats.RDamageHealedStat);
if ( v > 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<ScrnAchievements> 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<CustomData.length; ++i ) {
if ( CustomData[i].AchHandler == AchHandler && CustomData[i].StatName == StatName ) {
LastSearchedAchHandler = AchHandler;
LastSearchedStatName = StatName;
LastFoundCustomDataIndex = i;
return i;
}
}
if ( bCreate ) {
CustomData.insert(i, 1);
CustomData[i].AchHandler = AchHandler;
CustomData[i].StatName = StatName;
LastSearchedAchHandler = AchHandler;
LastSearchedStatName = StatName;
LastFoundCustomDataIndex = i;
return i;
}
return -1;
}
function bool HasCustomValue(ScrnAchHandlerBase AchHandler, name StatName)
{
return FindCustomData(AchHandler, StatName) != -1;
}
function string GetCustomString(ScrnAchHandlerBase AchHandler, name StatName)
{
local int idx;
idx = FindCustomData(AchHandler, StatName);
if (idx != -1)
return CustomData[idx].StrVal;
return "";
}
function int GetCustomValue(ScrnAchHandlerBase AchHandler, name StatName)
{
local int idx;
idx = FindCustomData(AchHandler, StatName);
if (idx != -1)
return CustomData[idx].IntVal;
return 0;
}
function float GetCustomFloat(ScrnAchHandlerBase AchHandler, name StatName)
{
local int idx;
idx = FindCustomData(AchHandler, StatName);
if (idx != -1)
return CustomData[idx].FloatVal;
return 0.f;
}
function SetCustomString(ScrnAchHandlerBase AchHandler, name StatName, string Value, optional bool bWaveReset)
{
local int idx;
idx = FindCustomData(AchHandler, StatName, true);
CustomData[idx].bWaveReset = bWaveReset;
CustomData[idx].StrVal = Value;
CustomData[idx].IntVal = int(Value);
CustomData[idx].FloatVal = float(Value);
}
function SetCustomValue(ScrnAchHandlerBase AchHandler, name StatName, int Value, optional bool bWaveReset)
{
local int idx;
idx = FindCustomData(AchHandler, StatName, true);
CustomData[idx].bWaveReset = bWaveReset;
CustomData[idx].StrVal = string(Value);
CustomData[idx].IntVal = Value;
CustomData[idx].FloatVal = Value;
}
function SetCustomFloat(ScrnAchHandlerBase AchHandler, name StatName, float Value, optional bool bWaveReset)
{
local int idx;
idx = FindCustomData(AchHandler, StatName, true);
CustomData[idx].bWaveReset = bWaveReset;
CustomData[idx].StrVal = string(Value);
CustomData[idx].IntVal = Value;
CustomData[idx].FloatVal = Value;
}
// incremets custom progress and returns its new value
function int IncCustomValue(ScrnAchHandlerBase AchHandler, name StatName, int Inc, optional bool bWaveReset)
{
local int idx;
idx = FindCustomData(AchHandler, StatName, true);
CustomData[idx].bWaveReset = bWaveReset;
CustomData[idx].IntVal += Inc;
CustomData[idx].StrVal = string(CustomData[idx].IntVal);
CustomData[idx].FloatVal = CustomData[idx].IntVal;
return CustomData[idx].IntVal;
}
function int IncCustomFloat(ScrnAchHandlerBase AchHandler, name StatName, float Inc, optional bool bWaveReset)
{
local int idx;
idx = FindCustomData(AchHandler, StatName, true);
CustomData[idx].bWaveReset = bWaveReset;
CustomData[idx].FloatVal += Inc;
CustomData[idx].StrVal = string(CustomData[idx].FloatVal);
CustomData[idx].IntVal = CustomData[idx].FloatVal;
return CustomData[idx].FloatVal;
}
// returns index in WeapInfos
function int RegisterDamageType(KFWeapon Weapon, class<DamageType> DamType, bool bClearPerShotInfo)
{
local int i;
local class<KFWeaponDamageType> KFDamType;
KFDamType = class<KFWeaponDamageType>(DamType);
if ( Weapon == none || KFDamType == none )
return -1;
for ( i=0; i<WeapInfos.Length; ++i ) {
if ( WeapInfos[i].WeaponClass == Weapon.class && WeapInfos[i].DamType == KFDamType ) {
WeapInfos[i].Weapon = Weapon;
if ( bClearPerShotInfo ) {
WeapInfos[i].KillsPerShot = 0;
WeapInfos[i].HeadshotsPerShot = 0;
WeapInfos[i].DecapsPerShot = 0;
WeapInfos[i].DamagePerShot = 0;
}
return i;
}
}
WeapInfos.insert(i, 1);
WeapInfos[i].Weapon = Weapon;
WeapInfos[i].WeaponClass = Weapon.class;
WeapInfos[i].PickupWave = GameRules.Mut.KF.WaveNum;
// Hack in ScrnHumanPawn forces the game to set Tier3WeaponGiver for all weapons despite their tier.
// After player kills somebody with this weapon, Tier3WeaponGiver will be set to none in KFGameType.Killed()
// So only PrevOwner can be used to identify origonal owner
if ( Weapon.Tier3WeaponGiver != none && Weapon.Tier3WeaponGiver != self ) {
WeapInfos[i].PrevOwner = Weapon.Tier3WeaponGiver;
WeapInfos[i].bPrevOwnerDead = Weapon.Tier3WeaponGiver.IsDead();
}
WeapInfos[i].DamType = KFDamType;
WeapInfos[i].TriggerRowHeadshots = 2;
WeapInfos[i].TriggerHeadshotsPerShot = 2;
WeapInfos[i].TriggerHeadshotsPerMagazine = 2;
WeapInfos[i].TriggerKillsPerShot = 2;
WeapInfos[i].TriggerKillsPerMagazine = 2;
WeapInfos[i].TriggerDecapsPerShot = 2;
WeapInfos[i].TriggerDecapsPerMagazine = 2;
WeapInfos[i].TriggerDamagePerShot = 2;
WeapInfos[i].TriggerDamagePerMagazine = 2;;
return i;
}
function ClearWeapInfos()
{
local int i;
while ( i<WeapInfos.Length ) {
WeapInfos[i].HeadshotsPerWave = 0;
WeapInfos[i].KillsPerWave = 0;
WeapInfos[i].DecapsPerWave = 0;
WeapInfos[i].DamagePerWave = 0;
i++;
}
}
// calculates all damage types together
// bHeadshot, RowHeadshots and per-shot stats not included and always be returned as 0
function TWeapInfo GetFullWeaponInfo(class<KFWeapon> WC)
{
local TWeapInfo result;
local int i;
for ( i=0; i<WeapInfos.Length; ++i ) {
if ( WeapInfos[i].WeaponClass == WC ) {
if ( WeapInfos[i].LastDmgTime > 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<Projectile> 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<LAWProj>(proj) != none )
RegisterDamageType(W, class<LAWProj>(proj).default.ImpactDamageType, true);
else if ( class<M79GrenadeProjectile>(proj) != none )
RegisterDamageType(W, class<M79GrenadeProjectile>(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<GameRules.AchHandlers.length; ++i )
GameRules.AchHandlers[i].WeaponReloaded(self, W);
for ( i=0; i<WeapInfos.Length; ++i ) {
if ( WeapInfos[i].Weapon == W ) {
WeapInfos[i].KillsPerShot = 0;
WeapInfos[i].KillsPerMagazine = 0;
WeapInfos[i].DecapsPerShot = 0;
WeapInfos[i].DecapsPerMagazine = 0;
WeapInfos[i].DamagePerShot = 0;
WeapInfos[i].DamagePerMagazine = 0;
WeapInfos[i].HeadshotsPerShot = 0;
WeapInfos[i].HeadshotsPerMagazine = 0;
}
}
}
function KFWeapon GetCurrentWeapon()
{
if ( PlayerOwner.Pawn == none || PlayerOwner.Pawn.Health <= 0 )
return none;
return KFWeapon(PlayerOwner.Pawn.Weapon);
}
/** Locates WeapInfos record by a given damage type. If there are multiple records with the same
* damage type, looks for a match with LastFiredWeapon. If such not found, returns last record with
* matched damage type
*
* @param DamType weapon damage type to search
* @return record index in WeapInfos array or -1, if record not found
*/
function int FindWeaponInfoByDamType(class<KFWeaponDamageType> DamType)
{
local int i, idx;
if ( LastWeapInfoIndex >= 0 && LastWeapInfoIndex < WeapInfos.Length
&& WeapInfos[LastWeapInfoIndex].DamType == DamType )
return LastWeapInfoIndex;
idx = -1;
for ( i=0; i<WeapInfos.Length; ++i ) {
if ( WeapInfos[i].DamType == DamType ) {
// if this damage type can be delivered by last fired weapon, then use it.
// Otherwice use last registered record with this damage type
idx = i;
if ( WeapInfos[i].Weapon == LastFiredWeapon )
break;
}
}
return idx;
}
function KFWeapon FindWeaponByDamType(class<KFWeaponDamageType> 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<KFWeaponDamageType> 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<GameRules.AchHandlers.length; ++i )
m = min(m, GameRules.AchHandlers[i].RowHeadhots(self, RowHeadshots));
TriggerRowHeadshots = m;
}
// END OF EVENT
}
else if ( !bWasDecapitated ) { // don't clear headshots when hitting decapitated bodies
RowHeadshots = 0;
if ( DamType.default.bCheckForHeadShots ) {
BodyshotsPerWave++;
BodyshotsPerGame++;
}
}
// count medic damage
if ( ClassIsChildOf(DamType, class'ScrnDamTypeMedicBase')
|| (ClassIsChildOf(DamType, class'DamTypeKatana')
&& KFPRI != none && ClassIsChildOf(KFPRI.ClientVeteranSkill, class'ScrnVetFieldMedic')) )
{
MedicDamage += min(Damage, Injured.Health);
if ( MedicDamage >= 1000 ) {
SRStatsBase(PlayerOwner.SteamStatsAndAchievements).AddDamageHealed(MedicDamage / 1000 * MEDICXP_PER_1000DMG);
MedicDamage = MedicDamage % 1000;
}
}
LastWeapInfoIndex = FindWeaponInfoByDamType(DamType);
for ( i=0; i<GameRules.AchHandlers.length; ++i ) {
GameRules.AchHandlers[i].MonsterDamaged(Damage, Injured, self, DamType, bHeadshot, bWasDecapitated);
}
if ( LastWeapInfoIndex != -1 ) {
Weapon = WeapInfos[LastWeapInfoIndex].Weapon;
WeapInfos[LastWeapInfoIndex].DamagePerShot += Damage;
WeapInfos[LastWeapInfoIndex].DamagePerMagazine += Damage;
WeapInfos[LastWeapInfoIndex].DamagePerWave += Damage;
WeapInfos[LastWeapInfoIndex].TotalDamage += Damage;
WeapInfos[LastWeapInfoIndex].bHeadshot = bHeadshot;
if ( bHeadshot ) {
WeapInfos[LastWeapInfoIndex].RowHeadshots++;
WeapInfos[LastWeapInfoIndex].HeadshotsPerShot++;
WeapInfos[LastWeapInfoIndex].HeadshotsPerMagazine++;
WeapInfos[LastWeapInfoIndex].HeadshotsPerWave++;
WeapInfos[LastWeapInfoIndex].TotalHeadshots++;
if ( Injured.bDecapitated ) {
WeapInfos[LastWeapInfoIndex].DecapsPerShot++;
WeapInfos[LastWeapInfoIndex].DecapsPerMagazine++;
WeapInfos[LastWeapInfoIndex].DecapsPerWave++;
WeapInfos[LastWeapInfoIndex].TotalDecaps++;
// EVENT
if ( WeapInfos[LastWeapInfoIndex].DecapsPerShot >= 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<GameRules.AchHandlers.length; ++i )
m = min(m, GameRules.AchHandlers[i].WDecapsPerShot(self, Weapon, DamType, v, t));
WeapInfos[LastWeapInfoIndex].TriggerDecapsPerShot = m;
}
if ( WeapInfos[LastWeapInfoIndex].DecapsPerMagazine >= WeapInfos[LastWeapInfoIndex].TriggerDecapsPerMagazine ) {
m = IGNORE_STAT; // next time trigger event when reaching minimal returned value
v = WeapInfos[LastWeapInfoIndex].DecapsPerMagazine;
for ( i=0; i<GameRules.AchHandlers.length; ++i )
m = min(m, GameRules.AchHandlers[i].WDecapsPerMagazine(self, Weapon, DamType, v));
WeapInfos[LastWeapInfoIndex].TriggerDecapsPerMagazine = m;
}
// END OF EVENT
}
// EVENT
if ( WeapInfos[LastWeapInfoIndex].RowHeadshots >= WeapInfos[LastWeapInfoIndex].TriggerRowHeadshots ) {
m = IGNORE_STAT; // next time trigger event when reaching minimal returned value
v = WeapInfos[LastWeapInfoIndex].RowHeadshots;
for ( i=0; i<GameRules.AchHandlers.length; ++i )
m = min(m, GameRules.AchHandlers[i].WRowHeadhots(self, Weapon, DamType, v));
WeapInfos[LastWeapInfoIndex].TriggerRowHeadshots = m;
}
if ( WeapInfos[LastWeapInfoIndex].HeadshotsPerShot >= WeapInfos[LastWeapInfoIndex].TriggerHeadshotsPerShot ) {
m = IGNORE_STAT; // next time trigger event when reaching minimal returned value
v = WeapInfos[LastWeapInfoIndex].HeadshotsPerShot;
for ( i=0; i<GameRules.AchHandlers.length; ++i )
m = min(m, GameRules.AchHandlers[i].WInstantHeadhots(self, Weapon, DamType, v));
WeapInfos[LastWeapInfoIndex].TriggerHeadshotsPerShot = m;
}
if ( WeapInfos[LastWeapInfoIndex].HeadshotsPerMagazine >= WeapInfos[LastWeapInfoIndex].TriggerHeadshotsPerMagazine ) {
m = IGNORE_STAT; // next time trigger event when reaching minimal returned value
v = WeapInfos[LastWeapInfoIndex].HeadshotsPerMagazine;
for ( i=0; i<GameRules.AchHandlers.length; ++i )
m = min(m, GameRules.AchHandlers[i].WHeadshotsPerMagazine(self, Weapon, DamType, v));
WeapInfos[LastWeapInfoIndex].TriggerHeadshotsPerMagazine = m;
}
// END OF EVENT
}
else if ( !bWasDecapitated ) {
WeapInfos[LastWeapInfoIndex].RowHeadshots = 0;
}
// EVENT
if ( WeapInfos[LastWeapInfoIndex].DamagePerShot >= 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<GameRules.AchHandlers.length; ++i )
m = min(m, GameRules.AchHandlers[i].WDamagePerShot(self, Weapon, DamType, v, t));
WeapInfos[LastWeapInfoIndex].TriggerDamagePerShot = m;
}
if ( WeapInfos[LastWeapInfoIndex].DamagePerMagazine >= WeapInfos[LastWeapInfoIndex].TriggerDamagePerMagazine ) {
m = IGNORE_STAT; // next time trigger event when reaching minimal returned value
v = WeapInfos[LastWeapInfoIndex].DamagePerMagazine;
for ( i=0; i<GameRules.AchHandlers.length; ++i )
m = min(m, GameRules.AchHandlers[i].WDamagePerMagazine(self, Weapon, DamType, v));
WeapInfos[LastWeapInfoIndex].TriggerDamagePerMagazine = m;
}
// END OF EVENT
WeapInfos[LastWeapInfoIndex].LastDmgTime = Level.TimeSeconds;
}
LastDmgTime = Level.TimeSeconds;
}
// took damage from the monster
function TookDamage(int Damage, KFMonster InstigatedBy, class<DamageType> DamType)
{
local int i;
DamageReceivedPerWave += Damage;
DamageReceivedPerGame += Damage;
for ( i=0; i<GameRules.AchHandlers.length; ++i ) {
GameRules.AchHandlers[i].PlayerDamaged(Damage, self, InstigatedBy, DamType);
}
}
function KilledMonster(KFMonster Killed, class<KFWeaponDamageType> 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<GameRules.AchHandlers.length; ++i )
m = min(m, GameRules.AchHandlers[i].WKillsPerShot(self, Weapon, DamType, v, t));
WeapInfos[LastWeapInfoIndex].TriggerKillsPerShot = m;
}
if ( WeapInfos[LastWeapInfoIndex].KillsPerMagazine >= WeapInfos[LastWeapInfoIndex].TriggerKillsPerMagazine ) {
m = IGNORE_STAT; // next time trigger event when reaching minimal returned value
v = WeapInfos[LastWeapInfoIndex].KillsPerMagazine;
for ( i=0; i<GameRules.AchHandlers.length; ++i )
m = min(m, GameRules.AchHandlers[i].WKillsPerMagazine(self, Weapon, DamType, v));
WeapInfos[LastWeapInfoIndex].TriggerKillsPerMagazine = m;
}
// END OF EVENT
WeapInfos[LastWeapInfoIndex].LastKillTime = Level.TimeSeconds;
}
for ( i=0; i<GameRules.AchHandlers.length; ++i ) {
GameRules.AchHandlers[i].MonsterKilled(Killed, self, DamType);
}
LastKillTime = Level.TimeSeconds;
}
function Died(Controller Killer, class<DamageType> DamType)
{
local int i;
if ( !bDied ) {
bDied = true;
Deaths++;
LastKilledBy = Killer;
if ( KFMonster(Killer.Pawn) != none )
DeathsByMonster++;
for ( i=0; i<GameRules.AchHandlers.length; ++i ) {
GameRules.AchHandlers[i].PlayerDied(self, Killer, DamType);
}
}
}
/* This player healed anothe one
* @param HealAmount the amount of health Patient received
* @param Patient player, who received healing
* @param MedicGun Instigator's weapon, which was used for healing
*/
function Healed(int HealAmount, ScrnHumanPawn Patient, KFWeapon MedicGun)
{
local int i;
HealedPointsInWave += HealAmount;
for ( i=0; i<GameRules.AchHandlers.length; ++i )
GameRules.AchHandlers[i].HealingMade(HealAmount, Patient, self, MedicGun);
}
/**
* Function is called every time after the start of new wave
* @param WaveNum - wave number, where 0 is first wave
*/
function WaveStarted(byte WaveNum)
{
local int i;
ClearWeapInfos();
for ( i=0; i<CustomData.length; ++i ) {
if ( CustomData[i].bWaveReset ) {
CustomData[i].StrVal = "";
CustomData[i].IntVal = 0;
CustomData[i].FloatVal = 0;
}
}
bDied = PlayerOwner.Pawn == none || PlayerOwner.Pawn.Health <= 0;
HeadshotsPerWave = 0;
BodyshotsPerWave = 0;
KillsPerWave = 0;
DecapsPerWave = 0;
DamagePerWave = 0;
DamageReceivedPerWave = 0;
HealedPointsInWave = 0;
CashDonatedPerWave = 0;
CashReceivedPerWave = 0;
CashFoundPerWave = 0;
}
function WaveEnded(byte WaveNum)
{
}
// backup vital player data from KFPRI
function BackupPRI()
{
local KFPlayerReplicationInfo KFPRI;
local ScrnCustomPRI ScrnPRI;
if ( PlayerOwner == none )
return;
KFPRI = KFPlayerReplicationInfo(PlayerOwner.PlayerReplicationInfo);
if ( KFPRI == none )
return;
ScrnPRI = class'ScrnCustomPRI'.static.FindMe(KFPRI);
if ( !GameRules.Mut.bLeaveCashOnDisconnect )
PRI_Score = KFPRI.Score;
PRI_Kills = KFPRI.Kills;
PRI_KillAssists = KFPRI.KillAssists;
PRI_Deaths = KFPRI.Deaths;
PRI_StartTime = KFPRI.StartTime;
PRI_ClientVeteranSkill = KFPRI.ClientVeteranSkill;
if ( KFPRI.Team == none )
PRI_TeamIndex = 255;
else
PRI_TeamIndex = KFPRI.Team.TeamIndex;
if ( ScrnPRI != none ) {
PRI_BlameCounter = ScrnPRI.BlameCounter;
}
}
function RestorePRI()
{
local KFPlayerReplicationInfo KFPRI;
local ScrnCustomPRI ScrnPRI;
if ( PlayerOwner == none )
return;
KFPRI = KFPlayerReplicationInfo(PlayerOwner.PlayerReplicationInfo);
if ( KFPRI == none )
return;
ScrnPRI = class'ScrnCustomPRI'.static.FindMe(KFPRI);
if ( KFPRI.Kills == 0 && KFPRI.KillAssists == 0 && KFPRI.Deaths == 0 ) {
if ( PRI_ClientVeteranSkill != none )
KFPRI.ClientVeteranSkill = PRI_ClientVeteranSkill;
if ( PRI_TeamIndex < 2 && ( KFPRI.Team == none || KFPRI.Team.TeamIndex != PRI_TeamIndex ) )
Level.Game.ChangeTeam( PlayerOwner, PRI_TeamIndex, true );
}
if ( !GameRules.Mut.bLeaveCashOnDisconnect )
KFPRI.Score = max(PRI_Score, KFPRI.Score);
KFPRI.Kills = max(PRI_Kills, KFPRI.Kills);
KFPRI.KillAssists = max(PRI_KillAssists, KFPRI.KillAssists);
KFPRI.Deaths = max(PRI_Deaths, KFPRI.Deaths);
KFPRI.StartTime = min(PRI_StartTime, KFPRI.StartTime);
if ( ScrnPRI != none ) {
ScrnPRI.BlameCounter = max(PRI_BlameCounter, ScrnPRI.BlameCounter);
}
}
function PickedCash(CashPickup Dosh)
{
local ScrnPlayerInfo DonatorSPI;
local int i;
if ( Dosh.CashAmount == 0 )
return;
if ( Dosh.bDroppedCash ) {
DonatorSPI = GameRules.GetPlayerInfo(PlayerController(Dosh.DroppedBy));
if ( DonatorSPI == self )
return; // picked up own dosh
// dosh was dropped by other player
CashReceived += Dosh.CashAmount;
CashReceivedPerWave += Dosh.CashAmount;
if ( DonatorSPI != none ) {
DonatorSPI.CashDonated += Dosh.CashAmount;
DonatorSPI.CashDonatedPerWave += Dosh.CashAmount;
}
}
else {
// dosh found on the map (wasn't dropped by a player)
CashFound += Dosh.CashAmount;
CashFoundPerWave += Dosh.CashAmount;
}
// achievements
for ( i=0; i<GameRules.AchHandlers.length; ++i )
GameRules.AchHandlers[i].PickedCash(Dosh.CashAmount, self, DonatorSPI, Dosh.bDroppedCash);
}
function PickedWeapon(KFWeaponPickup WeaponPickup)
{
local int i;
for ( i=0; i<GameRules.AchHandlers.length; ++i )
GameRules.AchHandlers[i].PickedWeapon(self, WeaponPickup);
}
function PickedItem(Pickup Item)
{
local int i;
for ( i=0; i<GameRules.AchHandlers.length; ++i )
GameRules.AchHandlers[i].PickedItem(self, Item);
}
function float GetAccuracyWave()
{
if ( HeadshotsPerWave+BodyShotsPerWave == 0 )
return 0.f;
return float(HeadshotsPerWave) / float(HeadshotsPerWave+BodyShotsPerWave);
}
function float GetAccuracyGame()
{
if ( HeadshotsPerGame+BodyShotsPerGame == 0 )
return 0.f;
return float(HeadshotsPerGame) / float(HeadshotsPerGame+BodyShotsPerGame);
}
defaultproperties
{
MEDICXP_PER_1000DMG=20
TriggerRowHeadshots=2
PRI_TeamIndex=255
}