KFWeaponStats

From Tripwire Interactive Wiki
Revision as of 13:09, 22 December 2012 by Benjamin (talk | contribs) (Created page with "Here's a new statistic generator, specifically for weapons. It is used for the information on the weapon pages. It supersedes KFStatOutput. --~~...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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)

:

//============================================================================= // 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. // // (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' };

// 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) { ResetStat(RegenRate);

if (class<KFMedicGun>(Weapon) != none) RegenRate =@ ((1.0 / class<KFMedicGun>(Weapon).default.AmmoRegenRate) * 10.0); }

/////////////////////////////////////////////////////////////////////////////// // 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." }