Static registry of game perks. Declares a Perk record, an ordered array of all perks with id, name, price and short description, helper arrays and methods for lookup and for returning a CSS hex accent color per perk id.
using System.Linq;
namespace NZombies;
/// <summary>
/// The 17 perks Der Wunderfizz rolls from.
///
/// ⛔ NOT "NAMES ONLY" ANY MORE, AND THIS HEADER SAID SO FOR A LONG TIME AFTER IT STOPPED
/// BEING TRUE. It read "names only, for now — before any perk does anything", which was
/// honest while this was a placeholder list and false once fourteen perks had wired base
/// effects and nine augments each. `AugmentEffects.WiredPerks()` is the live answer to
/// which ones do something; this list is only the roster and the prices.
///
/// ⛔ A SWITCH AND AN ARRAY PROPERTY, NOT A `static readonly` COLLECTION WITH
/// CONTENT IN ITS INITIALISER. Static initialisers do not re-run on hotload, so
/// a list built that way can never be corrected in a live session — the single
/// most expensive pattern in this project (INSTRUCTIONS.md §1, seven occurrences).
///
/// Roster and prices from the live GMod defs (`sh_perks.lua`), reconciled against the
/// final roster in `PERK_AUGMENT_AUDIT.md` (which lives on the GMod side, not in this
/// repo). Several of these merge two or more original perks, which is why the roster is
/// far short of the original 33.
///
/// ⚠️ THE COUNT WENT 18 → 17 WHEN TOMBSTONE SODA WAS REMOVED, and no specific
/// merge count is quoted here any more. The old header said "eight of these are merges";
/// folding Tombstone into Quick Revive changed that, and the audit that would settle the
/// new number is not in this repo to check against. A number nobody can verify is worse
/// than no number.
/// </summary>
public static class PerkRegistry
{
public record Perk( string Id, string Name, int Price, string Short );
/// <summary>Every perk, in menu order.</summary>
public static Perk[] All => new Perk[]
{
new( "jugg", "Juggernog", 2500, "Extra health." ),
// ⚠️ "reloads" ONLY — it said "reloads and repairs", which the perk does not
// do: barricade repair is deliberately out of Speed Cola's base effect for now
// (see Barricade.RepairInterval). A blurb promising an effect that is not wired
// is the same false-display problem as the weapon stats panel's three claims.
new( "speed", "Speed Cola", 3000, "Faster reloads." ),
new( "revive", "Quick Revive", 1500, "Revive yourself when alone; revive others faster." ),
new( "staminup", "Stamin-Up", 2000, "Move faster, sprint longer." ),
new( "dtap", "Double Tap", 2000, "Higher rate of fire." ),
new( "deadshot", "Deadshot Daiquiri", 2000, "Better handling; stronger headshots." ),
new( "phd", "PhD Flopper", 2000, "Immune to explosions; dive to blast." ),
new( "mulekick", "Mule Kick", 4000, "A third weapon slot." ),
// ⛔ TOMBSTONE SODA WAS REMOVED, NOT DEFERRED. Its nine augments moved into Quick Revive
// (M2 Grave Keeper, M4 Last Stand, m5 Phase Shift) when the two perks were merged, which
// left a 2000-point buyable whose base effect was never wired and whose augments duplicated
// ones you can already equip. Two ways to get the same effect from two machines is worse
// than one perk fewer.
//
// ⚠ `sounds/nz/perk/jingle/mus_tombstone_jingle.mp3` IS NOW ORPHANED and is left in
// place. Nothing in code ever referenced it — it was matched only by filename — and
// deleting audio nobody asked to delete is not this change's business.
new( "pop", "Elemental Pop", 5000, "Bullets can trigger random ammo mods." ),
new( "vulture", "Vulture Aid", 3000, "Enemies drop ammo, points and gas." ),
new( "widowswine", "Widow's Wine", 4000, "Snares attackers; web grenades." ),
new( "death", "Death Perception", 2500, "Headshot damage and a sixth sense." ),
new( "tortoise", "Victorious Tortoise", 2500, "Less damage from behind." ),
new( "time", "Timeslip Tonic", 1500, "Faster box, Pack-a-Punch and traps." ),
new( "banana", "Banana Colada", 3000, "Longer slides that keep momentum and chain." ),
new( "fire", "Napalm Nectar", 3000, "Immune to fire; shots ignite." ),
// ⚠️ THE NUMBER IS READ FROM `PerkEffects.VigorDamage` (2026-10-05): this said "Double bullet damage" long after the perk
// became x1.2, and the lobby's info booklet shows it to new players.
new( "vigor", "Vigor Rush", 3000, $"Bullets deal {(PerkEffects.VigorDamage - 1f) * 100f:0}% more damage." ),
};
/// <summary>Just the display names, for the menu.</summary>
public static string[] Names => All.Select( p => p.Name ).ToArray();
/// <summary>One perk by id, or null.</summary>
public static Perk Find( string id )
=> All.FirstOrDefault( p => p.Id == id );
/// <summary>
/// The perk's signature colour, as a CSS hex string.
///
/// ⚠️ LIFTED FROM `sh_perks.lua`'s `color` FIELD, one per perk — not invented and
/// not derived from the icon. The augment screen tints its header, accents and selected
/// rows with it, which is what makes the Juggernog screen instantly distinguishable
/// from the Speed Cola one; a single shared accent would make all 18 look the same.
///
/// ⚠️ A METHOD RETURNING A STRING, mirroring `ArsenalMenu.RarityHex`. Razor writes
/// it straight into an inline `style`, and a `Color` would need converting at every
/// call site.
///
/// ⚠️ Falls back to a neutral rather than throwing. An unknown id here means a perk
/// was added to a menu but not to this list, and a grey header is a far better failure
/// than a panel that stops drawing.
/// </summary>
public static string AccentHex( string id ) => id switch
{
"dtap" => "#ff7d00",
"speed" => "#19ff19",
"jugg" => "#ff1919",
"revive" => "#2323ff",
"vigor" => "#808040",
"fire" => "#de4502",
"staminup" => "#ffc832",
"phd" => "#c800c8",
"deadshot" => "#325f32",
"mulekick" => "#006400",
"pop" => "#ff32fa",
"vulture" => "#ff1414",
"widowswine" => "#ff144b",
"death" => "#dc2d05",
"tortoise" => "#46b91e",
"time" => "#820ac8",
"banana" => "#ffc800",
_ => "#8f96ad",
};
}