• Please make sure you are familiar with the forum rules. You can find them here: https://forums.tripwireinteractive.com/index.php?threads/forum-rules.2334636/

Code Weapon stat generator (with source)

Benjamin

Grizzled Veteran
May 17, 2009
3,630
619
France
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.

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: