sbox-mcp-server 1.20.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +107 -82
- package/dist/tools/assets.js +5 -5
- package/dist/tools/audio.js +12 -19
- package/dist/tools/audit.d.ts +8 -0
- package/dist/tools/audit.js +65 -0
- package/dist/tools/batch.d.ts +8 -0
- package/dist/tools/batch.js +77 -0
- package/dist/tools/characters.js +6 -6
- package/dist/tools/cinematics.js +2 -2
- package/dist/tools/components.js +2 -2
- package/dist/tools/debugdraw.js +4 -4
- package/dist/tools/diagnostics.js +7 -5
- package/dist/tools/discovery.js +6 -6
- package/dist/tools/gameobjects.js +7 -7
- package/dist/tools/gameplay.js +4 -4
- package/dist/tools/inspection.js +6 -6
- package/dist/tools/leveltools.js +5 -5
- package/dist/tools/materials.js +4 -4
- package/dist/tools/moviemaker.js +1 -1
- package/dist/tools/networking.js +22 -22
- package/dist/tools/physics.js +4 -4
- package/dist/tools/playmode.js +5 -5
- package/dist/tools/playtest.js +2 -1
- package/dist/tools/prefabs.js +7 -3
- package/dist/tools/project.js +2 -2
- package/dist/tools/publishing.js +3 -3
- package/dist/tools/scenes.js +2 -2
- package/dist/tools/scripts.js +3 -3
- package/dist/tools/status.js +1 -1
- package/dist/tools/templates.js +7 -7
- package/dist/tools/ui.js +5 -5
- package/dist/tools/uifeedback.js +1 -1
- package/dist/tools/vehicles.d.ts +8 -0
- package/dist/tools/vehicles.js +98 -0
- package/dist/tools/visuals.js +8 -8
- package/dist/tools/workflow.d.ts +8 -0
- package/dist/tools/workflow.js +107 -0
- package/dist/tools/world.js +61 -58
- package/package.json +1 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Workflow tools (Batch 53): scene checkpoints (the agent-side undo safety net),
|
|
4
|
+
* describe_scene orientation, and the team-assigner / idle-income scaffolds.
|
|
5
|
+
*/
|
|
6
|
+
export function registerWorkflowTools(server, bridge) {
|
|
7
|
+
// ── checkpoint_scene ─────────────────────────────────────────────
|
|
8
|
+
server.tool("checkpoint_scene", "Snapshot the ENTIRE open scene (every root GameObject, full serialization) to temp storage OUTSIDE the project. The agent-side undo: checkpoint before risky batch edits or experimental changes, roll back with restore_checkpoint if they go wrong. Returns { checkpointed, id, label, scene, rootObjects, sizeBytes } — keep the id. Refused during play mode (runtime state would poison the snapshot). Snapshots survive editor restarts (temp dir, subject to OS cleanup); browse them with list_checkpoints", {
|
|
9
|
+
label: z
|
|
10
|
+
.string()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Short label baked into the checkpoint id (e.g. 'before-batch-tint'). Defaults to 'checkpoint'"),
|
|
13
|
+
}, async (params) => {
|
|
14
|
+
const res = await bridge.send("checkpoint_scene", params);
|
|
15
|
+
if (!res.success) {
|
|
16
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
// ── restore_checkpoint ───────────────────────────────────────────
|
|
23
|
+
server.tool("restore_checkpoint", "REPLACE the open scene's entire contents with a checkpoint_scene snapshot: destroys every current root object, then rebuilds the snapshot's tree (guids preserved, so internal references stay wired). DESTRUCTIVE — requires an explicit id (from checkpoint_scene or list_checkpoints), never guesses. Returns { restored, id, destroyedRoots, restoredRoots }. The scene FILE on disk is untouched until save_scene. Scene-mutating: refused during play mode", {
|
|
24
|
+
id: z
|
|
25
|
+
.string()
|
|
26
|
+
.describe("Checkpoint id to restore (e.g. 'cp_20260709_153000_before-batch-tint')"),
|
|
27
|
+
}, async (params) => {
|
|
28
|
+
const res = await bridge.send("restore_checkpoint", params);
|
|
29
|
+
if (!res.success) {
|
|
30
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
// ── list_checkpoints ─────────────────────────────────────────────
|
|
37
|
+
server.tool("list_checkpoints", "List this project's scene checkpoints (newest first). Returns { total, checkpoints } — each { id, createdUtc, sizeBytes }. Pass an id to restore_checkpoint to roll the scene back, or create one first with checkpoint_scene. Read-only; no limit (checkpoints are few)", {}, async (params) => {
|
|
38
|
+
const res = await bridge.send("list_checkpoints", params);
|
|
39
|
+
if (!res.success) {
|
|
40
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
// ── describe_scene ───────────────────────────────────────────────
|
|
47
|
+
server.tool("describe_scene", "One-call orientation for the OPEN scene (works in edit and play mode): total/root object counts, component histogram (top 20 types), every camera with position, light count, tag histogram (top 12), and the aggregate world bounds of renderable content. Returns a structured summary — orient here, then find_objects by component/tag, get_scene_hierarchy for structure, find_broken_references for health, screenshot_orbit to look at something. Complements describe_project (project-level). Read-only", {}, async (params) => {
|
|
48
|
+
const res = await bridge.send("describe_scene", params);
|
|
49
|
+
if (!res.success) {
|
|
50
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
// ── create_team_assigner ─────────────────────────────────────────
|
|
57
|
+
server.tool("create_team_assigner", "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)", {
|
|
58
|
+
name: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("Class/file name (default 'TeamAssigner' -> Code/TeamAssigner.cs). Errors if the file exists"),
|
|
62
|
+
directory: z
|
|
63
|
+
.string()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("Directory for the .cs file. Default 'Code'"),
|
|
66
|
+
teams: z
|
|
67
|
+
.array(z.string())
|
|
68
|
+
.optional()
|
|
69
|
+
.describe('Team names in index order. Default ["Red", "Blue"]'),
|
|
70
|
+
}, async (params) => {
|
|
71
|
+
const res = await bridge.send("create_team_assigner", params);
|
|
72
|
+
if (!res.success) {
|
|
73
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
// ── create_idle_income ───────────────────────────────────────────
|
|
80
|
+
server.tool("create_idle_income", "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", {
|
|
81
|
+
name: z
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe("Class/file name (default 'IdleIncome' -> Code/IdleIncome.cs). Errors if the file exists"),
|
|
85
|
+
directory: z
|
|
86
|
+
.string()
|
|
87
|
+
.optional()
|
|
88
|
+
.describe("Directory for the .cs file. Default 'Code'"),
|
|
89
|
+
incomePerTick: z
|
|
90
|
+
.number()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("Amount granted per tick. Default 1"),
|
|
93
|
+
tickSeconds: z
|
|
94
|
+
.number()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe("Seconds between grants. Default 1"),
|
|
97
|
+
}, async (params) => {
|
|
98
|
+
const res = await bridge.send("create_idle_income", params);
|
|
99
|
+
if (!res.success) {
|
|
100
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=workflow.js.map
|
package/dist/tools/world.js
CHANGED
|
@@ -64,13 +64,13 @@ export function registerWorldTools(server, bridge) {
|
|
|
64
64
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
65
65
|
});
|
|
66
66
|
// ── add_terrain_hill ─────────────────────────────────────────────
|
|
67
|
-
server.tool("add_terrain_hill", "Add a hill (cosine-falloff bump) to MapBuilder. Negative height creates a depression.", {
|
|
67
|
+
server.tool("add_terrain_hill", "Add a hill (cosine-falloff bump) to MapBuilder's Hills list. Negative height creates a depression. Returns `added`, `total` (hills now in the list), and `rebuilt`; verify the surface with raycast_terrain at the hill center.", {
|
|
68
68
|
x: z.number().describe("World X of hill center"),
|
|
69
69
|
y: z.number().describe("World Y of hill center"),
|
|
70
70
|
radius: z.number().default(500).describe("Hill radius in world units"),
|
|
71
71
|
height: z.number().default(100).describe("Peak height (negative for depression)"),
|
|
72
72
|
rebuild: z.boolean().default(true).describe("Rebuild terrain after adding (set false to batch)"),
|
|
73
|
-
id: z.string().optional(),
|
|
73
|
+
id: z.string().optional().describe("GUID of the GameObject holding the MapBuilder; omit to auto-find the first MapBuilder in the scene"),
|
|
74
74
|
component: z
|
|
75
75
|
.string()
|
|
76
76
|
.optional()
|
|
@@ -82,12 +82,12 @@ export function registerWorldTools(server, bridge) {
|
|
|
82
82
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
83
83
|
});
|
|
84
84
|
// ── add_terrain_clearing ─────────────────────────────────────────
|
|
85
|
-
server.tool("add_terrain_clearing", "Add a flat clearing zone to MapBuilder (lerps height toward base inside radius).", {
|
|
86
|
-
x: z.number(),
|
|
87
|
-
y: z.number(),
|
|
88
|
-
radius: z.number().default(300),
|
|
89
|
-
rebuild: z.boolean().default(true),
|
|
90
|
-
id: z.string().optional(),
|
|
85
|
+
server.tool("add_terrain_clearing", "Add a flat clearing zone to MapBuilder's Clearings list (lerps height toward base inside radius). Returns `added`, `total` (clearing count), and `rebuilt`. Rebuilds terrain immediately by default; set rebuild=false to batch edits, then rebuild once (rebuild=true on the last call, or invoke_button 'Build Terrain').", {
|
|
86
|
+
x: z.number().describe("World X of clearing center"),
|
|
87
|
+
y: z.number().describe("World Y of clearing center"),
|
|
88
|
+
radius: z.number().default(300).describe("Clearing radius in world units. Default 300."),
|
|
89
|
+
rebuild: z.boolean().default(true).describe("Rebuild terrain after adding (default true; set false to batch)"),
|
|
90
|
+
id: z.string().optional().describe("GUID of the GameObject holding the MapBuilder; omit to auto-find the first MapBuilder in the scene"),
|
|
91
91
|
component: z
|
|
92
92
|
.string()
|
|
93
93
|
.optional()
|
|
@@ -99,11 +99,11 @@ export function registerWorldTools(server, bridge) {
|
|
|
99
99
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
100
100
|
});
|
|
101
101
|
// ── add_terrain_trail ────────────────────────────────────────────
|
|
102
|
-
server.tool("add_terrain_trail", "Carve a trail depression between two points on MapBuilder.", {
|
|
103
|
-
from: z.object({ x: z.number(), y: z.number() }),
|
|
104
|
-
to: z.object({ x: z.number(), y: z.number() }),
|
|
105
|
-
rebuild: z.boolean().default(true),
|
|
106
|
-
id: z.string().optional(),
|
|
102
|
+
server.tool("add_terrain_trail", "Carve a trail depression between two points on MapBuilder (appends to its Trails list). Returns `added`, `total` (trail count), and `rebuilt`. Rebuilds terrain immediately by default; set rebuild=false to batch, then invoke_button 'Build Terrain' once.", {
|
|
103
|
+
from: z.object({ x: z.number(), y: z.number() }).describe("Trail start point (world x/y)"),
|
|
104
|
+
to: z.object({ x: z.number(), y: z.number() }).describe("Trail end point (world x/y)"),
|
|
105
|
+
rebuild: z.boolean().default(true).describe("Rebuild terrain after adding (default true; set false to batch)"),
|
|
106
|
+
id: z.string().optional().describe("GUID of the GameObject holding the MapBuilder; omit to auto-find the first MapBuilder in the scene"),
|
|
107
107
|
component: z
|
|
108
108
|
.string()
|
|
109
109
|
.optional()
|
|
@@ -115,12 +115,13 @@ export function registerWorldTools(server, bridge) {
|
|
|
115
115
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
116
116
|
});
|
|
117
117
|
// ── clear_terrain_features ───────────────────────────────────────
|
|
118
|
-
server.tool("clear_terrain_features", "Wipe Hills, Clearings, Trails, or all
|
|
118
|
+
server.tool("clear_terrain_features", "Wipe MapBuilder feature lists — Hills, Clearings, Trails, CavePath, or all of them (default). Destructive: the feature definitions are removed. Returns `cleared` (map of list name → entries removed) and `rebuilt`; re-add features with add_terrain_hill / add_terrain_clearing / add_terrain_trail.", {
|
|
119
119
|
what: z
|
|
120
120
|
.enum(["Hills", "Clearings", "Trails", "CavePath", "all"])
|
|
121
|
-
.default("all")
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
.default("all")
|
|
122
|
+
.describe("Which feature list to clear. Default 'all' (clears all four)."),
|
|
123
|
+
rebuild: z.boolean().default(true).describe("Rebuild terrain after clearing (default true)"),
|
|
124
|
+
id: z.string().optional().describe("GUID of the GameObject holding the MapBuilder; omit to auto-find the first MapBuilder in the scene"),
|
|
124
125
|
component: z
|
|
125
126
|
.string()
|
|
126
127
|
.optional()
|
|
@@ -132,17 +133,17 @@ export function registerWorldTools(server, bridge) {
|
|
|
132
133
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
133
134
|
});
|
|
134
135
|
// ── add_cave_waypoint ────────────────────────────────────────────
|
|
135
|
-
server.tool("add_cave_waypoint", "Append (or insert) a waypoint to CaveBuilder.Path. Z is depth (negative = underground).", {
|
|
136
|
-
x: z.number(),
|
|
137
|
-
y: z.number(),
|
|
136
|
+
server.tool("add_cave_waypoint", "Append (or insert at `index`) a waypoint to CaveBuilder.Path. Z is depth (negative = underground). Returns `added`, `total` (waypoint count), and `rebuilt` — rebuilds the cave ('Build Cave') immediately unless rebuild=false.", {
|
|
137
|
+
x: z.number().describe("World X of the waypoint"),
|
|
138
|
+
y: z.number().describe("World Y of the waypoint"),
|
|
138
139
|
z: z.number().default(0).describe("Z depth — negative = underground"),
|
|
139
140
|
index: z
|
|
140
141
|
.number()
|
|
141
142
|
.int()
|
|
142
143
|
.optional()
|
|
143
144
|
.describe("Optional insert position (default: append to end)"),
|
|
144
|
-
rebuild: z.boolean().default(true),
|
|
145
|
-
id: z.string().optional(),
|
|
145
|
+
rebuild: z.boolean().default(true).describe("Rebuild the cave after adding (default true; set false to batch)"),
|
|
146
|
+
id: z.string().optional().describe("GUID of the GameObject holding the CaveBuilder; omit to auto-find the first CaveBuilder in the scene"),
|
|
146
147
|
component: z
|
|
147
148
|
.string()
|
|
148
149
|
.optional()
|
|
@@ -154,8 +155,8 @@ export function registerWorldTools(server, bridge) {
|
|
|
154
155
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
155
156
|
});
|
|
156
157
|
// ── clear_cave_path ──────────────────────────────────────────────
|
|
157
|
-
server.tool("clear_cave_path", "Clear all waypoints in CaveBuilder and remove the cave from the scene.", {
|
|
158
|
-
id: z.string().optional(),
|
|
158
|
+
server.tool("clear_cave_path", "Clear all waypoints in CaveBuilder.Path and remove the built cave from the scene (invokes 'Clear Cave'). Destructive. Returns `cleared` — the number of waypoints removed; start a new path with add_cave_waypoint.", {
|
|
159
|
+
id: z.string().optional().describe("GUID of the GameObject holding the CaveBuilder; omit to auto-find the first CaveBuilder in the scene"),
|
|
159
160
|
component: z
|
|
160
161
|
.string()
|
|
161
162
|
.optional()
|
|
@@ -167,11 +168,11 @@ export function registerWorldTools(server, bridge) {
|
|
|
167
168
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
168
169
|
});
|
|
169
170
|
// ── add_forest_poi ───────────────────────────────────────────────
|
|
170
|
-
server.tool("add_forest_poi", "Add a point of interest (clearing) to ForestGenerator.POIs. Returns
|
|
171
|
-
name: z.string().default("POI"),
|
|
172
|
-
x: z.number(),
|
|
173
|
-
y: z.number(),
|
|
174
|
-
radius: z.number().default(300),
|
|
171
|
+
server.tool("add_forest_poi", "Add a point of interest (clearing) to ForestGenerator.POIs. Returns `added`, `index` (the new POI's index — pass it to add_forest_trail as from_index/to_index), `total`, and `rebuilt`. Forest gen is slow (~1s), so rebuild defaults to false — batch your POIs/trails, then regenerate once (rebuild=true on the last call, or invoke_button 'Generate Forest').", {
|
|
172
|
+
name: z.string().default("POI").describe("Display name for the POI. Default 'POI'."),
|
|
173
|
+
x: z.number().describe("World X of POI center"),
|
|
174
|
+
y: z.number().describe("World Y of POI center"),
|
|
175
|
+
radius: z.number().default(300).describe("POI radius in world units. Default 300."),
|
|
175
176
|
density_multiplier: z
|
|
176
177
|
.number()
|
|
177
178
|
.default(1)
|
|
@@ -180,7 +181,7 @@ export function registerWorldTools(server, bridge) {
|
|
|
180
181
|
.boolean()
|
|
181
182
|
.default(false)
|
|
182
183
|
.describe("Forest gen is slow (~1s); default false to batch"),
|
|
183
|
-
id: z.string().optional(),
|
|
184
|
+
id: z.string().optional().describe("GUID of the GameObject holding the ForestGenerator; omit to auto-find the first ForestGenerator in the scene"),
|
|
184
185
|
component: z
|
|
185
186
|
.string()
|
|
186
187
|
.optional()
|
|
@@ -192,11 +193,11 @@ export function registerWorldTools(server, bridge) {
|
|
|
192
193
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
193
194
|
});
|
|
194
195
|
// ── add_forest_trail ─────────────────────────────────────────────
|
|
195
|
-
server.tool("add_forest_trail", "Add a trail gap between two POIs (
|
|
196
|
-
from_index: z.number().int(),
|
|
197
|
-
to_index: z.number().int(),
|
|
198
|
-
rebuild: z.boolean().default(false),
|
|
199
|
-
id: z.string().optional(),
|
|
196
|
+
server.tool("add_forest_trail", "Add a trail gap between two POIs to ForestGenerator.Trails. Returns `added`, `total` (trail count), and `rebuilt`. rebuild defaults to false — nothing changes visually until you regenerate (rebuild=true, or invoke_button 'Generate Forest').", {
|
|
197
|
+
from_index: z.number().int().describe("Index of the start POI (the `index` returned by add_forest_poi)"),
|
|
198
|
+
to_index: z.number().int().describe("Index of the end POI (the `index` returned by add_forest_poi)"),
|
|
199
|
+
rebuild: z.boolean().default(false).describe("Regenerate the forest after adding (default false — batch, then rebuild once)"),
|
|
200
|
+
id: z.string().optional().describe("GUID of the GameObject holding the ForestGenerator; omit to auto-find the first ForestGenerator in the scene"),
|
|
200
201
|
component: z
|
|
201
202
|
.string()
|
|
202
203
|
.optional()
|
|
@@ -208,10 +209,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
208
209
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
209
210
|
});
|
|
210
211
|
// ── set_forest_seed ──────────────────────────────────────────────
|
|
211
|
-
server.tool("set_forest_seed", "Set ForestGenerator.Seed and regenerate
|
|
212
|
-
seed: z.number().int().default(77),
|
|
213
|
-
rebuild: z.boolean().default(true),
|
|
214
|
-
id: z.string().optional(),
|
|
212
|
+
server.tool("set_forest_seed", "Set ForestGenerator.Seed and (by default) regenerate — re-rolls the forest layout while keeping POIs, trails, and density regions. Returns `set`, `seed`, and `rebuilt`; take a screenshot afterwards (screenshot_from) to judge the new layout.", {
|
|
213
|
+
seed: z.number().int().default(77).describe("New random seed (integer). Default 77."),
|
|
214
|
+
rebuild: z.boolean().default(true).describe("Regenerate the forest after setting (default true)"),
|
|
215
|
+
id: z.string().optional().describe("GUID of the GameObject holding the ForestGenerator; omit to auto-find the first ForestGenerator in the scene"),
|
|
215
216
|
component: z
|
|
216
217
|
.string()
|
|
217
218
|
.optional()
|
|
@@ -223,8 +224,8 @@ export function registerWorldTools(server, bridge) {
|
|
|
223
224
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
224
225
|
});
|
|
225
226
|
// ── clear_forest_pois ────────────────────────────────────────────
|
|
226
|
-
server.tool("clear_forest_pois", "Wipe all POIs and trails in ForestGenerator and clear placed forest objects from the scene.", {
|
|
227
|
-
id: z.string().optional(),
|
|
227
|
+
server.tool("clear_forest_pois", "Wipe all POIs and trails in ForestGenerator and clear placed forest objects from the scene (invokes 'Clear Forest'). Destructive. Returns `cleared` — the number of POIs removed; rebuild a layout with add_forest_poi + add_forest_trail, then invoke_button 'Generate Forest'.", {
|
|
228
|
+
id: z.string().optional().describe("GUID of the GameObject holding the ForestGenerator; omit to auto-find the first ForestGenerator in the scene"),
|
|
228
229
|
component: z
|
|
229
230
|
.string()
|
|
230
231
|
.optional()
|
|
@@ -236,13 +237,13 @@ export function registerWorldTools(server, bridge) {
|
|
|
236
237
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
237
238
|
});
|
|
238
239
|
// ── sculpt_terrain ───────────────────────────────────────────────
|
|
239
|
-
server.tool("sculpt_terrain", "Apply a heightmap brush at (x, y) to MapBuilder. Modes: raise, lower, flatten, smooth. Modifies the current heightmap directly and rebuilds the mesh;
|
|
240
|
+
server.tool("sculpt_terrain", "Apply a heightmap brush at (x, y) to MapBuilder. Modes: raise, lower, flatten, smooth. Modifies the current heightmap directly and rebuilds the mesh; edits survive between calls but are lost when 'Build Terrain' regenerates from the feature lists. Returns `sculpted`, `mode`, and `affected_vertices`; verify with raycast_terrain at the brush center.", {
|
|
240
241
|
x: z.number().describe("World X of brush center"),
|
|
241
242
|
y: z.number().describe("World Y of brush center"),
|
|
242
243
|
radius: z.number().default(400).describe("Brush radius in world units"),
|
|
243
244
|
strength: z.number().default(50).describe("Height delta (units) for raise/lower; ignored for flatten/smooth"),
|
|
244
|
-
mode: z.enum(["raise", "lower", "flatten", "smooth"]).default("raise"),
|
|
245
|
-
id: z.string().optional(),
|
|
245
|
+
mode: z.enum(["raise", "lower", "flatten", "smooth"]).default("raise").describe("Brush mode. Default 'raise'. `strength` applies to raise/lower only."),
|
|
246
|
+
id: z.string().optional().describe("GUID of the GameObject holding the MapBuilder; omit to auto-find the first MapBuilder in the scene"),
|
|
246
247
|
component: z
|
|
247
248
|
.string()
|
|
248
249
|
.optional()
|
|
@@ -254,13 +255,13 @@ export function registerWorldTools(server, bridge) {
|
|
|
254
255
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
255
256
|
});
|
|
256
257
|
// ── paint_forest_density ─────────────────────────────────────────
|
|
257
|
-
server.tool("paint_forest_density", "Add a circular biome region with overridden forest density. Multiple regions stack via cosine falloff. density: 0=no trees, 1=normal, 2=double.", {
|
|
258
|
-
x: z.number(),
|
|
259
|
-
y: z.number(),
|
|
260
|
-
radius: z.number().default(800),
|
|
258
|
+
server.tool("paint_forest_density", "Add a circular biome region with overridden forest density to ForestGenerator.DensityRegions. Multiple regions stack via cosine falloff. density: 0=no trees, 1=normal, 2=double. Returns `painted`, `total` (region count), and `rebuilt` — rebuild defaults to false, so regenerate (rebuild=true, or invoke_button 'Generate Forest') to see the change.", {
|
|
259
|
+
x: z.number().describe("World X of region center"),
|
|
260
|
+
y: z.number().describe("World Y of region center"),
|
|
261
|
+
radius: z.number().default(800).describe("Region radius in world units. Default 800."),
|
|
261
262
|
density: z.number().default(1).describe("Density multiplier (0=clear, 1=normal, 2=dense)"),
|
|
262
|
-
rebuild: z.boolean().default(false),
|
|
263
|
-
id: z.string().optional(),
|
|
263
|
+
rebuild: z.boolean().default(false).describe("Regenerate the forest after adding (default false; batch, then rebuild once)"),
|
|
264
|
+
id: z.string().optional().describe("GUID of the GameObject holding the ForestGenerator; omit to auto-find the first ForestGenerator in the scene"),
|
|
264
265
|
component: z
|
|
265
266
|
.string()
|
|
266
267
|
.optional()
|
|
@@ -272,7 +273,7 @@ export function registerWorldTools(server, bridge) {
|
|
|
272
273
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
273
274
|
});
|
|
274
275
|
// ── place_along_path ─────────────────────────────────────────────
|
|
275
|
-
server.tool("place_along_path", "Drop instances of a model along a path (list of points). Useful for fences, lampposts, road markers, lined-up rocks.", {
|
|
276
|
+
server.tool("place_along_path", "Drop instances of a model along a path (list of points). Useful for fences, lampposts, road markers, lined-up rocks. Returns `placed` (instance count) and `folder` — the GUID of the new parent GameObject grouping the instances; pass `folder` to delete_gameobject to remove the whole run, or to set_parent/set_transform to move it.", {
|
|
276
277
|
model: z.string().describe("Model path (e.g. 'models/dev/box.vmdl' or installed-asset path)"),
|
|
277
278
|
points: z
|
|
278
279
|
.array(z.object({ x: z.number(), y: z.number(), z: z.number().default(0) }))
|
|
@@ -280,9 +281,9 @@ export function registerWorldTools(server, bridge) {
|
|
|
280
281
|
.describe("Path waypoints (at least 2)"),
|
|
281
282
|
spacing: z.number().default(200).describe("Distance between placements (world units)"),
|
|
282
283
|
jitter: z.number().default(0).describe("Max random offset perpendicular to path"),
|
|
283
|
-
min_scale: z.number().default(1),
|
|
284
|
-
max_scale: z.number().default(1),
|
|
285
|
-
seed: z.number().int().default(42),
|
|
284
|
+
min_scale: z.number().default(1).describe("Minimum uniform scale per instance (random between min and max). Default 1."),
|
|
285
|
+
max_scale: z.number().default(1).describe("Maximum uniform scale per instance. Default 1."),
|
|
286
|
+
seed: z.number().int().default(42).describe("Random seed for jitter and scale — same seed reproduces the same placement. Default 42."),
|
|
286
287
|
name: z.string().default("PathItem").describe("Base name for placed objects"),
|
|
287
288
|
}, async (params) => {
|
|
288
289
|
const res = await bridge.send("place_along_path", params);
|
|
@@ -291,10 +292,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
291
292
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
292
293
|
});
|
|
293
294
|
// ── build_terrain_mesh ───────────────────────────────────────────
|
|
294
|
-
server.tool("build_terrain_mesh", "Build a standalone heightmap terrain mesh from a hills/clearings JSON spec — independent of MapBuilder. Use when you don't have a MapBuilder component in the scene and want one-shot terrain.", {
|
|
295
|
+
server.tool("build_terrain_mesh", "Build a standalone heightmap terrain mesh (a MeshComponent) from a hills/clearings JSON spec — independent of MapBuilder. Use when you don't have a MapBuilder component in the scene and want one-shot terrain. Returns `built`, `id` (the new GameObject's GUID), `name`, `vertices`, and `faces`; pass `id` to assign_material, set_transform, or delete_gameobject. Note: raycast_terrain cannot sample this mesh (it requires MapBuilder).", {
|
|
295
296
|
size: z.number().default(9600).describe("Total terrain size (world units, square)"),
|
|
296
297
|
resolution: z.number().int().default(64).describe("Grid resolution per side"),
|
|
297
|
-
name: z.string().default("Generated Terrain"),
|
|
298
|
+
name: z.string().default("Generated Terrain").describe("Name for the created terrain GameObject. Default 'Generated Terrain'."),
|
|
298
299
|
hills: z
|
|
299
300
|
.array(z.object({
|
|
300
301
|
x: z.number(),
|
|
@@ -302,10 +303,12 @@ export function registerWorldTools(server, bridge) {
|
|
|
302
303
|
radius: z.number().default(500),
|
|
303
304
|
height: z.number().default(100),
|
|
304
305
|
}))
|
|
305
|
-
.default([])
|
|
306
|
+
.default([])
|
|
307
|
+
.describe("Hill bumps: array of {x, y, radius (default 500), height (default 100; negative = depression)}. Default: none."),
|
|
306
308
|
clearings: z
|
|
307
309
|
.array(z.object({ x: z.number(), y: z.number(), radius: z.number().default(300) }))
|
|
308
|
-
.default([])
|
|
310
|
+
.default([])
|
|
311
|
+
.describe("Zones flattened back toward height 0: array of {x, y, radius (default 300)}. Default: none."),
|
|
309
312
|
}, async (params) => {
|
|
310
313
|
const res = await bridge.send("build_terrain_mesh", params);
|
|
311
314
|
if (!res.success)
|