Here's a little something I whipped up to output a list of weapon/specimen values to the log. It's meant for the standard weapons however you can add custom to the AdditionalWeapons array.
I've written it to get an accurate set of stats for the official KF wiki so if anyone sees any problems with it, please let me know.
A copy is stored here on the wiki itself.
I've written it to get an accurate set of stats for the official KF wiki so if anyone sees any problems with it, please let me know.
A copy is stored here on the wiki itself.
Code:
//=============================================================================
// Weapon stat generator 25-October-2012 Benjamin
//
// KNOWN ISSUES
//
// * Cannot detect where different fire modes share the same
// ammo (boomstick)
//
// * Cannot detect penetration for all weapons, must check manually.
//
// ===== INFO
//
// HEAD SHOTS
//
// Every weapon's head shot 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.
//
// NOTES
//
// (1) Certain 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 - ReloadCountDown stored in BoomStick.uc
// 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)
//=============================================================================
class KFStatOutputMut extends Mutator;
// Weapons
var array< class<KFWeaponPickup> > AdditionalWeapons;
var class<KFWeaponPickup> Pickup;
var class<KFWeapon> Weapon;
var class<WeaponFire> Fire[2];
var class<KFMonster> KFM;
// Specimens
var int SpecimenCount;
var array<float> GameDifficulty;
var array<float> BountyMultiplier;
var array<float> HealthMultiplier;
var array<float> HeadHealthMultiplier;
var array<float> SpeedMultiplier;
var array<float> DamageMultiplier;
function OutputStatNum(string Title, float Stat, optional float AltStat)
{
OutputStat(Title, string(Stat), string(AltStat));
}
function OutputStat(string Title, string Stat, optional string AltStat)
{
local int A, x;
// Adjust stat name
Title $= ":";
A = Len(Title);
if (A < 14)
{
for (x = 0; x < 14 - A; x++)
Title $= " ";
}
if (Fire[1] != none && AltStat != "")
Log(Title $ Stat @ "(" $ AltStat $ ")");
else
Log(Title $ Stat);
}
function GenerateWeaponStats()
{
local string Name;
local string Perk;
local int Cost;
local int AmmoCost;
local int Weight;
local int Capacity[2];
local int MagazineSize[2];
local float FireRate[2];
local float ReloadSpeed[2];
local float Spread[2];
local int Damage[2];
local float DamageRadius[2];
local float HeadMultiplier[2];
local int Pellets[2];
local int MaxPens[2];
local float PenReduction[2];
local float Range;
local int i, x;
local array< class<Pickup> > WeaponList;
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);
GetAmmoCost(AmmoCost);
GetWeight(Weight);
GetCapacity(Capacity);
GetMagazineSize(MagazineSize);
GetFireRate(FireRate);
GetReloadSpeed(ReloadSpeed);
GetSpread(Spread);
GetDamage(Damage);
GetDamageRadius(DamageRadius);
GetHeadMultiplier(HeadMultiplier);
GetPellets(Pellets);
GetMaxPens(MaxPens);
GetPenReduction(PenReduction);
GetRange(Range);
OutputStat("Name", Name);
OutputStat("Perk", Perk);
OutputStatNum("Cost", Cost);
OutputStatNum("Weight", Weight);
OutputStatNum("Ammo cost", AmmoCost);
OutputStatNum("Capacity", Capacity[0], Capacity[1]);
OutputStatNum("Magazine", MagazineSize[0], MagazineSize[1]);
OutputStatNum("Damage", Damage[0], Damage[1]);
OutputStatNum("Radius", DamageRadius[0], DamageRadius[0]);
OutputStatNum("Head", HeadMultiplier[0], HeadMultiplier[1] );
OutputStatNum("Pellets", Pellets[0], Pellets[1]);
OutputStatNum("Spread", Spread[0], Spread[1]);
OutputStatNum("Max pens", MaxPens[0], MaxPens[1]);
OutputStatNum("Pen reduc", PenReduction[0], PenReduction[1]);
OutputStatNum("Fire rate", FireRate[0], FireRate[1]);
OutputStatNum("Reload sp", ReloadSpeed[0], ReloadSpeed[1]);
Log("");
}
}
}
Log("");
Log("");
}
function GenerateSpecimenStats()
{
local string Name;
local int Bounty[5];
local int Health[5];
local float HeadHealth[5];
local float Speed[5];
local int Damage[5];
local float Range;
local KFGameType KF;
local int i;
KF = KFGameType(Level.Game);
Log("===========================================================");
Log("SPECIMEN STATS ============================================");
Log("===========================================================");
SpecimenCount = KF.StandardMonsterClasses.Length;
for (i = 0; i < SpecimenCount; i++)
{
// Log("Specimen #" $ i $ ":" @ class'KFGameType'.default.StandardMonsterClasses[i].MClassName);
KFM = class<KFMonster>(DynamicLoadObject(class'KFGameType'.default.
StandardMonsterClasses[i].MClassName, class'class'));
GetSpecimenName(Name);
GetSpecimenBounty(Bounty);
GetSpecimenHealth(Health);
GetSpecimenHeadHealth(HeadHealth);
GetSpecimenSpeed(Speed);
GetSpecimenDamage(Damage);
GetSpecimenRange(Range);
Log("Name: " $ Name);
Log("Bounty: " $ Bounty[0] @ Bounty[1] @ Bounty[2] @ Bounty[3] @ Bounty[4]);
Log("Health: " $ Health[0] @ Health[1] @ Health[2] @ Health[3] @ Health[4]);
Log("HeadHealth: " $ HeadHealth[0] @ HeadHealth[1] @ HeadHealth[2] @ HeadHealth[3] @ HeadHealth[4]);
Log("Speed: " $ Speed[0] @ Speed[1] @ Speed[2] @ Speed[3] @ Speed[4]);
Log("Damage: " $ Damage[0] @ Damage[1] @ Damage[2] @ Damage[3] @ Damage[4]);
Log("Range: " $ Range);
Log("");
}
Log("");
}
function PostBeginPlay()
{
GenerateWeaponStats();
GenerateSpecimenStats();
}
///////////////////////////////////////////////////////////////////////////////
// SPECIMEN STAT CALCULATION
///////////////////////////////////////////////////////////////////////////////
function GetSpecimenName(out string Name)
{
Name = KFM.default.MenuName;
}
function GetSpecimenBounty(out int KillScore[5])
{
local int i;
for (i = 0; i < 5; i++)
KillScore[i] = Max(1, int(KFM.default.ScoringValue * BountyMultiplier[i]));
}
function GetSpecimenHealth(out int Health[5])
{
local int i;
for (i = 0; i < 5; i++)
Health[i] = Ceil(KFM.default.Health * HealthMultiplier[i]);
}
function GetSpecimenHeadHealth(out float HeadHealth[5])
{
local int i;
for (i = 0; i < 5; i++)
HeadHealth[i] = Ceil(KFM.default.HeadHealth * HeadHealthMultiplier[i]);
}
function GetSpecimenSpeed(out float Speed[5])
{
local int i;
for (i = 0; i < 5; i++)
Speed[i] = KFM.default.GroundSpeed * SpeedMultiplier[i];
}
function GetSpecimenDamage(out int Damage[5])
{
local int i;
for (i = 0; i < 5; i++)
Damage[i] = KFM.default.MeleeDamage * DamageMultiplier[i];
}
function GetSpecimenRange(out float Range)
{
Range = KFM.default.MeleeRange;
}
///////////////////////////////////////////////////////////////////////////////
// WEAPON STAT CALCULATION
///////////////////////////////////////////////////////////////////////////////
function GetName(out string Name)
{
Name = Pickup.default.ItemName;
}
function GetPerk(out string Perk)
{
Perk = KFGameType(Level.Game).default.
LoadedSkills[Pickup.default.CorrespondingPerkIndex].default.VeterancyName;
}
function GetCost(out int Cost)
{
Cost = Pickup.default.Cost;
}
function GetAmmoCost(out int AmmoCost)
{
AmmoCost = Pickup.default.AmmoCost;
}
function GetWeight(out int Weight)
{
Weight = Pickup.default.Weight;
}
function GetCapacity(out int Capacity[2], optional int Index)
{
Capacity[Index] = 0;
if (Index == 0) GetCapacity(Capacity, 1);
if (Fire[Index] != none && !IsMeleeFire(Fire[Index]) && Fire[Index].default.AmmoClass != none)
Capacity[Index] = Fire[Index].default.AmmoClass.default.MaxAmmo;
}
function GetMagazineSize(out int MagazineSize[2])
{
MagazineSize[0] = Weapon.default.MagCapacity;
MagazineSize[1] = 0; // Can't be obtained normally
}
function GetFireRate(out float FireRate[2], optional int Index)
{
FireRate[Index] = 0;
if (Index == 0) GetFirerate(FireRate, 1);
if (IsAutoReloadingWeapon(Weapon)) // (4) Weapons that fire-reload in one animation
FireRate[Index] = 0;
else
{
if (Fire[Index] != none)
FireRate[Index] = Fire[Index].default.FireRate;
}
}
function GetReloadSpeed(out float ReloadSpeed[2], optional int Index)
{
ReloadSpeed[Index] = 0;
if (Index == 0) GetReloadSpeed(ReloadSpeed, 1);
if (IsAutoReloadingWeapon(Weapon)) // (4) Weapons that fire-reload in one animation
if (Fire[Index] != none)
ReloadSpeed[Index] = Fire[Index].default.FireRate;
else
{
if (Fire[Index] != none)
ReloadSpeed[Index] = Weapon.default.ReloadRate; // (3)
}
}
function GetSpread(out float Spread[2], optional int Index)
{
Spread[Index] = 0;
if (Index == 0) GetSpread(Spread, 1);
if (Fire[Index] != none)
Spread[Index] = Fire[Index].default.Spread;
}
function GetDamage(out int Damage[2], optional int Index)
{
Damage[Index] = 0;
if (Index == 0) GetDamage(Damage, 1);
if (Fire[Index] != none)
{
if (class<InstantFire>(Fire[Index]) != none) // HITSCAN
Damage[Index] = class<InstantFire>(Fire[Index]).default.DamageMax;
else if(class<BaseProjectileFire>(Fire[Index]) != none) // PROJECTILE
{
if (class<MP7MMedicGun>(Weapon) != none)
Damage[Index] = class<MP7MMedicGun>(Weapon).default.HealBoostAmount;
else if (class<M7A3MMedicGun>(Weapon) != none)
Damage[Index] = class<M7A3MMedicGun>(Weapon).default.HealBoostAmount;
else
Damage[Index] = class<BaseProjectileFire>(Fire[Index]).default.ProjectileClass.default.Damage;
}
else if (class<KFMeleeFire>(Fire[Index]) != none) // MELEE
{
Damage[Index] = class<KFMeleeFire>(Fire[Index]).default.DamageConst +
class<KFMeleeFire>(Fire[Index]).default.MaxAdditionalDamage;
}
}
}
function GetDamageRadius(out float DamageRadius[2], optional int Index)
{
local class<DamageType> DT;
DamageRadius[Index] = 0;
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 GetHeadMultiplier(out float HeadMultiplier[2], optional int Index)
{
local class<Projectile> P;
HeadMultiplier[Index] = 1.0;
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)
HeadMultiplier[Index] = 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
HeadMultiplier[Index] = class<CrossbuzzsawBlade>(P).default.HeadShotDamageMult;
else if (P == class'CrossbowArrow') // CROSSBOW
HeadMultiplier[Index] = class<CrossbowArrow>(P).default.HeadShotDamageMult;
else if (P == class'M99Bullet') // M99
HeadMultiplier[Index] = class<M99Bullet>(P).default.HeadShotDamageMult;
else if ( P.default.MyDamageType != none &&
class<KFWeaponDamageType>(P.default.MyDamageType) != none ) // ANY OTHER PROJECTILES USE DAMAGETYPE
HeadMultiplier[Index] = class<KFWeaponDamageType>(P.default.MyDamageType).default.HeadShotDamageMult;
}
else if (class<KFMeleeFire>(Fire[Index]) != none) // MELEE
{
if (class<DamTypeMelee>(class<KFMeleeFire>(Fire[Index]).default.hitDamageClass) != none)
HeadMultiplier[Index] = class<DamTypeMelee>(class<KFMeleeFire>(Fire[Index]).default.hitDamageClass).default.HeadShotDamageMult;
}
}
}
function GetPellets(out int Pellets[2], optional int Index)
{
Pellets[Index] = 0;
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;
}
}
function GetMaxPens(out int MaxPens[2], optional int Index)
{
local class<Projectile> P;
MaxPens[Index] = 0;
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;
}
}
function GetPenReduction(out float PenReduction[2], optional int Index)
{
local class<Projectile> P;
PenReduction[Index] = 0;
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 float Range)
{
if (class<KFMeleeFire>(Fire[0]) != none)
Range = class<KFMeleeFire>(Fire[0]).default.WeaponRange;
else
Range = 0;
}
// Utilities
function bool IsTrueFire(class<WeaponFire> Fire)
{
if (Fire.Name == 'NoFire'
|| Fire.Name == 'ShotgunLightFire'
|| Fire.Name == 'SingleALTFire'
)
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 IgnoresLoad(class<WeaponFire> Fire)
{
if (class<MP7MAltFire>(Fire) != none
|| class<M7A3MAltFire>(Fire) != none)
return true;
return false;
}
function bool IsAutoReloadingWeapon(class<Weapon> Weapon) // (4)
{
if (Weapon == class'KFMod.Boomstick')
return true;
else if (Weapon == class'KFMod.M79GrenadeLauncher')
return true;
else if (Weapon == class'KFMod.Crossbow')
return true;
else if (Weapon == class'KFMod.LAW')
return true;
else if (Weapon == class'KFMod.M99SniperRifle')
return true;
else if (Weapon == class'KFMod.Crossbuzzsaw')
return true;
return false;
}
defaultproperties
{
GroupName="KFStatOutputMut"
FriendlyName"KFStatOutput"
Description="..."
GameDifficulty=(1.0, 2.0, 4.0, 5.0, 7.0)
BountyMultiplier=(2.0, 1.0, 0.85, 0.65, 0.65)
HealthMultiplier=(0.5, 1.0, 1.35, 1.55, 1.75)
HeadHealthMultiplier=(0.5, 1.0, 1.35, 1.55, 1.75)
SpeedMultiplier=(0.95, 1.0, 1.15, 1.22, 1.3)
DamageMultiplier=(0.3, 1.0, 1.25, 1.50, 1.75)
}
Last edited: