Editor/Mcp/BridgeScaffoldPolishTools.cs

Editor toolset wrapper for MCP (bridge_scaffold_polish) exposing generator functions. It defines static methods that call McpGate.Run to emit code/assets for polish/game-feel systems like flicker lights, camera shake, combo meter, cutscene director, dialogue, floating combat text, proxy nametag, and world-panel UI.

NetworkingFile Access
// AUTO-GENERATED by scripts/emit-mcp-wrappers.mjs — DO NOT EDIT.
// Regenerate: node scripts/extract-manifest.mjs && node scripts/emit-mcp-wrappers.mjs
// Source of truth: sbox-mcp-server/src/tools/ (zod schemas) → scripts/tools-manifest.json

using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Editor.Mcp;

/// <summary>
/// Generate game-feel and presentation components: camera shake, flickering lights, floating combat
/// text, combo meters, nametags, world-panel UIs, cutscene directors, and dialogue systems.
/// </summary>
[McpToolset( "bridge_scaffold_polish", "Generate game-feel and presentation components: camera shake, flickering lights, floating combat text, combo meters, nametags, world-panel UIs, cutscene directors, and dialogue systems." )]
public static class BridgeScaffoldPolishTools
{
	/// <summary>
	/// Generate a light-flicker animator and optionally attach it to an existing light GameObject by
	/// GUID. Five presets: Candle (soft organic sway), Fluorescent (mostly steady with random dips),
	/// Faulty (hard on/off cuts), Pulse (slow sine breathing), Lightning (dim baseline with rare bright
	/// flashes). Modulates the sibling Light component's LightColor around the color it found on enable
	/// (works on PointLight / SpotLight / DirectionalLight) and restores it exactly on disable;
	/// intensity 0..1 sets flicker depth, speed scales the whole pattern. The single biggest atmosphere
	/// win per call for horror/night scenes — pairs with apply_atmosphere. LOCAL/visual-only.
	/// </summary>
	/// <param name="name">Class name for the generated component. Defaults to 'FlickerLight'.</param>
	/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
	/// <param name="style">Default flicker preset baked into the component (editable per-instance in the inspector). Defaults to 'Candle'. One of: Candle | Fluorescent | Faulty | Pulse | Lightning.</param>
	/// <param name="intensity">Flicker depth 0..1: 0 = steady, 1 = full blackouts / double-bright flashes. Defaults to 0.5.</param>
	/// <param name="speed">Speed multiplier for the whole pattern. Defaults to 1.</param>
	/// <param name="lightId">GUID of a GameObject holding a light component to attach to (only attaches if the type is already loaded — hotload first).</param>
	[McpTool( "add_flicker_light" )]
	public static Task<object> AddFlickerLight( string name = null, string directory = null, string style = null, double? intensity = null, double? speed = null, string lightId = null )
		=> McpGate.Run( "add_flicker_light", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "style", style ), ( "intensity", intensity ), ( "speed", speed ), ( "lightId", lightId ) ) );

	/// <summary>
	/// Generate a trauma-based camera shake component (the standard game-feel model: events add Trauma
	/// 0..1, shake magnitude = Trauma², smooth Perlin offsets — not white-noise jitter — and Trauma
	/// decays every frame, so explosions slam and footsteps barely register). Attach the generated
	/// component to the CAMERA GameObject; fire from any game code via the static
	/// &lt;Name&gt;.Shake(0.4f). Applies in OnPreRender AFTER controllers position the camera, with an
	/// un-apply guard so it neither fights a controller-driven camera nor accumulates on a static one,
	/// and restores the camera when trauma hits zero. LOCAL-only (no [Sync]) — call Shake inside an
	/// [Rpc.Broadcast] handler if every client should feel it. Optionally attached to an existing
	/// GameObject by GUID (after a hotload).
	/// </summary>
	/// <param name="name">Class name for the generated component. Defaults to 'CameraShake'.</param>
	/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
	/// <param name="maxOffset">Positional shake at full trauma, in world units. Defaults to 6.</param>
	/// <param name="maxAngle">Rotational shake at full trauma, in degrees (applied to pitch/yaw/roll). Defaults to 4.</param>
	/// <param name="frequency">Noise speed — higher = violent rattle, lower = drunken sway. Defaults to 10.</param>
	/// <param name="decayPerSecond">How much trauma drains per second. Defaults to 1.5.</param>
	/// <param name="targetId">GUID of the camera GameObject to attach to (only attaches if the type is already loaded — hotload first).</param>
	[McpTool( "create_camera_shake" )]
	public static Task<object> CreateCameraShake( string name = null, string directory = null, double? maxOffset = null, double? maxAngle = null, double? frequency = null, double? decayPerSecond = null, string targetId = null )
		=> McpGate.Run( "create_camera_shake", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "maxOffset", maxOffset ), ( "maxAngle", maxAngle ), ( "frequency", frequency ), ( "decayPerSecond", decayPerSecond ), ( "targetId", targetId ) ) );

	/// <summary>
	/// Generate a combo system: a sealed Component (the authoritative, headless state) PLUS a small
	/// Razor HUD (PanelComponent + scss). Three files: &lt;Name&gt;.cs + &lt;Name&gt;Hud.razor +
	/// &lt;Name&gt;Hud.razor.scss. The component exposes a static Bump() — call it on every hit and the
	/// Count rises; an idle window (ComboWindowSeconds, tracked with TimeSince) resets it; the
	/// Multiplier steps up through [Property] tier thresholds (Tier2Hits/Tier3Hits/Tier4Hits =&gt;
	/// 2x/3x/4x); and a static OnComboChanged(int count, float multiplier) event fires so any HUD/audio
	/// reacts without a reference (Bump() targets the active instance, so callers never need a handle —
	/// attach ONE to a persistent object). The HUD subscribes to OnComboChanged, shows "&lt;count&gt;
	/// HITS x&lt;mult&gt;", and pulses via a CSS animation on every change (razor_lint-safe: BuildHash
	/// folds count/mult/pulse, no switch-expressions / non-ASCII in @code, class-selector SCSS root;
	/// host it under a ScreenPanel via add_screen_panel). Pairs with create_health_system /
	/// create_floating_combat_text — call Bump() from the damage path and Spawn a popup that reflects
	/// the multiplier.
	/// </summary>
	/// <param name="name">Class name for the combo component; the HUD is generated as &lt;Name&gt;Hud. Defaults to 'ComboMeter'.</param>
	/// <param name="directory">Subdirectory for the generated .cs + .razor + .razor.scss. Defaults to 'Code'.</param>
	/// <param name="comboWindowSeconds">Idle seconds before the combo resets back to zero (clamped to &gt;= 0.25). Defaults to 3.</param>
	[McpTool( "create_combo_meter" )]
	public static Task<object> CreateComboMeter( string name = null, string directory = null, double? comboWindowSeconds = null )
		=> McpGate.Run( "create_combo_meter", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "comboWindowSeconds", comboWindowSeconds ) ) );

	/// <summary>
	/// Generate a hand-authored cutscene player component that needs NO .movie asset — the zero-asset
	/// alternative to the MovieMaker family (add_movie_player/play_movie, which play keyframed .movie
	/// clips authored in the editor's Movie Maker dock). You author the shots directly in the inspector
	/// as parallel lists: ShotPositions (Vector3), ShotAngles (pitch/yaw/roll — Angles, not raw
	/// quaternions), ShotHoldSeconds, ShotBlendSeconds, and an optional per-shot ShotLookAt GameObject
	/// (aim at a target instead of using ShotAngles). At runtime it takes over the main camera
	/// (Scene.Camera) in OnPreRender ONLY while playing — smoothstep-eased Vector3.Lerp +
	/// Rotation.Slerp between shots — captures the camera's prior transform and restores it exactly
	/// when finished (same un-apply discipline as create_camera_shake). Play from any game code via the
	/// static &lt;Name&gt;.Play() (first director) or &lt;Name&gt;.Play("name") (matches CutsceneName);
	/// subscribe the static &lt;Name&gt;.OnCutsceneFinished, or gate game logic on the static
	/// &lt;Name&gt;.IsCutscenePlaying. LockInput freezes player input each frame via
	/// Input.ClearActions() while still reading the SkipAction press first so the cutscene stays
	/// skippable. Optional letterbox generates a razor_lint-safe black-bars overlay panel (host under a
	/// ScreenPanel) shown while IsCutscenePlaying. LOCAL-only (each client renders its own view) —
	/// trigger inside an [Rpc.Broadcast] for all clients. Attach to ANY GameObject; it drives the
	/// camera itself and does not need to sit on the camera. Returns { created, path, className,
	/// skipAction, lockInput, letterbox, nextSteps } — path is the generated .cs (letterbox lists the
	/// overlay files when enabled). Follow with trigger_hotload, then get_compile_errors, then attach
	/// via add_component_with_properties (component=className).
	/// </summary>
	/// <param name="name">Class name for the generated component. Defaults to 'CutsceneDirector'.</param>
	/// <param name="directory">Subdirectory for the generated files. Defaults to 'Code'.</param>
	/// <param name="skipAction">Input action that ends the cutscene early (read before input is cleared, so a locked cutscene is still skippable). Sanitized to a safe token. Empty = not skippable. Defaults to 'jump'.</param>
	/// <param name="lockInput">Freeze player input during playback via Input.ClearActions() each frame. Defaults to true.</param>
	/// <param name="letterbox">Also generate a razor_lint-safe letterbox overlay (two black bars, shown while IsCutscenePlaying — host it under a ScreenPanel). Defaults to false.</param>
	[McpTool( "create_cutscene_director" )]
	public static Task<object> CreateCutsceneDirector( string name = null, string directory = null, string skipAction = null, bool? lockInput = null, bool? letterbox = null )
		=> McpGate.Run( "create_cutscene_director", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "skipAction", skipAction ), ( "lockInput", lockInput ), ( "letterbox", letterbox ) ) );

	/// <summary>
	/// Generate an NPC/story dialogue system — a sealed state+data Component paired with a
	/// razor_lint-safe Razor HUD panel. Lines are authored in the inspector as a List&lt;string&gt;
	/// using the 'Speaker: text' convention (the part before the first colon is the speaker). The
	/// generated HUD panel binds to &lt;Name&gt;.Current automatically (no wiring) and renders the
	/// current line with a TimeSince-driven typewriter reveal at CharsPerSecond, folding the visible
	/// substring into BuildHash so it re-renders as characters appear. Press the AdvanceAction once to
	/// snap the whole line into view instantly, again to move to the next line; dismissing the last
	/// line ends the conversation. Start from any game code via the static
	/// &lt;Name&gt;.StartDialogue(string[] lines) or set Lines and call the instance Begin(). Static
	/// events for hooks: OnLineShown(index, speaker) — pair with add_lipsync to drive facial morphs /
	/// audio per line — and OnDialogueFinished when the conversation ends. Pairs with
	/// create_interactable to trigger dialogue on use. LOCAL-only (per-client HUD) — call StartDialogue
	/// inside an [Rpc.Broadcast] if every client should see it. Attach the panel under a ScreenPanel
	/// (add_screen_panel) so the HUD renders. Returns { created, path, className, charsPerSecond,
	/// advanceAction, panel, nextSteps } — panel lists the generated '&lt;Name&gt;Panel' HUD files.
	/// Follow with trigger_hotload, then get_compile_errors, then attach both components via
	/// add_component_with_properties.
	/// </summary>
	/// <param name="name">Class name for the generated dialogue component (the HUD panel is generated as '&lt;Name&gt;Panel'). Defaults to 'DialogueSystem'.</param>
	/// <param name="directory">Subdirectory for the generated files. Defaults to 'Code'.</param>
	/// <param name="charsPerSecond">Typewriter reveal speed in characters per second (clamped to &gt;= 1). Defaults to 40.</param>
	/// <param name="advanceAction">Input action that completes the reveal / advances to the next line. Sanitized to a safe token. Defaults to 'use'.</param>
	[McpTool( "create_dialogue_system" )]
	public static Task<object> CreateDialogueSystem( string name = null, string directory = null, double? charsPerSecond = null, string advanceAction = null )
		=> McpGate.Run( "create_dialogue_system", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "charsPerSecond", charsPerSecond ), ( "advanceAction", advanceAction ) ) );

	/// <summary>
	/// Generate a floating combat text component — rising, fading, camera-billboarded world-space
	/// popups for damage numbers, '+10 gold', pickup names. TextRenderer-based: no Razor, no
	/// WorldPanel, zero UI setup. Nothing to place in the scene — the generated class carries a static
	/// factory: &lt;Name&gt;.Spawn(position, "-25", Color.Red[, sizeMultiplier]) spawns a popup that
	/// rises at RiseSpeed, fades over Lifetime, and destroys itself. Pairs with create_health_system
	/// (spawn from the damage path so every hit prints its number). LOCAL-only — spawn inside an
	/// [Rpc.Broadcast] handler if every client should see it.
	/// </summary>
	/// <param name="name">Class name for the generated component. Defaults to 'FloatingCombatText'.</param>
	/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
	/// <param name="riseSpeed">World units the popup rises per second. Defaults to 48.</param>
	/// <param name="lifetime">Seconds until the popup is fully faded and destroyed. Defaults to 1.1.</param>
	/// <param name="fontSize">Base font size baked into Spawn() (the optional Spawn size argument multiplies it). Defaults to 24.</param>
	[McpTool( "create_floating_combat_text" )]
	public static Task<object> CreateFloatingCombatText( string name = null, string directory = null, double? riseSpeed = null, double? lifetime = null, double? fontSize = null )
		=> McpGate.Run( "create_floating_combat_text", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "riseSpeed", riseSpeed ), ( "lifetime", lifetime ), ( "fontSize", fontSize ) ) );

	/// <summary>
	/// Generate a sealed Component that floats the OWNER'S display name above a networked player.
	/// TextRenderer-based (not WorldPanel): a nametag is one short string with a distance fade, so a
	/// TextRenderer on a managed child object is far simpler than a WorldPanel + Razor + WorldInput
	/// stack — no UI assets, no panel host, per-frame alpha is a one-liner (mirrors
	/// create_floating_combat_text). Reads GameObject.Network.Owner.DisplayName (Owner and
	/// OwnerConnection are the same Connection on this SDK; falls back to the object name offline).
	/// Visibility is the INVERSE of the usual proxy guard: it renders only when
	/// GameObject.Network.IsProxy is true — i.e. on OTHER clients' copies of the player — so you never
	/// see a tag over your own head (offline / no networking =&gt; IsProxy false everywhere =&gt; no
	/// tags, expected). Spawns a CHILD GameObject for the text so billboarding never rotates the player
	/// model; cleaned up on disable. [Property] MaxDistance fades the tag out with distance,
	/// HeightOffset floats it above the head, FontSize sizes it. Attach to the ROOT of your networked
	/// player object. Pairs with create_networked_player (add it to the generated player prefab so
	/// every remote player is labeled). Returns { created, path, className, maxDistance, heightOffset,
	/// note, nextSteps }. Follow with trigger_hotload, then get_compile_errors, then attach via
	/// add_component_with_properties (component=className).
	/// </summary>
	/// <param name="name">Class name for the generated component. Defaults to 'ProxyNametag'.</param>
	/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
	/// <param name="maxDistance">Full alpha up close; fades to zero as the camera approaches this distance and is hidden past it, in world units. Defaults to 2000.</param>
	/// <param name="heightOffset">Height above the object's origin to float the tag, in world units (~72 clears a Citizen's head). Defaults to 72.</param>
	[McpTool( "create_proxy_nametag" )]
	public static Task<object> CreateProxyNametag( string name = null, string directory = null, double? maxDistance = null, double? heightOffset = null )
		=> McpGate.Run( "create_proxy_nametag", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "maxDistance", maxDistance ), ( "heightOffset", heightOffset ) ) );

	/// <summary>
	/// Generate a diegetic, clickable world-space UI: a Razor PanelComponent (+ .razor.scss) meant to
	/// sit on a GameObject that ALSO carries a Sandbox.WorldPanel — the WorldPanel is the world-space
	/// render surface (PanelSize / RenderScale / InteractionRange), this component is the actual UI it
	/// renders (PanelComponent has no world-panel mode of its own). Ships two example buttons wired to
	/// @onclick that raise a static event OnButtonPressed(string id), so game code reacts WITHOUT
	/// editing the panel (subscribe: &lt;Name&gt;.OnButtonPressed += id =&gt; ...). razor_lint-safe by
	/// construction (BuildHash override, no switch-expressions / non-ASCII in @code, class-selector
	/// SCSS root). SCENE PREREQUISITE FOR CLICKS: a WorldPanel's buttons only fire when a
	/// Sandbox.WorldInput exists in the scene (typically on the camera or the player) with its
	/// LeftMouseAction set to your click input action (e.g. "attack1"); on this SDK WorldInput drives
	/// itself from the camera + that action — there is no manual ray to feed, and WorldInput.Hovered
	/// (read-only) reflects the panel under the cursor. Without a WorldInput present, @onclick never
	/// fires. Setup: add_world_panel to a GameObject, add this component to the same object, add a
	/// WorldInput to the scene.
	/// </summary>
	/// <param name="name">Class name for the generated panel. Defaults to 'WorldPanelUi'.</param>
	/// <param name="directory">Subdirectory for the generated .razor + .razor.scss. Defaults to 'Code/UI'.</param>
	/// <param name="title">Heading text baked into the panel (editable per-instance via the Title [Property]). Defaults to 'Interact'.</param>
	[McpTool( "create_worldpanel_ui" )]
	public static Task<object> CreateWorldpanelUi( string name = null, string directory = null, string title = null )
		=> McpGate.Run( "create_worldpanel_ui", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "title", title ) ) );
}