unreal-engine-mcp-server 0.5.0 → 0.5.2
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/.env.example +1 -1
- package/.github/release-drafter-config.yml +51 -0
- package/.github/workflows/greetings.yml +5 -1
- package/.github/workflows/labeler.yml +2 -1
- package/.github/workflows/publish-mcp.yml +2 -4
- package/.github/workflows/release-drafter.yml +3 -2
- package/.github/workflows/release.yml +3 -3
- package/CHANGELOG.md +109 -0
- package/CONTRIBUTING.md +1 -1
- package/GEMINI.md +115 -0
- package/Public/Plugin_setup_guide.mp4 +0 -0
- package/README.md +166 -200
- package/dist/automation/bridge.d.ts +1 -2
- package/dist/automation/bridge.js +24 -23
- package/dist/automation/connection-manager.d.ts +1 -0
- package/dist/automation/connection-manager.js +10 -0
- package/dist/automation/message-handler.js +5 -4
- package/dist/automation/request-tracker.d.ts +4 -0
- package/dist/automation/request-tracker.js +11 -3
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +4 -0
- package/dist/graphql/loaders.d.ts +64 -0
- package/dist/graphql/loaders.js +117 -0
- package/dist/graphql/resolvers.d.ts +3 -3
- package/dist/graphql/resolvers.js +33 -30
- package/dist/graphql/server.js +3 -1
- package/dist/graphql/types.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -2
- package/dist/server-setup.d.ts +0 -1
- package/dist/server-setup.js +0 -40
- package/dist/tools/actors.d.ts +58 -24
- package/dist/tools/actors.js +22 -6
- package/dist/tools/assets.d.ts +19 -71
- package/dist/tools/assets.js +28 -22
- package/dist/tools/base-tool.d.ts +4 -4
- package/dist/tools/base-tool.js +1 -1
- package/dist/tools/blueprint.d.ts +45 -61
- package/dist/tools/blueprint.js +43 -14
- package/dist/tools/consolidated-tool-definitions.js +2 -1
- package/dist/tools/consolidated-tool-handlers.js +96 -110
- package/dist/tools/dynamic-handler-registry.d.ts +11 -9
- package/dist/tools/dynamic-handler-registry.js +17 -95
- package/dist/tools/editor.d.ts +19 -193
- package/dist/tools/editor.js +11 -2
- package/dist/tools/environment.d.ts +8 -14
- package/dist/tools/foliage.d.ts +18 -143
- package/dist/tools/foliage.js +4 -2
- package/dist/tools/handlers/actor-handlers.d.ts +1 -1
- package/dist/tools/handlers/actor-handlers.js +14 -13
- package/dist/tools/handlers/asset-handlers.js +454 -454
- package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
- package/dist/tools/handlers/sequence-handlers.js +24 -13
- package/dist/tools/introspection.d.ts +1 -1
- package/dist/tools/introspection.js +1 -1
- package/dist/tools/landscape.d.ts +16 -116
- package/dist/tools/landscape.js +7 -3
- package/dist/tools/level.d.ts +22 -103
- package/dist/tools/level.js +26 -18
- package/dist/tools/lighting.d.ts +54 -7
- package/dist/tools/lighting.js +9 -5
- package/dist/tools/materials.d.ts +1 -1
- package/dist/tools/materials.js +5 -1
- package/dist/tools/niagara.js +37 -2
- package/dist/tools/performance.d.ts +0 -1
- package/dist/tools/performance.js +0 -1
- package/dist/tools/physics.js +5 -1
- package/dist/tools/sequence.d.ts +24 -24
- package/dist/tools/sequence.js +13 -0
- package/dist/tools/ui.d.ts +0 -2
- package/dist/types/automation-responses.d.ts +115 -0
- package/dist/types/automation-responses.js +2 -0
- package/dist/types/responses.d.ts +249 -0
- package/dist/types/responses.js +2 -0
- package/dist/types/tool-interfaces.d.ts +135 -135
- package/dist/types/tool-types.d.ts +2 -0
- package/dist/unreal-bridge.js +4 -4
- package/dist/utils/command-validator.js +7 -5
- package/dist/utils/error-handler.d.ts +24 -2
- package/dist/utils/error-handler.js +58 -23
- package/dist/utils/normalize.d.ts +7 -4
- package/dist/utils/normalize.js +12 -10
- package/dist/utils/path-security.d.ts +2 -0
- package/dist/utils/path-security.js +24 -0
- package/dist/utils/response-factory.d.ts +4 -4
- package/dist/utils/response-factory.js +15 -21
- package/dist/utils/response-validator.js +88 -73
- package/dist/utils/unreal-command-queue.d.ts +2 -0
- package/dist/utils/unreal-command-queue.js +8 -1
- package/docs/Migration-Guide-v0.5.0.md +1 -9
- package/docs/handler-mapping.md +4 -2
- package/docs/testing-guide.md +2 -2
- package/package.json +12 -6
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +298 -33
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +7 -8
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +229 -319
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +98 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +24 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +96 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +52 -5
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +5 -268
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +57 -2
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -1
- package/scripts/run-all-tests.mjs +25 -20
- package/server.json +3 -2
- package/src/automation/bridge.ts +27 -25
- package/src/automation/connection-manager.ts +18 -0
- package/src/automation/message-handler.ts +33 -8
- package/src/automation/request-tracker.ts +39 -7
- package/src/config.ts +1 -1
- package/src/constants.ts +7 -0
- package/src/graphql/loaders.ts +244 -0
- package/src/graphql/resolvers.ts +47 -49
- package/src/graphql/server.ts +3 -1
- package/src/graphql/types.ts +3 -0
- package/src/index.ts +15 -2
- package/src/resources/assets.ts +5 -4
- package/src/server/tool-registry.ts +3 -3
- package/src/server-setup.ts +3 -37
- package/src/tools/actors.ts +77 -44
- package/src/tools/animation.ts +1 -0
- package/src/tools/assets.ts +76 -65
- package/src/tools/base-tool.ts +3 -3
- package/src/tools/blueprint.ts +170 -104
- package/src/tools/consolidated-tool-definitions.ts +2 -1
- package/src/tools/consolidated-tool-handlers.ts +129 -150
- package/src/tools/dynamic-handler-registry.ts +22 -140
- package/src/tools/editor.ts +43 -29
- package/src/tools/environment.ts +21 -27
- package/src/tools/foliage.ts +28 -25
- package/src/tools/handlers/actor-handlers.ts +16 -17
- package/src/tools/handlers/asset-handlers.ts +484 -484
- package/src/tools/handlers/sequence-handlers.ts +85 -62
- package/src/tools/introspection.ts +7 -7
- package/src/tools/landscape.ts +34 -28
- package/src/tools/level.ts +100 -80
- package/src/tools/lighting.ts +25 -20
- package/src/tools/materials.ts +9 -3
- package/src/tools/niagara.ts +44 -2
- package/src/tools/performance.ts +1 -2
- package/src/tools/physics.ts +7 -1
- package/src/tools/sequence.ts +42 -26
- package/src/tools/ui.ts +1 -3
- package/src/types/automation-responses.ts +119 -0
- package/src/types/responses.ts +355 -0
- package/src/types/tool-interfaces.ts +135 -135
- package/src/types/tool-types.ts +4 -0
- package/src/unreal-bridge.ts +71 -26
- package/src/utils/command-validator.ts +47 -5
- package/src/utils/error-handler.ts +128 -45
- package/src/utils/normalize.test.ts +162 -0
- package/src/utils/normalize.ts +38 -16
- package/src/utils/path-security.ts +43 -0
- package/src/utils/response-factory.ts +29 -24
- package/src/utils/response-validator.ts +103 -87
- package/src/utils/safe-json.test.ts +90 -0
- package/src/utils/unreal-command-queue.ts +13 -1
- package/src/utils/validation.test.ts +184 -0
- package/tests/test-animation.mjs +358 -33
- package/tests/test-asset-graph.mjs +311 -0
- package/tests/test-audio.mjs +314 -116
- package/tests/test-behavior-tree.mjs +327 -144
- package/tests/test-blueprint-graph.mjs +343 -12
- package/tests/test-control-editor.mjs +85 -53
- package/tests/test-graphql.mjs +58 -8
- package/tests/test-input.mjs +349 -0
- package/tests/test-inspect.mjs +291 -61
- package/tests/test-landscape.mjs +304 -48
- package/tests/test-lighting.mjs +428 -0
- package/tests/test-manage-level.mjs +70 -51
- package/tests/test-performance.mjs +539 -0
- package/tests/test-sequence.mjs +82 -46
- package/tests/test-system.mjs +72 -33
- package/tests/test-wasm.mjs +98 -8
- package/vitest.config.ts +35 -0
- package/.github/release-drafter.yml +0 -148
- package/dist/prompts/index.d.ts +0 -21
- package/dist/prompts/index.js +0 -217
- package/dist/tools/blueprint/helpers.d.ts +0 -29
- package/dist/tools/blueprint/helpers.js +0 -182
- package/src/prompts/index.ts +0 -249
- package/src/tools/blueprint/helpers.ts +0 -189
- package/tests/test-blueprint-events.mjs +0 -35
- package/tests/test-extra-tools.mjs +0 -38
- package/tests/test-render.mjs +0 -33
- package/tests/test-search-assets.mjs +0 -66
package/src/tools/editor.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { BaseTool } from './base-tool.js';
|
|
2
|
-
import { IEditorTools } from '../types/tool-interfaces.js';
|
|
2
|
+
import { IEditorTools, StandardActionResponse } from '../types/tool-interfaces.js';
|
|
3
3
|
import { toVec3Object, toRotObject } from '../utils/normalize.js';
|
|
4
4
|
import { DEFAULT_SCREENSHOT_RESOLUTION } from '../constants.js';
|
|
5
|
+
import { EditorResponse } from '../types/automation-responses.js';
|
|
6
|
+
import { wasmIntegration } from '../wasm/index.js';
|
|
5
7
|
|
|
6
8
|
export class EditorTools extends BaseTool implements IEditorTools {
|
|
7
9
|
private cameraBookmarks = new Map<string, { location: [number, number, number]; rotation: [number, number, number]; savedAt: number }>();
|
|
@@ -10,14 +12,14 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
10
12
|
|
|
11
13
|
async isInPIE(): Promise<boolean> {
|
|
12
14
|
try {
|
|
13
|
-
const response = await this.sendAutomationRequest(
|
|
15
|
+
const response = await this.sendAutomationRequest<EditorResponse>(
|
|
14
16
|
'check_pie_state',
|
|
15
17
|
{},
|
|
16
18
|
{ timeoutMs: 5000 }
|
|
17
19
|
);
|
|
18
20
|
|
|
19
21
|
if (response && response.success !== false) {
|
|
20
|
-
return response.isInPIE === true || response.result?.isInPIE === true;
|
|
22
|
+
return response.isInPIE === true || (response.result as any)?.isInPIE === true;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
return false;
|
|
@@ -34,10 +36,10 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
async playInEditor(timeoutMs: number = 30000) {
|
|
39
|
+
async playInEditor(timeoutMs: number = 30000): Promise<StandardActionResponse> {
|
|
38
40
|
try {
|
|
39
41
|
try {
|
|
40
|
-
const response = await this.sendAutomationRequest(
|
|
42
|
+
const response = await this.sendAutomationRequest<EditorResponse>(
|
|
41
43
|
'control_editor',
|
|
42
44
|
{ action: 'play' },
|
|
43
45
|
{ timeoutMs }
|
|
@@ -46,10 +48,11 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
46
48
|
return { success: true, message: response.message || 'PIE started' };
|
|
47
49
|
}
|
|
48
50
|
return { success: false, error: response?.error || response?.message || 'Failed to start PIE' };
|
|
49
|
-
} catch (err:
|
|
51
|
+
} catch (err: unknown) {
|
|
50
52
|
// If it's a timeout, return error instead of falling back
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
54
|
+
if (errMsg && /time.*out/i.test(errMsg)) {
|
|
55
|
+
return { success: false, error: `Timeout waiting for PIE to start: ${errMsg}` };
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
// Fallback to console commands if automation bridge is unavailable or fails (non-timeout)
|
|
@@ -62,10 +65,10 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
async stopPlayInEditor() {
|
|
68
|
+
async stopPlayInEditor(): Promise<StandardActionResponse> {
|
|
66
69
|
try {
|
|
67
70
|
try {
|
|
68
|
-
const response = await this.sendAutomationRequest(
|
|
71
|
+
const response = await this.sendAutomationRequest<EditorResponse>(
|
|
69
72
|
'control_editor',
|
|
70
73
|
{ action: 'stop' },
|
|
71
74
|
{ timeoutMs: 30000 }
|
|
@@ -92,7 +95,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
92
95
|
}
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
async pausePlayInEditor() {
|
|
98
|
+
async pausePlayInEditor(): Promise<StandardActionResponse> {
|
|
96
99
|
try {
|
|
97
100
|
// Pause/Resume PIE
|
|
98
101
|
await this.bridge.executeConsoleCommand('pause');
|
|
@@ -103,11 +106,11 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
// Alias for consistency with naming convention
|
|
106
|
-
async pauseInEditor() {
|
|
109
|
+
async pauseInEditor(): Promise<StandardActionResponse> {
|
|
107
110
|
return this.pausePlayInEditor();
|
|
108
111
|
}
|
|
109
112
|
|
|
110
|
-
async buildLighting() {
|
|
113
|
+
async buildLighting(): Promise<StandardActionResponse> {
|
|
111
114
|
try {
|
|
112
115
|
// Use console command to build lighting
|
|
113
116
|
await this.bridge.executeConsoleCommand('BuildLighting');
|
|
@@ -125,12 +128,12 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
125
128
|
message?: string;
|
|
126
129
|
}> {
|
|
127
130
|
try {
|
|
128
|
-
const resp = await this.sendAutomationRequest(
|
|
131
|
+
const resp = await this.sendAutomationRequest<EditorResponse>(
|
|
129
132
|
'control_editor',
|
|
130
133
|
{ action: 'get_camera' },
|
|
131
134
|
{ timeoutMs: 3000 }
|
|
132
135
|
);
|
|
133
|
-
const result = resp?.result ?? resp;
|
|
136
|
+
const result: any = resp?.result ?? resp;
|
|
134
137
|
const loc = result?.location ?? result?.camera?.location;
|
|
135
138
|
const rot = result?.rotation ?? result?.camera?.rotation;
|
|
136
139
|
const locArr: [number, number, number] | undefined = Array.isArray(loc) && loc.length === 3 ? [Number(loc[0]) || 0, Number(loc[1]) || 0, Number(loc[2]) || 0] : undefined;
|
|
@@ -144,7 +147,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
144
147
|
}
|
|
145
148
|
}
|
|
146
149
|
|
|
147
|
-
async setViewportCamera(location?: { x: number; y: number; z: number } | [number, number, number] | null | undefined, rotation?: { pitch: number; yaw: number; roll: number } | [number, number, number] | null | undefined) {
|
|
150
|
+
async setViewportCamera(location?: { x: number; y: number; z: number } | [number, number, number] | null | undefined, rotation?: { pitch: number; yaw: number; roll: number } | [number, number, number] | null | undefined): Promise<StandardActionResponse> {
|
|
148
151
|
// Special handling for when both location and rotation are missing/invalid
|
|
149
152
|
// Allow rotation-only updates
|
|
150
153
|
if (location === null) {
|
|
@@ -182,7 +185,18 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
182
185
|
|
|
183
186
|
// Use native control_editor.set_camera when available
|
|
184
187
|
try {
|
|
185
|
-
|
|
188
|
+
// Use WASM composeTransform for camera transform calculation
|
|
189
|
+
const locArray: [number, number, number] = location
|
|
190
|
+
? [((location as any).x ?? (location as any)[0] ?? 0), ((location as any).y ?? (location as any)[1] ?? 0), ((location as any).z ?? (location as any)[2] ?? 0)]
|
|
191
|
+
: [0, 0, 0];
|
|
192
|
+
const rotArray: [number, number, number] = rotation
|
|
193
|
+
? [((rotation as any).pitch ?? (rotation as any)[0] ?? 0), ((rotation as any).yaw ?? (rotation as any)[1] ?? 0), ((rotation as any).roll ?? (rotation as any)[2] ?? 0)]
|
|
194
|
+
: [0, 0, 0];
|
|
195
|
+
// Compose transform to validate and process camera positioning via WASM
|
|
196
|
+
wasmIntegration.composeTransform(locArray, rotArray, [1, 1, 1]);
|
|
197
|
+
// console.error('[WASM] Using composeTransform for camera positioning');
|
|
198
|
+
|
|
199
|
+
const resp = await this.sendAutomationRequest<EditorResponse>('control_editor', {
|
|
186
200
|
action: 'set_camera',
|
|
187
201
|
location: location as any,
|
|
188
202
|
rotation: rotation as any
|
|
@@ -196,7 +210,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
196
210
|
}
|
|
197
211
|
}
|
|
198
212
|
|
|
199
|
-
async setCameraSpeed(speed: number) {
|
|
213
|
+
async setCameraSpeed(speed: number): Promise<StandardActionResponse> {
|
|
200
214
|
try {
|
|
201
215
|
await this.bridge.executeConsoleCommand(`camspeed ${speed}`);
|
|
202
216
|
return { success: true, message: `Camera speed set to ${speed}` };
|
|
@@ -205,7 +219,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
205
219
|
}
|
|
206
220
|
}
|
|
207
221
|
|
|
208
|
-
async setFOV(fov: number) {
|
|
222
|
+
async setFOV(fov: number): Promise<StandardActionResponse> {
|
|
209
223
|
try {
|
|
210
224
|
await this.bridge.executeConsoleCommand(`fov ${fov}`);
|
|
211
225
|
return { success: true, message: `FOV set to ${fov}` };
|
|
@@ -214,7 +228,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
214
228
|
}
|
|
215
229
|
}
|
|
216
230
|
|
|
217
|
-
async takeScreenshot(filename?: string, resolution?: string) {
|
|
231
|
+
async takeScreenshot(filename?: string, resolution?: string): Promise<StandardActionResponse> {
|
|
218
232
|
try {
|
|
219
233
|
if (resolution && !/^\d+x\d+$/.test(resolution)) {
|
|
220
234
|
return { success: false, error: 'Invalid resolution format. Use WxH (e.g. 1920x1080)' };
|
|
@@ -237,7 +251,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
237
251
|
}
|
|
238
252
|
}
|
|
239
253
|
|
|
240
|
-
async resumePlayInEditor() {
|
|
254
|
+
async resumePlayInEditor(): Promise<StandardActionResponse> {
|
|
241
255
|
try {
|
|
242
256
|
// Use console command to toggle pause (resumes if paused)
|
|
243
257
|
await this.bridge.executeConsoleCommand('pause');
|
|
@@ -250,7 +264,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
250
264
|
}
|
|
251
265
|
}
|
|
252
266
|
|
|
253
|
-
async stepPIEFrame(steps: number = 1) {
|
|
267
|
+
async stepPIEFrame(steps: number = 1): Promise<StandardActionResponse> {
|
|
254
268
|
const clampedSteps = Number.isFinite(steps) ? Math.max(1, Math.floor(steps)) : 1;
|
|
255
269
|
try {
|
|
256
270
|
// Use console command to step frames
|
|
@@ -267,7 +281,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
267
281
|
}
|
|
268
282
|
}
|
|
269
283
|
|
|
270
|
-
async startRecording(options?: { filename?: string; frameRate?: number; durationSeconds?: number; metadata?: Record<string, unknown> }) {
|
|
284
|
+
async startRecording(options?: { filename?: string; frameRate?: number; durationSeconds?: number; metadata?: Record<string, unknown> }): Promise<StandardActionResponse> {
|
|
271
285
|
const startedAt = Date.now();
|
|
272
286
|
this.activeRecording = {
|
|
273
287
|
name: typeof options?.filename === 'string' ? options.filename.trim() : undefined,
|
|
@@ -286,7 +300,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
286
300
|
};
|
|
287
301
|
}
|
|
288
302
|
|
|
289
|
-
async stopRecording() {
|
|
303
|
+
async stopRecording(): Promise<StandardActionResponse> {
|
|
290
304
|
if (!this.activeRecording) {
|
|
291
305
|
return {
|
|
292
306
|
success: true as const,
|
|
@@ -304,7 +318,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
304
318
|
};
|
|
305
319
|
}
|
|
306
320
|
|
|
307
|
-
async createCameraBookmark(name: string) {
|
|
321
|
+
async createCameraBookmark(name: string): Promise<StandardActionResponse> {
|
|
308
322
|
const trimmedName = name.trim();
|
|
309
323
|
if (!trimmedName) {
|
|
310
324
|
return { success: false as const, error: 'bookmarkName is required' };
|
|
@@ -335,7 +349,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
335
349
|
};
|
|
336
350
|
}
|
|
337
351
|
|
|
338
|
-
async jumpToCameraBookmark(name: string) {
|
|
352
|
+
async jumpToCameraBookmark(name: string): Promise<StandardActionResponse> {
|
|
339
353
|
const trimmedName = name.trim();
|
|
340
354
|
if (!trimmedName) {
|
|
341
355
|
return { success: false as const, error: 'bookmarkName is required' };
|
|
@@ -360,7 +374,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
360
374
|
};
|
|
361
375
|
}
|
|
362
376
|
|
|
363
|
-
async setEditorPreferences(category: string | undefined, preferences: Record<string, unknown>) {
|
|
377
|
+
async setEditorPreferences(category: string | undefined, preferences: Record<string, unknown>): Promise<StandardActionResponse> {
|
|
364
378
|
const resolvedCategory = typeof category === 'string' && category.trim().length > 0 ? category.trim() : 'General';
|
|
365
379
|
const existing = this.editorPreferences.get(resolvedCategory) ?? {};
|
|
366
380
|
this.editorPreferences.set(resolvedCategory, { ...existing, ...preferences });
|
|
@@ -372,7 +386,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
372
386
|
};
|
|
373
387
|
}
|
|
374
388
|
|
|
375
|
-
async setViewportResolution(width: number, height: number) {
|
|
389
|
+
async setViewportResolution(width: number, height: number): Promise<StandardActionResponse> {
|
|
376
390
|
try {
|
|
377
391
|
// Clamp to reasonable limits
|
|
378
392
|
const clampedWidth = Math.max(320, Math.min(7680, width));
|
|
@@ -393,7 +407,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
393
407
|
}
|
|
394
408
|
}
|
|
395
409
|
|
|
396
|
-
async executeConsoleCommand(command: string) {
|
|
410
|
+
async executeConsoleCommand(command: string): Promise<StandardActionResponse> {
|
|
397
411
|
try {
|
|
398
412
|
// Sanitize and validate command
|
|
399
413
|
if (!command || typeof command !== 'string') {
|
package/src/tools/environment.ts
CHANGED
|
@@ -3,15 +3,9 @@ import path from 'node:path';
|
|
|
3
3
|
import { AutomationBridge } from '../automation/index.js';
|
|
4
4
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
5
5
|
import { DEFAULT_SKYLIGHT_INTENSITY, DEFAULT_SUN_INTENSITY, DEFAULT_TIME_OF_DAY } from '../constants.js';
|
|
6
|
+
import { IEnvironmentTools, StandardActionResponse } from '../types/tool-interfaces.js';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
success: boolean;
|
|
9
|
-
message?: string;
|
|
10
|
-
error?: string;
|
|
11
|
-
details?: Record<string, unknown>;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class EnvironmentTools {
|
|
8
|
+
export class EnvironmentTools implements IEnvironmentTools {
|
|
15
9
|
constructor(_bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
|
|
16
10
|
|
|
17
11
|
setAutomationBridge(automationBridge?: AutomationBridge) {
|
|
@@ -69,7 +63,7 @@ export class EnvironmentTools {
|
|
|
69
63
|
return num;
|
|
70
64
|
}
|
|
71
65
|
|
|
72
|
-
private async invoke(action: string, payload: Record<string, unknown>): Promise<
|
|
66
|
+
private async invoke(action: string, payload: Record<string, unknown>): Promise<StandardActionResponse> {
|
|
73
67
|
try {
|
|
74
68
|
const bridge = this.ensureAutomationBridge();
|
|
75
69
|
const response = await bridge.sendAutomationRequest(action, payload, { timeoutMs: 40000 });
|
|
@@ -79,42 +73,42 @@ export class EnvironmentTools {
|
|
|
79
73
|
error: typeof response.error === 'string' && response.error.length > 0 ? response.error : 'ENVIRONMENT_CONTROL_FAILED',
|
|
80
74
|
message: typeof response.message === 'string' ? response.message : undefined,
|
|
81
75
|
details: typeof response.result === 'object' && response.result ? response.result as Record<string, unknown> : undefined
|
|
82
|
-
};
|
|
76
|
+
} as StandardActionResponse;
|
|
83
77
|
}
|
|
84
78
|
const resultPayload = typeof response.result === 'object' && response.result ? response.result as Record<string, unknown> : undefined;
|
|
85
79
|
return {
|
|
86
80
|
success: true,
|
|
87
81
|
message: typeof response.message === 'string' ? response.message : 'Environment control action succeeded',
|
|
88
82
|
details: resultPayload
|
|
89
|
-
};
|
|
83
|
+
} as StandardActionResponse;
|
|
90
84
|
} catch (error) {
|
|
91
85
|
const message = error instanceof Error ? error.message : String(error);
|
|
92
86
|
return {
|
|
93
87
|
success: false,
|
|
94
88
|
error: message || 'ENVIRONMENT_CONTROL_FAILED'
|
|
95
|
-
};
|
|
89
|
+
} as StandardActionResponse;
|
|
96
90
|
}
|
|
97
91
|
}
|
|
98
92
|
|
|
99
|
-
async setTimeOfDay(hour: unknown): Promise<
|
|
93
|
+
async setTimeOfDay(hour: unknown): Promise<StandardActionResponse> {
|
|
100
94
|
const normalizedHour = this.normalizeNumber(hour, 'hour', this.getDefaultTimeOfDay());
|
|
101
95
|
const clampedHour = Math.min(Math.max(normalizedHour, 0), 24);
|
|
102
96
|
return this.invoke('set_time_of_day', { hour: clampedHour });
|
|
103
97
|
}
|
|
104
98
|
|
|
105
|
-
async setSunIntensity(intensity: unknown): Promise<
|
|
99
|
+
async setSunIntensity(intensity: unknown): Promise<StandardActionResponse> {
|
|
106
100
|
const normalized = this.normalizeNumber(intensity, 'intensity', this.getDefaultSunIntensity());
|
|
107
101
|
const finalValue = Math.max(normalized, 0);
|
|
108
102
|
return this.invoke('set_sun_intensity', { intensity: finalValue });
|
|
109
103
|
}
|
|
110
104
|
|
|
111
|
-
async setSkylightIntensity(intensity: unknown): Promise<
|
|
105
|
+
async setSkylightIntensity(intensity: unknown): Promise<StandardActionResponse> {
|
|
112
106
|
const normalized = this.normalizeNumber(intensity, 'intensity', this.getDefaultSkylightIntensity());
|
|
113
107
|
const finalValue = Math.max(normalized, 0);
|
|
114
108
|
return this.invoke('set_skylight_intensity', { intensity: finalValue });
|
|
115
109
|
}
|
|
116
110
|
|
|
117
|
-
async exportSnapshot(params: { path?: unknown; filename?: unknown }): Promise<
|
|
111
|
+
async exportSnapshot(params: { path?: unknown; filename?: unknown }): Promise<StandardActionResponse> {
|
|
118
112
|
try {
|
|
119
113
|
const rawPath = typeof params?.path === 'string' && params.path.trim().length > 0
|
|
120
114
|
? params.path.trim()
|
|
@@ -157,17 +151,17 @@ export class EnvironmentTools {
|
|
|
157
151
|
success: true,
|
|
158
152
|
message: `Environment snapshot exported to ${targetPath}`,
|
|
159
153
|
details: { path: targetPath }
|
|
160
|
-
};
|
|
154
|
+
} as StandardActionResponse;
|
|
161
155
|
} catch (error) {
|
|
162
156
|
const message = error instanceof Error ? error.message : String(error);
|
|
163
157
|
return {
|
|
164
158
|
success: false,
|
|
165
159
|
error: `Failed to export environment snapshot: ${message}`
|
|
166
|
-
};
|
|
160
|
+
} as StandardActionResponse;
|
|
167
161
|
}
|
|
168
162
|
}
|
|
169
163
|
|
|
170
|
-
async importSnapshot(params: { path?: unknown; filename?: unknown }): Promise<
|
|
164
|
+
async importSnapshot(params: { path?: unknown; filename?: unknown }): Promise<StandardActionResponse> {
|
|
171
165
|
const rawPath = typeof params?.path === 'string' && params.path.trim().length > 0
|
|
172
166
|
? params.path.trim()
|
|
173
167
|
: './tests/reports/env_snapshot.json';
|
|
@@ -210,7 +204,7 @@ export class EnvironmentTools {
|
|
|
210
204
|
return {
|
|
211
205
|
success: true,
|
|
212
206
|
message: `Environment snapshot file not found at ${targetPath}; import treated as no-op`
|
|
213
|
-
};
|
|
207
|
+
} as StandardActionResponse;
|
|
214
208
|
}
|
|
215
209
|
throw err;
|
|
216
210
|
}
|
|
@@ -219,17 +213,17 @@ export class EnvironmentTools {
|
|
|
219
213
|
success: true,
|
|
220
214
|
message: `Environment snapshot import handled from ${targetPath}`,
|
|
221
215
|
details: parsed && typeof parsed === 'object' ? parsed as Record<string, unknown> : undefined
|
|
222
|
-
};
|
|
216
|
+
} as StandardActionResponse;
|
|
223
217
|
} catch (error) {
|
|
224
218
|
const message = error instanceof Error ? error.message : String(error);
|
|
225
219
|
return {
|
|
226
220
|
success: false,
|
|
227
221
|
error: `Failed to import environment snapshot: ${message}`
|
|
228
|
-
};
|
|
222
|
+
} as StandardActionResponse;
|
|
229
223
|
}
|
|
230
224
|
}
|
|
231
225
|
|
|
232
|
-
async cleanup(params?: { names?: unknown; name?: unknown }): Promise<
|
|
226
|
+
async cleanup(params?: { names?: unknown; name?: unknown }): Promise<StandardActionResponse> {
|
|
233
227
|
try {
|
|
234
228
|
const rawNames = Array.isArray(params?.names) ? params.names : [];
|
|
235
229
|
if (typeof params?.name === 'string' && params.name.trim().length > 0) {
|
|
@@ -244,7 +238,7 @@ export class EnvironmentTools {
|
|
|
244
238
|
return {
|
|
245
239
|
success: true,
|
|
246
240
|
message: 'No environment actor names provided for cleanup; no-op'
|
|
247
|
-
};
|
|
241
|
+
} as StandardActionResponse;
|
|
248
242
|
}
|
|
249
243
|
|
|
250
244
|
const bridge = this.ensureAutomationBridge();
|
|
@@ -264,7 +258,7 @@ export class EnvironmentTools {
|
|
|
264
258
|
: 'Failed to delete environment actors'),
|
|
265
259
|
message: typeof resp?.message === 'string' ? resp.message : undefined,
|
|
266
260
|
details: result
|
|
267
|
-
};
|
|
261
|
+
} as StandardActionResponse;
|
|
268
262
|
}
|
|
269
263
|
|
|
270
264
|
const result = resp && typeof resp.result === 'object' ? resp.result as Record<string, unknown> : undefined;
|
|
@@ -275,13 +269,13 @@ export class EnvironmentTools {
|
|
|
275
269
|
? resp.message
|
|
276
270
|
: `Environment actors deleted: ${cleaned.join(', ')}`,
|
|
277
271
|
details: result ?? { names: cleaned }
|
|
278
|
-
};
|
|
272
|
+
} as StandardActionResponse;
|
|
279
273
|
} catch (error) {
|
|
280
274
|
const message = error instanceof Error ? error.message : String(error);
|
|
281
275
|
return {
|
|
282
276
|
success: false,
|
|
283
277
|
error: `Failed to cleanup environment actors: ${message}`
|
|
284
|
-
};
|
|
278
|
+
} as StandardActionResponse;
|
|
285
279
|
}
|
|
286
280
|
}
|
|
287
281
|
}
|
package/src/tools/foliage.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
3
3
|
import { AutomationBridge } from '../automation/index.js';
|
|
4
4
|
import { coerceBoolean, coerceNumber, coerceString } from '../utils/result-helpers.js';
|
|
5
|
+
import { IFoliageTools, StandardActionResponse } from '../types/tool-interfaces.js';
|
|
5
6
|
|
|
6
|
-
export class FoliageTools {
|
|
7
|
+
export class FoliageTools implements IFoliageTools {
|
|
7
8
|
constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
|
|
8
9
|
|
|
9
10
|
setAutomationBridge(automationBridge?: AutomationBridge) { this.automationBridge = automationBridge; }
|
|
@@ -24,7 +25,7 @@ export class FoliageTools {
|
|
|
24
25
|
alignToNormal?: boolean;
|
|
25
26
|
randomYaw?: boolean;
|
|
26
27
|
groundSlope?: number;
|
|
27
|
-
}) {
|
|
28
|
+
}): Promise<StandardActionResponse> {
|
|
28
29
|
// Basic validation to prevent bad inputs like 'undefined' and empty strings
|
|
29
30
|
const errors: string[] = [];
|
|
30
31
|
const name = String(params?.name ?? '').trim();
|
|
@@ -99,7 +100,7 @@ export class FoliageTools {
|
|
|
99
100
|
message: exists
|
|
100
101
|
? `Foliage type '${name}' ready (${method})`
|
|
101
102
|
: `Created foliage '${name}' but verification did not find it yet`
|
|
102
|
-
};
|
|
103
|
+
} as StandardActionResponse;
|
|
103
104
|
} catch (error) {
|
|
104
105
|
return {
|
|
105
106
|
success: false,
|
|
@@ -115,7 +116,7 @@ export class FoliageTools {
|
|
|
115
116
|
brushSize?: number;
|
|
116
117
|
paintDensity?: number;
|
|
117
118
|
eraseMode?: boolean;
|
|
118
|
-
}) {
|
|
119
|
+
}): Promise<StandardActionResponse> {
|
|
119
120
|
const errors: string[] = [];
|
|
120
121
|
const foliageType = String(params?.foliageType ?? '').trim();
|
|
121
122
|
const pos = Array.isArray(params?.position) ? params.position : [0, 0, 0];
|
|
@@ -174,7 +175,7 @@ export class FoliageTools {
|
|
|
174
175
|
added,
|
|
175
176
|
note,
|
|
176
177
|
message: `Painted ${added} instances for '${foliageType}' around (${pos[0]}, ${pos[1]}, ${pos[2]})`
|
|
177
|
-
};
|
|
178
|
+
} as StandardActionResponse;
|
|
178
179
|
} catch (error) {
|
|
179
180
|
return {
|
|
180
181
|
success: false,
|
|
@@ -184,7 +185,7 @@ export class FoliageTools {
|
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
// Query foliage instances (plugin-native)
|
|
187
|
-
async getFoliageInstances(params: { foliageType?: string }) {
|
|
188
|
+
async getFoliageInstances(params: { foliageType?: string }): Promise<StandardActionResponse> {
|
|
188
189
|
if (!this.automationBridge) {
|
|
189
190
|
throw new Error('Automation Bridge not available. Foliage operations require plugin support.');
|
|
190
191
|
}
|
|
@@ -200,15 +201,16 @@ export class FoliageTools {
|
|
|
200
201
|
return {
|
|
201
202
|
success: true,
|
|
202
203
|
count: coerceNumber(payload.count) ?? 0,
|
|
203
|
-
instances: (payload.instances as any[]) ?? []
|
|
204
|
-
|
|
204
|
+
instances: (payload.instances as any[]) ?? [],
|
|
205
|
+
message: 'Foliage instances retrieved'
|
|
206
|
+
} as StandardActionResponse;
|
|
205
207
|
} catch (error) {
|
|
206
208
|
return { success: false, error: `Failed to get foliage instances: ${error instanceof Error ? error.message : String(error)}` };
|
|
207
209
|
}
|
|
208
210
|
}
|
|
209
211
|
|
|
210
212
|
// Remove foliage (plugin-native)
|
|
211
|
-
async removeFoliage(params: { foliageType?: string; removeAll?: boolean }) {
|
|
213
|
+
async removeFoliage(params: { foliageType?: string; removeAll?: boolean }): Promise<StandardActionResponse> {
|
|
212
214
|
if (!this.automationBridge) {
|
|
213
215
|
throw new Error('Automation Bridge not available. Foliage operations require plugin support.');
|
|
214
216
|
}
|
|
@@ -224,8 +226,9 @@ export class FoliageTools {
|
|
|
224
226
|
const payload = response.result as Record<string, unknown>;
|
|
225
227
|
return {
|
|
226
228
|
success: true,
|
|
227
|
-
instancesRemoved: coerceNumber(payload.instancesRemoved) ?? 0
|
|
228
|
-
|
|
229
|
+
instancesRemoved: coerceNumber(payload.instancesRemoved) ?? 0,
|
|
230
|
+
message: 'Foliage removed'
|
|
231
|
+
} as StandardActionResponse;
|
|
229
232
|
} catch (error) {
|
|
230
233
|
return { success: false, error: `Failed to remove foliage: ${error instanceof Error ? error.message : String(error)}` };
|
|
231
234
|
}
|
|
@@ -242,7 +245,7 @@ export class FoliageTools {
|
|
|
242
245
|
}>;
|
|
243
246
|
enableCulling?: boolean;
|
|
244
247
|
cullDistance?: number;
|
|
245
|
-
}) {
|
|
248
|
+
}): Promise<StandardActionResponse> {
|
|
246
249
|
const commands: string[] = [];
|
|
247
250
|
|
|
248
251
|
commands.push(`CreateInstancedStaticMesh ${params.name} ${params.meshPath}`);
|
|
@@ -271,7 +274,7 @@ export class FoliageTools {
|
|
|
271
274
|
foliageType: string;
|
|
272
275
|
lodDistances?: number[];
|
|
273
276
|
screenSize?: number[];
|
|
274
|
-
}) {
|
|
277
|
+
}): Promise<StandardActionResponse> {
|
|
275
278
|
const commands: string[] = [];
|
|
276
279
|
|
|
277
280
|
if (params.lodDistances) {
|
|
@@ -288,7 +291,7 @@ export class FoliageTools {
|
|
|
288
291
|
}
|
|
289
292
|
|
|
290
293
|
// Alias for addFoliageType to match interface/handler usage
|
|
291
|
-
async addFoliage(params: { foliageType: string; locations: Array<{ x: number; y: number; z: number }> }) {
|
|
294
|
+
async addFoliage(params: { foliageType: string; locations: Array<{ x: number; y: number; z: number }> }): Promise<StandardActionResponse> {
|
|
292
295
|
// Delegate to paintFoliage which handles placing instances at locations
|
|
293
296
|
if (params.locations && params.locations.length > 0) {
|
|
294
297
|
if (!this.automationBridge) {
|
|
@@ -331,7 +334,7 @@ export class FoliageTools {
|
|
|
331
334
|
size?: [number, number, number];
|
|
332
335
|
seed?: number;
|
|
333
336
|
tileSize?: number;
|
|
334
|
-
}) {
|
|
337
|
+
}): Promise<StandardActionResponse> {
|
|
335
338
|
if (!this.automationBridge) {
|
|
336
339
|
throw new Error('Automation Bridge not available.');
|
|
337
340
|
}
|
|
@@ -376,7 +379,7 @@ export class FoliageTools {
|
|
|
376
379
|
volumeActor: result?.volume_actor,
|
|
377
380
|
spawnerPath: result?.spawner_path,
|
|
378
381
|
foliageTypesCount: result?.foliage_types_count
|
|
379
|
-
};
|
|
382
|
+
} as StandardActionResponse;
|
|
380
383
|
}
|
|
381
384
|
|
|
382
385
|
/**
|
|
@@ -390,7 +393,7 @@ export class FoliageTools {
|
|
|
390
393
|
rotation?: [number, number, number];
|
|
391
394
|
scale?: [number, number, number];
|
|
392
395
|
}>;
|
|
393
|
-
}) {
|
|
396
|
+
}): Promise<StandardActionResponse> {
|
|
394
397
|
if (!this.automationBridge) {
|
|
395
398
|
throw new Error('Automation Bridge not available. Foliage instance placement requires plugin support.');
|
|
396
399
|
}
|
|
@@ -417,7 +420,7 @@ export class FoliageTools {
|
|
|
417
420
|
success: true,
|
|
418
421
|
message: response.message || `Added ${result?.instances_count || params.transforms.length} foliage instances`,
|
|
419
422
|
instancesCount: result?.instances_count
|
|
420
|
-
};
|
|
423
|
+
} as StandardActionResponse;
|
|
421
424
|
} catch (error) {
|
|
422
425
|
return {
|
|
423
426
|
success: false,
|
|
@@ -432,7 +435,7 @@ export class FoliageTools {
|
|
|
432
435
|
collisionEnabled?: boolean;
|
|
433
436
|
collisionProfile?: string;
|
|
434
437
|
generateOverlapEvents?: boolean;
|
|
435
|
-
}) {
|
|
438
|
+
}): Promise<StandardActionResponse> {
|
|
436
439
|
const commands: string[] = [];
|
|
437
440
|
|
|
438
441
|
if (params.collisionEnabled !== undefined) {
|
|
@@ -463,7 +466,7 @@ export class FoliageTools {
|
|
|
463
466
|
}>;
|
|
464
467
|
windStrength?: number;
|
|
465
468
|
windSpeed?: number;
|
|
466
|
-
}) {
|
|
469
|
+
}): Promise<StandardActionResponse> {
|
|
467
470
|
const commands: string[] = [];
|
|
468
471
|
|
|
469
472
|
commands.push(`CreateGrassSystem ${params.name}`);
|
|
@@ -492,7 +495,7 @@ export class FoliageTools {
|
|
|
492
495
|
foliageType: string;
|
|
493
496
|
position: [number, number, number];
|
|
494
497
|
radius: number;
|
|
495
|
-
}) {
|
|
498
|
+
}): Promise<StandardActionResponse> {
|
|
496
499
|
const command = `RemoveFoliageInRadius ${params.foliageType} ${params.position.join(' ')} ${params.radius}`;
|
|
497
500
|
return this.bridge.executeConsoleCommand(command);
|
|
498
501
|
}
|
|
@@ -503,7 +506,7 @@ export class FoliageTools {
|
|
|
503
506
|
position?: [number, number, number];
|
|
504
507
|
radius?: number;
|
|
505
508
|
selectAll?: boolean;
|
|
506
|
-
}) {
|
|
509
|
+
}): Promise<StandardActionResponse> {
|
|
507
510
|
let command: string;
|
|
508
511
|
|
|
509
512
|
if (params.selectAll) {
|
|
@@ -523,7 +526,7 @@ export class FoliageTools {
|
|
|
523
526
|
updateTransforms?: boolean;
|
|
524
527
|
updateMesh?: boolean;
|
|
525
528
|
newMeshPath?: string;
|
|
526
|
-
}) {
|
|
529
|
+
}): Promise<StandardActionResponse> {
|
|
527
530
|
const commands: string[] = [];
|
|
528
531
|
|
|
529
532
|
if (params.updateTransforms) {
|
|
@@ -546,7 +549,7 @@ export class FoliageTools {
|
|
|
546
549
|
name: string;
|
|
547
550
|
spawnArea: 'Landscape' | 'StaticMesh' | 'BSP' | 'Foliage' | 'All';
|
|
548
551
|
excludeAreas?: Array<[number, number, number, number]>; // [x, y, z, radius]
|
|
549
|
-
}) {
|
|
552
|
+
}): Promise<StandardActionResponse> {
|
|
550
553
|
const commands: string[] = [];
|
|
551
554
|
|
|
552
555
|
commands.push(`CreateFoliageSpawner ${params.name} ${params.spawnArea}`);
|
|
@@ -568,7 +571,7 @@ export class FoliageTools {
|
|
|
568
571
|
generateClusters?: boolean;
|
|
569
572
|
clusterSize?: number;
|
|
570
573
|
reduceDrawCalls?: boolean;
|
|
571
|
-
}) {
|
|
574
|
+
}): Promise<StandardActionResponse> {
|
|
572
575
|
const commands = [];
|
|
573
576
|
|
|
574
577
|
if (params.mergeInstances) {
|