KFWeaponStats

From Tripwire Interactive Wiki
Jump to navigation Jump to search

Here's a new statistic generator, specifically for weapons. It is used for the information on the weapon pages. It supersedes KFStatOutput. --Benjamin (talk) 08:09, 22 December 2012 (EST)

Output is stored here.

:

//============================================================================= // Weapon stat generator 22-December-2012 Version 0.1 // Author: Benjamin // // KNOWN ISSUES // // * Cannot detect where different fire modes share the same // ammo (boomstick). Take this into account when using info. // // * Cannot detect penetration for all weapons, must check manually // for some, see note (2) below. // //============================================================================= //== INFO ===================================================================== // // HEADSHOTS // // Every weapon's headshot multiplier is stored in its associated damagetype // class, except (some of) those weapons whose projectiles implement // ProcessTouch themselves: CrossbowArrow, M99Bullet CrossbuzzsawBlade. These // projectiles have their own HeadShotDamageMult variable. Other classes // may have such a variable but the code never allows it to be used (seemingly // a bug). // // Explosive projectiles (LAWProj) do not check for headshots, and certain // fire-based ones do not either (FlameTendril). However, if these projectiles // have ImpactDamage then its associated DamType may have a multiplier instead. // //============================================================================= //== NOTES ==================================================================== // // (1) Some classes don't use AmmoPerFire: MP7MAltFire, M7A3MAltFire. // // (2) Hardcoded penetration values: Deagle, MK23, 44Magnum, Crossbow, M99, // and Buzzsaw bow. Look in script files manually for these stats. // // (3) Weapons only have a single ReloadRate stat (none for alt-fire). // // (4) Weapons that auto-reload with a specific time: // // Hunting shotgun // // Fire: Take the minimum of ReloadCountDown(BoomStick) and FireLastRate(BoomStickAltFire) // Alt-fire: Take the minimum of ReloadCountDown(BoomStick) and FireRate(BoomStickFire) // Use both for output (2 firing modes). // // M79 - FireRate (fire + reload are same animation) // Crossbow - FireRate (fire + reload are same animation) // LAW - FireRate (fire + reload are same animation) // M99 - FireRate (fire + reload are same animation) // Buzzsaw Bow - FireRate (fire + reload are same animation) // // (5) Weapons that use ImpactDamage: // // LAWProj - Defines it for the below classes. Only uses it if the rocket duds. HS multiplier applies to impact. // FlareRevolverProjectile(LAWProj) - Does both impact and 'explosive' fire damage. HS multiplier applies to impact. // HuskGunProjectile(LAWProj) - Does both impact and 'explosive' fire damage. HS multiplier applies to impact. // ZEDGunProjectile(LAWProj) - Ignore, only does regular non-explosive projectile damage without impact. // // M79GrenadeProjectile - Defines it and uses in the same way as LAWProj. // M32GrenadeProjectile(M79GrenadeProjectile) - Same as above. // M203GrenadeProjectile(M79GrenadeProjectile) - Same as above. // // (6) Hunting shotgun uses very specific reload time. See note (4). // // (7) FlameThrower penetration is non-existent since the multiplier is 0, // resulting in the tendril being destroyed after the first hit. Nonetheless, // penetration count is shown in case this is a bug that will be fixed. Take // this into account when using info.

// ? Add max charge stats for husk gun? // M79 can do more impact-head damage than explosive damage //============================================================================= class KFWeaponStatsMut extends Mutator;

struct StatInfo { var int iValue; var float fValue; var bool bValue; var string StrValue; var int Type; // 0 = int, 1 = float, 2 = bool, 3 = string, 4 = unknown (string) var bool bUsed;

// Stat-specific variables var bool bHeals; // Damage that heals, ie. healing dart 'damage' };

const MaxAmmoCountSub = 5; // 500 / 100

// Weapons var array< class<KFWeaponPickup> > AdditionalWeapons; var class<KFWeaponPickup> Pickup; var class<KFWeapon> Weapon; var class<WeaponFire> Fire[2]; var class<KFMonster> KFM;

static final preoperator StatInfo @(int A) { local StatInfo DI; DI.iValue = A; DI.Type = 0; DI.bUsed = true; return DI; }

static final preoperator StatInfo @(float A) { local StatInfo DI; DI.fValue = A; DI.Type = 1; DI.bUsed = true; return DI; }

static final preoperator StatInfo @(bool A) { local StatInfo DI; DI.bValue = A; DI.Type = 2; DI.bUsed = true; return DI; }

static final preoperator StatInfo @(string A) { local StatInfo DI; DI.StrValue = A; DI.Type = 3; DI.bUsed = true; return DI; }

static final function SetStatUnknown(out StatInfo A) { A.Type = 4; A.bUsed = true; }

static final function string GetStatString(StatInfo A) { if (A.Type == 0) return string(A.iValue); else if (A.Type == 1) return string(A.fValue); else if (A.Type == 2) return string(A.bValue); else if (A.Type == 3) return A.StrValue; else if (A.Type == 4) return "Unknown"; }

static final function ResetStat(out StatInfo A) { A.iValue = 0; A.fValue = 0; A.bValue = false; A.StrValue = ""; A.Type = 0; A.bUsed = false; A.bHeals = false; }

function OutputStat(string Title, StatInfo Stat, optional StatInfo AltStat) { local int A, x;

if (!Stat.bUsed && !AltStat.bUsed) return;

// Adjust stat name for alignment Title $= ":"; A = Len(Title); if (A < 16) { for (x = 0; x < 16 - A; x++) Title $= " "; }

if (Stat.bUsed) Title $= GetStatString(Stat); else Title $= "---"; if (AltStat.bUsed) Title $= " (" $ GetStatString(AltStat) $ ")";

Log(Title); }

/*

Fix flamethrower penetration detection

  • /

function PostBeginPlay() { // General local StatInfo Name; local StatInfo Perk; local StatInfo Cost; local StatInfo Weight; local StatInfo CanSellBuy;

// Ammo local StatInfo AmmoCost; local StatInfo AmmoCapacity[2]; local StatInfo MagazineCapacity[2];

// Damage local StatInfo Damage[2]; local StatInfo DamageRadius[2]; local StatInfo ImpactDamage[2]; local StatInfo HeadMultiplier[2];

// Firing characteristics local StatInfo FireRate[2]; local StatInfo ReloadTime[2]; local StatInfo Spread[2]; local StatInfo Pellets[2];

// Range and penetration local StatInfo MaxPens[2]; local StatInfo PenReduction[2]; local StatInfo Range[2]; local StatInfo RangeAngle[2];

// Misc local StatInfo RegenRate;

local int i, x; local array< class<Pickup> > WeaponList; local string t;

for (i = 0; i < ArrayCount(class'KFLevelRules'.default.ItemForSale); i++) if (class'KFLevelRules'.default.ItemForSale[i] != none) WeaponList[WeaponList.Length] = class'KFLevelRules'.default.ItemForSale[i];

for (i = 0; i < AdditionalWeapons.Length; i++) WeaponList[WeaponList.Length] = AdditionalWeapons[i];

// Main loop for (i = 0; i < WeaponList.Length; i++) { Pickup = class<KFWeaponPickup>(WeaponList[i]); if (Pickup != none) { Weapon = class<KFWeapon>(Pickup.default.InventoryType); if (Weapon != none) { for (x = 0; x < 2; x++) { if (Weapon.default.FireModeClass[x] != none && IsTrueFire(Weapon.default.FireModeClass[x])) Fire[x] = Weapon.default.FireModeClass[x]; else Fire[x] = none; }

// GetName(Name); GetPerk(Perk); GetCost(Cost); GetWeight(Weight); GetCanSellBuy(CanSellBuy); // GetAmmoCost(AmmoCost); GetAmmoCapacity(AmmoCapacity); GetMagazineCapacity(MagazineCapacity); // GetDamage(Damage); GetDamageRadius(DamageRadius); GetImpactDamage(ImpactDamage); GetHeadMultiplier(HeadMultiplier); // GetFireRate(FireRate); GetReloadTime(ReloadTime); GetSpread(Spread); GetPellets(Pellets); // GetMaxPens(MaxPens); GetPenReduction(PenReduction); GetRange(Range); // GetRangeAngle(RangeAngle); GetRegenRate(RegenRate); //

OutputStat("Name", Name); OutputStat("Perk", Perk); OutputStat("Cost", Cost); OutputStat("Weight", Weight); OutputStat("Can sell/buy", CanSellBuy);

OutputStat("Ammo cost", AmmoCost); OutputStat("Ammo capacity", AmmoCapacity[0], AmmoCapacity[1]); OutputStat("Mag capacity", MagazineCapacity[0], MagazineCapacity[1]);

OutputStat("Damage", Damage[0], Damage[1]); OutputStat("Impact damage", ImpactDamage[0], ImpactDamage[1]); OutputStat("Radius", DamageRadius[0], DamageRadius[1]); OutputStat("HS multiplier", HeadMultiplier[0], HeadMultiplier[1] );

OutputStat("Fire rate", FireRate[0], FireRate[1]); OutputStat("Reload sp", ReloadTime[0], ReloadTime[1]); OutputStat("Spread", Spread[0], Spread[1]); OutputStat("Pellets", Pellets[0], Pellets[1]);

OutputStat("Max pens", MaxPens[0], MaxPens[1]); OutputStat("Pen multiplier",PenReduction[0], PenReduction[1]); OutputStat("Range", Range[0], Range[1]); OutputStat("Range angle", RangeAngle[0], RangeAngle[1]);

OutputStat("Regen rate", RegenRate);

Log(""); } } }

Log(""); Log(""); }

/////////////////////////////////////////////////////////////////////////////// // WEAPON STAT CALCULATION ///////////////////////////////////////////////////////////////////////////////

// General

function GetName(out StatInfo Name) { Name =@ Pickup.default.ItemName; }

function GetPerk(out StatInfo Perk) { ResetStat(Perk); if (Pickup.default.CorrespondingPerkIndex < KFGameType(Level.Game).default.LoadedSkills.Length) Perk =@ KFGameType(Level.Game).default. LoadedSkills[Pickup.default.CorrespondingPerkIndex].default.VeterancyName; }

function GetCost(out StatInfo Cost) { ResetStat(Cost); if (Pickup.default.Cost != 0 && !Weapon.default.bKFNeverThrow) Cost =@ Pickup.default.Cost; }

function GetWeight(out StatInfo Weight) { ResetStat(Weight); if (Pickup.default.Weight != 0) Weight =@ int(Pickup.default.Weight); }

function GetCanSellBuy(out StatInfo CanSellBuy) { CanSellBuy =@ (Weapon.default.bKFNeverThrow == false); }

// Ammo

function GetAmmoCost(out StatInfo AmmoCost) { ResetStat(AmmoCost); if (!IsMeleeWeapon(Weapon)) AmmoCost =@ Pickup.default.AmmoCost; }

function GetAmmoCapacity(out StatInfo AmmoCapacity[2], optional int Index) { ResetStat(AmmoCapacity[Index]); if (Index == 0) GetAmmoCapacity(AmmoCapacity, 1);

if (Fire[Index] != none && !IsMeleeFire(Fire[Index]) && Fire[Index].default.AmmoClass != none) AmmoCapacity[Index] =@ Fire[Index].default.AmmoClass.default.MaxAmmo; }

function GetMagazineCapacity(out StatInfo MagazineCapacity[2]) { ResetStat(MagazineCapacity[0]); ResetStat(MagazineCapacity[1]);

if (!IsMeleeWeapon(Weapon)) MagazineCapacity[0] =@ Weapon.default.MagCapacity; // MagazineCapacity[1] =@ 0; // Can't be obtained normally }

// Damage

function GetDamage(out StatInfo Damage[2], optional int Index) { local int Dmg;

ResetStat(Damage[Index]); if (Index == 0) GetDamage(Damage, 1);

if (Fire[Index] != none) { if (class<InstantFire>(Fire[Index]) != none) // HITSCAN Dmg = class<InstantFire>(Fire[Index]).default.DamageMax; else if(class<BaseProjectileFire>(Fire[Index]) != none) // PROJECTILE { if (class<KFMedicGun>(Weapon) != none) { Dmg = class<KFMedicGun>(Weapon).default.HealBoostAmount; Damage[Index].bHeals = true; } else Dmg = int(class<BaseProjectileFire>(Fire[Index]).default.ProjectileClass.default.Damage); } else if (class<KFMeleeFire>(Fire[Index]) != none) // MELEE { Dmg = class<KFMeleeFire>(Fire[Index]).default.MeleeDamage; }

if (Dmg != 0) { if (Damage[Index].bHeals) Damage[Index] =@ ("+" $ Dmg); else Damage[Index] =@ Dmg; } } }

function GetDamageRadius(out StatInfo DamageRadius[2], optional int Index) { local class<DamageType> DT;

ResetStat(DamageRadius[Index]); if (Index == 0) GetDamageRadius(DamageRadius, 1);

if (Fire[Index] != none && class<BaseProjectileFire>(Fire[Index]) != none) { DT = class<BaseProjectileFire>(Fire[Index]).default.ProjectileClass.default.MyDamageType; if (class<KFWeaponDamageType>(DT) != none) { if (class<KFWeaponDamageType>(DT).default.bIsExplosive) DamageRadius[Index] =@ class<BaseProjectileFire>(Fire[Index]).default.ProjectileClass.default.DamageRadius; } } }

function GetImpactDamage(out StatInfo Damage[2], optional int Index) { local class<Projectile> P;

ResetStat(Damage[Index]); if (Index == 0) GetImpactDamage(Damage, 1);

if (Fire[Index] != none) { if (class<BaseProjectileFire>(Fire[Index]) != none) { P = class<BaseProjectileFire>(Fire[Index]).default.ProjectileClass; if (class<LAWProj>(P) != none && P != class'ZEDGunProjectile') Damage[Index] =@ class<LAWProj>(P).default.ImpactDamage; else if (class<M79GrenadeProjectile>(P) != none) Damage[Index] =@ class<M79GrenadeProjectile>(P).default.ImpactDamage; } } }

function GetHeadMultiplier(out StatInfo HeadMultiplier[2], optional int Index) { local class<Projectile> P; local float Multi;

ResetStat(HeadMultiplier[Index]); if (Index == 0) GetHeadMultiplier(HeadMultiplier, 1);

if (Fire[Index] != none) { if (class<InstantFire>(Fire[Index]) != none) // HITSCAN { if (class<KFWeaponDamageType>(class<InstantFire>(Fire[Index]).default.DamageType) != none) Multi = class<KFWeaponDamageType>( class<InstantFire>(Fire[Index]).default.DamageType).default.HeadShotDamageMult; } else if (class<BaseProjectileFire>(Fire[Index]) != none) // PROJECTILE { P = class<BaseProjectileFire>(Fire[Index]).default.ProjectileClass;

if (P == class'CrossbuzzsawBlade') // Buzzsaw bow Multi = class<CrossbuzzsawBlade>(P).default.HeadShotDamageMult; else if (P == class'CrossbowArrow') // CROSSBOW Multi = class<CrossbowArrow>(P).default.HeadShotDamageMult; else if (P == class'M99Bullet') // M99 Multi = class<M99Bullet>(P).default.HeadShotDamageMult; else if (IsKFDamageType(P.default.MyDamageType)) // ANY OTHER PROJECTILES USE DAMAGETYPE { if (CheckForHeadShots(P.default.MyDamageType)) Multi = class<KFWeaponDamageType>(P.default.MyDamageType).default.HeadShotDamageMult; else // Handle impact damage headshot multiplier if available { if (class<LAWProj>(P) != none && P != class'ZEDGunProjectile') Multi = class<KFWeaponDamageType>(class<LAWProj>(P).default.ImpactDamageType).default.HeadShotDamageMult; else if (class<M79GrenadeProjectile>(P) != none && P != class'ZEDGunProjectile') Multi = class<KFWeaponDamageType>(class<M79GrenadeProjectile>(P).default.ImpactDamageType).default.HeadShotDamageMult; } } } else if (class<KFMeleeFire>(Fire[Index]) != none) // MELEE { if (class<DamTypeMelee>(class<KFMeleeFire>(Fire[Index]).default.hitDamageClass) != none) Multi = class<DamTypeMelee>(class<KFMeleeFire>(Fire[Index]).default.hitDamageClass).default.HeadShotDamageMult; }

if (Multi != 0 && Multi != 1.0) HeadMultiplier[Index] =@ (int(Multi * 100.0) $ "%"); } }

// Firing characteristics

function GetFireRate(out StatInfo FireRate[2], optional int Index) { ResetStat(FireRate[Index]); if (Index == 0) GetFirerate(FireRate, 1);

if (IsAutoReloadingWeapon(Weapon)) // (4) Weapons that fire-reload in one animation // Do nothing ; else { if (Fire[Index] != none) FireRate[Index] =@ Fire[Index].default.FireRate; } }

function GetReloadTime(out StatInfo ReloadTime[2], optional int Index) { ResetStat(ReloadTime[Index]); if (Index == 0) GetReloadTime(ReloadTime, 1); // (3)

if (Index == 1 && Weapon != class'BoomStick') return; // Only do 2nd reload time for hunting shotgun

if (IsAutoReloadingWeapon(Weapon)) // (4) Weapons that fire-reload in one animation { if (Weapon == class'BoomStick') // (5) Hunting shotgun is special case { if (Fire[Index] == class'BoomStickAltFire') ReloadTime[Index] =@ FMax(class'BoomStickAltFire'.default.FireLastRate, class'BoomStick'.default.ReloadCountDown); else ReloadTime[Index] =@ FMax(class'BoomStickFire'.default.FireRate, class'BoomStick'.default.ReloadCountDown); } else if (Fire[Index] != none) ReloadTime[Index] =@ Fire[Index].default.FireRate; } else if (!IsMeleeFire(Fire[Index])) { if (Fire[Index] != none) ReloadTime[Index] =@ Weapon.default.ReloadRate; } }

function GetSpread(out StatInfo Spread[2], optional int Index) { ResetStat(Spread[Index]); if (Index == 0) GetSpread(Spread, 1);

if (Fire[Index] != none && Fire[Index].default.Spread != 0) Spread[Index] =@ Fire[Index].default.Spread; }

function GetPellets(out StatInfo Pellets[2], optional int Index) { ResetStat(Pellets[Index]); if (Index == 0) GetPellets(Pellets, 1);

if (Fire[Index] != none && class<KFShotgunFire>(Fire[Index]) != none) { if (IgnoresLoad(Fire[Index])) // (1) see note Pellets[Index] =@ class<BaseProjectileFire>(Fire[Index]).default.ProjPerFire; else Pellets[Index] =@ ( class<BaseProjectileFire>(Fire[Index]).default.ProjPerFire * class<BaseProjectileFire>(Fire[Index]).default.AmmoPerFire ); } }

// Range and penetration

function GetMaxPens(out StatInfo MaxPens[2], optional int Index) { local class<Projectile> P;

ResetStat(MaxPens[Index]); if (Index == 0) GetMaxPens(MaxPens, 1);

if (class<InstantFire>(Fire[Index]) != none) // HITSCAN { // (2) Deagle, MK23, 44Magnum } else if (class<BaseProjectileFire>(Fire[Index]) != none) // PROJECTILE { P = class<BaseProjectileFire>(Fire[Index]).default.ProjectileClass;

if (class<ShotgunBullet>(P) != none) MaxPens[Index] =@ class<ShotgunBullet>(P).default.MaxPenetrations; // (7) } }

function GetPenReduction(out StatInfo PenReduction[2], optional int Index) { local class<Projectile> P;

ResetStat(PenReduction[Index]); if (Index == 0) GetPenReduction(PenReduction, 1);

if (class<BaseProjectileFire>(Fire[Index]) != none) { P = class<BaseProjectileFire>(Fire[Index]).default.ProjectileClass; if (class<ShotgunBullet>(P) != none) PenReduction[Index] =@ class<ShotgunBullet>(P).default.PenDamageReduction; } }

function GetRange(out StatInfo Range[2], optional int Index) { ResetStat(Range[Index]); if (Index == 0) GetRange(Range, 1);

if (class<KFMeleeFire>(Fire[Index]) != none) Range[Index] =@ class<KFMeleeFire>(Fire[Index]).default.WeaponRange; }

function GetRangeAngle(out StatInfo RangeAngle[2], optional int Index) { local float Angle; ResetStat(RangeAngle[Index]); if (Index == 0) GetRangeAngle(RangeAngle, 1);

if (class<KFMeleeFire>(Fire[Index]) != none) { Angle = class<KFMeleeFire>(Fire[Index]).default.WideDamageMinHitAngle; if (Angle != 0) { Angle = acos(Angle) * (180.0 / Pi) * 2.0; RangeAngle[Index] =@ (Angle $ "°"); } } }

// Misc

function GetRegenRate(out StatInfo RegenRate) { local float RR; ResetStat(RegenRate);

if (class<KFMedicGun>(Weapon) != none) { RR = class<KFMedicGun>(Weapon).default.AmmoRegenRate; RegenRate =@ (((1.0 / RR) * 10) / (500 / 100)); } }

/////////////////////////////////////////////////////////////////////////////// // UTILITIES ///////////////////////////////////////////////////////////////////////////////

function bool IsTrueFire(class<WeaponFire> Fire) // Returns false for flashlight/nofire/etc fire modes { if ( Fire == class'NoFire' || Fire == class'ShotgunLightFire' || Fire == class'SingleALTFire' || Fire == class'NailGunAltFire' || Fire == none ) return false;

return true; }

function bool IsMeleeFire(class<WeaponFire> Fire) { if (Fire == none) return false; if (class<KFMeleeFire>(Fire) != none) return true; return false; }

function bool IsMeleeWeapon(class<KFWeapon> Weapon) { if (class<KFMeleeGun>(Weapon) != none) return true; else return false; }

function bool IgnoresLoad(class<WeaponFire> Fire) { if (class<MP7MAltFire>(Fire) != none || class<M7A3MAltFire>(Fire) != none)

return true;

return false; }

function bool IsAutoReloadingWeapon(class<Weapon> WeaponClass) // (4) { if (WeaponClass == class'KFMod.Boomstick') return true; else if (WeaponClass == class'KFMod.M79GrenadeLauncher') return true; else if (WeaponClass == class'KFMod.Crossbow') return true; else if (WeaponClass == class'KFMod.LAW') return true; else if (WeaponClass == class'KFMod.M99SniperRifle') return true; else if (WeaponClass == class'KFMod.Crossbuzzsaw') return true;

return false; }

function bool CheckForHeadShots(class<DamageType> DT) { if (class<KFWeaponDamageType>(DT) != none) { if (!class<KFWeaponDamageType>(DT).default.bCheckForHeadShots) return false; if (class<DamTypeBurned>(DT) != none || class<DamTypeFlameThrower>(DT) != none) return false;

return true; } return false; }

function bool IsKFDamageType(class<DamageType> DT) { if (DT == none || class<KFWeaponDamageType>(DT) == none) return false;

return true; }

defaultproperties { GroupName="KFWeaponStatsMut" FriendlyName"Generate Weapon Stats 0.1" Description="Generates a list of weapon statistics and outputs it to the log." }