Static utility class defining string identifiers for map editor tools and two helper functions that return a Color and a human-readable name for each tool id. It centralises tool ids and marker colours used by the editor and runtime.
using Sandbox;
namespace NZombies;
/// <summary>
/// Tool identifiers and their marker colours.
///
/// One place for the string ids so the Q menu, the editor and the save format
/// cannot drift apart — a typo in any of them would otherwise silently select
/// nothing.
///
/// Colours match the dev menu's category colours, so a marker in the world and
/// its button in the menu are the same colour. That is the whole point of
/// colouring them: recognising a marker at a distance without reading a label.
/// </summary>
public static class NZTools
{
public const string PlayerSpawn = "player_spawn";
public const string ZombieSpawn = "zombie_spawn";
/// <summary>Where specials come from on their own rounds. See
/// SpecialEnemies and MapConfig.SpecialSettings.</summary>
public const string SpecialSpawn = "special_spawn";
/// <summary>
/// Where BOSSES come from. Separate from <see cref="SpecialSpawn"/> on purpose.
///
/// ⛔ A SEPARATE LIST FOR THE REASON `MapConfig.SpecialSpawns` ALREADY GIVES ABOUT ITSELF:
/// "A dog round replaces the wave rather than joining it, and reusing the walker's window
/// spawns would put hounds at the barricades they are specifically built to ignore." The same
/// argument goes one step further for a boss - Brutus has a 22-unit body radius and an 80-unit
/// height, so a window a walker climbs through is not somewhere he can appear at all.
///
/// ⚠️ WHICH BOSS is chosen per spawner via `SpawnPoint.Special`, exactly as specials do.
/// </summary>
public const string BossSpawn = "boss_spawn";
/// <summary>Hand-authored navmesh shortcut — a drop, a jump, a one-way
/// route. See NavLinkManager for why the generated mesh cannot imply these
/// and GMod's nav editor could.</summary>
public const string NavLink = "nav_link";
/// <summary>Buyable barrier — a door in everything but name. See Debris.</summary>
public const string Debris = "debris";
/// <summary>The electricity switch. One per map is enough — see Power.</summary>
public const string PowerSwitch = "power_switch";
/// <summary>Solid, unbuyable, nav-transparent geometry. See InvisibleWall.
/// ⚠️ Built with the SAME corner+height flow as Debris — that is a feature,
/// not an accident, and the code is shared rather than copied.</summary>
public const string InvisibleWall = "invisible_wall";
/// <summary>Weapon bought off a wall. See WallBuy — full price to buy, half
/// (floored to 10) to refill, free in Creative.</summary>
public const string WallBuy = "wall_buy";
/// <summary>Boarded barricade — TWO points and a fixed vault height, unlike
/// Debris/InvisibleWall's four-corners-plus-height. See BarricadeSpot.</summary>
public const string Barricade = "barricade";
/// <summary>The mystery box. One click, faces you. See MysteryBoxSpot.</summary>
public const string MysteryBox = "mystery_box";
/// <summary>
/// Pack-a-Punch. One click, faces you, like the box. See PackAPunchSpot.
///
/// ⚠️ Sits in "Player assisting" beside the perk machines because that is where
/// a mapper looks for it — even though it is NOT a perk in the code, and the
/// original files it under perks for the same reason: it is a machine you walk
/// up to and spend points at.
/// </summary>
public const string PackAPunch = "pack_a_punch";
/// <summary>Der Wunderfizz — random perk machine. Sits beside Pack-a-Punch in
/// "Player assisting" for the same reason it does: that is where a mapper
/// looks for a machine you walk up to and spend points at.</summary>
public const string Wunderfizz = "wunderfizz";
/// <summary>
/// A perk machine — one machine, one named perk. Sits beside the Wunderfizz in
/// "Player assisting" for the same reason it does.
///
/// ⚠️ The perk it will place is a TOOL SETTING (MapEditor.PerkMachinePerk), not
/// a separate tool per perk. Seventeen menu entries for one placeable would
/// bury every other machine in the category.
/// Console: nz_perk_machine, nz_perk_machine_list, nz_perk_machine_clear.
/// </summary>
public const string PerkMachine = "perk_machine";
/// <summary>
/// A teleporter — click the pad, then click where it sends you.
///
/// ⚠️ TWO CLICKS FOR ONE PLACEABLE, like the nav link and unlike every other
/// machine here. The second click is not a second object; A and B are one
/// record, because a destination with no pad is invisible and unusable.
/// Console: nz_teleporter, nz_teleporter_list, nz_teleporter_clear.
/// </summary>
public const string Teleporter = "teleporter";
/// <summary>
/// A springboard — an invisible pad that flings a player straight up, with its own strength and radius. One click, on the
/// floor; a click on one already placed gives it the panel's current numbers. See SpringboardSpot.
/// Console: nz_springboard, nz_springboard_list, nz_springboard_clear.
/// </summary>
public const string Springboard = "springboard";
/// <summary>
/// A soul box — kills near it fill it, and a full SET of them opens a flag.
///
/// ⛔ ITS FLAG IS AN "ALL", NOT AN "ANY", unlike every other flag in the game. Five
/// boxes on one flag open it only when all five are full. See SoulBoxSpot.
/// Console: nz_soul, nz_soul_list, nz_soul_clear, nz_soul_feed.
/// </summary>
public const string SoulBox = "soul_box";
/// <summary>The ammo box — refills the held weapon's reserve, at a price that climbs with each
/// refill of that weapon in the same round. Filed beside the other walk-up-and-spend machines.
/// Console: nz_ammobox, nz_ammobox_list, nz_ammobox_clear.</summary>
public const string AmmoBox = "ammo_box";
/// <summary>The trading table — leave a weapon, swap it, or take someone else's. Free. Filed in
/// "Player assisting" because that is literally its job, even though nothing is bought at it.
/// Console: nz_trade, nz_trade_list, nz_trade_clear.</summary>
public const string TradeTable = "trade_table";
/// <summary>The building table — where scattered parts become a wonder weapon. Same bench as
/// the trading table, filed beside it for the same reason: it is a thing you walk up to and
/// use. Nothing is buildable at it yet; this places the table.
/// Console: nz_buildtable, nz_buildtable_list, nz_buildtable_clear, nz_buildtable_rebuild.</summary>
public const string BuildTable = "build_table";
/// <summary>One piece of the buildable wonder weapon. Which piece is set by
/// `nz_buildpart_set 1..3` and shown by the marker; the tool places whichever is selected.
/// Console: nz_buildpart, nz_buildpart_set, nz_buildpart_list, nz_buildpart_clear.</summary>
public const string BuildPart = "build_part";
/// <summary>The Arsenal — armor tiers, and later weapon tech and ammo mods.
/// Filed beside the other spend-points-here machines for the same reason they
/// are. See ARSENAL_REMAKE.md.</summary>
public const string Arsenal = "arsenal";
/// <summary>The buyable ending — walk up, pay, the run is over. The prop is whatever
/// the mapper points it at; the original only DEFAULTS to a teddy bear.
/// Console: nz_ending, nz_ending_list, nz_ending_clear, nz_ending_price.</summary>
public const string BuyableEnding = "buyable_ending";
/// <summary>The damage wall — the invisible wall's twin, drawn the same way, with no
/// collision, that hurts any player standing in it.
/// Console: nz_dmgwall_list, nz_dmgwall_clear, nz_dmgwall_set, nz_dmgwall_where.</summary>
public const string DamageWall = "damage_wall";
/// <summary>The misery acceleration device — interact to toggle the run into overdrive:
/// no gap between rounds, minimum spawn delay, top zombie speed tier.
/// Console: nz_misery, nz_mad, nz_mad_list, nz_mad_clear.</summary>
/// <summary>
/// A placed light. See MapLight.
///
/// ⛔ THE ONLY PLACEABLE HERE WITH A PER-FRAME COST, and it is worth saying out loud on the
/// tool itself. s&box has no lightmaps: every light is dynamic every frame, and this project
/// already paid for that once when `ttt_canyon_labs`'s 71 ported Source lights produced a 3.5x
/// fps swing by view direction. Shadows are OFF by default for the same reason — see LightBake.
/// </summary>
public const string MapLight = "map_light";
/// <summary>A placed looping sound. See SoundSpot.</summary>
public const string SoundSpot = "sound_spot";
/// <summary>A patch of haze you walk into. See FogArea.</summary>
public const string FogArea = "fog_area";
/// <summary>A room zone — a drawn volume naming the part of the map it covers: walk into it and its name goes up at the top
/// left (`RoomNames`). ⚠️ NOT THE EASTER EGG'S "Zone" (`Docs/EASTER_EGG_TOOLSET.md`, a step's occupancy area), which is
/// still a placeholder. Console: nz_room_zones, nz_room_zone_name, nz_room_zone_clear, nz_room_zone_where.</summary>
public const string RoomZone = "room_zone";
public const string Misery = "misery_device";
/// <summary>An easter-egg clue — text or an image on a wall. Carries NO flags and no
/// conditions; see Docs/EASTER_EGG_TOOLSET.md.
/// Console: nz_clue, nz_clue_list, nz_clue_text, nz_clue_clear.</summary>
public const string Clue = "ee_clue";
/// <summary>An easter-egg pressable — a button or lever carrying an EggStep.
/// Console: nz_press, nz_press_list, nz_press_flags, nz_press_clear.</summary>
public const string Pressable = "ee_pressable";
/// <summary>An easter-egg shootable — a symbol or weak point triggered by a bullet.
/// Console: nz_shoot, nz_shoot_list, nz_shoot_hit, nz_shoot_where, nz_shoot_clear.</summary>
public const string Shootable = "ee_shootable";
/// <summary>Basalt seal 1's hex slot — a hexagon of light on a wall showing a number, I-IV, in one of the four colours,
/// rolled again every round. See `HexSlotManager`.
/// Console: nz_hex_slot, nz_hex_slots, nz_hex_slots_roll, nz_hex_slots_clear, nz_hex_slots_from_wallbuys.</summary>
public const string HexSlot = "ee_hexslot";
/// <summary>Marker colour for a tool. Spawners are green in the dev menu.</summary>
public static Color ColorFor( string tool ) => tool switch
{
// Saturated and fully opaque. The earlier pastel values washed out
// against a bright floor, and alpha is stated rather than assumed.
PlayerSpawn => new Color( 0.10f, 0.55f, 1f, 1f ), // blue — player things
ZombieSpawn => new Color( 0.15f, 0.95f, 0.25f, 1f ), // green — matches Spawners
// ⚠️ RED, not another green. Special spawners sit in the same menu
// category as zombie spawns and are placed the same way, so at a distance
// a second green marker would be indistinguishable from the walker spawn
// beside it — and telling them apart across a room is the entire reason
// these are coloured at all.
SpecialSpawn => new Color( 0.95f, 0.20f, 0.20f, 1f ),
BossSpawn => new Color( 0.62f, 0.05f, 0.35f, 1f ),
// Cyan — nothing else uses it, and a nav link is the one marker that
// describes MOVEMENT rather than a thing standing somewhere.
NavLink => new Color( 0.20f, 0.95f, 0.95f, 1f ),
Debris => new Color( 1f, 0.62f, 0.10f, 1f ), // amber — barriers
PowerSwitch => new Color( 1f, 0.92f, 0.15f, 1f ), // yellow — matches the block
// teal — matches the "Player assisting" category in the dev menu
WallBuy => new Color( 0.31f, 0.79f, 0.66f, 1f ),
// Violet — deliberately far from the amber of a debris block. The two are
// the same shape, built the same way, and one is buyable while the other
// is permanent, so colour is the ONLY thing distinguishing them at a
// glance across a room.
InvisibleWall => new Color( 0.72f, 0.45f, 1f, 1f ),
// Red — it is the only marker that means "this hurts", and it must not be
// mistaken for the violet invisible wall it is otherwise identical to: same
// tool, same footprint, same box, opposite consequence.
DamageWall => new Color( 1f, 0.18f, 0.18f, 1f ),
// Orange-red — near the damage wall's red because both mean "this makes the game
// worse", but distinct enough not to be mistaken for one across a room.
// ⚠️ WARM WHITE for the light and CYAN for the sound — deliberately unlike any existing
// marker colour, because these two will often sit near each other and near a damage wall.
MapLight => new Color( 1f, 0.95f, 0.65f, 1f ),
SoundSpot => new Color( 0.35f, 0.95f, 0.90f, 1f ),
FogArea => new Color( 0.62f, 0.58f, 0.54f, 1f ),
// Pink — a name for a space: not a hazard, not a wall, and nothing else on a map is pink.
RoomZone => new Color( 1f, 0.45f, 0.75f, 1f ),
Misery => new Color( 1f, 0.45f, 0.10f, 1f ),
// Gold — the easter-egg category's own colour in the dev menu, and a clue is the
// only EE tool with no gameplay logic, so it should read as a note rather than a
// mechanism.
Clue => new Color( 0.91f, 0.78f, 0.29f, 1f ),
// Brighter gold than the clue — same easter-egg family, but this one is a
// MECHANISM and the clue is a note, so they must not read the same at a distance.
Pressable => new Color( 1f, 0.87f, 0.15f, 1f ),
// ⚠️ PALE CYAN, DELIBERATELY NOT THE EGG FAMILY'S GOLD. A pressable is walked up to
// and a shootable is hit from across the room; mistaking one for the other at a
// distance costs the trip, which is exactly the mistake colour is here to prevent.
Shootable => new Color( 0.45f, 0.95f, 1f, 1f ),
// The map's own white light, a little blue: what a slot is drawn in before it is rolled. Nothing else uses it.
HexSlot => new Color( 0.82f, 0.88f, 1f, 1f ),
// Brown — it is made of wood, and it needs to be told apart at a glance
// from the amber debris block it is otherwise most similar to.
Barricade => new Color( 0.60f, 0.36f, 0.16f, 1f ),
// Magenta — the box is the one thing on the map you want to spot from
// across a room, and nothing else uses it.
MysteryBox => new Color( 0.95f, 0.30f, 0.85f, 1f ),
// Mint — the machine's own colour. Every other marker is named after what
// the thing DOES; this one can afford to be named after what it looks like,
// because the Pack-a-Punch is the most recognisable object in the mode.
PackAPunch => new Color( 0.35f, 0.90f, 0.75f, 1f ),
// Magenta — the perk machines are teal-ish and this must not be mistaken
// for one at a distance, which is the whole job of these colours.
Wunderfizz => new Color( 0.90f, 0.35f, 0.85f, 1f ),
// Teal — the colour the dev menu already gives the whole "Player assisting"
// category, and the one the comment above Wunderfizz is talking about when
// it says the perk machines are teal-ish.
PerkMachine => new Color( 0.31f, 0.79f, 0.66f, 1f ),
// Gold — the one marker on the map that means "the run stops here". Nothing
// else is gold, and it must not read as another spend-points machine at a
// glance, which is exactly what teal would have made it.
BuyableEnding => new Color( 1f, 0.80f, 0.20f, 1f ),
// Cyan — the colour the dev menu already gives the Transportation category.
// ⚠️ Close to the nav link's cyan on purpose: both describe MOVEMENT from
// one place to another, and both draw an A-to-B arrow.
Teleporter => new Color( 0.35f, 0.83f, 0.83f, 1f ),
// Lime — the same Transportation family as the teleporter, and green enough to tell from its cyan, the nav link's and
// the sound's. The Banana Colada pad's own colour is a pale yellow, which a power switch's yellow would swallow.
Springboard => new Color( 0.65f, 1f, 0.30f, 1f ),
// Warm orange — the colour of the souls themselves. Upstream's wisp is
// `math.random(200,255), math.random(100,200), math.random(100,150)`, which is
// this hue, and it is far enough from the amber debris block to tell apart.
SoulBox => new Color( 1f, 0.62f, 0.25f, 1f ),
AmmoBox => new Color( 0.42f, 0.80f, 0.35f, 1f ),
TradeTable => new Color( 0.78f, 0.60f, 0.38f, 1f ),
// ⚠️ COOLER AND BLUER THAN THE TRADING TABLE'S, ON PURPOSE. The two share a mesh, so on a
// map carrying both the marker colour is the ONLY thing telling you which one you are
// about to remove.
BuildTable => new Color( 0.42f, 0.66f, 0.86f, 1f ),
BuildPart => new Color( 0.30f, 0.86f, 0.92f, 1f ),
// Orange — the machine's own accent, and far from both the Wunderfizz's
// magenta and Pack-a-Punch's mint, which are the two it sits next to in the
// menu and would be confused with across a room.
Arsenal => new Color( 1f, 0.55f, 0.15f, 1f ),
_ => Color.White,
};
/// <summary>Human name, for logs and the tool readout.</summary>
public static string NameFor( string tool ) => tool switch
{
PlayerSpawn => "Player spawn",
ZombieSpawn => "Zombie spawn",
Debris => "Debris",
PowerSwitch => "Electricity switch",
InvisibleWall => "Invisible wall",
Barricade => "Barricade",
MysteryBox => "Mystery box",
PackAPunch => "Pack-a-Punch",
// ⚠️ These four were absent and fell through to `_ => tool`, so the log and
// the tool readout printed the raw id — "wunderfizz", "wall_buy". Harmless
// but it reads as a missing translation, and the removal message uses this.
Wunderfizz => "Der Wunderfizz",
AmmoBox => "Ammo box",
TradeTable => "Trading table",
BuildTable => "Building table",
BuildPart => "Build part",
Arsenal => "Arsenal",
WallBuy => "Weapon buy",
SpecialSpawn => "Special spawn",
BossSpawn => "Boss spawn",
NavLink => "Nav link",
BuyableEnding => "Buyable ending",
Springboard => "Springboard",
DamageWall => "Damage wall",
MapLight => "Light",
SoundSpot => "Sound",
FogArea => "Fog",
RoomZone => "Room zone",
Misery => "Misery acceleration device",
Clue => "Clue",
Pressable => "Pressable",
Shootable => "Shootable",
HexSlot => "Hex slot",
_ => tool,
};
}