Static class implementing the Deadshot perk augments for NZombies. Defines tunable values, checks player augment ownership, applies effects like Lucky Shot promotion, Deadeye/Focus headshot scaling, First Blood damage on fresh targets, Concussion stun, Recycler ammo refund, Cranial Detonation AOE, HUD/reporting and console commands to inspect and change values.
using Sandbox;
using System;
using System.Linq;
namespace NZombies;
/// <summary>
/// Deadshot Daiquiri's augments. Base perk: ADS time, ADS recoil and spread all ×0.5.
///
/// | id | effect | status |
/// |----|--------|--------|
/// | M1 Deadeye | headshot damage ×1.5 | ⚠ was ×2 |
/// | M2 First Blood | ANY hit on a full-HP zombie deals ×3 | ⚠ no longer headshot-gated |
/// | M3 Cranial Detonation | a headshot KILL splashes 10% of the damage dealt | ⚠ redesigned |
/// | M4 Focus | +15% headshot damage per headshot kill, up to ×5 | ⚠ was capped at +150% |
/// | m1 Lucky Shot | 10% of hits count as headshots | ⚠ NEW — replaced Steady Hands |
/// | m2 Trophy | +25 points per headshot kill | as the original |
/// | m3 Concussion | 25% chance a headshot stuns 0.8–1.2s | as the original |
/// | m4 Hip Precision | hip-fire spread ×0.65, ADS untouched | as the original |
/// | m5 Recycler | headshot kills refund 2 rounds | as the original |
///
/// ⛔ M3 WAS REDESIGNED BECAUSE HALF OF THE ORIGINAL WAS UNREACHABLE. Its first clause is
/// *"head-pop meter builds 2× faster"* — and that meter does not exist in this project.
/// `PERK_BASE_EFFECTS.md` lists Deadshot's base as carrying a 25%-capped headshot-explosion
/// chance (`nz.DeadshotChance`), but nothing in the code implements it, so there was nothing
/// to accelerate. The blast half is now unconditional and scales off the killing hit rather
/// than off a round-health curve, which also means it cannot outgrow the weapon that fired it.
///
/// ⛔ m1 WAS REPLACED BECAUSE IT DUPLICATED SPEED COLA. "Steady Hands" was faster ADS settle
/// — which Speed Cola's m3 Sleight of Hand already does, on the same multiplier. Two perks
/// selling one stat is a worse outcome than a gap. Its sway half was never wired in GMod
/// either (its own note: the cross-base system exposes no `SwayMult` key), and sway here
/// exists only as sniper-scope idle drift, so nothing was lost.
///
/// ⚠️ m1 "LUCKY SHOT" DELIBERATELY AMPLIFIES EVERY OTHER AUGMENT HERE. A proc'd hit is
/// marked as a real headshot at the one place headshots are decided, so Focus counts it,
/// Trophy pays for it, Recycler refunds on it and Cranial Detonation splashes from it. That
/// is the point of putting it on Deadshot rather than anywhere else.
/// </summary>
public static class DeadshotAugments
{
const string Perk = "deadshot";
// ── tuning ───────────────────────────────────────────────────────────────
/// <summary>M1 Deadeye — headshot damage multiplier. ×1.5.</summary>
public static float DeadeyeHeadshot { get; set; } = 1.5f;
/// <summary>
/// M2 First Blood — multiplier on a hit against an undamaged zombie. ×3.
///
/// ⛔ NOT HEADSHOT-GATED, unlike the original. Its test was
/// `headshot AND health >= maxhealth`; this one drops the headshot half by request, so
/// any first hit on a fresh zombie triples. That makes it a genuinely different major
/// from Deadeye rather than a bigger version of it — Deadeye rewards aim, First Blood
/// rewards opening on new targets.
/// </summary>
public static float FirstBloodMultiplier { get; set; } = 3f;
/// <summary>
/// M3 Cranial Detonation — the fraction of the killing hit splashed to everything nearby.
///
/// ⚠️ A FRACTION OF THE DAMAGE DEALT, not a round-health curve. The original computed
/// `roundHealth * 0.5 + 150`, which is independent of the weapon — a pistol headshot and
/// a Pack-a-Punched LMG headshot produced the same blast. Scaling off the hit means the
/// augment tracks whatever gun you are actually holding.
/// </summary>
public static float DetonationShare { get; set; } = 0.10f;
/// <summary>M3 Cranial Detonation — blast radius. The original's 260.</summary>
public static float DetonationRadius { get; set; } = 260f;
/// <summary>M4 Focus — headshot damage gained per consecutive headshot kill. +15%.</summary>
public static float FocusPerHeadshot { get; set; } = 0.15f;
// ⛔ PER HEADSHOT, NOT PER HEADSHOT KILL, and the asymmetry is the design: it builds
// on every head HIT and is only thrown away by a non-headshot KILL. Counting kills made
// the bar crawl - 27 head kills to reach the cap - and made it invisible against anything
// that takes more than one shot to drop, which by the late rounds is everything.
/// <summary>
/// M4 Focus — the ceiling on the headshot multiplier. ×5.
///
/// ⚠️ ADDITIVE STEPS AGAINST A MULTIPLICATIVE CAP, so the streak needed is
/// `(5 - 1) / 0.15` ≈ **27 headshot kills**. That is a long build on purpose — a
/// compounding 1.15^n would reach ×5 in twelve and make the cap almost incidental.
/// </summary>
public static float FocusMax { get; set; } = 5f;
/// <summary>m1 Lucky Shot — chance a hit is treated as a headshot. 10%.</summary>
public static float LuckyShotChance { get; set; } = 0.10f;
/// <summary>m2 Trophy — bonus points per headshot kill.</summary>
public static int TrophyPoints { get; set; } = 25;
/// <summary>m3 Concussion — chance a headshot stuns. 25%.</summary>
public static float ConcussionChance { get; set; } = 0.25f;
/// <summary>m3 Concussion — stun duration bounds, seconds.</summary>
public static float ConcussionMin { get; set; } = 0.8f;
public static float ConcussionMax { get; set; } = 1.2f;
/// <summary>m4 Hip Precision — hip-fire spread multiplier. 0.65.</summary>
public static float HipPrecisionSpread { get; set; } = 0.65f;
/// <summary>m5 Recycler — rounds refunded per headshot kill.</summary>
public static int RecyclerRounds { get; set; } = 2;
// ── helpers ──────────────────────────────────────────────────────────────
static bool Has( NZPlayer p, string augId )
=> p.IsValid() && p.HasPerk( Perk ) && PerkAugments.Has( p, Perk, augId );
static NZPlayer PlayerOf( GameObject attacker )
=> attacker.IsValid()
? attacker.Components.Get<NZPlayer>( FindMode.EverythingInSelfAndAncestors )
: null;
// ── m1 Lucky Shot ────────────────────────────────────────────────────────
/// <summary>
/// Roll m1 Lucky Shot — should this non-headshot hit be promoted to one.
///
/// ⛔ CALLED AT THE ONE PLACE HEADSHOTS ARE DECIDED (`Health.OnDamage`'s
/// `var headshot = ...`), which is what makes the promotion complete rather than
/// cosmetic. Everything downstream — the head damage product, Focus's streak, Trophy's
/// points, Recycler's refund, Cranial Detonation's blast and the 100-point kill award —
/// reads that one local. Scaling the damage instead would have given the multiplier and
/// none of the rest.
///
/// ⚠️ ONLY PROMOTES, NEVER DEMOTES. A real headshot is already a headshot; rolling on it
/// too would mean a 90% chance of downgrading one, which is the opposite augment.
/// </summary>
public static bool RollLuckyShot( GameObject attacker )
=> Has( PlayerOf( attacker ), "m1" )
&& Game.Random.Float() < LuckyShotChance;
// ── M1 · M4 · the head damage product ────────────────────────────────────
/// <summary>
/// Multiplier on the HEADSHOT product from M1 Deadeye and M4 Focus.
///
/// ⚠️ IT RIDES THE EXISTING PRODUCT rather than replacing it, exactly as Death
/// Perception, Precision Rounds and Deadeye already do on that line — so a Deadshot
/// player still gets `HeadshotDamageScale` and every node they own.
/// </summary>
public static float HeadshotScale( GameObject attacker )
{
var p = PlayerOf( attacker );
if ( !p.IsValid() || !p.HasPerk( Perk ) ) return 1f;
var mult = 1f;
if ( Has( p, "M1" ) ) mult *= DeadeyeHeadshot;
if ( Has( p, "M4" ) ) mult *= FocusMultiplier( p );
return mult;
}
/// <summary>
/// M4 Focus's current multiplier for a player. 1 at no streak.
///
/// ⚠️ CLAMPED AT BOTH ENDS. The floor of 1 matters because `FocusMax` is settable and a
/// console value below 1 would otherwise make a streak REDUCE headshot damage.
/// </summary>
public static float FocusMultiplier( NZPlayer player )
{
if ( !player.IsValid() ) return 1f;
return MathX.Clamp( 1f + player.DeadshotFocus * FocusPerHeadshot,
1f, MathF.Max( 1f, FocusMax ) );
}
/// <summary>How far along Focus is, 0-1. For the HUD bar.</summary>
public static float FocusProgress( NZPlayer player )
{
var span = MathF.Max( 0.01f, MathF.Max( 1f, FocusMax ) - 1f );
return MathX.Clamp( (FocusMultiplier( player ) - 1f) / span, 0f, 1f );
}
/// <summary>Headshot kills needed to reach the cap from nothing.</summary>
public static int FocusHitsToCap
=> FocusPerHeadshot <= 0f
? 0
: (int)MathF.Ceiling( (MathF.Max( 1f, FocusMax ) - 1f) / FocusPerHeadshot );
// ── M2 First Blood ───────────────────────────────────────────────────────
/// <summary>
/// Multiplier on a hit against a zombie at full health.
///
/// ⚠️ `>=`, NOT `==`. Health is a float and a zombie topped up by a round-scaling
/// `Reset( max )` can land a hair above its own maximum; an equality test would make
/// the augment fire on some spawns and not others with nothing to distinguish them.
///
/// ⚠️ TAKES THE VICTIM'S NUMBERS rather than looking them up, because the one caller is
/// inside the victim's own Health component and already has them.
/// </summary>
public static float FirstBloodScale( GameObject attacker, float current, float max )
{
// ⚠️ THE VICTIM TEST STAYS WHEREVER THIS IS CALLED, and it is only meaningful on the
// HOST — a client's copy of a zombie is never damaged locally, so `current` there is
// permanently `max`. This method is deliberately no longer called from the client's
// pre-multiply; see `Health.AttackerScale`.
if ( max <= 0f || current < max ) return 1f;
return ScaleFor( PlayerOf( attacker ), FirstBloodScaleLocal, p => p.FirstBloodLuck );
}
/// <summary>
/// A multiplier owned by a player, from wherever the truth for that player is.
///
/// ⚠️ THE SAME SHAPE AS `DeathAugments.ScaleFor`, duplicated rather than shared because the
/// two read different synced fields and a shared helper would need the field passed in anyway.
/// </summary>
static float ScaleFor( NZPlayer player, Func<NZPlayer, float> local,
Func<NZPlayer, float> published )
{
if ( !player.IsValid() ) return 1f;
return Networking.IsActive && PlayerPresence.Theirs( player.GameObject )
? MathF.Max( 0f, published( player ) )
: local( player );
}
/// <summary>M2's multiplier read from the REAL loadout. Only meaningful on the owner.</summary>
public static float FirstBloodScaleLocal( NZPlayer player )
=> Has( player, "M2" ) ? MathF.Max( 0f, FirstBloodMultiplier ) : 1f;
// ── m4 Hip Precision ─────────────────────────────────────────────────────
/// <summary>Hip-fire spread multiplier from a weapon. Read by GetRealSpread.</summary>
public static float HipSpreadFor( Component weapon )
{
var p = weapon.IsValid()
? weapon.Components.Get<NZPlayer>( FindMode.InAncestors | FindMode.Enabled )
: null;
return Has( p, "m4" ) ? HipPrecisionSpread : 1f;
}
// ── m3 Concussion ────────────────────────────────────────────────────────
/// <summary>
/// Roll m3 Concussion on a headshot that did NOT kill.
///
/// ⚠️ THE DURATION IS RANDOMISED PER APPLICATION, which `StatusEffects.Apply` can only
/// do because Juggernog's m5 needed a `seconds` override. Without that this would have
/// had to either accept the rule's flat 1s or add a second stun rule.
/// </summary>
public static void TryConcuss( GameObject attacker, GameObject victim )
{
if ( !Has( PlayerOf( attacker ), "m3" ) ) return;
if ( !victim.IsValid() ) return;
if ( Game.Random.Float() >= ConcussionChance ) return;
var zombie = victim.Components.Get<ZombieAI>( FindMode.EverythingInSelfAndAncestors );
if ( !zombie.IsValid() ) return;
StatusEffects.Apply( zombie.GameObject, "stun", attacker,
seconds: Game.Random.Float( ConcussionMin, ConcussionMax ) );
}
// ── M3 · M4 · m2 · m5 · on a kill ────────────────────────────────────────
/// <summary>
/// Everything that happens on a zombie death.
///
/// ⛔ THE STREAK IS UPDATED FOR EVERY KILL, NOT ONLY HEADSHOT ONES, because a
/// non-headshot kill is what RESETS it. Gating the whole method on `headshot` would have
/// made Focus a permanent buff after the first head kill — the augment's entire cost is
/// that a sloppy kill throws it away.
/// </summary>
/// <summary>
/// A headshot LANDED - build M4 Focus. Called from `Health` on every head hit, killing or
/// not.
///
/// ⚠ CAPPED WHERE IT IS COUNTED, not only where it is read. An uncapped counter would
/// climb for the rest of the game, so a player who reached the ceiling and then made one
/// body kill would still be at the cap and the reset would be decorative.
///
/// ⚠ IT DOES NOT BOOST THE SHOT THAT CAUSED IT. `Health` computes the headshot bonus
/// well above the point it calls this, so the increment lands for the NEXT shot. A shot
/// that paid its own bonus forward would make the first headshot of a streak worth more
/// than the second, which is backwards.
/// </summary>
public static void OnHeadshotHit( GameObject attacker )
{
if ( !attacker.IsValid() ) return;
var player = attacker.Components.Get<NZPlayer>( FindMode.EverythingInSelfAndAncestors );
if ( !player.IsValid() || !player.HasPerk( Perk ) ) return;
if ( !Has( player, "M4" ) ) return;
player.DeadshotFocus = Math.Min( player.DeadshotFocus + 1, FocusHitsToCap );
}
public static void OnZombieKilled( NZPlayer player, bool headshot, Vector3 position,
float damage, GameObject victim )
{
if ( !player.IsValid() || !player.HasPerk( Perk ) ) return;
// M4 Focus — the streak.
// ⛔ THE KILL PATH ONLY RESETS NOW. Building the streak moved to `OnHeadshotHit`,
// which `Health` calls on every head hit - so a headshot that also KILLS is counted
// there, once, and must not be counted again here. A second increment on the kill
// would make head-killing worth double head-wounding, quietly reintroducing the
// per-kill design this replaced.
//
// ⚠ THE RESET STAYS ON THE KILL, which is why this method still runs for every
// death rather than only headshot ones. A body SHOT costs nothing; a body KILL is the
// mistake the augment charges you for.
if ( Has( player, "M4" ) && !headshot )
{
if ( player.DeadshotFocus > 0 )
Log.Info( $"[nz-aug] deadshot M4 Focus - streak of {player.DeadshotFocus}"
+ " lost to a body kill" );
player.DeadshotFocus = 0;
}
if ( !headshot ) return;
// m2 Trophy — ⚠️ nothing while basalt's altar defense runs, when a kill pays only its 10 (`HexPlatforms.DefensePoints`)
if ( Has( player, "m2" ) && !HexPlatforms.DefensePoints ) player.AddPoints( TrophyPoints );
// m5 Recycler
if ( Has( player, "m5" ) )
{
var wep = player.Components.Get<SWB.Base.Weapon>( FindMode.EverythingInDescendants );
var si = wep.IsValid() ? wep.Primary : null;
// ⚠️ Clamped to the CLIP, so a refund cannot overfill a magazine — and skipped
// entirely on a weapon with no magazine (melee reports ClipSize <= 0).
if ( si is not null && si.ClipSize > 0 && si.Ammo < si.ClipSize )
si.Ammo = Math.Min( si.ClipSize, si.Ammo + RecyclerRounds );
}
// M3 Cranial Detonation
if ( Has( player, "M3" ) ) Detonate( player, position, damage, victim );
}
/// <summary>
/// M3's splash.
///
/// ⛔ THE CORPSE IS EXCLUDED BY OBJECT, not by health. The zombie that just died still
/// has a `Health` for its whole corpse linger and `IsDead` only becomes true once the
/// death handler has run — so a health test would sometimes splash the thing that
/// caused the splash and sometimes not, depending on ordering.
///
/// ⚠️ NO RE-ENTRY GUARD IS NEEDED HERE, unlike the original's `deadshotAoE` flag. Its
/// blast ran through the same `EntityTakeDamage` hook as its damage augments, so it had
/// to mask itself out. This calls `Health.OnDamage` directly with an empty TagSet, so
/// it carries no bullet tag — Vigor Rush, the head product and First Blood all test for
/// one and none of them can see it.
///
/// ⚠️ AND THAT ALSO MEANS THE SPLASH CANNOT CHAIN. A blast kill does not produce another
/// blast, because a splash hit is not a headshot. One detonation per headshot kill, full
/// stop — which is what keeps a dense horde from becoming one screen-clearing cascade.
/// </summary>
static void Detonate( NZPlayer player, Vector3 position, float damage, GameObject victim )
{
// ⚠ NAPALM NECTAR M2 (area damage x3) / m4 (radius +20%). One of the six sites listed in
// `FireAugments.AreaDamageScale` — there is no explosive damage TYPE here, so "all area
// damage" is a register of call sites. A new AoE that is not wrapped is not covered.
var splash = damage * MathF.Max( 0f, DetonationShare )
* FireAugments.AreaDamageScale( player?.GameObject );
if ( splash <= 0f ) return;
var hit = 0;
foreach ( var z in ZombieAI.All )
{
if ( !z.IsValid() || !z.GameObject.IsValid() ) continue;
if ( victim.IsValid() && z.GameObject == victim ) continue;
var target = z.WorldPosition + Vector3.Up * 32f;
if ( position.Distance( target )
> DetonationRadius * FireAugments.AreaRadiusScale( player?.GameObject ) ) continue;
var hp = z.Components.Get<Health>( FindMode.EverythingInSelfAndAncestors );
if ( !hp.IsValid() || hp.IsDead ) continue;
hp.OnDamage( new DamageInfo
{
Damage = splash,
Attacker = player.GameObject,
Position = target,
Tags = new TagSet(),
} );
hit++;
}
if ( hit > 0 )
Log.Info( $"[nz-aug] deadshot M3 Cranial Detonation — {splash:0.#} to {hit} zombie(s)" );
}
// ── diagnostics ──────────────────────────────────────────────────────────
public static void Report( NZPlayer player )
{
if ( !player.IsValid() ) { Log.Warning( "[nz-aug] no player" ); return; }
var has = player.HasPerk( Perk );
var equipped = PerkAugments.EquippedOn( player, Perk );
var hp = player.Components.Get<Health>( FindMode.EverythingInSelf );
Log.Info( $"[nz-aug] DEADSHOT {(has ? "owned" : "NOT OWNED — every line below is inert")}"
+ $" · equipped [{(equipped.Length == 0 ? "none" : string.Join( "+", equipped ))}]"
+ $" · base handling x{PerkEffects.DeadshotFactor:0.##}" );
// ⚠️ PRINTS THE WHOLE HEAD PRODUCT, not just this perk's share. Five things
// multiply into it — the base 2.5, Death Perception, Precision Rounds, Deadeye and
// these two augments — and "x1.5" tells you nothing about what a headshot deals.
var baseHead = hp.IsValid() ? hp.HeadshotDamageScale : 2.5f;
var augHead = HeadshotScale( player.GameObject );
Log.Info( $"[nz-aug] M1 Deadeye x{(Has( player, "M1" ) ? DeadeyeHeadshot : 1f):0.##}"
+ $" head product {baseHead:0.##} → {baseHead * augHead:0.##}"
+ " (before Death Perception and tech)" );
Log.Info( $"[nz-aug] M2 First Blood {(Has( player, "M2" ) ? $"x{FirstBloodMultiplier:0.##} on any hit against an undamaged zombie" : "-")}" );
Log.Info( $"[nz-aug] M3 Cranial Det {(Has( player, "M3" ) ? $"{DetonationShare * 100f:0.#}% of the killing hit within {DetonationRadius:0}u" : "-")}" );
Log.Info( $"[nz-aug] M4 Focus streak {player.DeadshotFocus}/{FocusHitsToCap}"
+ $" → x{FocusMultiplier( player ):0.##} of x{FocusMax:0.##}"
+ $" ({FocusProgress( player ) * 100f:0}% charged, +{FocusPerHeadshot * 100f:0}% per HEADSHOT, reset by a body KILL)" );
Log.Info( $"[nz-aug] m1 Lucky Shot {(Has( player, "m1" ) ? $"{LuckyShotChance * 100f:0}% of hits count as headshots" : "-")}" );
Log.Info( $"[nz-aug] m2 Trophy {(Has( player, "m2" ) ? $"+{TrophyPoints} points per headshot kill" : "-")}"
+ $" m5 Recycler {(Has( player, "m5" ) ? $"+{RecyclerRounds} rounds" : "-")}" );
Log.Info( $"[nz-aug] m3 Concussion {(Has( player, "m3" ) ? $"{ConcussionChance * 100f:0}% for {ConcussionMin:0.##}-{ConcussionMax:0.##}s" : "-")}"
+ $" m4 Hip Precision spread x{(Has( player, "m4" ) ? HipPrecisionSpread : 1f):0.##}" );
// ⚠️ THE ×5 HEADSHOT DATA BUG IS NAMED HERE because Focus multiplies straight into
// it. AWM, G3, SVD and WA2000 author `HeadMultiplier: 2.0` on top of the 2.5, so
// those four already deal ×5 headshots — a capped Focus on an AWM is ×25 before any
// tech. Health.cs records the fault; this is where it becomes alarming.
if ( Has( player, "M4" ) )
Log.Info( "[nz-aug] ⚠ Focus multiplies the WHOLE head product. On the four"
+ " prefabs that author HeadMultiplier 2.0 (AWM, G3, SVD, WA2000) the head"
+ " product is already doubled — see the note in Health.cs." );
}
// ── commands ─────────────────────────────────────────────────────────────
static NZPlayer Me()
=> NZPlayer.Local;
/// <summary>`nz_aug_deadshot` — the report.</summary>
[ConCmd( "nz_aug_deadshot" )]
public static void DeadshotCmd() => Report( Me() );
/// <summary>
/// `nz_aug_deadshot_set [deadeye] [firstBlood] [detShare] [detRadius]` — the majors,
/// Focus excepted.
/// </summary>
[ConCmd( "nz_aug_deadshot_set" )]
public static void SetCmd( float deadeye = -1f, float firstBlood = -1f,
float detShare = -1f, float detRadius = -1f )
{
if ( deadeye >= 0f ) DeadeyeHeadshot = deadeye;
if ( firstBlood >= 0f ) FirstBloodMultiplier = firstBlood;
if ( detShare >= 0f ) DetonationShare = detShare;
if ( detRadius >= 0f ) DetonationRadius = detRadius;
Report( Me() );
}
/// <summary>
/// `nz_aug_deadshot_focus [perKill] [max] [streak]` — Focus, including its live streak.
///
/// ⛔ THE STREAK ARGUMENT IS THE ONLY WAY TO SEE THE MIDDLE OF THE BAR. Reaching the cap
/// legitimately is 27 consecutive headshot kills; nobody is doing that to check a HUD
/// element renders at 40%.
/// </summary>
[ConCmd( "nz_aug_deadshot_focus" )]
public static void FocusCmd( float perKill = -1f, float max = -1f, int streak = -1 )
{
if ( perKill >= 0f ) FocusPerHeadshot = perKill;
if ( max >= 0f ) FocusMax = max;
var p = Me();
if ( streak >= 0 && p.IsValid() ) p.DeadshotFocus = Math.Min( streak, FocusHitsToCap );
Report( p );
}
/// <summary>
/// `nz_aug_deadshot_minor [luckyChance] [trophy] [concussChance] [hipSpread] [recycler]`.
/// </summary>
[ConCmd( "nz_aug_deadshot_minor" )]
public static void MinorCmd( float luckyChance = -1f, int trophy = -1,
float concussChance = -1f, float hipSpread = -1f, int recycler = -1 )
{
if ( luckyChance >= 0f ) LuckyShotChance = MathX.Clamp( luckyChance, 0f, 1f );
if ( trophy >= 0 ) TrophyPoints = trophy;
if ( concussChance >= 0f ) ConcussionChance = MathX.Clamp( concussChance, 0f, 1f );
if ( hipSpread >= 0f ) HipPrecisionSpread = hipSpread;
if ( recycler >= 0 ) RecyclerRounds = recycler;
Report( Me() );
}
}