Editor-side static bridge of generated gameplay tool wrappers, exposing many McpTool methods that call McpGate.Run to request code-generation or scene edits (create components, UI panels, economy systems, loot tables, etc.). Each method is a thin Task<object> forwarder with XML docs describing the generated artifact and parameters.
// 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 complete, compile-verified gameplay C# components: player/NPC controllers, game
/// managers, health, pickups, inventory, save systems, economy, loot tables, round/phase machines,
/// interaction systems, placement mode, and more. Each tool writes a .cs file into the project;
/// follow with trigger_hotload + compile_status.
/// </summary>
[McpToolset( "bridge_scaffold_gameplay", "Generate complete, compile-verified gameplay C# components: player/NPC controllers, game managers, health, pickups, inventory, save systems, economy, loot tables, round/phase machines, interaction systems, placement mode, and more. Each tool writes a .cs file into the project; follow with trigger_hotload + compile_status." )]
public static class BridgeScaffoldGameplayTools
{
/// <summary>
/// SCENE-MUTATING: generate a data-driven achievement trigger-zone component AND create its
/// GameObject now (named zone with a sized BoxCollider, IsTrigger=true, at the given position).
/// When an object tagged triggerTag enters, the component calls
/// <achievementSetClass>.Instance.Progress(achievementId, amount) — or Unlock() when
/// unlock=true — with a once-only latch and optional destroy-after-fire. Returns { created, path,
/// className, achievementSetClass, achievementId, gameObject, attached, note, nextSteps }. The
/// generated component only attaches to the zone after trigger_hotload — until then `attached` is
/// false and nextSteps carries the exact add_component_with_properties follow-up. The generated
/// code references the set class BY NAME: run create_achievement_set first or the project will not
/// compile (the result warns via `note`). Re-running with the same name fails unless
/// reuseClass=true, which skips codegen and just places another zone (attaching + configuring
/// immediately since the class is already compiled). Refused during play mode.
/// </summary>
/// <param name="achievementId">Id of the achievement to progress/unlock (sanitized to [a-z0-9_-]).</param>
/// <param name="name">Class name for the generated trigger component. Defaults to 'AchievementTrigger'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="achievementSetClass">Class name of the achievement set the zone reports to (from create_achievement_set). Defaults to 'AchievementSet'.</param>
/// <param name="amount">Progress amount added per fire (ignored when unlock=true). Defaults to 1.</param>
/// <param name="unlock">Call Unlock() instead of Progress(). Defaults to false.</param>
/// <param name="triggerTag">Tag the entering object must carry (put it on the player via set_tags). Defaults to 'player'.</param>
/// <param name="onceOnly">Only the first tagged entry fires. Defaults to true.</param>
/// <param name="destroyAfterFire">Destroy the zone GameObject after firing. Defaults to false.</param>
/// <param name="createObject">Create the zone GameObject now (with BoxCollider). Defaults to true; false = code-gen only.</param>
/// <param name="objectName">Name for the zone GameObject. Defaults to '<name>Zone'.</param>
/// <param name="position">World position of the zone GameObject. As "x,y,z" (or JSON {x,y,z}).</param>
/// <param name="scale">BoxCollider size — uniform number, object {x,y,z}, or comma string "x,y,z". Defaults to 100,100,100. As "x,y,z" (or JSON {x,y,z}).</param>
/// <param name="reuseClass">If the .cs already exists, skip codegen and just place another zone with the existing class. Defaults to false.</param>
[McpTool( "add_achievement_trigger" )]
public static Task<object> AddAchievementTrigger( string achievementId, string name = null, string directory = null, string achievementSetClass = null, double? amount = null, bool? unlock = null, string triggerTag = null, bool? onceOnly = null, bool? destroyAfterFire = null, bool? createObject = null, string objectName = null, string position = null, string scale = null, bool? reuseClass = null )
=> McpGate.Run( "add_achievement_trigger", McpGate.Args( ( "achievementId", achievementId ), ( "name", name ), ( "directory", directory ), ( "achievementSetClass", achievementSetClass ), ( "amount", amount ), ( "unlock", unlock ), ( "triggerTag", triggerTag ), ( "onceOnly", onceOnly ), ( "destroyAfterFire", destroyAfterFire ), ( "createObject", createObject ), ( "objectName", objectName ), ( "position", position ), ( "scale", scale ), ( "reuseClass", reuseClass ) ) );
/// <summary>
/// Generate an eye-traced interaction-prompt HUD — a PanelComponent (.razor + .razor.scss pair,
/// like create_leaderboard_panel) that every frame traces a ray from the scene camera
/// (Scene.Trace.Ray, out to [Property] float Range) and, when the crosshair is on a component
/// implementing Component.IPressable, shows a centered "Press E"-style pill. The prompt text comes
/// from the target's IPressable.GetTooltip() when it overrides it (most don't), else a [Property]
/// DefaultPrompt built from the action. This is the visible half of the interaction loop: it PAIRS
/// with create_interactable / add_interaction_station (which implement IPressable) — this tool
/// tells the player they CAN press, those tools handle the press. Host it under a ScreenPanel
/// (add_screen_panel), then add the component to that panel object. The generated Razor is
/// razor_lint-safe by construction: PanelComponent + BuildHash override folding the visible state,
/// no switch-expressions and no non-ASCII in @code, and a class root selector in the SCSS.
/// LOCAL/visual-only (no [Sync]).
/// </summary>
/// <param name="name">Class/file name for the generated .razor. Defaults to 'InteractionPrompt'.</param>
/// <param name="directory">Subdirectory for the generated .razor + .razor.scss. Defaults to 'Code/UI'.</param>
/// <param name="action">Verb woven into the default prompt text ('Press E to <action>'). Defaults to 'use'.</param>
/// <param name="range">Eye-trace reach in world units — how close the crosshair must be to a pressable to show the prompt. Defaults to 120.</param>
[McpTool( "add_interaction_prompt" )]
public static Task<object> AddInteractionPrompt( string name = null, string directory = null, string action = null, double? range = null )
=> McpGate.Run( "add_interaction_prompt", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "action", action ), ( "range", range ) ) );
/// <summary>
/// Generate a Component.IPressable 'station' prop (crafting bench / shop till / arcade cabinet)
/// that ONE user occupies at a time. Occupancy is host-authoritative: the occupant is a
/// [Sync(SyncFlags.FromHost)] Guid (GameObject/Connection aren't [Sync]-able) and Press() routes
/// the claim to the host via an [Rpc.Host] Occupy(). Includes a reservation grace window (the
/// station stays reserved for its last user for graceSeconds after they leave, so a brief walk-away
/// can't jump the queue), an optional unlock-level gate (users below requiredLevel can't use it —
/// wire the static ResolveUserLevel hook to your progression system to activate it), and an
/// overlay-open hook (a static OnStationOpened(GameObject) event to open your UI, plus an opt-in
/// [Rpc.Broadcast] mirror). Single-player safe. Optionally attached to an existing GameObject by
/// GUID (only after a trigger_hotload). Give the prop a Collider so the player's use key can
/// raycast it. Mined from interaction-station patterns across shipped s&box games.
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'InteractionStation'.</param>
/// <param name="directory">Subdirectory for the .cs file (path override). Defaults to 'Code'.</param>
/// <param name="graceSeconds">Seconds the station stays reserved for its last user after they leave, before anyone else can claim it. 0 = no grace window. Defaults to 5.</param>
/// <param name="requiredLevel">Unlock-level gate: users below this level can't use the station. 0 = no gate. The gate only bites once you wire the static ResolveUserLevel hook to your progression system. Defaults to 0.</param>
/// <param name="targetId">GUID of an existing GameObject to attach the station component to (only attaches if the type is already loaded — generate, trigger_hotload, then it places; otherwise add it after the hotload).</param>
[McpTool( "add_interaction_station" )]
public static Task<object> AddInteractionStation( string name = null, string directory = null, double? graceSeconds = null, int? requiredLevel = null, string targetId = null )
=> McpGate.Run( "add_interaction_station", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "graceSeconds", graceSeconds ), ( "requiredLevel", requiredLevel ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a batched write-side stat reporter component for Sandbox.Services.Stats — the write
/// partner of create_leaderboard_panel. Gameplay code calls the static <Name>.Report("kills",
/// 1) from anywhere; amounts accumulate locally and flush as Stats.Increment deltas on a timer
/// (default every 12 s, also on disable/destroy). Baseline-delta bookkeeping means a partial flush
/// retries the un-sent remainder instead of double-counting, and deltas larger than maxChunk are
/// sent in chunks. Returns { created, path, className, placedOn, note, nextSteps }. Place ONE in
/// the scene after trigger_hotload (add_component_to_new_object), or pass targetId to attach
/// immediately when the type is already compiled. Stats are PER LOCAL PLAYER (each client reports
/// its own) and only exist on leaderboards once the stat is registered for the project ident on
/// sbox.game. Fails if the file already exists.
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'StatReporter'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="flushIntervalSeconds">Seconds between batched flushes to the backend. Defaults to 12, clamped to >= 1.</param>
/// <param name="maxChunk">Largest amount sent in a single Stats.Increment call; bigger deltas are chunked. Defaults to 1000, clamped to >= 1.</param>
/// <param name="targetId">GUID of a GameObject to attach to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "add_leaderboard_stat" )]
public static Task<object> AddLeaderboardStat( string name = null, string directory = null, double? flushIntervalSeconds = null, double? maxChunk = null, string targetId = null )
=> McpGate.Run( "add_leaderboard_stat", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "flushIntervalSeconds", flushIntervalSeconds ), ( "maxChunk", maxChunk ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a currency component (sealed) persisted over Sandbox.Services.Stats — Steam-cloud
/// persistence, per Steam account, per package ident, with NO local save file. The stat stores the
/// ABSOLUTE balance: every Add(double)/TrySpend(double) pushes Stats.SetValue(statName, balance);
/// Flush() (and OnDestroy) pushes the buffered writes. On start it reads the balance back
/// asynchronously via Stats.GetLocalPlayerStats(ident) -> Refresh() -> Get(statName).Value
/// and fires the static OnBalanceLoaded(double); wait for IsLoaded before showing the balance.
/// CLOUD SEMANTICS (surprising): stat writes are buffered/rate-limited by the backend and apply
/// ONLY to the LOCAL Steam user — calling this for another player silently does nothing, so attach
/// it to the LOCAL player's GameObject (IsProxy guards keep remote copies inert); read-back is
/// eventually consistent and can lag minutes behind writes — the in-session Balance property is the
/// runtime truth. Dev sessions without a real published package ident may read back nothing
/// (balance starts 0 with a log line). packageIdent defaults to the running package (Game.Ident).
/// Returns { created, path, className, statName, packageIdent, flushEveryChange, placedOn, note,
/// nextSteps }. Next: trigger_hotload, attach to the local player, bind OnBalanceChanged for the
/// HUD. Refused during play mode. Use create_economy_wallet/create_currency_account for in-run
/// networked money, create_signed_save for offline local persistence; pair with
/// create_leaderboard_panel (the same stat can back a leaderboard).
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'SteamStatCurrency'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="statName">Sandbox.Services stat that stores the balance (the stat-name string is the contract between write and read-back). Defaults to 'currency'.</param>
/// <param name="packageIdent">Package ident to read stats from. Omit/empty = the running package (Game.Ident).</param>
/// <param name="flushEveryChange">Call Stats.Flush() after every balance change instead of relying on the buffered flush + OnDestroy flush (the backend rate-limits flushes). Defaults to false.</param>
/// <param name="targetId">GUID of the LOCAL player's GameObject to attach to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "add_steam_stat_currency" )]
public static Task<object> AddSteamStatCurrency( string name = null, string directory = null, string statName = null, string packageIdent = null, bool? flushEveryChange = null, string targetId = null )
=> McpGate.Run( "add_steam_stat_currency", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "statName", statName ), ( "packageIdent", packageIdent ), ( "flushEveryChange", flushEveryChange ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate an achievement engine: a component with an AchievementDef list
/// (id/title/description/target), per-achievement progress persisted via FileSystem.Data JSON
/// (survives restarts), Progress(id, amount) / Unlock(id) API on a static Instance, a static
/// OnAchievementUnlocked event, and an optional Stats.Increment mirror ('ach-<id>' += 1) on
/// unlock. Also emits a Razor unlock-toast HUD (<Name>Toast.razor + .razor.scss, razor_lint
/// clean) unless makeToast=false. Returns { created, path, className, toastRazorPath,
/// toastScssPath, toastClassName, achievements, placedOn, note, nextSteps }. Ids are sanitized to
/// [a-z0-9_-]; omitting achievements bakes 3 editable samples. After trigger_hotload: place ONE set
/// in the scene, and host the toast under a ScreenPanel (add_screen_panel). Pair with
/// add_achievement_trigger for world-trigger unlocks. LOCAL-only: achievements belong to each
/// client's local player. Fails if the .cs or toast .razor already exists.
/// </summary>
/// <param name="name">Class name for the generated engine component (toast panel becomes <name>Toast). Defaults to 'AchievementSet'.</param>
/// <param name="directory">Subdirectory for all generated files. Defaults to 'Code'.</param>
/// <param name="achievements">Achievement definitions baked into the component. Omit for 3 editable samples (first_steps, collector, veteran). JSON array.</param>
/// <param name="fileName">Save file name inside FileSystem.Data. Defaults to 'achievements.json'.</param>
/// <param name="mirrorToStats">Mirror each unlock into Sandbox.Services.Stats as 'ach-<id>' += 1. Defaults to true.</param>
/// <param name="makeToast">Also emit the <name>Toast.razor + .razor.scss unlock toast HUD. Defaults to true.</param>
/// <param name="toastSeconds">Seconds each unlock toast stays on screen. Defaults to 4, clamped to >= 0.5.</param>
/// <param name="targetId">GUID of a GameObject to attach the engine to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_achievement_set" )]
public static Task<object> CreateAchievementSet( string name = null, string directory = null, JsonNode achievements = null, string fileName = null, bool? mirrorToStats = null, bool? makeToast = null, double? toastSeconds = null, string targetId = null )
=> McpGate.Run( "create_achievement_set", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "achievements", achievements ), ( "fileName", fileName ), ( "mirrorToStats", mirrorToStats ), ( "makeToast", makeToast ), ( "toastSeconds", toastSeconds ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a first-person pickup / carry / throw component (sealed Component) for physics props.
/// Attach it to the PLAYER (the object that owns the camera). It eye-traces from Scene.Camera for a
/// Rigidbody-bearing GameObject tagged [Property] CarryTag (default 'carryable') within [Property]
/// Range; grabbing routes a host-authoritative [Rpc.Host] request that re-validates the target and
/// caller, hands the object's network ownership to the carrier
/// (GameObject.Network.AssignOwnership), and disables the rigidbody's MotionEnabled while held. The
/// held object follows a hold point ([Property] Vector3 HoldOffset in front of the camera) each
/// FixedUpdate; dropping restores physics, throwing applies an impulse ([Property] float
/// ThrowForce). The held-object id is [Sync(SyncFlags.FromHost)] so proxies see the carrying state,
/// and static OnPickedUp / OnDropped events fire uniformly for SFX/VFX. PAIRS with physics props —
/// give each carryable a Rigidbody + Collider and the CarryTag (set_tags); network-spawn them for
/// multiplayer so ownership + transform replicate. Single-player safe (IsProxy is false and RPCs
/// run locally with no session). Inputs: GrabAction (default 'use') grabs/drops, ThrowAction
/// (default 'attack1') throws. Optionally attach to an existing player GameObject by GUID after a
/// hotload.
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'CarrySystem'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="range">Eye-trace reach for grabbing a carryable, in world units. Defaults to 130.</param>
/// <param name="throwForce">Impulse magnitude applied on throw (scales with the prop's mass — tune per game). Defaults to 20000.</param>
/// <param name="carryTag">Only objects with this tag (and a Rigidbody) can be picked up; lower-cased/underscored to match s&box tag convention. Defaults to 'carryable'.</param>
/// <param name="targetId">GUID of the PLAYER GameObject (the one with the camera) to attach to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_carry_system" )]
public static Task<object> CreateCarrySystem( string name = null, string directory = null, double? range = null, double? throwForce = null, string carryTag = null, string targetId = null )
=> McpGate.Run( "create_carry_system", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "range", range ), ( "throwForce", throwForce ), ( "carryTag", carryTag ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a host-authoritative currency ACCOUNT component (sealed) — the audited sibling of
/// create_economy_wallet (wallet = simple money, account = money + a ledger). Balance is
/// [Sync(SyncFlags.FromHost)] so clients can't author their own money; host-guarded Deposit(amount,
/// reason), Withdraw(amount, reason) -> bool, and TryTransfer(otherAccount, amount, reason)
/// -> bool each record a Transaction { Time (Time.Now), signed Amount, Reason, BalanceAfter }
/// into a fixed-size ring buffer (historySize, default 32; oldest entries overwritten SILENTLY).
/// GetRecentTransactions(max) returns them NEWEST FIRST — the ledger is HOST-SIDE ONLY and does not
/// replicate (Balance does); proxies get an empty list. Bind the instance OnBalanceChanged(long)
/// for HUD labels. Single-player safe. Returns { created, path, className, startingBalance,
/// historySize, placedOn, note, nextSteps }. Next: trigger_hotload, then attach via targetId re-run
/// or add_component_to_new_object. Refuses if the file already exists; refused during play mode.
/// Use create_economy_wallet when you don't need the audit trail; pair with create_idle_economy (it
/// auto-wires this account's Money/TrySpend).
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'CurrencyAccount'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="startingBalance">Balance the account opens with (host seeds it in OnStart). Defaults to 0.</param>
/// <param name="historySize">Transaction ring-buffer capacity (clamped 1..4096); fixed once the first transaction is recorded, oldest overwritten silently after that. Defaults to 32.</param>
/// <param name="targetId">GUID of a per-player/bank GameObject to attach to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_currency_account" )]
public static Task<object> CreateCurrencyAccount( string name = null, string directory = null, int? startingBalance = null, int? historySize = null, string targetId = null )
=> McpGate.Run( "create_currency_account", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "startingBalance", startingBalance ), ( "historySize", historySize ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a networked coin / currency pickup component (sealed, Component.ITriggerListener).
/// Host-spawned; when a GameObject carrying PlayerTag ('player') enters its trigger the HOST
/// validates and grants Value (default 1) into a wallet on the player, then destroys the pickup
/// network-wide (the host Destroy() replicates — there is no NetworkDestroy on this SDK). Optional
/// magnet: while MagnetRadius (default 0 = off) is > 0 the coin accelerates toward the nearest
/// player each FixedUpdate (host-side, capped by MaxMagnetSpeed). IsProxy guards keep the grant +
/// despawn host-only in multiplayer (NetworkSpawn the coin on the host); single-player works with
/// no networking. The deposit is reflection-free and dependency-free: a static Grant seam is wired
/// ONCE to the direct typed call — player.Components.Get<EconomyWallet>()?.AddMoney(amount) —
/// so the component compiles with NO hard reference to a specific wallet class (rename the wallet
/// type if yours differs; mirrors create_pickup's self-contained convention). WalletComponentName
/// (default 'EconomyWallet') is used to locate the wallet and name the fix if Grant is left unwired
/// (never silent). Pairs with create_economy_wallet (AddMoney/TrySpend/CanAfford) and
/// create_floating_combat_text (spawn a '+N' popup from OnCollected).
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'CurrencyPickup'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="value">How much currency the pickup grants into the wallet. Defaults to 1.</param>
/// <param name="magnetRadius">Magnet range in world units — within it the coin flies to the nearest player each FixedUpdate. 0 = magnet off. Defaults to 0.</param>
/// <param name="walletComponentName">Type name of the wallet component to deposit into (used to locate it and to name the fix if the Grant seam is left unwired). Defaults to 'EconomyWallet'.</param>
/// <param name="targetId">GUID of a coin GameObject to attach to — give it a trigger Collider (SphereCollider, IsTrigger=true). Only attaches if the type is already loaded — hotload first.</param>
[McpTool( "create_currency_pickup" )]
public static Task<object> CreateCurrencyPickup( string name = null, string directory = null, int? value = null, double? magnetRadius = null, string walletComponentName = null, string targetId = null )
=> McpGate.Run( "create_currency_pickup", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "value", value ), ( "magnetRadius", magnetRadius ), ( "walletComponentName", walletComponentName ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a host-authoritative time-of-day clock: [Sync(SyncFlags.FromHost)] TimeOfDay (0–24) +
/// Day advancing by Time.Delta, IsDay/IsNight from sunrise/sunset hours, and static OnNewDay /
/// OnDayNightChanged events to drive lighting, NPC schedules, or spawns. Single-player safe. Pairs
/// with create_round_phase_machine. Optionally attached to a GameObject by GUID (after a hotload).
/// </summary>
/// <param name="name">Class name. Defaults to 'DayNightClock'.</param>
/// <param name="directory">Subdirectory for the .cs file. Defaults to 'Code'.</param>
/// <param name="dayLengthSeconds">Real seconds per in-game day. Defaults to 600 (10 min).</param>
/// <param name="startHour">Hour the clock starts at (0–24). Defaults to 8.</param>
/// <param name="sunriseHour">Hour day begins. Defaults to 6.</param>
/// <param name="sunsetHour">Hour night begins. Defaults to 20.</param>
/// <param name="targetId">GUID of an existing GameObject to attach to (hotload first).</param>
[McpTool( "create_day_night_clock" )]
public static Task<object> CreateDayNightClock( string name = null, string directory = null, double? dayLengthSeconds = null, double? startHour = null, double? sunriseHour = null, double? sunsetHour = null, string targetId = null )
=> McpGate.Run( "create_day_night_clock", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "dayLengthSeconds", dayLengthSeconds ), ( "startHour", startHour ), ( "sunriseHour", sunriseHour ), ( "sunsetHour", sunsetHour ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a host-authoritative currency Wallet component: a [Sync(SyncFlags.FromHost)] Money
/// balance (only the host can write it — plain [Sync] money is the classic economy exploit) with
/// AddMoney / TrySpend / SetMoney / CanAfford and an OnMoneyChanged event. Single-player safe.
/// Optionally attached to an existing GameObject by GUID (after a hotload). Pairs with a save
/// system for persistence. Mined from the most-requested currency pattern across 51 games.
/// </summary>
/// <param name="name">Class name. Defaults to 'Wallet'.</param>
/// <param name="directory">Subdirectory for the .cs file. Defaults to 'Code'.</param>
/// <param name="startingMoney">Initial balance the host seeds on start. Defaults to 0.</param>
/// <param name="targetId">GUID of an existing GameObject to attach the Wallet to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_economy_wallet" )]
public static Task<object> CreateEconomyWallet( string name = null, string directory = null, int? startingMoney = null, string targetId = null )
=> McpGate.Run( "create_economy_wallet", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "startingMoney", startingMoney ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a self-contained, host-authoritative elo rating component: standard elo math (expected
/// = 1/(1+10^((Rb-Ra)/400)), delta = K * (score - expected)) with a [Property] K-factor, ratings in
/// a [Sync(SyncFlags.FromHost)] NetDictionary<long,float> keyed by SteamId, and host-side
/// persistence via FileSystem.Data JSON. API on a static Instance: ReportMatch(winnerSteamId,
/// loserSteamId) for 1v1 and ReportTeamMatch(winnerIds, loserIds) for teams (team-average elo,
/// uniform delta per member) — both are IsProxy-guarded no-ops on clients; GetRating(steamId) works
/// anywhere (unknown players = defaultRating); the static OnRatingChanged(steamId, newRating) fires
/// on EVERY machine via an [Rpc.Broadcast]. Returns { created, path, className, kFactor,
/// defaultRating, placedOn, note, nextSteps }. After trigger_hotload: place ONE in the scene and
/// network its GameObject (network_spawn) or the [Sync] never replicates. Only the HOST's disk
/// holds the ratings ledger. Fails if the file already exists.
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'EloRatingSystem'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="kFactor">Elo K-factor — how far one result moves ratings (32 = fast, 16 = stable). Defaults to 32, clamped to >= 1.</param>
/// <param name="defaultRating">Rating assigned to players with no recorded matches. Defaults to 1000.</param>
/// <param name="fileName">Save file name inside FileSystem.Data (host-side ledger). Defaults to 'elo_ratings.json'.</param>
/// <param name="targetId">GUID of a GameObject to attach to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_elo_rating_system" )]
public static Task<object> CreateEloRatingSystem( string name = null, string directory = null, double? kFactor = null, double? defaultRating = null, string fileName = null, string targetId = null )
=> McpGate.Run( "create_elo_rating_system", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "kFactor", kFactor ), ( "defaultRating", defaultRating ), ( "fileName", fileName ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a typed LOCAL pub/sub event bus: a pure STATIC class (NOT a Component — nothing to
/// place in the scene) with Subscribe<T>(owner, Action<T>), Unsubscribe(owner) (removes
/// all of that owner's handlers across every event type), Publish<T>(evt) (synchronous,
/// exact-type-T subscribers only, snapshot-iterated so handlers may subscribe/unsubscribe
/// mid-publish), Count<T>() and Clear(), keyed by a plain Dictionary<Type,
/// List<(object, Delegate)>> — plus a tiny example event record ({name}Ping). Decouples
/// game systems: the quest system publishes 'EnemyDied', UI and achievements subscribe, neither
/// knows the other. Returns {created, path, className, exampleEvent, api[], note}. Next:
/// trigger_hotload + get_compile_errors, then Subscribe in components' OnStart and — REQUIRED —
/// Unsubscribe(this) in OnDestroy: handler lists hold PLAIN references (no weak refs), so a
/// component that never unsubscribes leaks itself for the scene's life; call Clear() on scene
/// teardown. Limits: LOCAL only — Publish reaches the calling machine's subscribers, NOT other
/// clients; for networked events pair with [Rpc.Broadcast]/[Rpc.Host] methods that Publish on
/// arrival. No base-type dispatch (Publish<Base> won't reach Subscribe<Derived>).
/// Refuses to overwrite an existing file; refused during play mode.
/// </summary>
/// <param name="name">Static class/file name. Defaults to 'EventBus'. The example event record is named {name}Ping. Sanitized to a valid C# identifier.</param>
/// <param name="directory">Subdirectory under the project root for the .cs file. Defaults to 'Code'.</param>
[McpTool( "create_event_bus" )]
public static Task<object> CreateEventBus( string name = null, string directory = null )
=> McpGate.Run( "create_event_bus", McpGate.Args( ( "name", name ), ( "directory", directory ) ) );
/// <summary>
/// Generate a generalized L4D-style AI/pacing director component (host-authoritative). On a
/// configurable interval the host rolls a weighted pick over a [Property] List<GameObject>
/// EventPrefabs (with a parallel List<float> Weights), skips any event already active
/// (dedupe) and anything past a MaxActive concurrency cap, clones the chosen prefab, NetworkSpawns
/// it, and attaches a generated {name}TimedEvent companion so each spawned event self-destructs
/// after EventLifetime seconds. Great for ambient events, waves, and world events. Single-player
/// safe (IsProxy guard; NetworkSpawn falls back to a local clone). Fill EventPrefabs/Weights in the
/// inspector or via the bridge after a hotload; edit the RollInterval() stub to make pacing
/// adaptive (player-count/inactivity/time-pressure factors) per the ai-director cookbook.
/// Optionally attached to an existing GameObject by GUID (after a hotload). NOTE: emits ONE .cs
/// file containing two classes ({name} + {name}TimedEvent); the type only resolves after
/// trigger_hotload.
/// </summary>
/// <param name="name">Class name for the director (a {name}TimedEvent companion is generated alongside it). Defaults to 'EventDirector'.</param>
/// <param name="path">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="intervalSeconds">Base seconds between director rolls. Defaults to 30 (clamped to >= 0.1).</param>
/// <param name="maxActive">Maximum number of concurrently-live events. Defaults to 3 (clamped to >= 1).</param>
/// <param name="eventLifetime">Seconds before each spawned event self-destructs. Defaults to 60 (clamped to >= 0.1).</param>
/// <param name="targetId">GUID of an existing GameObject to attach the director to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_event_director" )]
public static Task<object> CreateEventDirector( string name = null, string path = null, double? intervalSeconds = null, int? maxActive = null, double? eventLifetime = null, string targetId = null )
=> McpGate.Run( "create_event_director", McpGate.Args( ( "name", name ), ( "path", path ), ( "intervalSeconds", intervalSeconds ), ( "maxActive", maxActive ), ( "eventLifetime", eventLifetime ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a host-authoritative gacha / loot-box roller component. Two-level pick: parallel
/// [Property] lists RarityNames + RarityWeights select a RARITY by cumulative weight (the
/// create_weighted_loot_table shape), then a flat 'Rarity:Item' [Property] list (e.g.
/// 'Legendary:Dragon Fang') picks an ITEM uniformly within that rarity — simple and
/// inspector-editable. A pity counter (PityAfter, default 50) guarantees the rarest tier (the LAST
/// entry in RarityNames) after N rolls without it and resets on a hit. Duplicate detection against
/// an owned-items set fires a host-side OnDuplicate hook (marked TODO: convert dupes to
/// shards/currency). Roll() routes to the host via an [Rpc.Host] RequestRoll (Rpc.Caller
/// re-validated — NetFlags is not security) and the result fans out via [Rpc.Broadcast] so every
/// machine fires the static OnRolled(rarity, item, isDuplicate) event; single-player safe (RPCs run
/// locally). Use create_weighted_loot_table instead for a simpler single-tier weighted pick with no
/// pity/dupe/networking. Pairs with create_economy_wallet (spend currency to roll) and
/// create_inventory (store the pulls).
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'GachaDropTable'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="pityAfter">Rolls without a rarest-tier hit before the next roll is guaranteed rarest. 0 disables pity. Defaults to 50.</param>
/// <param name="targetId">GUID of a per-player/manager GameObject to attach to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_gacha_drop_table" )]
public static Task<object> CreateGachaDropTable( string name = null, string directory = null, int? pityAfter = null, string targetId = null )
=> McpGate.Run( "create_gacha_drop_table", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "pityAfter", pityAfter ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a minimal game-manager Component: a static Instance singleton, [Property] MaxPlayers /
/// GameState, and a Component.INetworkListener OnActive hook that logs player connects. Writes
/// <name>.cs and returns { created, path, className }. NOTE: the
/// includeScore/includeTimer/includeSpawning params are not currently applied — the same minimal
/// manager is always generated (for richer game-loop scaffolds see create_round_phase_machine /
/// create_objective_system / create_economy_wallet). Follow with trigger_hotload, then
/// get_compile_errors, then place via add_component_to_new_object.
/// </summary>
/// <param name="name">Class name. Defaults to 'GameManager'.</param>
/// <param name="directory">Subdirectory under code/ for the file.</param>
/// <param name="includeScore">Include score tracking (currently not applied by the handler).</param>
/// <param name="includeTimer">Include round timer with countdown (currently not applied by the handler).</param>
/// <param name="includeSpawning">Include player spawning from prefab at spawn point (currently not applied by the handler).</param>
[McpTool( "create_game_manager" )]
public static Task<object> CreateGameManager( string name = null, string directory = null, bool? includeScore = null, bool? includeTimer = null, bool? includeSpawning = null )
=> McpGate.Run( "create_game_manager", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "includeScore", includeScore ), ( "includeTimer", includeTimer ), ( "includeSpawning", includeSpawning ) ) );
/// <summary>
/// Generate a Health component: MaxHealth, [Sync] CurrentHealth, TakeDamage/Heal, an OnDeath event,
/// optional regen and respawn. Host-authoritative damage when networked, single-player safe.
/// Optionally attached to an existing GameObject by GUID.
/// </summary>
/// <param name="name">Class name. Defaults to 'Health'.</param>
/// <param name="directory">Subdirectory for the .cs file. Defaults to 'Code'.</param>
/// <param name="maxHealth">Starting/maximum health. Defaults to 100.</param>
/// <param name="regen">Include passive health regeneration after a delay. Defaults to false.</param>
/// <param name="respawn">On death, respawn at a RespawnPoint (wire it with set_component_reference) instead of disabling. Defaults to false.</param>
/// <param name="targetId">GUID of an existing GameObject to attach the Health component to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_health_system" )]
public static Task<object> CreateHealthSystem( string name = null, string directory = null, double? maxHealth = null, bool? regen = null, bool? respawn = null, string targetId = null )
=> McpGate.Run( "create_health_system", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "maxHealth", maxHealth ), ( "regen", regen ), ( "respawn", respawn ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a hold-to-confirm action component (sealed Component). While a named input action is
/// held (Input.Down), a public Progress value fills 0→1 over [Property] float HoldSeconds;
/// releasing early snaps back to 0, or drains down if [Property] bool DecayOnRelease. Reaching 1
/// fires the static OnConfirmed(GameObject) event, then a short CooldownSeconds blocks
/// re-triggering. The classic 'hold E to disarm / open / revive' interaction. No UI is generated —
/// read the public Progress (0..1) from your own HUD to draw a radial or bar; a #region Feedback
/// hook marks where to tie in a sound/effect. LOCAL/owner-only: input is IsProxy-guarded so it
/// never fires on proxies and is single-player safe. For a host-authoritative outcome, call an
/// [Rpc.Host] from inside the OnConfirmed subscriber. Attach to the player (or any owned object
/// that reads input); optionally attach to an existing GameObject by GUID after a hotload.
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'HoldToConfirm'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="action">Input action name that must be held (must exist in the project's Input settings — see ensure_input_action). Defaults to 'use'.</param>
/// <param name="holdSeconds">Seconds of continuous hold required to confirm. Defaults to 1.5.</param>
/// <param name="decayOnRelease">Baked default for DecayOnRelease: if true, releasing early drains Progress back down instead of snapping to 0 (editable per-instance). Defaults to false.</param>
/// <param name="targetId">GUID of a GameObject to attach the component to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_hold_to_confirm" )]
public static Task<object> CreateHoldToConfirm( string name = null, string directory = null, string action = null, double? holdSeconds = null, bool? decayOnRelease = null, string targetId = null )
=> McpGate.Run( "create_hold_to_confirm", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "action", action ), ( "holdSeconds", holdSeconds ), ( "decayOnRelease", decayOnRelease ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a geometric idle-economy component (sealed): generators on the classic BaseCost *
/// Growth^Owned cost curve with Buy 1 / Buy N / Buy Max — CostOf(index, count),
/// MaxAffordable(index), TryBuy(index, count) and BuyMax(index) all use the CLOSED-FORM geometric
/// series (cost = c0*(g^n-1)/(g-1), buyMax = floor(log_g(funds*(g-1)/c0+1))) — no per-copy loops,
/// Buy 1000 is the same math as Buy 1. Wallet wiring is TypeLibrary reflection with NO compile-time
/// wallet dependency (the shipped create_idle_income pattern): each income tick invokes
/// AddMoney(long|int) on the first sibling component that has one, purchases invoke
/// TrySpend(long|int), Buy Max reads the sibling's Money (or Balance) property — works out of the
/// box next to create_economy_wallet or create_currency_account; with NO wallet sibling, purchases
/// are refused with a Log.Warning (never silent) while TotalEarned still accumulates.
/// Host-authoritative: mutations IsProxy-guarded; owned counts are HOST-SIDE state (not
/// replicated); TotalEarned is [Sync(FromHost)]. Static events OnPurchased(index, count, cost) and
/// OnIncomeTick(amount, total). BuyMax steps down once past a whole-currency rounding edge rather
/// than failing. Returns { created, path, className, generators, tickSeconds, placedOn, note,
/// nextSteps }. Next: trigger_hotload, place it NEXT TO a wallet on the same GameObject, tune the
/// parallel GeneratorNames/BaseCosts/Growths/IncomesPerSecond lists with set_property. Refused
/// during play mode. Pair with create_offline_progress for away-time earnings; use
/// create_idle_income for a bare income ticker with no purchasing.
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'IdleEconomy'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="tickSeconds">Seconds between income grants (floored at 0.1). Defaults to 1.</param>
/// <param name="generators">Baked-in generator defaults (inspector-tunable after generation). Omit for a starter trio: Cursor 15/1.15/0.5, Farm 200/1.15/4, Factory 3000/1.12/30. JSON array.</param>
/// <param name="targetId">GUID of the GameObject to attach to — put it on the SAME GameObject as the wallet so the reflection wiring finds it (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_idle_economy" )]
public static Task<object> CreateIdleEconomy( string name = null, string directory = null, double? tickSeconds = null, JsonNode generators = null, string targetId = null )
=> McpGate.Run( "create_idle_economy", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "tickSeconds", tickSeconds ), ( "generators", generators ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a host-authoritative passive income component: every tickSeconds the host grants
/// incomePerTick × Multiplier, auto-wiring the first sibling component with an AddMoney(int) method
/// (a create_economy_wallet scaffold plugs in with zero code) or an overridable Grant() seam;
/// TotalEarned is [Sync(FromHost)] and static OnIncomeTick fires per grant. The idle-game kit:
/// wallet (create_economy_wallet) + this + create_offline_progress. Writes a .cs file and returns {
/// created, path, className, nextSteps } — follow with trigger_hotload + compile_status.
/// </summary>
/// <param name="name">Class/file name (default 'IdleIncome' -> Code/IdleIncome.cs). Errors if the file exists.</param>
/// <param name="directory">Directory for the .cs file. Default 'Code'.</param>
/// <param name="incomePerTick">Amount granted per tick. Default 1.</param>
/// <param name="tickSeconds">Seconds between grants. Default 1.</param>
[McpTool( "create_idle_income" )]
public static Task<object> CreateIdleIncome( string name = null, string directory = null, double? incomePerTick = null, double? tickSeconds = null )
=> McpGate.Run( "create_idle_income", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "incomePerTick", incomePerTick ), ( "tickSeconds", tickSeconds ) ) );
/// <summary>
/// Generate a Component.IPressable interactable: the built-in PlayerController 'use' key drives
/// Press()/Hover()/Blur() with no custom player code. Includes a static OnPressed event, an
/// optional cooldown (TimeUntil), and a private OnPress() extensionpoint for effects. For
/// host-authoritative side-effects call an [Rpc.Host] from OnPress(). The Prompt property is left
/// to your game's HUD. Optionally attached to an existing GameObject by GUID (after a hotload).
/// </summary>
/// <param name="name">Class name. Defaults to 'Interactable'.</param>
/// <param name="directory">Subdirectory for the .cs file. Defaults to 'Code'.</param>
/// <param name="prompt">Prompt string shown by the game's HUD when hovering. Defaults to 'Press'.</param>
/// <param name="cooldownSeconds">Seconds before the interactable can be pressed again. 0 = no cooldown. Defaults to 0.</param>
/// <param name="targetId">GUID of an existing GameObject to attach the component to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_interactable" )]
public static Task<object> CreateInteractable( string name = null, string directory = null, string prompt = null, double? cooldownSeconds = null, string targetId = null )
=> McpGate.Run( "create_interactable", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "prompt", prompt ), ( "cooldownSeconds", cooldownSeconds ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a slot-based inventory component using parallel List<string> ItemIds /
/// List<int> Counts (serialization-safe, inspector-editable). Includes TryAdd (stack-first,
/// partial-add rejected), TryRemove, CountOf, Move (swap or merge same-id slots), and Clear. Static
/// OnChanged event fires after every successful mutation. Host-authoritative usage note: mutate on
/// the host in multiplayer, replicate via your own [Sync]/RPC. Pairs with create_pickup.
/// </summary>
/// <param name="name">Class name. Defaults to 'Inventory'.</param>
/// <param name="directory">Subdirectory for the .cs file. Defaults to 'Code'.</param>
/// <param name="capacity">Total slot count. Defaults to 24.</param>
/// <param name="maxStack">Maximum items per slot (stack cap). Defaults to 99.</param>
/// <param name="targetId">GUID of an existing GameObject to attach to (hotload first).</param>
[McpTool( "create_inventory" )]
public static Task<object> CreateInventory( string name = null, string directory = null, int? capacity = null, int? maxStack = null, string targetId = null )
=> McpGate.Run( "create_inventory", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "capacity", capacity ), ( "maxStack", maxStack ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a Razor PanelComponent that fetches and displays a Sandbox.Services leaderboard derived
/// from a stat name. Produces TWO files: {name}.razor and {name}.razor.scss. The panel
/// auto-refreshes every 30 s, shows rank/displayName/value rows, handles loading state, and
/// includes a BuildHash() override (razor-lint clean). Must be hosted under a ScreenPanel or
/// WorldPanel. Stats must be configured for the project ident on sbox.game. Uses
/// Leaderboards.Get(statName) + board.Refresh() -- the exact API from ServicesQueryHandler. Returns
/// { created, razorPath, scssPath, className, note }. Follow with trigger_hotload, then
/// get_compile_errors, then host it via add_screen_panel (panelComponent=className).
/// </summary>
/// <param name="name">Class name for the panel component. Defaults to 'LeaderboardPanel'.</param>
/// <param name="directory">Subdirectory for the generated files. Defaults to 'Code/UI'.</param>
/// <param name="statName">Sandbox.Services stat name the leaderboard is derived from. Defaults to 'score'.</param>
/// <param name="title">Display title shown at the top of the panel. Defaults to 'Leaderboard'.</param>
/// <param name="maxRows">Maximum leaderboard rows to fetch and display. Defaults to 10.</param>
[McpTool( "create_leaderboard_panel" )]
public static Task<object> CreateLeaderboardPanel( string name = null, string directory = null, string statName = null, string title = null, int? maxRows = null )
=> McpGate.Run( "create_leaderboard_panel", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "statName", statName ), ( "title", title ), ( "maxRows", maxRows ) ) );
/// <summary>
/// Generate GameResource-based loot tables — the data-asset sibling of create_weighted_loot_table.
/// One .cs file containing THREE types: an entry POCO { Name, Weight, optional NestedTable
/// reference }, a [AssetType]-registered GameResource loot-table class (designers author '.loot'
/// files in the editor asset browser — New > Loot Table — after the hotload; NOTE:
/// [AssetType(Name=..., Extension=..., Category=...)] is used because GameResourceAttribute is
/// [Obsolete] on this SDK), and a '<name>Resolver' Component that rolls an assigned table by
/// cumulative weight. Nested tables: an entry with a NestedTable rolls INTO that table instead of
/// dropping its Name, capped at maxDepth (default 4) with a self-reference guard so cycles
/// terminate (at the cap the deepest entry's Name is returned). Resolver.Roll() returns the item
/// name (null + warning when no Table is assigned or the table is empty; entries with weight <=
/// 0 never win; all-zero weights fall back to the first entry) and fires the static
/// OnLoot(GameObject, item) event; roll HOST-SIDE and replicate the result yourself. targetId
/// attaches the RESOLVER (the resource is an asset type, not a component). SURPRISING: pick an
/// extension that is NOT a suffix of a built-in one (e.g. avoid 'cfg') or ResourceLibrary picks up
/// engine files as phantom instances. Returns { created, path, className, resolverClass, extension,
/// maxDepth, placedOn, note, nextSteps }. Next: trigger_hotload -> author .loot assets in the
/// editor -> assign the resolver's Table (set_property with the asset path). Refused during play
/// mode. Use create_weighted_loot_table for a single inline component with no asset files;
/// create_gacha_drop_table for pity + duplicate mechanics.
/// </summary>
/// <param name="name">Class name for the generated GameResource (the resolver becomes '<name>Resolver'). Defaults to 'LootTableResource'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="extension">Asset file extension (lowercase alphanumerics; avoid suffixes of built-in extensions like 'cfg'). Defaults to 'loot'.</param>
/// <param name="title">Display name of the asset type in the editor's New-asset menu. Defaults to 'Loot Table'.</param>
/// <param name="maxDepth">Default nested-table resolve depth cap baked into the resolver (clamped 0..16; also a [Property]). Defaults to 4.</param>
/// <param name="targetId">GUID of a GameObject to attach the RESOLVER component to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_loot_table_resource" )]
public static Task<object> CreateLootTableResource( string name = null, string directory = null, string extension = null, string title = null, int? maxDepth = null, string targetId = null )
=> McpGate.Run( "create_loot_table_resource", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "extension", extension ), ( "title", title ), ( "maxDepth", maxDepth ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a between-runs roguelite meta-progression component (sealed, owner-only): persistent
/// meta-currency + an unlock-flag dictionary saved to FileSystem.Data JSON (dirty-flag autosave +
/// OnDestroy, the create_save_system shape). API: Grant(long), TrySpend(long) -> bool,
/// Unlock(key) (idempotent — the static OnUnlocked(key) event fires only on the FIRST unlock, and
/// unlocks write through to disk immediately), IsUnlocked(key) -> bool, and the run-end seam
/// BankRun(int earned) which converts a finished run's earnings into meta-currency, bumps
/// RunsBanked, and saves immediately — call it from your round machine's end-of-run transition
/// (create_round_state_machine / create_round_phase_machine). Instance OnCurrencyChanged(long)
/// drives meta-shop balance labels. Versioned payload: old-version files start fresh.
/// IsProxy-guarded — in multiplayer each machine banks only its own local meta file (this is
/// per-machine persistence, not a server economy). Returns { created, path, className, fileName,
/// version, placedOn, note, nextSteps }. Next: trigger_hotload, attach to a persistent
/// hub/menu-scene manager GameObject, gate content with IsUnlocked when building the player.
/// Refused during play mode. Pair with create_currency_account (in-run money) and
/// create_signed_save (if the meta file needs tamper evidence — this one is unsigned).
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'MetaProgression'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="fileName">FileSystem.Data path the meta state is written to. Defaults to 'meta.json'.</param>
/// <param name="version">Payload version; mismatched files start fresh. Defaults to 1.</param>
/// <param name="autosaveSeconds">Dirty-flag autosave cadence in seconds; 0 disables the heartbeat (unlocks and BankRun still write through immediately). Defaults to 10.</param>
/// <param name="targetId">GUID of a persistent manager GameObject to attach to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_meta_progression" )]
public static Task<object> CreateMetaProgression( string name = null, string directory = null, string fileName = null, int? version = null, double? autosaveSeconds = null, string targetId = null )
=> McpGate.Run( "create_meta_progression", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "fileName", fileName ), ( "version", version ), ( "autosaveSeconds", autosaveSeconds ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a sim/tycoon needs engine component: a [Property] list of need definitions (name, decay
/// rate/s, critical threshold, weight) with per-need 0..100 values that decay over Time.Delta,
/// Satisfy(name, amount) to restore, an aggregate Happiness (weighted mean, [Sync(FromHost)] when
/// networked), and static OnNeedCritical (edge-triggered: fires once crossing below threshold,
/// re-arms above) + OnHappinessChanged (>0.25-point moves) events. Returns {created, path,
/// className, needs[], propertyNames[], note}. Next: trigger_hotload, get_compile_errors, then
/// attach via targetId re-call or add_component_with_properties; drive from game code (e.g. a
/// create_interactable that calls Satisfy). Limits: per-need values live on the simulating machine
/// only (host) — sync per-need UI yourself via RPCs; events fire on the simulating machine only;
/// networked default true means a no-session solo playtest won't tick (everything is a proxy) —
/// pass networked:false to iterate solo. Refused during play mode; refuses to overwrite an existing
/// file.
/// </summary>
/// <param name="name">Class/file name. Defaults to 'NeedsSystem'. Sanitized to a valid C# identifier.</param>
/// <param name="directory">Subdirectory under the project root for the .cs file. Defaults to 'Code'.</param>
/// <param name="needs">Need definitions baked as inspector-editable defaults. Defaults to the classic sim trio: Hunger(0.8/s), Energy(0.5/s), Fun(0.3/s). JSON array.</param>
/// <param name="networked">true (default): host-authoritative (IsProxy guard) + [Sync(FromHost)] Happiness — needs a host session. false: local build that ticks in a solo playtest.</param>
/// <param name="targetId">GUID of a GameObject to attach the component to (only attaches if the type is already in the TypeLibrary — hotload first, then re-call or use add_component_with_properties).</param>
[McpTool( "create_needs_system" )]
public static Task<object> CreateNeedsSystem( string name = null, string directory = null, JsonNode needs = null, bool? networked = null, string targetId = null )
=> McpGate.Run( "create_needs_system", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "needs", needs ), ( "networked", networked ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate an NPC controller script with NavMeshAgent pathfinding. Supports patrol, chase, and
/// patrol-chase behaviors.
/// </summary>
/// <param name="name">Class name. Defaults to 'NpcController'.</param>
/// <param name="directory">Subdirectory under code/ for the file.</param>
/// <param name="behavior">AI behavior: 'patrol' (follow waypoints), 'chase' (follow player), 'patrol_chase' (patrol until player nearby). Defaults to 'patrol'. One of: patrol | chase | patrol_chase.</param>
/// <param name="moveSpeed">Movement speed. Defaults to 150.</param>
/// <param name="chaseRange">Detection range for chase behavior. Defaults to 500.</param>
[McpTool( "create_npc_controller" )]
public static Task<object> CreateNpcController( string name = null, string directory = null, string behavior = null, double? moveSpeed = null, double? chaseRange = null )
=> McpGate.Run( "create_npc_controller", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "behavior", behavior ), ( "moveSpeed", moveSpeed ), ( "chaseRange", chaseRange ) ) );
/// <summary>
/// Generate an ObjectiveManager component — the win/lose brain of a game. Tracks an objective
/// (collect_all / reach_goal / survive_time / eliminate_all), fires a win, and handles a lose
/// condition (fall below kill-Z / timer / out of lives). Self-contained C#; other systems call
/// ObjectiveManager.Instance. Optionally placed as a scene singleton. Returns { created, path,
/// className, gameObject, note } — gameObject is the placed singleton, or null with a note when the
/// fresh type isn't in the TypeLibrary yet. Follow with trigger_hotload, then get_compile_errors;
/// if placement was skipped, place with add_component_to_new_object after the hotload.
/// </summary>
/// <param name="name">Class name. Defaults to 'ObjectiveManager'.</param>
/// <param name="directory">Subdirectory for the .cs file. Defaults to 'Code'.</param>
/// <param name="objective">Win condition. Defaults to 'reach_goal'. One of: collect_all | reach_goal | survive_time | eliminate_all.</param>
/// <param name="targetCount">How many to collect/eliminate (for collect_all / eliminate_all). Defaults to 3.</param>
/// <param name="timeLimit">Seconds — survive this long to win (survive_time) or before losing (loseOn=timer). Defaults to 60.</param>
/// <param name="loseOn">Lose condition. 'fall' = player drops below killZ. Defaults to 'fall'. One of: fall | timer | lives | none.</param>
/// <param name="killZ">World Z below which the player is considered fallen out of the world. Defaults to -1000.</param>
/// <param name="lives">Lives before game over (loseOn=lives). Defaults to 1.</param>
/// <param name="placeInScene">Place the manager as a scene singleton. Defaults to true. (Only attaches if the type is already loaded — generate, hotload, then it places; otherwise add it after hotload.).</param>
[McpTool( "create_objective_system" )]
public static Task<object> CreateObjectiveSystem( string name = null, string directory = null, string objective = null, int? targetCount = null, double? timeLimit = null, string loseOn = null, double? killZ = null, int? lives = null, bool? placeInScene = null )
=> McpGate.Run( "create_objective_system", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "objective", objective ), ( "targetCount", targetCount ), ( "timeLimit", timeLimit ), ( "loseOn", loseOn ), ( "killZ", killZ ), ( "lives", lives ), ( "placeInScene", placeInScene ) ) );
/// <summary>
/// Generate an offline / idle-progress component (sealed, owner/host-only) — the idle-game staple.
/// Persists LastSeenUtc (DateTime) to FileSystem.Data JSON on a dirty-flag autosave heartbeat
/// (AutosaveSeconds) and on OnDisabled, copying create_save_system's persistence patterns. On
/// enable it computes elapsed = now − LastSeenUtc, guards a clock rollback (negative → 0), clamps
/// to MaxOfflineHours (default 8), then replays that time through a SimulateOffline(double seconds)
/// TODO hook in fixed TickSeconds chunks (default 1) so idle accumulation is deterministic
/// (frame-rate independent), and fires the static OnOfflineProgressApplied(seconds) event (drive a
/// 'welcome back, you earned X' screen). IsProxy-guarded so a client can't author their own offline
/// earnings. Fill in the SimulateOffline hook with your idle math (e.g.
/// wallet.AddMoney(rate*seconds)). Pairs with create_economy_wallet / create_save_system /
/// create_stat_modifier_system.
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'OfflineProgress'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="maxOfflineHours">Offline time is clamped to this many hours (stops a week-away paying out a week). Defaults to 8.</param>
/// <param name="tickSeconds">SimulateOffline chunk size in seconds — smaller = finer-grained deterministic replay (floored at 0.1). Defaults to 1.</param>
/// <param name="targetId">GUID of an idle/save-manager GameObject to attach to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_offline_progress" )]
public static Task<object> CreateOfflineProgress( string name = null, string directory = null, double? maxOfflineHours = null, double? tickSeconds = null, string targetId = null )
=> McpGate.Run( "create_offline_progress", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "maxOfflineHours", maxOfflineHours ), ( "tickSeconds", tickSeconds ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a trigger-based collectible component. On enter by a tagged object it raises
/// OnCollected (wire it to your objective/score system) and despawns. Optionally builds a visible
/// pickup GameObject with a trigger SphereCollider (+ a model) in one call. Returns { created,
/// path, className, gameObject, note } — gameObject is the placed pickup (null unless
/// placeInScene=true); a note flags when the component couldn't attach because the fresh type needs
/// a hotload. Follow with trigger_hotload, then get_compile_errors.
/// </summary>
/// <param name="name">Class name. Defaults to 'Pickup'.</param>
/// <param name="directory">Subdirectory for the .cs file. Defaults to 'Code'.</param>
/// <param name="action">Effect flavour (all self-contained; the heal/item branches show the typed call to a companion system in comments). Defaults to 'score'. One of: score | heal | item | custom.</param>
/// <param name="amount">Magnitude of the effect (score points, heal amount). Defaults to 1.</param>
/// <param name="filterTag">Only collect for objects with this tag. Defaults to 'player'.</param>
/// <param name="placeInScene">Also build a pickup GameObject (trigger SphereCollider + optional model). Defaults to false.</param>
/// <param name="position">World position when placeInScene is true. As "x,y,z" (or JSON {x,y,z}).</param>
/// <param name="radius">Trigger sphere radius when placed. Defaults to 24.</param>
/// <param name="model">Optional model path for a visible pickup (e.g. 'models/dev/box.vmdl'). Cloud assets must be installed first.</param>
[McpTool( "create_pickup" )]
public static Task<object> CreatePickup( string name = null, string directory = null, string action = null, double? amount = null, string filterTag = null, bool? placeInScene = null, string position = null, double? radius = null, string model = null )
=> McpGate.Run( "create_pickup", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "action", action ), ( "amount", amount ), ( "filterTag", filterTag ), ( "placeInScene", placeInScene ), ( "position", position ), ( "radius", radius ), ( "model", model ) ) );
/// <summary>
/// Generate a ghost-preview + commit placement component (single class). StartPlacing() clones
/// GhostPrefab as a NetworkMode.Never preview with colliders disabled and ModelRenderers tinted
/// semi-transparent. Each frame while placing: ray from Scene.Camera.GetMouseRay(),
/// IgnoreGameObjectHierarchy(ghost), snap hit position to GridSize (0 = freeform), move ghost. On
/// Input.Pressed('attack1') TryPlace() re-validates distance and commits a real clone.
/// StopPlacing() destroys the ghost. Static OnPlaced(GameObject, Vector3) event. Includes a
/// multiplayer RPC note. API grounded in building-placement cookbook (enifun.shop_manager pattern).
/// </summary>
/// <param name="name">Class name. Defaults to 'PlacementMode'.</param>
/// <param name="directory">Subdirectory for the .cs file. Defaults to 'Code'.</param>
/// <param name="gridSize">Snap grid size in world units (0 = freeform placement). Defaults to 0.</param>
/// <param name="targetId">GUID of an existing GameObject to attach to (hotload first).</param>
[McpTool( "create_placement_mode" )]
public static Task<object> CreatePlacementMode( string name = null, string directory = null, double? gridSize = null, string targetId = null )
=> McpGate.Run( "create_placement_mode", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "gridSize", gridSize ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a player controller script with WASD movement, mouse look, jumping, and sprint.
/// Supports first-person, third-person, and top-down movement modes. Optionally places a player rig
/// (GameObject + CharacterController + Camera) in the scene — note the generated component is
/// attached AFTER a trigger_hotload (it isn't in the TypeLibrary until a recompile).
/// </summary>
/// <param name="name">Class name. Defaults to 'PlayerController'.</param>
/// <param name="directory">Subdirectory under code/ for the file.</param>
/// <param name="type">Movement mode: 'first_person' (mouse-look body+camera, WASD relative to facing), 'third_person' (mouse yaw, WASD relative to facing, boom camera), or 'top_down' (screen-relative WASD, fixed overhead camera, no jump). Defaults to 'first_person'. One of: first_person | third_person | top_down.</param>
/// <param name="moveSpeed">Movement speed in units/sec. Defaults to 300.</param>
/// <param name="jumpForce">Jump force (ignored for top_down). Defaults to 350.</param>
/// <param name="sprintMultiplier">Sprint speed multiplier (held 'run' action). Defaults to 1.5.</param>
/// <param name="placeInScene">If true, build a player rig in the scene: a GameObject (tagged 'player') with a CharacterController and (unless createCamera=false) a Camera. The generated controller component is NOT attached in this call — trigger_hotload then add_component_with_properties on the returned GameObject. Defaults to false (file-only).</param>
/// <param name="createCamera">When placeInScene is true, also create a Camera (FP/TP: child at eye/boom offset; top_down: fixed overhead). Defaults to true.</param>
/// <param name="spawnPosition">When placeInScene is true, the world position to spawn the player rig at — object {x,y,z} or comma string "x,y,z". Defaults to the origin. As "x,y,z" (or JSON {x,y,z}).</param>
[McpTool( "create_player_controller" )]
public static Task<object> CreatePlayerController( string name = null, string directory = null, string type = null, double? moveSpeed = null, double? jumpForce = null, double? sprintMultiplier = null, bool? placeInScene = null, bool? createCamera = null, string spawnPosition = null )
=> McpGate.Run( "create_player_controller", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "type", type ), ( "moveSpeed", moveSpeed ), ( "jumpForce", jumpForce ), ( "sprintMultiplier", sprintMultiplier ), ( "placeInScene", placeInScene ), ( "createCamera", createCamera ), ( "spawnPosition", spawnPosition ) ) );
/// <summary>
/// Generate a host-authoritative round/phase machine: a [Sync(SyncFlags.FromHost)] CurrentPhase
/// cycled through your named phases on a per-phase timer (host-only), with a static OnPhaseChanged
/// event that fires on every machine. Great for round/match flow, match phases, or a day/night
/// cycle. Single-player safe. Optionally attached to an existing GameObject by GUID (after a
/// hotload). Mined from the round-flow pattern across the 51 games.
/// </summary>
/// <param name="name">Class name. Defaults to 'GameDirector'.</param>
/// <param name="directory">Subdirectory for the .cs file. Defaults to 'Code'.</param>
/// <param name="phases">Ordered phase names (become an enum), e.g. ["Lobby","Day","Night","Payout"]. Defaults to ["Lobby","Active","Ended"].</param>
/// <param name="duration">Default seconds per phase (each phase also gets its own tunable [Property]). Defaults to 60.</param>
/// <param name="loop">Loop back to the first phase after the last (true) or hold on the last phase (false). Defaults to true.</param>
/// <param name="targetId">GUID of an existing GameObject to attach to (only if the type is already loaded — hotload first).</param>
[McpTool( "create_round_phase_machine" )]
public static Task<object> CreateRoundPhaseMachine( string name = null, string directory = null, string[] phases = null, double? duration = null, bool? loop = null, string targetId = null )
=> McpGate.Run( "create_round_phase_machine", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "phases", phases ), ( "duration", duration ), ( "loop", loop ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a host-authoritative MULTI-STATE round machine (the complex variant of
/// create_round_phase_machine). Produces one .cs file: a RoundManager singleton component + an
/// abstract RoundState base (Begin/Tick/OnTimeUp/Finish lifecycle with a per-state
/// [Sync(SyncFlags.FromHost)] TimeUntil timer) + one sealed stub class per named state. The manager
/// auto-attaches the state components on start (you only place the manager), ticks ONLY the active
/// state on the host, Advance()s on timeout with index-wrap, SKIPS any state whose CanEnter()
/// returns false, and announces every transition via a static OnStateChanged event plus an
/// [Rpc.Broadcast] mirror so the host fires immediately and proxies converge without waiting a
/// snapshot (the [Sync] index reconciles late joiners). Single-player safe. USE THIS (not
/// create_round_phase_machine) when each phase needs its OWN behaviour — entry side-effects,
/// per-frame Tick logic, a skip condition, or copy-data-out-on-exit; use the phase machine for 3–5
/// light phases that differ only in duration. Optionally attached to an existing GameObject by GUID
/// (after a hotload).
/// </summary>
/// <param name="name">Manager class name. Defaults to 'RoundManager'. The abstract base is derived from it (RoundManager → RoundState).</param>
/// <param name="directory">Subdirectory for the .cs file (path override). Defaults to 'Code'.</param>
/// <param name="states">Ordered state names — each becomes a sealed {Name}State stub class. Defaults to ["Waiting","Active","PostRound"].</param>
/// <param name="duration">Default seconds each state lasts (each state also gets its own tunable [Property] Duration). 0 = no auto-advance for a state. Defaults to 30.</param>
/// <param name="durations">Optional per-state duration override: an array aligned to `states` ([10,120,8]) OR an object keyed by state name ({"Waiting":10,"Active":120}). Any state not covered falls back to `duration`. JSON value.</param>
/// <param name="loop">Loop back to the first state after the last (true) or hold on the last state (false). Defaults to true.</param>
/// <param name="targetId">GUID of an existing GameObject to attach the manager to (only if the type is already loaded — trigger_hotload first).</param>
[McpTool( "create_round_state_machine" )]
public static Task<object> CreateRoundStateMachine( string name = null, string directory = null, string[] states = null, double? duration = null, JsonNode durations = null, bool? loop = null, string targetId = null )
=> McpGate.Run( "create_round_state_machine", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "states", states ), ( "duration", duration ), ( "durations", durations ), ( "loop", loop ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a multi-slot save MANAGER component (the slot-picker sibling of create_save_system).
/// Use this when the game needs SEVERAL named save slots the player chooses between (New Game /
/// Load Game menu, per-character or per-run saves) — not one silent autosave. Use
/// create_save_system instead when a single implicit save file is enough. Emits one sealed
/// Component that lists / creates / loads / saves / deletes N slots: a lightweight manifest file
/// (saveslots.json) holds per-slot metadata for the picker (Used flag + Name + SavedAtUnix
/// timestamp + PlaytimeSeconds) so listing never loads a heavy payload, and each slot's game state
/// lives in its own saveslot_<i>.json. Versioned SlotData POCO with clamp-on-load Sanitize()
/// and delete-on-version-mismatch; runs only on the owning machine (IsProxy guard). Static
/// OnSlotLoaded / OnSlotSaved / OnSlotDeleted hooks for HUD. Storage stays within the verified
/// FileSystem.Data.ReadJsonOrDefault / WriteJson / DeleteFile surface (index-file pattern, no
/// directory enumeration). Set sceneReconciliation:true to also reconcile scene objects by
/// GameObject.Id on load — records the save marks destroyed are destroyed, survivors repositioned,
/// missing skipped (good for a placeable-world tycoon). Optionally attached to an existing
/// GameObject by GUID (after a hotload).
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'SaveSlotManager'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="maxSlots">How many save slots the manager manages (manifest is normalized to exactly this many, indexed 0..N-1). Clamped to 1..100. Defaults to 3.</param>
/// <param name="sceneReconciliation">If true, saved records carry each object's GameObject.Id GUID and load reconciles the live scene against them (destroy the save's destroyed records via Scene.Directory.FindByGuid, reposition survivors, skip missing) — call RecordObject(go) to track a placeable. If false (default), the slot save is a plain payload with no scene reconciliation. Defaults to false.</param>
/// <param name="targetId">GUID of an existing GameObject to attach the manager to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_save_slots" )]
public static Task<object> CreateSaveSlots( string name = null, string directory = null, int? maxSlots = null, bool? sceneReconciliation = null, string targetId = null )
=> McpGate.Run( "create_save_slots", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "maxSlots", maxSlots ), ( "sceneReconciliation", sceneReconciliation ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a versioned save-system component: a SaveData POCO with Version bump on schema change,
/// dirty-flag autosave on a TimeUntil timer, clamp-on-load Sanitize() for corrupt/hand-edited
/// saves, and delete-on-version-mismatch to start fresh instead of crashing. Runs only on the
/// owning machine (IsProxy guard). Fires static OnLoaded/OnSaved hooks for HUD and analytics.
/// FileSystem.Data.ReadJsonOrDefault/WriteJson verified live on the current SDK. Optionally
/// attached to an existing GameObject by GUID (after a hotload).
/// </summary>
/// <param name="name">Class name. Defaults to 'SaveSystem'.</param>
/// <param name="directory">Subdirectory for the .cs file. Defaults to 'Code'.</param>
/// <param name="fileName">Save file name under FileSystem.Data (e.g. 'save.json'). Defaults to 'save.json'.</param>
/// <param name="version">Schema version embedded in SaveData. Old saves with a different version start fresh. Defaults to 1.</param>
/// <param name="autosaveSeconds">Seconds between autosave ticks (0 disables autosave). Defaults to 10.</param>
/// <param name="targetId">GUID of an existing GameObject to attach to (only if the type is already loaded -- hotload first).</param>
[McpTool( "create_save_system" )]
public static Task<object> CreateSaveSystem( string name = null, string directory = null, string fileName = null, int? version = null, double? autosaveSeconds = null, string targetId = null )
=> McpGate.Run( "create_save_system", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "fileName", fileName ), ( "version", version ), ( "autosaveSeconds", autosaveSeconds ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a tamper-evident, versioned save-system component (sealed, owner-only). The SaveData
/// payload POCO is serialized to JSON (Sandbox.Json), FNV-1a-64 hashed over payload + version +
/// salt, and written as a signed envelope { Version, Payload, Signature } to FileSystem.Data.
/// Load() re-verifies: a signature mismatch (hand-edited/corrupt file) triggers a FORCED RESET —
/// the save file is DELETED, defaults are used, and the static OnTampered(reason) event fires
/// (destructive and deliberate; tell the player). A version mismatch starts fresh without the
/// tamper event (add migrations in Load). Loaded values pass a Sanitize() clamp hook so even a
/// re-signed save can't smuggle absurd values. Dirty-flag autosave (autosaveSeconds, default 10;
/// MarkDirty() to arm) + a final save in OnDestroy. HONEST LIMIT: the salt ships inside the game
/// assembly, so this is tamper-EVIDENT (stops notepad edits), NOT cryptographically secure. If you
/// omit salt, a unique random one is baked into the generated file — changing it later invalidates
/// existing saves. Returns { created, path, className, fileName, version, autosaveSeconds,
/// placedOn, note, nextSteps }. Next: trigger_hotload, attach, add your fields to SaveData + clamps
/// to Sanitize(), bump version on shape changes. Refused during play mode. Use create_save_system
/// for a plain unsigned save, create_save_slots for multi-slot UI flows, create_meta_progression
/// for roguelite meta-state.
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'SignedSave'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="fileName">FileSystem.Data path the signed envelope is written to. Defaults to 'save_signed.json'.</param>
/// <param name="version">Save-shape version baked into the file and the signature; mismatched files start fresh. Defaults to 1.</param>
/// <param name="salt">Signing salt baked into the generated code. Omit to bake a unique random salt (recommended); changing it later invalidates existing saves.</param>
/// <param name="autosaveSeconds">Dirty-flag autosave cadence in seconds; 0 disables the heartbeat (OnDestroy still saves). Defaults to 10.</param>
/// <param name="targetId">GUID of a save-manager GameObject to attach to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_signed_save" )]
public static Task<object> CreateSignedSave( string name = null, string directory = null, string fileName = null, int? version = null, string salt = null, double? autosaveSeconds = null, string targetId = null )
=> McpGate.Run( "create_signed_save", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "fileName", fileName ), ( "version", version ), ( "salt", salt ), ( "autosaveSeconds", autosaveSeconds ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a speedrun timer component plus a leaderboard display panel. The timer
/// (<Name>.cs) is TimeSince-based with a static Instance: StartTimer() at run start,
/// StopTimer() at the finish (pairs with a trigger zone), ResetTimer() to abort. StopTimer persists
/// the local best via FileSystem.Data and submits Stats.SetValue(statName, seconds) ONLY when the
/// run beats it — configure the stat with MIN aggregation on sbox.game so the global board keeps
/// best times. The panel (<Name>Panel.razor + .razor.scss, razor_lint clean) fetches via
/// Leaderboards.GetFromStat with min aggregation + ascending sort, has a clickable Friends-only
/// filter button, and overlays a local-best row read from the same save file. Returns { created,
/// path, className, panelRazorPath, panelScssPath, panelClassName, statName, placedOn, note,
/// nextSteps }. After trigger_hotload: place ONE timer (add_component_to_new_object or targetId)
/// and host the panel under a ScreenPanel/WorldPanel (add_screen_panel). maxRows clamps to 1..50;
/// makePanel=false skips the panel files. Fails if the .cs or panel .razor already exists.
/// </summary>
/// <param name="name">Class name for the generated timer component (panel becomes <name>Panel). Defaults to 'SpeedrunTimer'.</param>
/// <param name="directory">Subdirectory for all generated files. Defaults to 'Code'.</param>
/// <param name="statName">Sandbox.Services stat the best time is written to (sanitized to [a-z0-9_-]). Defaults to 'best_time'.</param>
/// <param name="fileName">Save file name inside FileSystem.Data for the local best. Defaults to 'speedrun.json'.</param>
/// <param name="title">Panel title text. Defaults to 'Best Times'.</param>
/// <param name="maxRows">Leaderboard rows fetched/shown. Defaults to 10, clamped to 1..50.</param>
/// <param name="makePanel">Also emit the <name>Panel.razor + .razor.scss display panel. Defaults to true.</param>
/// <param name="targetId">GUID of a GameObject to attach the timer to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_speedrun_leaderboard" )]
public static Task<object> CreateSpeedrunLeaderboard( string name = null, string directory = null, string statName = null, string fileName = null, string title = null, double? maxRows = null, bool? makePanel = null, string targetId = null )
=> McpGate.Run( "create_speedrun_leaderboard", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "statName", statName ), ( "fileName", fileName ), ( "title", title ), ( "maxRows", maxRows ), ( "makePanel", makePanel ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate an enum-keyed stat modifier system with three modifier layers: SET
/// (highest-priority-wins hard override), ADD (summed bonuses), MULT (multiplied factors applied
/// last). Modifier storage uses parallel private Lists of primitive types (serialization-safe).
/// RemoveModifiersFrom(source) cleans up all mods from a buff/debuff source by reference. Static
/// OnStatChanged(stat, value) event fires after every add/remove. Mined from RPG/buff/debuff
/// patterns across shipped s&box games. Returns { created, path, className, stats, placedOn,
/// note } — stats echoes the sanitized stat names ({name}Stat enum values); placedOn is the target
/// GameObject when attached (needs the type hotloaded). Follow with trigger_hotload, then
/// get_compile_errors.
/// </summary>
/// <param name="name">Class name prefix -- generates {name}Stat enum + {name} Component. Defaults to 'StatSystem'.</param>
/// <param name="directory">Subdirectory for the .cs file. Defaults to 'Code'.</param>
/// <param name="stats">Stat names as a JSON array or comma-separated string. Defaults to 'Health,Speed,Damage'. JSON value.</param>
/// <param name="targetId">GUID of an existing GameObject to attach to (hotload first).</param>
[McpTool( "create_stat_modifier_system" )]
public static Task<object> CreateStatModifierSystem( string name = null, string directory = null, JsonNode stats = null, string targetId = null )
=> McpGate.Run( "create_stat_modifier_system", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "stats", stats ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a host-authoritative balanced team assigner component (smallest-bucket draft):
/// AssignSmallest(steamId) drops a joining player into the emptiest team, announces via
/// [Rpc.Broadcast] so every client's roster agrees, and fires static OnTeamAssigned(steamId, index,
/// name); plus Rebalance(), GetTeam, GetMembers. Writes a .cs file and returns { created, path,
/// className, teams, nextSteps } — follow with trigger_hotload + compile_status, attach to your
/// game manager, call AssignSmallest from your join hook (e.g. INetworkListener.OnActive).
/// </summary>
/// <param name="name">Class/file name (default 'TeamAssigner' -> Code/TeamAssigner.cs). Errors if the file exists.</param>
/// <param name="directory">Directory for the .cs file. Default 'Code'.</param>
/// <param name="teams">Team names in index order. Default ["Red", "Blue"].</param>
[McpTool( "create_team_assigner" )]
public static Task<object> CreateTeamAssigner( string name = null, string directory = null, string[] teams = null )
=> McpGate.Run( "create_team_assigner", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "teams", teams ) ) );
/// <summary>
/// Generate a trigger-zone Component (Component.ITriggerListener): auto-adds a trigger BoxCollider
/// on start, filters entrants by a TriggerTag [Property] (default 'player'), and logs enter/exit
/// via private OnPlayerEnter/OnPlayerExit extension points you fill in. Writes <name>.cs and
/// returns { created, path, className }. NOTE: the action/filterTag params are not currently
/// applied at generation time — the zone always logs; implement teleport/damage/spawn in the
/// generated methods (edit_script). Follow with trigger_hotload, then get_compile_errors.
/// </summary>
/// <param name="name">Class name. Defaults to 'TriggerZone'.</param>
/// <param name="directory">Subdirectory under code/ for the file.</param>
/// <param name="action">What happens on trigger (currently not applied by the handler — the generated zone always logs; implement the effect in OnPlayerEnter yourself). One of: log | teleport | damage | spawn.</param>
/// <param name="filterTag">Only trigger for objects with this tag (currently not applied at generation — the generated TriggerTag [Property] defaults to 'player'; change it per-instance with set_property).</param>
[McpTool( "create_trigger_zone" )]
public static Task<object> CreateTriggerZone( string name = null, string directory = null, string action = null, string filterTag = null )
=> McpGate.Run( "create_trigger_zone", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "action", action ), ( "filterTag", filterTag ) ) );
/// <summary>
/// Generate a cumulative-weight random loot picker: parallel Name/Weight lists
/// (inspector-editable), a Roll() method that returns a winning entry name and fires a static
/// OnLoot event, and optional pity (guarantee the last/rarest entry after PityAfter consecutive
/// non-rare rolls). Roll() is host-authoritative -- only call it on the host and replicate the
/// result (clients rolling their own loot is equivalent to clients writing their own money
/// balance). Optionally attached to an existing GameObject by GUID (after a hotload).
/// </summary>
/// <param name="name">Class name. Defaults to 'LootTable'.</param>
/// <param name="directory">Subdirectory for the .cs file. Defaults to 'Code'.</param>
/// <param name="entries">Loot table entries. Defaults to common:70 / uncommon:25 / rare:5. JSON value.</param>
/// <param name="pity">If true, guarantee the last (rarest) entry after PityAfter consecutive non-rare rolls. Defaults to false.</param>
/// <param name="targetId">GUID of an existing GameObject to attach to (only if the type is already loaded -- hotload first).</param>
[McpTool( "create_weighted_loot_table" )]
public static Task<object> CreateWeightedLootTable( string name = null, string directory = null, JsonNode entries = null, bool? pity = null, string targetId = null )
=> McpGate.Run( "create_weighted_loot_table", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "entries", entries ), ( "pity", pity ), ( "targetId", targetId ) ) );
/// <summary>
/// Scaffold an end-of-round map vote. Three files: <Name>.cs (sealed host-authoritative
/// controller) + <Name>Panel.razor + <Name>Panel.razor.scss (vote UI: one button per
/// map, live tallies, countdown, own-pick highlight, winner banner). Flow: host calls StartVote()
/// (usually from a post-round phase/state, or set the AutoStart [Property]) -> clients click
/// -> votes route client-to-host via [Rpc.Host] SubmitVote with the caller re-resolved HOST-SIDE
/// from Rpc.Caller (null-checked — Connection has no IsValid on this SDK) and the map index
/// re-validated (re-votes overwrite, keyed by SteamId) -> tallies replicate via [Sync(FromHost)]
/// NetList<int> -> when the [Sync] TimeUntil countdown expires the host picks the winner
/// (most votes; ties break deterministically via one LCG scramble of a time seed — no
/// System.Random) -> after resultLingerSeconds the HOST calls Scene.LoadFromFile(winner) (API
/// verified live on this SDK; clients follow via the scene networking layer — verify the client
/// hand-off in a real multi-client session). Static event OnVoteFinished(sceneFile) fires on every
/// machine. Returns { created, componentPath, razorPath, scssPath, className, panelClassName, maps,
/// voteDurationSeconds, resultLingerSeconds, autoStart, note, nextSteps }. REQUIREMENTS: the
/// controller must sit on a NETWORK-SPAWNED object in multiplayer or [Sync] never replicates; if
/// maps is omitted the MapScenes list is generated EMPTY and StartVote() refuses with a warning
/// until you fill it in the inspector. Follow with trigger_hotload, attach via
/// add_component_with_properties, host the panel under add_screen_panel.
/// </summary>
/// <param name="name">Class name for the controller; the panel is generated as <Name>Panel. Defaults to 'MapVote'.</param>
/// <param name="directory">Subdirectory for the generated .cs + .razor + .razor.scss. Defaults to 'Code'.</param>
/// <param name="maps">Scene files to vote between, e.g. ["scenes/arena.scene", "scenes/docks.scene"] (find them with list_scenes). Baked into the MapScenes [Property] list, editable later in the inspector. Defaults to an EMPTY list (StartVote() then refuses until it's filled).</param>
/// <param name="voteDurationSeconds">Seconds the vote stays open once StartVote() is called (clamped to >= 3). Defaults to 20.</param>
/// <param name="resultLingerSeconds">Seconds the winner banner shows before the host loads the winning scene (clamped to >= 0). Defaults to 4.</param>
/// <param name="autoStart">Start the vote automatically on spawn (host only). Usually false — call StartVote() from your round machine's post-round state instead. Defaults to false.</param>
[McpTool( "scaffold_map_vote_flow" )]
public static Task<object> ScaffoldMapVoteFlow( string name = null, string directory = null, string[] maps = null, double? voteDurationSeconds = null, double? resultLingerSeconds = null, bool? autoStart = null )
=> McpGate.Run( "scaffold_map_vote_flow", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "maps", maps ), ( "voteDurationSeconds", voteDurationSeconds ), ( "resultLingerSeconds", resultLingerSeconds ), ( "autoStart", autoStart ) ) );
}