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
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { cleanObject } from '../../utils/safe-json.js';
|
|
2
|
-
import { ITools } from '../../types/tool-interfaces.js';
|
|
2
|
+
import { ITools, StandardActionResponse } from '../../types/tool-interfaces.js';
|
|
3
3
|
import { executeAutomationRequest, requireNonEmptyString } from './common-handlers.js';
|
|
4
4
|
|
|
5
|
+
/** Extended response with common sequence fields */
|
|
6
|
+
interface SequenceActionResponse extends StandardActionResponse {
|
|
7
|
+
result?: {
|
|
8
|
+
sequencePath?: string;
|
|
9
|
+
results?: Array<{ success?: boolean; error?: string }>;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
};
|
|
12
|
+
bindings?: Array<{ name?: string;[key: string]: unknown }>;
|
|
13
|
+
message?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
5
16
|
const managedSequences = new Set<string>();
|
|
6
17
|
const deletedSequences = new Set<string>();
|
|
7
18
|
|
|
@@ -22,37 +33,48 @@ function markSequenceDeleted(path: unknown) {
|
|
|
22
33
|
const norm = normalizeSequencePath(path);
|
|
23
34
|
if (!norm) return;
|
|
24
35
|
managedSequences.delete(norm);
|
|
25
|
-
deletedSequences.
|
|
36
|
+
deletedSequences.delete(norm);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Helper to safely get string from error/message */
|
|
40
|
+
function getErrorString(res: SequenceActionResponse | null | undefined): string {
|
|
41
|
+
if (!res) return '';
|
|
42
|
+
return typeof res.error === 'string' ? res.error : '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getMessageString(res: SequenceActionResponse | null | undefined): string {
|
|
46
|
+
if (!res) return '';
|
|
47
|
+
return typeof res.message === 'string' ? res.message : '';
|
|
26
48
|
}
|
|
27
49
|
|
|
28
50
|
|
|
29
51
|
|
|
30
|
-
export async function handleSequenceTools(action: string, args:
|
|
52
|
+
export async function handleSequenceTools(action: string, args: Record<string, unknown>, tools: ITools) {
|
|
31
53
|
const seqAction = String(action || '').trim();
|
|
32
54
|
switch (seqAction) {
|
|
33
55
|
case 'create': {
|
|
34
56
|
const name = requireNonEmptyString(args.name, 'name', 'Missing required parameter: name');
|
|
35
|
-
const res = await tools.sequenceTools.create({ name, path: args.path });
|
|
57
|
+
const res = await tools.sequenceTools.create({ name, path: args.path as string | undefined }) as SequenceActionResponse;
|
|
36
58
|
|
|
37
59
|
let sequencePath: string | undefined;
|
|
38
|
-
if (res &&
|
|
39
|
-
sequencePath =
|
|
60
|
+
if (res && res.result && typeof res.result.sequencePath === 'string') {
|
|
61
|
+
sequencePath = res.result.sequencePath;
|
|
40
62
|
} else if (typeof args.path === 'string' && args.path.trim().length > 0) {
|
|
41
63
|
const basePath = args.path.trim().replace(/\/$/, '');
|
|
42
64
|
sequencePath = `${basePath}/${name}`;
|
|
43
65
|
}
|
|
44
|
-
if (sequencePath && res &&
|
|
66
|
+
if (sequencePath && res && res.success !== false) {
|
|
45
67
|
markSequenceCreated(sequencePath);
|
|
46
68
|
}
|
|
47
69
|
|
|
48
|
-
const errorCode =
|
|
49
|
-
const msgLower =
|
|
50
|
-
if (res &&
|
|
70
|
+
const errorCode = getErrorString(res).toUpperCase();
|
|
71
|
+
const msgLower = getMessageString(res).toLowerCase();
|
|
72
|
+
if (res && res.success === false && (errorCode === 'FACTORY_NOT_AVAILABLE' || msgLower.includes('ulevelsequencefactorynew not available'))) {
|
|
51
73
|
const path = sequencePath || (typeof args.path === 'string' ? args.path : undefined);
|
|
52
74
|
return cleanObject({
|
|
53
75
|
success: false,
|
|
54
76
|
error: 'FACTORY_NOT_AVAILABLE',
|
|
55
|
-
message:
|
|
77
|
+
message: res.message || 'Sequence creation failed: factory not available',
|
|
56
78
|
action: 'create',
|
|
57
79
|
name,
|
|
58
80
|
path,
|
|
@@ -69,7 +91,7 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
69
91
|
return cleanObject(res);
|
|
70
92
|
}
|
|
71
93
|
case 'add_camera': {
|
|
72
|
-
const res = await tools.sequenceTools.addCamera({ spawnable: args.spawnable, path: args.path });
|
|
94
|
+
const res = await tools.sequenceTools.addCamera({ spawnable: args.spawnable as boolean | undefined, path: args.path as string | undefined });
|
|
73
95
|
return cleanObject(res);
|
|
74
96
|
}
|
|
75
97
|
case 'add_actor': {
|
|
@@ -82,18 +104,18 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
82
104
|
subAction: 'add_actor'
|
|
83
105
|
};
|
|
84
106
|
|
|
85
|
-
const res = await executeAutomationRequest(tools, 'manage_sequence', payload);
|
|
107
|
+
const res = await executeAutomationRequest(tools, 'manage_sequence', payload) as SequenceActionResponse;
|
|
86
108
|
|
|
87
|
-
const errorCode =
|
|
88
|
-
const msgLower =
|
|
109
|
+
const errorCode = getErrorString(res).toUpperCase();
|
|
110
|
+
const msgLower = getMessageString(res).toLowerCase();
|
|
89
111
|
|
|
90
|
-
if (res &&
|
|
112
|
+
if (res && res.success === false && path) {
|
|
91
113
|
const isInvalidSequence = errorCode === 'INVALID_SEQUENCE' || msgLower.includes('sequence_add_actor requires a sequence path') || msgLower.includes('sequence not found');
|
|
92
114
|
if (isInvalidSequence) {
|
|
93
115
|
return cleanObject({
|
|
94
116
|
success: false,
|
|
95
117
|
error: 'NOT_FOUND',
|
|
96
|
-
message:
|
|
118
|
+
message: res.message || 'Sequence not found',
|
|
97
119
|
action: 'add_actor',
|
|
98
120
|
path,
|
|
99
121
|
actorName
|
|
@@ -101,8 +123,8 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
101
123
|
}
|
|
102
124
|
}
|
|
103
125
|
|
|
104
|
-
const results = res &&
|
|
105
|
-
?
|
|
126
|
+
const results = res && res.result && Array.isArray(res.result.results)
|
|
127
|
+
? res.result.results
|
|
106
128
|
: undefined;
|
|
107
129
|
if (results && results.length) {
|
|
108
130
|
const failed = results.find((item) => item && item.success === false && typeof item.error === 'string');
|
|
@@ -124,24 +146,24 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
124
146
|
return cleanObject(res);
|
|
125
147
|
}
|
|
126
148
|
case 'add_actors': {
|
|
127
|
-
const actorNames: string[] = Array.isArray(args.actorNames) ? args.actorNames : [];
|
|
128
|
-
const res = await tools.sequenceTools.addActors({ actorNames, path: args.path });
|
|
129
|
-
const errorCode =
|
|
130
|
-
const msgLower =
|
|
131
|
-
if (actorNames.length === 0 && res &&
|
|
149
|
+
const actorNames: string[] = Array.isArray(args.actorNames) ? args.actorNames as string[] : [];
|
|
150
|
+
const res = await tools.sequenceTools.addActors({ actorNames, path: args.path as string | undefined }) as SequenceActionResponse;
|
|
151
|
+
const errorCode = getErrorString(res).toUpperCase();
|
|
152
|
+
const msgLower = getMessageString(res).toLowerCase();
|
|
153
|
+
if (actorNames.length === 0 && res && res.success === false && errorCode === 'INVALID_ARGUMENT') {
|
|
132
154
|
return cleanObject({
|
|
133
155
|
success: false,
|
|
134
156
|
error: 'INVALID_ARGUMENT',
|
|
135
|
-
message:
|
|
157
|
+
message: res.message || 'Invalid argument: actorNames required',
|
|
136
158
|
action: 'add_actors',
|
|
137
159
|
actorNames
|
|
138
160
|
});
|
|
139
161
|
}
|
|
140
|
-
if (res &&
|
|
162
|
+
if (res && res.success === false && msgLower.includes('actor not found')) {
|
|
141
163
|
return cleanObject({
|
|
142
164
|
success: false,
|
|
143
165
|
error: 'NOT_FOUND',
|
|
144
|
-
message:
|
|
166
|
+
message: res.message || 'Actor not found',
|
|
145
167
|
action: 'add_actors',
|
|
146
168
|
actorNames
|
|
147
169
|
});
|
|
@@ -149,8 +171,8 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
149
171
|
return cleanObject(res);
|
|
150
172
|
}
|
|
151
173
|
case 'remove_actors': {
|
|
152
|
-
const actorNames: string[] = Array.isArray(args.actorNames) ? args.actorNames : [];
|
|
153
|
-
const res = await tools.sequenceTools.removeActors({ actorNames, path: args.path });
|
|
174
|
+
const actorNames: string[] = Array.isArray(args.actorNames) ? args.actorNames as string[] : [];
|
|
175
|
+
const res = await tools.sequenceTools.removeActors({ actorNames, path: args.path as string | undefined });
|
|
154
176
|
return cleanObject(res);
|
|
155
177
|
}
|
|
156
178
|
case 'get_bindings': {
|
|
@@ -164,7 +186,7 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
164
186
|
const property = typeof args.property === 'string' ? args.property : undefined;
|
|
165
187
|
const frame = typeof args.frame === 'number' ? args.frame : Number(args.frame);
|
|
166
188
|
|
|
167
|
-
const payload = {
|
|
189
|
+
const payload: Record<string, unknown> = {
|
|
168
190
|
...args,
|
|
169
191
|
path: path || args.path,
|
|
170
192
|
actorName,
|
|
@@ -185,16 +207,16 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
185
207
|
payload.value = { scale: args.value };
|
|
186
208
|
}
|
|
187
209
|
|
|
188
|
-
const res = await executeAutomationRequest(tools, 'manage_sequence', payload);
|
|
189
|
-
const errorCode =
|
|
190
|
-
const msgLower =
|
|
210
|
+
const res = await executeAutomationRequest(tools, 'manage_sequence', payload) as SequenceActionResponse;
|
|
211
|
+
const errorCode = getErrorString(res).toUpperCase();
|
|
212
|
+
const msgLower = getMessageString(res).toLowerCase();
|
|
191
213
|
|
|
192
214
|
// Keep explicit INVALID_ARGUMENT for missing frame as a real error
|
|
193
215
|
if (errorCode === 'INVALID_ARGUMENT' || msgLower.includes('frame number is required')) {
|
|
194
216
|
return cleanObject(res);
|
|
195
217
|
}
|
|
196
218
|
|
|
197
|
-
if (res &&
|
|
219
|
+
if (res && res.success === false) {
|
|
198
220
|
const isBindingIssue = errorCode === 'BINDING_NOT_FOUND' || msgLower.includes('binding not found');
|
|
199
221
|
const isUnsupported = errorCode === 'UNSUPPORTED_PROPERTY' || msgLower.includes('unsupported property') || msgLower.includes('invalid_sequence_type');
|
|
200
222
|
const isInvalidSeq = errorCode === 'INVALID_SEQUENCE' || msgLower.includes('sequence not found') || msgLower.includes('requires a sequence path');
|
|
@@ -203,7 +225,7 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
203
225
|
return cleanObject({
|
|
204
226
|
success: false,
|
|
205
227
|
error: 'NOT_FOUND',
|
|
206
|
-
message:
|
|
228
|
+
message: res.message || 'Sequence not found',
|
|
207
229
|
action: 'add_keyframe',
|
|
208
230
|
path,
|
|
209
231
|
actorName,
|
|
@@ -222,28 +244,28 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
222
244
|
}
|
|
223
245
|
case 'add_spawnable_from_class': {
|
|
224
246
|
const className = requireNonEmptyString(args.className, 'className', 'Missing required parameter: className');
|
|
225
|
-
const res = await tools.sequenceTools.addSpawnableFromClass({ className, path: args.path });
|
|
247
|
+
const res = await tools.sequenceTools.addSpawnableFromClass({ className, path: args.path as string | undefined });
|
|
226
248
|
return cleanObject(res);
|
|
227
249
|
}
|
|
228
250
|
case 'play': {
|
|
229
|
-
const res = await tools.sequenceTools.play({ path: args.path, startTime: args.startTime, loopMode: args.loopMode });
|
|
251
|
+
const res = await tools.sequenceTools.play({ path: args.path as string | undefined, startTime: args.startTime as number | undefined, loopMode: args.loopMode as 'once' | 'loop' | 'pingpong' | undefined });
|
|
230
252
|
return cleanObject(res);
|
|
231
253
|
}
|
|
232
254
|
case 'pause': {
|
|
233
|
-
const res = await tools.sequenceTools.pause({ path: args.path });
|
|
255
|
+
const res = await tools.sequenceTools.pause({ path: args.path as string | undefined });
|
|
234
256
|
return cleanObject(res);
|
|
235
257
|
}
|
|
236
258
|
case 'stop': {
|
|
237
|
-
const res = await tools.sequenceTools.stop({ path: args.path });
|
|
259
|
+
const res = await tools.sequenceTools.stop({ path: args.path as string | undefined });
|
|
238
260
|
return cleanObject(res);
|
|
239
261
|
}
|
|
240
262
|
case 'set_properties': {
|
|
241
263
|
const res = await tools.sequenceTools.setSequenceProperties({
|
|
242
|
-
path: args.path,
|
|
243
|
-
frameRate: args.frameRate,
|
|
244
|
-
lengthInFrames: args.lengthInFrames,
|
|
245
|
-
playbackStart: args.playbackStart,
|
|
246
|
-
playbackEnd: args.playbackEnd
|
|
264
|
+
path: args.path as string | undefined,
|
|
265
|
+
frameRate: args.frameRate as number | undefined,
|
|
266
|
+
lengthInFrames: args.lengthInFrames as number | undefined,
|
|
267
|
+
playbackStart: args.playbackStart as number | undefined,
|
|
268
|
+
playbackEnd: args.playbackEnd as number | undefined
|
|
247
269
|
});
|
|
248
270
|
return cleanObject(res);
|
|
249
271
|
}
|
|
@@ -258,31 +280,32 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
258
280
|
throw new Error('Invalid speed: must be a positive number');
|
|
259
281
|
}
|
|
260
282
|
// Try setting speed
|
|
261
|
-
let res = await tools.sequenceTools.setPlaybackSpeed({ speed, path: args.path });
|
|
283
|
+
let res = await tools.sequenceTools.setPlaybackSpeed({ speed, path: args.path as string | undefined }) as SequenceActionResponse;
|
|
262
284
|
|
|
263
285
|
// Fix: Auto-open if editor not open
|
|
264
|
-
const errorCode =
|
|
286
|
+
const errorCode = getErrorString(res).toUpperCase();
|
|
265
287
|
if ((!res || res.success === false) && errorCode === 'EDITOR_NOT_OPEN' && args.path) {
|
|
266
288
|
// Attempt to open the sequence
|
|
267
|
-
await tools.sequenceTools.open({ path: args.path });
|
|
289
|
+
await tools.sequenceTools.open({ path: args.path as string });
|
|
268
290
|
|
|
269
291
|
// Wait a short moment for editor to initialize on game thread
|
|
270
292
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
271
293
|
|
|
272
294
|
// Retry
|
|
273
|
-
res = await tools.sequenceTools.setPlaybackSpeed({ speed, path: args.path });
|
|
295
|
+
res = await tools.sequenceTools.setPlaybackSpeed({ speed, path: args.path as string | undefined }) as SequenceActionResponse;
|
|
274
296
|
}
|
|
275
297
|
|
|
276
298
|
return cleanObject(res);
|
|
277
299
|
}
|
|
278
300
|
case 'list': {
|
|
279
|
-
const res = await tools.sequenceTools.list({ path: args.path });
|
|
301
|
+
const res = await tools.sequenceTools.list({ path: args.path as string | undefined });
|
|
280
302
|
return cleanObject(res);
|
|
281
303
|
}
|
|
282
304
|
case 'duplicate': {
|
|
283
305
|
const path = requireNonEmptyString(args.path, 'path', 'Missing required parameter: path');
|
|
284
306
|
const destDir = requireNonEmptyString(args.destinationPath, 'destinationPath', 'Missing required parameter: destinationPath');
|
|
285
|
-
const
|
|
307
|
+
const defaultNewName = path.split('/').pop() || '';
|
|
308
|
+
const newName = requireNonEmptyString(args.newName || defaultNewName, 'newName', 'Missing required parameter: newName');
|
|
286
309
|
const baseDir = destDir.replace(/\/$/, '');
|
|
287
310
|
const destPath = `${baseDir}/${newName}`;
|
|
288
311
|
const res = await tools.sequenceTools.duplicate({ path, destinationPath: destPath });
|
|
@@ -291,15 +314,15 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
291
314
|
case 'rename': {
|
|
292
315
|
const path = requireNonEmptyString(args.path, 'path', 'Missing required parameter: path');
|
|
293
316
|
const newName = requireNonEmptyString(args.newName, 'newName', 'Missing required parameter: newName');
|
|
294
|
-
const res = await tools.sequenceTools.rename({ path, newName });
|
|
295
|
-
const errorCode =
|
|
296
|
-
const msgLower =
|
|
297
|
-
if (res &&
|
|
317
|
+
const res = await tools.sequenceTools.rename({ path, newName }) as SequenceActionResponse;
|
|
318
|
+
const errorCode = getErrorString(res).toUpperCase();
|
|
319
|
+
const msgLower = getMessageString(res).toLowerCase();
|
|
320
|
+
if (res && res.success === false && (errorCode === 'OPERATION_FAILED' || msgLower.includes('failed to rename sequence'))) {
|
|
298
321
|
// Return actual failure, not best-effort success - rename is a destructive operation
|
|
299
322
|
return cleanObject({
|
|
300
323
|
success: false,
|
|
301
324
|
error: 'OPERATION_FAILED',
|
|
302
|
-
message:
|
|
325
|
+
message: res.message || 'Failed to rename sequence',
|
|
303
326
|
action: 'rename',
|
|
304
327
|
path,
|
|
305
328
|
newName
|
|
@@ -309,20 +332,20 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
309
332
|
}
|
|
310
333
|
case 'delete': {
|
|
311
334
|
const path = requireNonEmptyString(args.path, 'path', 'Missing required parameter: path');
|
|
312
|
-
const res = await tools.sequenceTools.deleteSequence({ path });
|
|
335
|
+
const res = await tools.sequenceTools.deleteSequence({ path }) as SequenceActionResponse;
|
|
313
336
|
|
|
314
|
-
if (res &&
|
|
337
|
+
if (res && res.success !== false) {
|
|
315
338
|
markSequenceDeleted(path);
|
|
316
339
|
}
|
|
317
340
|
return cleanObject(res);
|
|
318
341
|
}
|
|
319
342
|
case 'get_metadata': {
|
|
320
|
-
const res = await tools.sequenceTools.getMetadata({ path: args.path });
|
|
343
|
+
const res = await tools.sequenceTools.getMetadata({ path: args.path as string });
|
|
321
344
|
return cleanObject(res);
|
|
322
345
|
}
|
|
323
346
|
case 'set_metadata': {
|
|
324
347
|
const path = requireNonEmptyString(args.path, 'path', 'Missing required parameter: path');
|
|
325
|
-
const metadata = (args.metadata && typeof args.metadata === 'object') ? args.metadata : {};
|
|
348
|
+
const metadata = (args.metadata && typeof args.metadata === 'object') ? args.metadata as Record<string, unknown> : {};
|
|
326
349
|
const res = await executeAutomationRequest(tools, 'set_metadata', { assetPath: path, metadata });
|
|
327
350
|
return cleanObject(res);
|
|
328
351
|
}
|
|
@@ -335,10 +358,10 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
335
358
|
|
|
336
359
|
// Fix: Check if actor is bound before adding track
|
|
337
360
|
if (actorName) {
|
|
338
|
-
const bindingsRes = await tools.sequenceTools.getBindings({ path });
|
|
361
|
+
const bindingsRes = await tools.sequenceTools.getBindings({ path }) as SequenceActionResponse;
|
|
339
362
|
if (bindingsRes && bindingsRes.success) {
|
|
340
363
|
const bindings = bindingsRes.bindings || [];
|
|
341
|
-
const isBound = bindings.some((b
|
|
364
|
+
const isBound = bindings.some((b) => b.name === actorName);
|
|
342
365
|
if (!isBound) {
|
|
343
366
|
return cleanObject({
|
|
344
367
|
success: false,
|
|
@@ -399,7 +422,7 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
399
422
|
if (!Number.isFinite(end)) throw new Error('Invalid end: must be a number');
|
|
400
423
|
|
|
401
424
|
const res = await tools.sequenceTools.setWorkRange({
|
|
402
|
-
path: args.path,
|
|
425
|
+
path: args.path as string | undefined,
|
|
403
426
|
start,
|
|
404
427
|
end
|
|
405
428
|
});
|
|
@@ -35,7 +35,7 @@ export interface PropertyInfo {
|
|
|
35
35
|
type: string;
|
|
36
36
|
value?: any;
|
|
37
37
|
flags?: string[];
|
|
38
|
-
metadata?: Record<string,
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
39
|
category?: string;
|
|
40
40
|
tooltip?: string;
|
|
41
41
|
description?: string;
|
|
@@ -141,7 +141,7 @@ export class IntrospectionTools {
|
|
|
141
141
|
return value;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
private isPlainObject(value: any): value is Record<string,
|
|
144
|
+
private isPlainObject(value: any): value is Record<string, unknown> {
|
|
145
145
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
146
146
|
}
|
|
147
147
|
|
|
@@ -200,7 +200,7 @@ export class IntrospectionTools {
|
|
|
200
200
|
const rawValue = entry.value ?? entry.currentValue ?? entry.defaultValue ?? entry.data ?? entry;
|
|
201
201
|
const value = this.convertPropertyValue(rawValue, type);
|
|
202
202
|
const flags: string[] | undefined = entry.flags ?? entry.attributes;
|
|
203
|
-
const metadata: Record<string,
|
|
203
|
+
const metadata: Record<string, unknown> | undefined = entry.metadata ?? entry.annotations;
|
|
204
204
|
const filtered = this.shouldFilterProperty(name, value, flags, detailed);
|
|
205
205
|
const dictionaryEntry = lookupPropertyMetadata(name);
|
|
206
206
|
const propertyInfo: PropertyInfo = {
|
|
@@ -220,12 +220,12 @@ export class IntrospectionTools {
|
|
|
220
220
|
return propertyInfo;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
private flattenPropertyMap(source: Record<string,
|
|
223
|
+
private flattenPropertyMap(source: Record<string, unknown>, prefix = '', detailed = false): PropertyInfo[] {
|
|
224
224
|
const properties: PropertyInfo[] = [];
|
|
225
225
|
for (const [rawKey, rawValue] of Object.entries(source)) {
|
|
226
226
|
const name = prefix ? `${prefix}.${rawKey}` : rawKey;
|
|
227
227
|
if (this.isLikelyPropertyDescriptor(rawValue)) {
|
|
228
|
-
const normalized = this.normalizePropertyEntry({ ...rawValue, name }, detailed);
|
|
228
|
+
const normalized = this.normalizePropertyEntry({ ...(rawValue as Record<string, unknown>), name }, detailed);
|
|
229
229
|
if (normalized) properties.push(normalized);
|
|
230
230
|
continue;
|
|
231
231
|
}
|
|
@@ -441,8 +441,8 @@ export class IntrospectionTools {
|
|
|
441
441
|
}
|
|
442
442
|
|
|
443
443
|
return res;
|
|
444
|
-
} catch (err:
|
|
445
|
-
const errorMsg = err
|
|
444
|
+
} catch (err: unknown) {
|
|
445
|
+
const errorMsg = (err instanceof Error ? err.message : undefined) || String(err);
|
|
446
446
|
if (errorMsg.includes('404')) {
|
|
447
447
|
return { success: false, error: `Property '${params.propertyName}' not found on object '${params.objectPath}'` };
|
|
448
448
|
}
|
package/src/tools/landscape.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
3
3
|
import { AutomationBridge } from '../automation/index.js';
|
|
4
4
|
import { ensureVector3 } from '../utils/validation.js';
|
|
5
|
+
import { wasmIntegration } from '../wasm/index.js';
|
|
6
|
+
import { ILandscapeTools, StandardActionResponse } from '../types/tool-interfaces.js';
|
|
5
7
|
|
|
6
|
-
export class LandscapeTools {
|
|
8
|
+
export class LandscapeTools implements ILandscapeTools {
|
|
7
9
|
constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
|
|
8
10
|
|
|
9
11
|
setAutomationBridge(automationBridge?: AutomationBridge) { this.automationBridge = automationBridge; }
|
|
@@ -23,7 +25,7 @@ export class LandscapeTools {
|
|
|
23
25
|
runtimeGrid?: string;
|
|
24
26
|
isSpatiallyLoaded?: boolean;
|
|
25
27
|
dataLayers?: string[];
|
|
26
|
-
}) {
|
|
28
|
+
}): Promise<StandardActionResponse> {
|
|
27
29
|
const name = params.name?.trim();
|
|
28
30
|
if (!name) {
|
|
29
31
|
return { success: false, error: 'Landscape name is required' };
|
|
@@ -46,6 +48,10 @@ export class LandscapeTools {
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
const [locX, locY, locZ] = ensureVector3(params.location ?? [0, 0, 0], 'landscape location');
|
|
51
|
+
// Use WASM vectorAdd for landscape location processing
|
|
52
|
+
const zeroVector: [number, number, number] = [0, 0, 0];
|
|
53
|
+
const processedLocation = wasmIntegration.vectorAdd(zeroVector, [locX, locY, locZ]);
|
|
54
|
+
console.error('[WASM] Using vectorAdd for landscape positioning');
|
|
49
55
|
const sectionsPerComponent = Math.max(1, Math.floor(params.sectionsPerComponent ?? 1));
|
|
50
56
|
const quadsPerSection = Math.max(1, Math.floor(params.quadsPerSection ?? 63));
|
|
51
57
|
|
|
@@ -57,9 +63,9 @@ export class LandscapeTools {
|
|
|
57
63
|
|
|
58
64
|
const payload: Record<string, unknown> = {
|
|
59
65
|
name,
|
|
60
|
-
x:
|
|
61
|
-
y:
|
|
62
|
-
z:
|
|
66
|
+
x: processedLocation[0],
|
|
67
|
+
y: processedLocation[1],
|
|
68
|
+
z: processedLocation[2],
|
|
63
69
|
componentsX,
|
|
64
70
|
componentsY,
|
|
65
71
|
quadsPerComponent,
|
|
@@ -101,7 +107,7 @@ export class LandscapeTools {
|
|
|
101
107
|
result.spatiallyLoaded = params.isSpatiallyLoaded;
|
|
102
108
|
}
|
|
103
109
|
|
|
104
|
-
return result;
|
|
110
|
+
return result as StandardActionResponse;
|
|
105
111
|
} catch (error) {
|
|
106
112
|
return {
|
|
107
113
|
success: false,
|
|
@@ -120,7 +126,7 @@ export class LandscapeTools {
|
|
|
120
126
|
strength?: number;
|
|
121
127
|
location?: [number, number, number];
|
|
122
128
|
radius?: number;
|
|
123
|
-
}) {
|
|
129
|
+
}): Promise<StandardActionResponse> {
|
|
124
130
|
const [x, y, z] = ensureVector3(params.location ?? [0, 0, 0], 'sculpt location');
|
|
125
131
|
|
|
126
132
|
const tool = (params.tool || '').trim();
|
|
@@ -161,7 +167,7 @@ export class LandscapeTools {
|
|
|
161
167
|
success: true,
|
|
162
168
|
message: `Sculpting applied to ${params.landscapeName}`,
|
|
163
169
|
details: response
|
|
164
|
-
};
|
|
170
|
+
} as StandardActionResponse;
|
|
165
171
|
}
|
|
166
172
|
|
|
167
173
|
// Paint landscape
|
|
@@ -174,7 +180,7 @@ export class LandscapeTools {
|
|
|
174
180
|
targetValue?: number;
|
|
175
181
|
radius?: number;
|
|
176
182
|
density?: number;
|
|
177
|
-
}) {
|
|
183
|
+
}): Promise<StandardActionResponse> {
|
|
178
184
|
if (!this.automationBridge) {
|
|
179
185
|
throw new Error('Automation Bridge not available.');
|
|
180
186
|
}
|
|
@@ -208,7 +214,7 @@ export class LandscapeTools {
|
|
|
208
214
|
success: true,
|
|
209
215
|
message: `Painted layer ${params.layerName}`,
|
|
210
216
|
details: response
|
|
211
|
-
};
|
|
217
|
+
} as StandardActionResponse;
|
|
212
218
|
}
|
|
213
219
|
|
|
214
220
|
// Create procedural terrain using ProceduralMeshComponent
|
|
@@ -221,7 +227,7 @@ export class LandscapeTools {
|
|
|
221
227
|
heightFunction?: string; // Expression for height calculation
|
|
222
228
|
material?: string;
|
|
223
229
|
settings?: Record<string, unknown>;
|
|
224
|
-
}) {
|
|
230
|
+
}): Promise<StandardActionResponse> {
|
|
225
231
|
if (!this.automationBridge) {
|
|
226
232
|
throw new Error('Automation Bridge not available. Procedural terrain creation requires plugin support.');
|
|
227
233
|
}
|
|
@@ -261,7 +267,7 @@ export class LandscapeTools {
|
|
|
261
267
|
size: result?.size,
|
|
262
268
|
subdivisions: result?.subdivisions,
|
|
263
269
|
details: result
|
|
264
|
-
};
|
|
270
|
+
} as StandardActionResponse;
|
|
265
271
|
} catch (error) {
|
|
266
272
|
return {
|
|
267
273
|
success: false,
|
|
@@ -279,7 +285,7 @@ export class LandscapeTools {
|
|
|
279
285
|
maxScale?: number;
|
|
280
286
|
path?: string; // Legacy support
|
|
281
287
|
staticMesh?: string; // Legacy support
|
|
282
|
-
}): Promise<
|
|
288
|
+
}): Promise<StandardActionResponse> {
|
|
283
289
|
if (!this.automationBridge) {
|
|
284
290
|
throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
|
|
285
291
|
}
|
|
@@ -323,7 +329,7 @@ export class LandscapeTools {
|
|
|
323
329
|
success: true,
|
|
324
330
|
message: response?.message || `Landscape grass type '${name}' created`,
|
|
325
331
|
assetPath: result?.asset_path || response?.assetPath || response?.asset_path
|
|
326
|
-
};
|
|
332
|
+
} as StandardActionResponse;
|
|
327
333
|
} catch (error) {
|
|
328
334
|
return {
|
|
329
335
|
success: false,
|
|
@@ -333,7 +339,7 @@ export class LandscapeTools {
|
|
|
333
339
|
}
|
|
334
340
|
|
|
335
341
|
// Set the material used by an existing landscape actor
|
|
336
|
-
async setLandscapeMaterial(params: { landscapeName: string; materialPath: string }): Promise<
|
|
342
|
+
async setLandscapeMaterial(params: { landscapeName: string; materialPath: string }): Promise<StandardActionResponse> {
|
|
337
343
|
const landscapeName = typeof params.landscapeName === 'string' ? params.landscapeName.trim() : '';
|
|
338
344
|
const materialPath = typeof params.materialPath === 'string' ? params.materialPath.trim() : '';
|
|
339
345
|
|
|
@@ -366,7 +372,7 @@ export class LandscapeTools {
|
|
|
366
372
|
message: response?.message || `Landscape material set on '${landscapeName}'`,
|
|
367
373
|
landscapeName: response?.landscapeName || landscapeName,
|
|
368
374
|
materialPath: response?.materialPath || materialPath
|
|
369
|
-
};
|
|
375
|
+
} as StandardActionResponse;
|
|
370
376
|
} catch (error) {
|
|
371
377
|
return {
|
|
372
378
|
success: false,
|
|
@@ -383,7 +389,7 @@ export class LandscapeTools {
|
|
|
383
389
|
minScale?: number;
|
|
384
390
|
maxScale?: number;
|
|
385
391
|
randomRotation?: boolean;
|
|
386
|
-
}) {
|
|
392
|
+
}): Promise<StandardActionResponse> {
|
|
387
393
|
const commands: string[] = [];
|
|
388
394
|
|
|
389
395
|
commands.push(`CreateLandscapeGrass ${params.landscapeName} ${params.grassType}`);
|
|
@@ -410,7 +416,7 @@ export class LandscapeTools {
|
|
|
410
416
|
landscapeName: string;
|
|
411
417
|
collisionMipLevel?: number;
|
|
412
418
|
simpleCollision?: boolean;
|
|
413
|
-
}) {
|
|
419
|
+
}): Promise<StandardActionResponse> {
|
|
414
420
|
const commands: string[] = [];
|
|
415
421
|
|
|
416
422
|
if (params.collisionMipLevel !== undefined) {
|
|
@@ -433,7 +439,7 @@ export class LandscapeTools {
|
|
|
433
439
|
landscapeName: string;
|
|
434
440
|
targetTriangleCount?: number;
|
|
435
441
|
preserveDetails?: boolean;
|
|
436
|
-
}) {
|
|
442
|
+
}): Promise<StandardActionResponse> {
|
|
437
443
|
const commands: string[] = [];
|
|
438
444
|
|
|
439
445
|
if (params.targetTriangleCount !== undefined) {
|
|
@@ -458,7 +464,7 @@ export class LandscapeTools {
|
|
|
458
464
|
location?: [number, number, number];
|
|
459
465
|
size?: [number, number];
|
|
460
466
|
depth?: number;
|
|
461
|
-
}) {
|
|
467
|
+
}): Promise<StandardActionResponse> {
|
|
462
468
|
const loc = params.location || [0, 0, 0];
|
|
463
469
|
const size = params.size || [1000, 1000];
|
|
464
470
|
const depth = params.depth || 100;
|
|
@@ -475,7 +481,7 @@ export class LandscapeTools {
|
|
|
475
481
|
runtimeGrid?: string;
|
|
476
482
|
dataLayers?: string[];
|
|
477
483
|
streamingDistance?: number;
|
|
478
|
-
}) {
|
|
484
|
+
}): Promise<StandardActionResponse> {
|
|
479
485
|
if (!this.automationBridge) {
|
|
480
486
|
throw new Error('Automation Bridge not available. World Partition operations require plugin support.');
|
|
481
487
|
}
|
|
@@ -502,7 +508,7 @@ export class LandscapeTools {
|
|
|
502
508
|
success: true,
|
|
503
509
|
message: response.message || 'World Partition configured',
|
|
504
510
|
changes: response.changes
|
|
505
|
-
};
|
|
511
|
+
} as StandardActionResponse;
|
|
506
512
|
} catch (err) {
|
|
507
513
|
return { success: false, error: `Failed to configure World Partition: ${err instanceof Error ? err.message : String(err)}` };
|
|
508
514
|
}
|
|
@@ -513,7 +519,7 @@ export class LandscapeTools {
|
|
|
513
519
|
landscapeName: string;
|
|
514
520
|
dataLayerNames: string[];
|
|
515
521
|
operation: 'add' | 'remove' | 'set';
|
|
516
|
-
}) {
|
|
522
|
+
}): Promise<StandardActionResponse> {
|
|
517
523
|
try {
|
|
518
524
|
const commands = [];
|
|
519
525
|
|
|
@@ -535,7 +541,7 @@ export class LandscapeTools {
|
|
|
535
541
|
success: true,
|
|
536
542
|
message: `Data layers ${params.operation === 'add' ? 'added' : params.operation === 'remove' ? 'removed' : 'set'} for landscape`,
|
|
537
543
|
layers: params.dataLayerNames
|
|
538
|
-
};
|
|
544
|
+
} as StandardActionResponse;
|
|
539
545
|
} catch (err) {
|
|
540
546
|
return { success: false, error: `Failed to manage data layers: ${err}` };
|
|
541
547
|
}
|
|
@@ -547,7 +553,7 @@ export class LandscapeTools {
|
|
|
547
553
|
cellSize?: number;
|
|
548
554
|
loadingRange?: number;
|
|
549
555
|
enableHLOD?: boolean;
|
|
550
|
-
}) {
|
|
556
|
+
}): Promise<StandardActionResponse> {
|
|
551
557
|
const commands = [];
|
|
552
558
|
|
|
553
559
|
// World Partition runtime commands
|
|
@@ -573,7 +579,7 @@ export class LandscapeTools {
|
|
|
573
579
|
loadingRange: params.loadingRange,
|
|
574
580
|
hlod: params.enableHLOD
|
|
575
581
|
}
|
|
576
|
-
};
|
|
582
|
+
} as StandardActionResponse;
|
|
577
583
|
} catch (err) {
|
|
578
584
|
return { success: false, error: `Failed to configure streaming cells: ${err}` };
|
|
579
585
|
}
|
|
@@ -588,7 +594,7 @@ export class LandscapeTools {
|
|
|
588
594
|
maxX: number;
|
|
589
595
|
maxY: number;
|
|
590
596
|
updateNormals?: boolean;
|
|
591
|
-
}) {
|
|
597
|
+
}): Promise<StandardActionResponse> {
|
|
592
598
|
if (!this.automationBridge) {
|
|
593
599
|
throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
|
|
594
600
|
}
|
|
@@ -634,7 +640,7 @@ export class LandscapeTools {
|
|
|
634
640
|
return {
|
|
635
641
|
success: true,
|
|
636
642
|
message: response.message || 'Heightmap modified successfully'
|
|
637
|
-
};
|
|
643
|
+
} as StandardActionResponse;
|
|
638
644
|
} catch (err) {
|
|
639
645
|
return { success: false, error: `Failed to modify heightmap: ${err instanceof Error ? err.message : String(err)}` };
|
|
640
646
|
}
|