unreal-engine-mcp-server 0.5.0 → 0.5.1
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 +1 -0
- package/.github/workflows/release-drafter.yml +1 -1
- package/.github/workflows/release.yml +3 -3
- package/CHANGELOG.md +71 -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/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 +40 -24
- package/dist/tools/actors.js +8 -2
- 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 +33 -61
- 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 +8 -0
- 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.js +0 -5
- package/dist/tools/handlers/asset-handlers.js +454 -454
- 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 +24 -16
- package/dist/tools/lighting.js +5 -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/utils/command-validator.js +3 -2
- 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/docs/Migration-Guide-v0.5.0.md +1 -9
- package/docs/testing-guide.md +2 -2
- package/package.json +12 -6
- package/scripts/run-all-tests.mjs +25 -20
- package/server.json +3 -2
- 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-setup.ts +3 -37
- package/src/tools/actors.ts +36 -28
- package/src/tools/animation.ts +1 -0
- package/src/tools/assets.ts +74 -63
- package/src/tools/base-tool.ts +3 -3
- package/src/tools/blueprint.ts +59 -59
- package/src/tools/consolidated-tool-handlers.ts +129 -150
- package/src/tools/dynamic-handler-registry.ts +22 -140
- package/src/tools/editor.ts +39 -26
- package/src/tools/environment.ts +21 -27
- package/src/tools/foliage.ts +28 -25
- package/src/tools/handlers/actor-handlers.ts +2 -8
- package/src/tools/handlers/asset-handlers.ts +484 -484
- package/src/tools/handlers/sequence-handlers.ts +1 -1
- package/src/tools/landscape.ts +34 -28
- package/src/tools/level.ts +96 -76
- package/src/tools/lighting.ts +6 -1
- package/src/tools/materials.ts +8 -2
- 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 +41 -25
- package/src/tools/ui.ts +0 -2
- 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/utils/command-validator.ts +3 -2
- package/src/utils/normalize.test.ts +162 -0
- package/src/utils/path-security.ts +43 -0
- package/src/utils/response-factory.ts +29 -24
- package/src/utils/safe-json.test.ts +90 -0
- 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 }
|
|
@@ -62,10 +64,10 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
async stopPlayInEditor() {
|
|
67
|
+
async stopPlayInEditor(): Promise<StandardActionResponse> {
|
|
66
68
|
try {
|
|
67
69
|
try {
|
|
68
|
-
const response = await this.sendAutomationRequest(
|
|
70
|
+
const response = await this.sendAutomationRequest<EditorResponse>(
|
|
69
71
|
'control_editor',
|
|
70
72
|
{ action: 'stop' },
|
|
71
73
|
{ timeoutMs: 30000 }
|
|
@@ -92,7 +94,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
async pausePlayInEditor() {
|
|
97
|
+
async pausePlayInEditor(): Promise<StandardActionResponse> {
|
|
96
98
|
try {
|
|
97
99
|
// Pause/Resume PIE
|
|
98
100
|
await this.bridge.executeConsoleCommand('pause');
|
|
@@ -103,11 +105,11 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
// Alias for consistency with naming convention
|
|
106
|
-
async pauseInEditor() {
|
|
108
|
+
async pauseInEditor(): Promise<StandardActionResponse> {
|
|
107
109
|
return this.pausePlayInEditor();
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
async buildLighting() {
|
|
112
|
+
async buildLighting(): Promise<StandardActionResponse> {
|
|
111
113
|
try {
|
|
112
114
|
// Use console command to build lighting
|
|
113
115
|
await this.bridge.executeConsoleCommand('BuildLighting');
|
|
@@ -125,12 +127,12 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
125
127
|
message?: string;
|
|
126
128
|
}> {
|
|
127
129
|
try {
|
|
128
|
-
const resp = await this.sendAutomationRequest(
|
|
130
|
+
const resp = await this.sendAutomationRequest<EditorResponse>(
|
|
129
131
|
'control_editor',
|
|
130
132
|
{ action: 'get_camera' },
|
|
131
133
|
{ timeoutMs: 3000 }
|
|
132
134
|
);
|
|
133
|
-
const result = resp?.result ?? resp;
|
|
135
|
+
const result: any = resp?.result ?? resp;
|
|
134
136
|
const loc = result?.location ?? result?.camera?.location;
|
|
135
137
|
const rot = result?.rotation ?? result?.camera?.rotation;
|
|
136
138
|
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 +146,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
144
146
|
}
|
|
145
147
|
}
|
|
146
148
|
|
|
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) {
|
|
149
|
+
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
150
|
// Special handling for when both location and rotation are missing/invalid
|
|
149
151
|
// Allow rotation-only updates
|
|
150
152
|
if (location === null) {
|
|
@@ -182,7 +184,18 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
182
184
|
|
|
183
185
|
// Use native control_editor.set_camera when available
|
|
184
186
|
try {
|
|
185
|
-
|
|
187
|
+
// Use WASM composeTransform for camera transform calculation
|
|
188
|
+
const locArray: [number, number, number] = location
|
|
189
|
+
? [((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)]
|
|
190
|
+
: [0, 0, 0];
|
|
191
|
+
const rotArray: [number, number, number] = rotation
|
|
192
|
+
? [((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)]
|
|
193
|
+
: [0, 0, 0];
|
|
194
|
+
// Compose transform to validate and process camera positioning via WASM
|
|
195
|
+
wasmIntegration.composeTransform(locArray, rotArray, [1, 1, 1]);
|
|
196
|
+
// console.error('[WASM] Using composeTransform for camera positioning');
|
|
197
|
+
|
|
198
|
+
const resp = await this.sendAutomationRequest<EditorResponse>('control_editor', {
|
|
186
199
|
action: 'set_camera',
|
|
187
200
|
location: location as any,
|
|
188
201
|
rotation: rotation as any
|
|
@@ -196,7 +209,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
196
209
|
}
|
|
197
210
|
}
|
|
198
211
|
|
|
199
|
-
async setCameraSpeed(speed: number) {
|
|
212
|
+
async setCameraSpeed(speed: number): Promise<StandardActionResponse> {
|
|
200
213
|
try {
|
|
201
214
|
await this.bridge.executeConsoleCommand(`camspeed ${speed}`);
|
|
202
215
|
return { success: true, message: `Camera speed set to ${speed}` };
|
|
@@ -205,7 +218,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
205
218
|
}
|
|
206
219
|
}
|
|
207
220
|
|
|
208
|
-
async setFOV(fov: number) {
|
|
221
|
+
async setFOV(fov: number): Promise<StandardActionResponse> {
|
|
209
222
|
try {
|
|
210
223
|
await this.bridge.executeConsoleCommand(`fov ${fov}`);
|
|
211
224
|
return { success: true, message: `FOV set to ${fov}` };
|
|
@@ -214,7 +227,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
214
227
|
}
|
|
215
228
|
}
|
|
216
229
|
|
|
217
|
-
async takeScreenshot(filename?: string, resolution?: string) {
|
|
230
|
+
async takeScreenshot(filename?: string, resolution?: string): Promise<StandardActionResponse> {
|
|
218
231
|
try {
|
|
219
232
|
if (resolution && !/^\d+x\d+$/.test(resolution)) {
|
|
220
233
|
return { success: false, error: 'Invalid resolution format. Use WxH (e.g. 1920x1080)' };
|
|
@@ -237,7 +250,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
237
250
|
}
|
|
238
251
|
}
|
|
239
252
|
|
|
240
|
-
async resumePlayInEditor() {
|
|
253
|
+
async resumePlayInEditor(): Promise<StandardActionResponse> {
|
|
241
254
|
try {
|
|
242
255
|
// Use console command to toggle pause (resumes if paused)
|
|
243
256
|
await this.bridge.executeConsoleCommand('pause');
|
|
@@ -250,7 +263,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
250
263
|
}
|
|
251
264
|
}
|
|
252
265
|
|
|
253
|
-
async stepPIEFrame(steps: number = 1) {
|
|
266
|
+
async stepPIEFrame(steps: number = 1): Promise<StandardActionResponse> {
|
|
254
267
|
const clampedSteps = Number.isFinite(steps) ? Math.max(1, Math.floor(steps)) : 1;
|
|
255
268
|
try {
|
|
256
269
|
// Use console command to step frames
|
|
@@ -267,7 +280,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
267
280
|
}
|
|
268
281
|
}
|
|
269
282
|
|
|
270
|
-
async startRecording(options?: { filename?: string; frameRate?: number; durationSeconds?: number; metadata?: Record<string, unknown> }) {
|
|
283
|
+
async startRecording(options?: { filename?: string; frameRate?: number; durationSeconds?: number; metadata?: Record<string, unknown> }): Promise<StandardActionResponse> {
|
|
271
284
|
const startedAt = Date.now();
|
|
272
285
|
this.activeRecording = {
|
|
273
286
|
name: typeof options?.filename === 'string' ? options.filename.trim() : undefined,
|
|
@@ -286,7 +299,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
286
299
|
};
|
|
287
300
|
}
|
|
288
301
|
|
|
289
|
-
async stopRecording() {
|
|
302
|
+
async stopRecording(): Promise<StandardActionResponse> {
|
|
290
303
|
if (!this.activeRecording) {
|
|
291
304
|
return {
|
|
292
305
|
success: true as const,
|
|
@@ -304,7 +317,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
304
317
|
};
|
|
305
318
|
}
|
|
306
319
|
|
|
307
|
-
async createCameraBookmark(name: string) {
|
|
320
|
+
async createCameraBookmark(name: string): Promise<StandardActionResponse> {
|
|
308
321
|
const trimmedName = name.trim();
|
|
309
322
|
if (!trimmedName) {
|
|
310
323
|
return { success: false as const, error: 'bookmarkName is required' };
|
|
@@ -335,7 +348,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
335
348
|
};
|
|
336
349
|
}
|
|
337
350
|
|
|
338
|
-
async jumpToCameraBookmark(name: string) {
|
|
351
|
+
async jumpToCameraBookmark(name: string): Promise<StandardActionResponse> {
|
|
339
352
|
const trimmedName = name.trim();
|
|
340
353
|
if (!trimmedName) {
|
|
341
354
|
return { success: false as const, error: 'bookmarkName is required' };
|
|
@@ -360,7 +373,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
360
373
|
};
|
|
361
374
|
}
|
|
362
375
|
|
|
363
|
-
async setEditorPreferences(category: string | undefined, preferences: Record<string, unknown>) {
|
|
376
|
+
async setEditorPreferences(category: string | undefined, preferences: Record<string, unknown>): Promise<StandardActionResponse> {
|
|
364
377
|
const resolvedCategory = typeof category === 'string' && category.trim().length > 0 ? category.trim() : 'General';
|
|
365
378
|
const existing = this.editorPreferences.get(resolvedCategory) ?? {};
|
|
366
379
|
this.editorPreferences.set(resolvedCategory, { ...existing, ...preferences });
|
|
@@ -372,7 +385,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
372
385
|
};
|
|
373
386
|
}
|
|
374
387
|
|
|
375
|
-
async setViewportResolution(width: number, height: number) {
|
|
388
|
+
async setViewportResolution(width: number, height: number): Promise<StandardActionResponse> {
|
|
376
389
|
try {
|
|
377
390
|
// Clamp to reasonable limits
|
|
378
391
|
const clampedWidth = Math.max(320, Math.min(7680, width));
|
|
@@ -393,7 +406,7 @@ export class EditorTools extends BaseTool implements IEditorTools {
|
|
|
393
406
|
}
|
|
394
407
|
}
|
|
395
408
|
|
|
396
|
-
async executeConsoleCommand(command: string) {
|
|
409
|
+
async executeConsoleCommand(command: string): Promise<StandardActionResponse> {
|
|
397
410
|
try {
|
|
398
411
|
// Sanitize and validate command
|
|
399
412
|
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) {
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { cleanObject } from '../../utils/safe-json.js';
|
|
2
2
|
import { ITools } from '../../types/tool-interfaces.js';
|
|
3
3
|
import { executeAutomationRequest } from './common-handlers.js';
|
|
4
|
-
import { Logger } from '../../utils/logger.js';
|
|
5
4
|
import { normalizeArgs } from './argument-helper.js';
|
|
6
5
|
|
|
7
6
|
type ActorActionHandler = (args: any, tools: ITools) => Promise<any>;
|
|
8
7
|
|
|
9
|
-
const logger = new Logger('ActorHandlers');
|
|
10
|
-
|
|
11
8
|
const handlers: Record<string, ActorActionHandler> = {
|
|
12
9
|
spawn: async (args, tools) => {
|
|
13
10
|
// Class name aliases for user-friendly names
|
|
@@ -35,7 +32,7 @@ const handlers: Record<string, ActorActionHandler> = {
|
|
|
35
32
|
{ key: 'actorName', aliases: ['name'] },
|
|
36
33
|
{ key: 'timeoutMs', default: undefined }
|
|
37
34
|
]);
|
|
38
|
-
|
|
35
|
+
|
|
39
36
|
const timeoutMs = typeof params.timeoutMs === 'number' ? params.timeoutMs : undefined;
|
|
40
37
|
|
|
41
38
|
// Extremely small timeouts are treated as an immediate timeout-style
|
|
@@ -113,7 +110,6 @@ const handlers: Record<string, ActorActionHandler> = {
|
|
|
113
110
|
// Auto-enable physics logic
|
|
114
111
|
const compsResult = await tools.actorTools.getComponents(params.actorName);
|
|
115
112
|
if (compsResult && compsResult.success && Array.isArray(compsResult.components)) {
|
|
116
|
-
logger.debug('Components found:', JSON.stringify(compsResult.components));
|
|
117
113
|
const meshComp = compsResult.components.find((c: any) => {
|
|
118
114
|
const name = c.name || c;
|
|
119
115
|
const match = typeof name === 'string' && (
|
|
@@ -121,13 +117,11 @@ const handlers: Record<string, ActorActionHandler> = {
|
|
|
121
117
|
name.toLowerCase().includes('mesh') ||
|
|
122
118
|
name.toLowerCase().includes('primitive')
|
|
123
119
|
);
|
|
124
|
-
logger.debug(`Checking component '${name}' matches? ${match}`);
|
|
125
120
|
return match;
|
|
126
121
|
});
|
|
127
122
|
|
|
128
123
|
if (meshComp) {
|
|
129
124
|
const compName = meshComp.name || meshComp;
|
|
130
|
-
logger.debug(`Auto-enabling physics for component: ${compName}`); // Debug log
|
|
131
125
|
await tools.actorTools.setComponentProperties({
|
|
132
126
|
actorName: params.actorName,
|
|
133
127
|
componentName: compName,
|
|
@@ -253,7 +247,7 @@ const handlers: Record<string, ActorActionHandler> = {
|
|
|
253
247
|
const params = normalizeArgs(args, [
|
|
254
248
|
{ key: 'name', aliases: ['actorName', 'query'], required: true }
|
|
255
249
|
]);
|
|
256
|
-
|
|
250
|
+
|
|
257
251
|
// Use the plugin's fuzzy query endpoint (contains-match) instead of the
|
|
258
252
|
// exact lookup endpoint. This improves "spawn then find" reliability.
|
|
259
253
|
return tools.actorTools.findByName(params.name);
|