unreal-engine-mcp-server 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.production +6 -1
- package/Dockerfile +11 -28
- package/README.md +1 -2
- package/dist/index.js +120 -54
- package/dist/resources/actors.js +71 -13
- package/dist/resources/assets.d.ts +3 -2
- package/dist/resources/assets.js +96 -72
- package/dist/resources/levels.js +2 -2
- package/dist/tools/assets.js +6 -2
- package/dist/tools/build_environment_advanced.js +46 -42
- package/dist/tools/consolidated-tool-definitions.d.ts +232 -15
- package/dist/tools/consolidated-tool-definitions.js +173 -8
- package/dist/tools/consolidated-tool-handlers.js +331 -718
- package/dist/tools/debug.js +4 -6
- package/dist/tools/rc.js +2 -2
- package/dist/tools/sequence.js +21 -2
- package/dist/unreal-bridge.d.ts +4 -1
- package/dist/unreal-bridge.js +211 -53
- package/dist/utils/http.js +4 -2
- package/dist/utils/response-validator.d.ts +6 -1
- package/dist/utils/response-validator.js +43 -15
- package/package.json +5 -5
- package/server.json +2 -2
- package/src/index.ts +120 -56
- package/src/resources/actors.ts +51 -13
- package/src/resources/assets.ts +97 -73
- package/src/resources/levels.ts +2 -2
- package/src/tools/assets.ts +6 -2
- package/src/tools/build_environment_advanced.ts +46 -42
- package/src/tools/consolidated-tool-definitions.ts +173 -8
- package/src/tools/consolidated-tool-handlers.ts +318 -747
- package/src/tools/debug.ts +4 -6
- package/src/tools/rc.ts +2 -2
- package/src/tools/sequence.ts +21 -2
- package/src/unreal-bridge.ts +163 -60
- package/src/utils/http.ts +7 -4
- package/src/utils/response-validator.ts +48 -19
- package/dist/tools/tool-definitions.d.ts +0 -4919
- package/dist/tools/tool-definitions.js +0 -1007
- package/dist/tools/tool-handlers.d.ts +0 -47
- package/dist/tools/tool-handlers.js +0 -863
- package/src/tools/tool-definitions.ts +0 -1023
- package/src/tools/tool-handlers.ts +0 -973
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Consolidated tool handlers - maps 13 tools to all 36 operations
|
|
2
|
-
import { handleToolCall } from './tool-handlers.js';
|
|
3
2
|
import { cleanObject } from '../utils/safe-json.js';
|
|
3
|
+
import { Logger } from '../utils/logger.js';
|
|
4
|
+
|
|
5
|
+
const log = new Logger('ConsolidatedToolHandler');
|
|
4
6
|
|
|
5
7
|
export async function handleConsolidatedToolCall(
|
|
6
8
|
name: string,
|
|
@@ -8,7 +10,8 @@ export async function handleConsolidatedToolCall(
|
|
|
8
10
|
tools: any
|
|
9
11
|
) {
|
|
10
12
|
const startTime = Date.now();
|
|
11
|
-
|
|
13
|
+
// Use scoped logger (stderr) to avoid polluting stdout JSON
|
|
14
|
+
log.debug(`Starting execution of ${name} at ${new Date().toISOString()}`);
|
|
12
15
|
|
|
13
16
|
try {
|
|
14
17
|
// Validate args is not null/undefined
|
|
@@ -16,8 +19,6 @@ export async function handleConsolidatedToolCall(
|
|
|
16
19
|
throw new Error('Invalid arguments: null or undefined');
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
let mappedName: string;
|
|
20
|
-
let mappedArgs: any = { ...args };
|
|
21
22
|
|
|
22
23
|
switch (name) {
|
|
23
24
|
// 1. ASSET MANAGER
|
|
@@ -32,81 +33,34 @@ export async function handleConsolidatedToolCall(
|
|
|
32
33
|
throw new Error('Missing required parameter: action');
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
case 'list':
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (typeof args.directory !== 'string') {
|
|
40
|
-
throw new Error('Invalid directory: must be a string');
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
mappedName = 'list_assets';
|
|
45
|
-
mappedArgs = {
|
|
46
|
-
directory: args.directory
|
|
47
|
-
// recursive removed - always false internally
|
|
48
|
-
};
|
|
49
|
-
break;
|
|
50
|
-
|
|
51
|
-
case 'import':
|
|
52
|
-
// Validate required parameters
|
|
53
|
-
if (args.sourcePath === undefined || args.sourcePath === null) {
|
|
54
|
-
throw new Error('Missing required parameter: sourcePath');
|
|
55
|
-
}
|
|
56
|
-
if (typeof args.sourcePath !== 'string') {
|
|
57
|
-
throw new Error('Invalid sourcePath: must be a string');
|
|
36
|
+
switch (args.action) {
|
|
37
|
+
case 'list': {
|
|
38
|
+
if (args.directory !== undefined && args.directory !== null && typeof args.directory !== 'string') {
|
|
39
|
+
throw new Error('Invalid directory: must be a string');
|
|
58
40
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (args.
|
|
64
|
-
throw new Error('
|
|
65
|
-
}
|
|
66
|
-
if (typeof args.destinationPath !== 'string') {
|
|
67
|
-
throw new Error('Invalid destinationPath: must be a string');
|
|
68
|
-
}
|
|
69
|
-
if (args.destinationPath.trim() === '') {
|
|
70
|
-
throw new Error('Invalid destinationPath: cannot be empty');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
mappedName = 'import_asset';
|
|
74
|
-
mappedArgs = {
|
|
75
|
-
sourcePath: args.sourcePath,
|
|
76
|
-
destinationPath: args.destinationPath
|
|
77
|
-
};
|
|
78
|
-
break;
|
|
79
|
-
|
|
80
|
-
case 'create_material':
|
|
81
|
-
// Validate required parameters
|
|
82
|
-
if (args.name === undefined || args.name === null) {
|
|
83
|
-
throw new Error('Missing required parameter: name');
|
|
84
|
-
}
|
|
85
|
-
if (typeof args.name !== 'string') {
|
|
86
|
-
throw new Error('Invalid name: must be a string');
|
|
41
|
+
const res = await tools.assetResources.list(args.directory || '/Game', false);
|
|
42
|
+
return cleanObject({ success: true, ...res });
|
|
43
|
+
}
|
|
44
|
+
case 'import': {
|
|
45
|
+
if (typeof args.sourcePath !== 'string' || args.sourcePath.trim() === '') {
|
|
46
|
+
throw new Error('Invalid sourcePath');
|
|
87
47
|
}
|
|
88
|
-
if (args.
|
|
89
|
-
throw new Error('Invalid
|
|
48
|
+
if (typeof args.destinationPath !== 'string' || args.destinationPath.trim() === '') {
|
|
49
|
+
throw new Error('Invalid destinationPath');
|
|
90
50
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
51
|
+
const res = await tools.assetTools.importAsset(args.sourcePath, args.destinationPath);
|
|
52
|
+
return cleanObject(res);
|
|
53
|
+
}
|
|
54
|
+
case 'create_material': {
|
|
55
|
+
if (typeof args.name !== 'string' || args.name.trim() === '') {
|
|
56
|
+
throw new Error('Invalid name: must be a non-empty string');
|
|
97
57
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
name: args.name,
|
|
102
|
-
path: args.path
|
|
103
|
-
};
|
|
104
|
-
break;
|
|
105
|
-
|
|
58
|
+
const res = await tools.materialTools.createMaterial(args.name, args.path || '/Game/Materials');
|
|
59
|
+
return cleanObject(res);
|
|
60
|
+
}
|
|
106
61
|
default:
|
|
107
62
|
throw new Error(`Unknown asset action: ${args.action}`);
|
|
108
63
|
}
|
|
109
|
-
break;
|
|
110
64
|
|
|
111
65
|
// 2. ACTOR CONTROL
|
|
112
66
|
case 'control_actor':
|
|
@@ -115,70 +69,42 @@ export async function handleConsolidatedToolCall(
|
|
|
115
69
|
throw new Error('Missing required parameter: action');
|
|
116
70
|
}
|
|
117
71
|
|
|
118
|
-
|
|
119
|
-
case 'spawn':
|
|
120
|
-
|
|
121
|
-
if (!args.classPath) {
|
|
122
|
-
throw new Error('Missing required parameter: classPath');
|
|
123
|
-
}
|
|
124
|
-
if (typeof args.classPath !== 'string' || args.classPath.trim() === '') {
|
|
72
|
+
switch (args.action) {
|
|
73
|
+
case 'spawn': {
|
|
74
|
+
if (!args.classPath || typeof args.classPath !== 'string' || args.classPath.trim() === '') {
|
|
125
75
|
throw new Error('Invalid classPath: must be a non-empty string');
|
|
126
76
|
}
|
|
127
|
-
|
|
128
|
-
mappedName = 'spawn_actor';
|
|
129
|
-
mappedArgs = {
|
|
77
|
+
const res = await tools.actorTools.spawn({
|
|
130
78
|
classPath: args.classPath,
|
|
131
79
|
location: args.location,
|
|
132
80
|
rotation: args.rotation
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
case 'delete':
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
throw new Error('Missing required parameter: actorName');
|
|
140
|
-
}
|
|
141
|
-
if (typeof args.actorName !== 'string' || args.actorName.trim() === '') {
|
|
142
|
-
throw new Error('Invalid actorName: must be a non-empty string');
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
mappedName = 'delete_actor';
|
|
146
|
-
mappedArgs = {
|
|
147
|
-
actorName: args.actorName
|
|
148
|
-
};
|
|
149
|
-
break;
|
|
150
|
-
|
|
151
|
-
case 'apply_force':
|
|
152
|
-
// Validate apply_force parameters
|
|
153
|
-
if (!args.actorName) {
|
|
154
|
-
throw new Error('Missing required parameter: actorName');
|
|
155
|
-
}
|
|
156
|
-
if (typeof args.actorName !== 'string' || args.actorName.trim() === '') {
|
|
157
|
-
throw new Error('Invalid actorName: must be a non-empty string');
|
|
158
|
-
}
|
|
159
|
-
if (!args.force) {
|
|
160
|
-
throw new Error('Missing required parameter: force');
|
|
81
|
+
});
|
|
82
|
+
return cleanObject(res);
|
|
83
|
+
}
|
|
84
|
+
case 'delete': {
|
|
85
|
+
if (!args.actorName || typeof args.actorName !== 'string' || args.actorName.trim() === '') {
|
|
86
|
+
throw new Error('Invalid actorName');
|
|
161
87
|
}
|
|
162
|
-
|
|
163
|
-
|
|
88
|
+
const res = await tools.bridge.executeEditorFunction('DELETE_ACTOR', { actor_name: args.actorName });
|
|
89
|
+
return cleanObject(res);
|
|
90
|
+
}
|
|
91
|
+
case 'apply_force': {
|
|
92
|
+
if (!args.actorName || typeof args.actorName !== 'string' || args.actorName.trim() === '') {
|
|
93
|
+
throw new Error('Invalid actorName');
|
|
164
94
|
}
|
|
165
|
-
if (typeof args.force.x !== 'number' ||
|
|
166
|
-
|
|
167
|
-
typeof args.force.z !== 'number') {
|
|
168
|
-
throw new Error('Invalid force: x, y, z must all be numbers');
|
|
95
|
+
if (!args.force || typeof args.force.x !== 'number' || typeof args.force.y !== 'number' || typeof args.force.z !== 'number') {
|
|
96
|
+
throw new Error('Invalid force: must have numeric x,y,z');
|
|
169
97
|
}
|
|
170
|
-
|
|
171
|
-
mappedName = 'apply_force';
|
|
172
|
-
mappedArgs = {
|
|
98
|
+
const res = await tools.physicsTools.applyForce({
|
|
173
99
|
actorName: args.actorName,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
100
|
+
forceType: 'Force',
|
|
101
|
+
vector: [args.force.x, args.force.y, args.force.z]
|
|
102
|
+
});
|
|
103
|
+
return cleanObject(res);
|
|
104
|
+
}
|
|
178
105
|
default:
|
|
179
106
|
throw new Error(`Unknown actor action: ${args.action}`);
|
|
180
107
|
}
|
|
181
|
-
break;
|
|
182
108
|
|
|
183
109
|
// 3. EDITOR CONTROL
|
|
184
110
|
case 'control_editor':
|
|
@@ -187,657 +113,317 @@ export async function handleConsolidatedToolCall(
|
|
|
187
113
|
throw new Error('Missing required parameter: action');
|
|
188
114
|
}
|
|
189
115
|
|
|
190
|
-
|
|
191
|
-
case 'play':
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
case 'stop':
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
case 'pause':
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
case 'set_game_speed':
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
throw new Error('Missing required parameter: speed');
|
|
207
|
-
}
|
|
208
|
-
if (typeof args.speed !== 'number') {
|
|
209
|
-
throw new Error('Invalid speed: must be a number');
|
|
210
|
-
}
|
|
211
|
-
if (isNaN(args.speed)) {
|
|
212
|
-
throw new Error('Invalid speed: cannot be NaN');
|
|
213
|
-
}
|
|
214
|
-
if (!isFinite(args.speed)) {
|
|
215
|
-
throw new Error('Invalid speed: must be finite');
|
|
216
|
-
}
|
|
217
|
-
if (args.speed <= 0) {
|
|
218
|
-
throw new Error('Invalid speed: must be positive');
|
|
219
|
-
}
|
|
220
|
-
mappedName = 'set_game_speed';
|
|
221
|
-
mappedArgs = {
|
|
222
|
-
speed: args.speed
|
|
223
|
-
};
|
|
224
|
-
break;
|
|
225
|
-
case 'eject':
|
|
226
|
-
mappedName = 'eject_from_pawn';
|
|
227
|
-
mappedArgs = {};
|
|
228
|
-
break;
|
|
229
|
-
case 'possess':
|
|
230
|
-
mappedName = 'possess_pawn';
|
|
231
|
-
mappedArgs = {};
|
|
232
|
-
break;
|
|
233
|
-
case 'set_camera':
|
|
234
|
-
// Allow either location or rotation or both
|
|
235
|
-
// Don't require both to be present
|
|
236
|
-
mappedName = 'set_camera';
|
|
237
|
-
mappedArgs = {
|
|
238
|
-
location: args.location,
|
|
239
|
-
rotation: args.rotation
|
|
240
|
-
};
|
|
241
|
-
break;
|
|
242
|
-
case 'set_view_mode':
|
|
243
|
-
// Validate view mode parameter
|
|
244
|
-
if (!args.viewMode) {
|
|
245
|
-
throw new Error('Missing required parameter: viewMode');
|
|
246
|
-
}
|
|
247
|
-
if (typeof args.viewMode !== 'string' || args.viewMode.trim() === '') {
|
|
248
|
-
throw new Error('Invalid viewMode: must be a non-empty string');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Normalize view mode to match what debug.ts expects
|
|
252
|
-
const validModes = ['lit', 'unlit', 'wireframe', 'detail_lighting', 'lighting_only',
|
|
253
|
-
'light_complexity', 'shader_complexity', 'lightmap_density',
|
|
254
|
-
'stationary_light_overlap', 'reflections', 'visualize_buffer',
|
|
255
|
-
'collision_pawn', 'collision_visibility', 'lod_coloration', 'quad_overdraw'];
|
|
256
|
-
const normalizedMode = args.viewMode.toLowerCase().replace(/_/g, '');
|
|
257
|
-
|
|
258
|
-
// Map to proper case for debug.ts
|
|
259
|
-
let mappedMode = '';
|
|
260
|
-
switch(normalizedMode) {
|
|
261
|
-
case 'lit': mappedMode = 'Lit'; break;
|
|
262
|
-
case 'unlit': mappedMode = 'Unlit'; break;
|
|
263
|
-
case 'wireframe': mappedMode = 'Wireframe'; break;
|
|
264
|
-
case 'detaillighting': mappedMode = 'DetailLighting'; break;
|
|
265
|
-
case 'lightingonly': mappedMode = 'LightingOnly'; break;
|
|
266
|
-
case 'lightcomplexity': mappedMode = 'LightComplexity'; break;
|
|
267
|
-
case 'shadercomplexity': mappedMode = 'ShaderComplexity'; break;
|
|
268
|
-
case 'lightmapdensity': mappedMode = 'LightmapDensity'; break;
|
|
269
|
-
case 'stationarylightoverlap': mappedMode = 'StationaryLightOverlap'; break;
|
|
270
|
-
case 'reflections': mappedMode = 'ReflectionOverride'; break;
|
|
271
|
-
case 'visualizebuffer': mappedMode = 'VisualizeBuffer'; break;
|
|
272
|
-
case 'collisionpawn': mappedMode = 'CollisionPawn'; break;
|
|
273
|
-
case 'collisionvisibility': mappedMode = 'CollisionVisibility'; break;
|
|
274
|
-
case 'lodcoloration': mappedMode = 'LODColoration'; break;
|
|
275
|
-
case 'quadoverdraw': mappedMode = 'QuadOverdraw'; break;
|
|
276
|
-
default:
|
|
277
|
-
throw new Error(`Invalid viewMode: '${args.viewMode}'. Valid modes are: ${validModes.join(', ')}`);
|
|
116
|
+
switch (args.action) {
|
|
117
|
+
case 'play': {
|
|
118
|
+
const res = await tools.editorTools.playInEditor();
|
|
119
|
+
return cleanObject(res);
|
|
120
|
+
}
|
|
121
|
+
case 'stop': {
|
|
122
|
+
const res = await tools.editorTools.stopPlayInEditor();
|
|
123
|
+
return cleanObject(res);
|
|
124
|
+
}
|
|
125
|
+
case 'pause': {
|
|
126
|
+
const res = await tools.editorTools.pausePlayInEditor();
|
|
127
|
+
return cleanObject(res);
|
|
128
|
+
}
|
|
129
|
+
case 'set_game_speed': {
|
|
130
|
+
if (typeof args.speed !== 'number' || !isFinite(args.speed) || args.speed <= 0) {
|
|
131
|
+
throw new Error('Invalid speed: must be a positive number');
|
|
278
132
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
133
|
+
// Use console command via bridge
|
|
134
|
+
const res = await tools.bridge.executeConsoleCommand(`slomo ${args.speed}`);
|
|
135
|
+
return cleanObject(res);
|
|
136
|
+
}
|
|
137
|
+
case 'eject': {
|
|
138
|
+
const res = await tools.bridge.executeConsoleCommand('eject');
|
|
139
|
+
return cleanObject(res);
|
|
140
|
+
}
|
|
141
|
+
case 'possess': {
|
|
142
|
+
const res = await tools.bridge.executeConsoleCommand('viewself');
|
|
143
|
+
return cleanObject(res);
|
|
144
|
+
}
|
|
145
|
+
case 'set_camera': {
|
|
146
|
+
const res = await tools.editorTools.setViewportCamera(args.location, args.rotation);
|
|
147
|
+
return cleanObject(res);
|
|
148
|
+
}
|
|
149
|
+
case 'set_view_mode': {
|
|
150
|
+
if (!args.viewMode || typeof args.viewMode !== 'string') throw new Error('Missing required parameter: viewMode');
|
|
151
|
+
const res = await tools.bridge.setSafeViewMode(args.viewMode);
|
|
152
|
+
return cleanObject(res);
|
|
153
|
+
}
|
|
285
154
|
default:
|
|
286
155
|
throw new Error(`Unknown editor action: ${args.action}`);
|
|
287
156
|
}
|
|
288
|
-
break;
|
|
289
157
|
|
|
290
158
|
// 4. LEVEL MANAGER
|
|
291
|
-
|
|
159
|
+
case 'manage_level':
|
|
160
|
+
if (!args.action) throw new Error('Missing required parameter: action');
|
|
292
161
|
switch (args.action) {
|
|
293
|
-
case 'load':
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
case '
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (!args.lightType) {
|
|
322
|
-
throw new Error('Missing required parameter: lightType');
|
|
323
|
-
}
|
|
324
|
-
const validLightTypes = ['directional', 'point', 'spot', 'rect', 'sky'];
|
|
325
|
-
const normalizedLightType = String(args.lightType).toLowerCase();
|
|
326
|
-
if (!validLightTypes.includes(normalizedLightType)) {
|
|
327
|
-
throw new Error(`Invalid lightType: '${args.lightType}'. Valid types are: ${validLightTypes.join(', ')}`);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Validate name
|
|
331
|
-
if (!args.name) {
|
|
332
|
-
throw new Error('Missing required parameter: name');
|
|
333
|
-
}
|
|
334
|
-
if (typeof args.name !== 'string' || args.name.trim() === '') {
|
|
335
|
-
throw new Error('Invalid name: must be a non-empty string');
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Validate intensity if provided
|
|
339
|
-
if (args.intensity !== undefined) {
|
|
340
|
-
if (typeof args.intensity !== 'number' || !isFinite(args.intensity)) {
|
|
341
|
-
throw new Error(`Invalid intensity: must be a finite number, got ${typeof args.intensity}`);
|
|
342
|
-
}
|
|
343
|
-
if (args.intensity < 0) {
|
|
344
|
-
throw new Error('Invalid intensity: must be non-negative');
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Validate location if provided
|
|
349
|
-
if (args.location !== undefined && args.location !== null) {
|
|
350
|
-
if (!Array.isArray(args.location) && typeof args.location !== 'object') {
|
|
351
|
-
throw new Error('Invalid location: must be an array [x,y,z] or object {x,y,z}');
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
mappedName = 'create_light';
|
|
356
|
-
mappedArgs = {
|
|
357
|
-
lightType: args.lightType,
|
|
358
|
-
name: args.name,
|
|
359
|
-
location: args.location,
|
|
360
|
-
intensity: args.intensity
|
|
361
|
-
};
|
|
362
|
-
break;
|
|
363
|
-
case 'build_lighting':
|
|
364
|
-
mappedName = 'build_lighting';
|
|
365
|
-
mappedArgs = {
|
|
366
|
-
quality: args.quality
|
|
367
|
-
};
|
|
368
|
-
break;
|
|
162
|
+
case 'load': {
|
|
163
|
+
if (!args.levelPath || typeof args.levelPath !== 'string') throw new Error('Missing required parameter: levelPath');
|
|
164
|
+
const res = await tools.levelTools.loadLevel({ levelPath: args.levelPath, streaming: !!args.streaming });
|
|
165
|
+
return cleanObject(res);
|
|
166
|
+
}
|
|
167
|
+
case 'save': {
|
|
168
|
+
const res = await tools.levelTools.saveLevel({ levelName: args.levelName, savePath: args.savePath });
|
|
169
|
+
return cleanObject(res);
|
|
170
|
+
}
|
|
171
|
+
case 'stream': {
|
|
172
|
+
if (!args.levelName || typeof args.levelName !== 'string') throw new Error('Missing required parameter: levelName');
|
|
173
|
+
const res = await tools.levelTools.streamLevel({ levelName: args.levelName, shouldBeLoaded: !!args.shouldBeLoaded, shouldBeVisible: !!args.shouldBeVisible });
|
|
174
|
+
return cleanObject(res);
|
|
175
|
+
}
|
|
176
|
+
case 'create_light': {
|
|
177
|
+
if (!args.lightType) throw new Error('Missing required parameter: lightType');
|
|
178
|
+
if (!args.name || typeof args.name !== 'string' || args.name.trim() === '') throw new Error('Invalid name');
|
|
179
|
+
const t = String(args.lightType).toLowerCase();
|
|
180
|
+
if (t === 'directional') return cleanObject(await tools.lightingTools.createDirectionalLight({ name: args.name, intensity: args.intensity }));
|
|
181
|
+
if (t === 'point') return cleanObject(await tools.lightingTools.createPointLight({ name: args.name, location: args.location ? [args.location.x, args.location.y, args.location.z] : [0,0,0], intensity: args.intensity }));
|
|
182
|
+
if (t === 'spot') return cleanObject(await tools.lightingTools.createSpotLight({ name: args.name, location: args.location ? [args.location.x, args.location.y, args.location.z] : [0,0,0], rotation: [0,0,0], intensity: args.intensity }));
|
|
183
|
+
if (t === 'rect') return cleanObject(await tools.lightingTools.createRectLight({ name: args.name, location: args.location ? [args.location.x, args.location.y, args.location.z] : [0,0,0], rotation: [0,0,0], intensity: args.intensity }));
|
|
184
|
+
throw new Error(`Unknown light type: ${args.lightType}`);
|
|
185
|
+
}
|
|
186
|
+
case 'build_lighting': {
|
|
187
|
+
const res = await tools.lightingTools.buildLighting({ quality: args.quality || 'High', buildReflectionCaptures: true });
|
|
188
|
+
return cleanObject(res);
|
|
189
|
+
}
|
|
369
190
|
default:
|
|
370
191
|
throw new Error(`Unknown level action: ${args.action}`);
|
|
371
192
|
}
|
|
372
|
-
break;
|
|
373
193
|
|
|
374
194
|
// 5. ANIMATION & PHYSICS
|
|
375
|
-
|
|
195
|
+
case 'animation_physics':
|
|
376
196
|
// Validate action exists
|
|
377
197
|
if (!args.action) {
|
|
378
198
|
throw new Error('Missing required parameter: action');
|
|
379
199
|
}
|
|
380
200
|
|
|
381
201
|
switch (args.action) {
|
|
382
|
-
case 'create_animation_bp':
|
|
383
|
-
|
|
384
|
-
if (args.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (args.name.trim() === '') {
|
|
391
|
-
throw new Error('Invalid name: cannot be empty');
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (args.skeletonPath === undefined || args.skeletonPath === null) {
|
|
395
|
-
throw new Error('Missing required parameter: skeletonPath');
|
|
396
|
-
}
|
|
397
|
-
if (typeof args.skeletonPath !== 'string') {
|
|
398
|
-
throw new Error('Invalid skeletonPath: must be a string');
|
|
399
|
-
}
|
|
400
|
-
if (args.skeletonPath.trim() === '') {
|
|
401
|
-
throw new Error('Invalid skeletonPath: cannot be empty');
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Optional savePath validation
|
|
405
|
-
if (args.savePath !== undefined && args.savePath !== null) {
|
|
406
|
-
if (typeof args.savePath !== 'string') {
|
|
407
|
-
throw new Error('Invalid savePath: must be a string');
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
mappedName = 'create_animation_blueprint';
|
|
412
|
-
mappedArgs = {
|
|
413
|
-
name: args.name,
|
|
414
|
-
skeletonPath: args.skeletonPath,
|
|
415
|
-
savePath: args.savePath
|
|
416
|
-
};
|
|
417
|
-
break;
|
|
418
|
-
|
|
419
|
-
case 'play_montage':
|
|
420
|
-
// Validate required parameters
|
|
421
|
-
if (args.actorName === undefined || args.actorName === null) {
|
|
422
|
-
throw new Error('Missing required parameter: actorName');
|
|
423
|
-
}
|
|
424
|
-
if (typeof args.actorName !== 'string') {
|
|
425
|
-
throw new Error('Invalid actorName: must be a string');
|
|
426
|
-
}
|
|
427
|
-
if (args.actorName.trim() === '') {
|
|
428
|
-
throw new Error('Invalid actorName: cannot be empty');
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Check for montagePath or animationPath
|
|
202
|
+
case 'create_animation_bp': {
|
|
203
|
+
if (typeof args.name !== 'string' || args.name.trim() === '') throw new Error('Invalid name');
|
|
204
|
+
if (typeof args.skeletonPath !== 'string' || args.skeletonPath.trim() === '') throw new Error('Invalid skeletonPath');
|
|
205
|
+
const res = await tools.animationTools.createAnimationBlueprint({ name: args.name, skeletonPath: args.skeletonPath, savePath: args.savePath });
|
|
206
|
+
return cleanObject(res);
|
|
207
|
+
}
|
|
208
|
+
case 'play_montage': {
|
|
209
|
+
if (typeof args.actorName !== 'string' || args.actorName.trim() === '') throw new Error('Invalid actorName');
|
|
432
210
|
const montagePath = args.montagePath || args.animationPath;
|
|
433
|
-
if (montagePath
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
if (
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
// Optional playRate validation
|
|
444
|
-
if (args.playRate !== undefined && args.playRate !== null) {
|
|
445
|
-
if (typeof args.playRate !== 'number') {
|
|
446
|
-
throw new Error('Invalid playRate: must be a number');
|
|
447
|
-
}
|
|
448
|
-
if (isNaN(args.playRate)) {
|
|
449
|
-
throw new Error('Invalid playRate: cannot be NaN');
|
|
450
|
-
}
|
|
451
|
-
if (!isFinite(args.playRate)) {
|
|
452
|
-
throw new Error('Invalid playRate: must be finite');
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
mappedName = 'play_animation_montage';
|
|
457
|
-
mappedArgs = {
|
|
458
|
-
actorName: args.actorName,
|
|
459
|
-
montagePath: montagePath,
|
|
460
|
-
playRate: args.playRate
|
|
461
|
-
};
|
|
462
|
-
break;
|
|
463
|
-
|
|
464
|
-
case 'setup_ragdoll':
|
|
465
|
-
// Validate required parameters
|
|
466
|
-
if (args.skeletonPath === undefined || args.skeletonPath === null) {
|
|
467
|
-
throw new Error('Missing required parameter: skeletonPath');
|
|
468
|
-
}
|
|
469
|
-
if (typeof args.skeletonPath !== 'string') {
|
|
470
|
-
throw new Error('Invalid skeletonPath: must be a string');
|
|
471
|
-
}
|
|
472
|
-
if (args.skeletonPath.trim() === '') {
|
|
473
|
-
throw new Error('Invalid skeletonPath: cannot be empty');
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
if (args.physicsAssetName === undefined || args.physicsAssetName === null) {
|
|
477
|
-
throw new Error('Missing required parameter: physicsAssetName');
|
|
478
|
-
}
|
|
479
|
-
if (typeof args.physicsAssetName !== 'string') {
|
|
480
|
-
throw new Error('Invalid physicsAssetName: must be a string');
|
|
481
|
-
}
|
|
482
|
-
if (args.physicsAssetName.trim() === '') {
|
|
483
|
-
throw new Error('Invalid physicsAssetName: cannot be empty');
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// Optional blendWeight validation
|
|
487
|
-
if (args.blendWeight !== undefined && args.blendWeight !== null) {
|
|
488
|
-
if (typeof args.blendWeight !== 'number') {
|
|
489
|
-
throw new Error('Invalid blendWeight: must be a number');
|
|
490
|
-
}
|
|
491
|
-
if (isNaN(args.blendWeight)) {
|
|
492
|
-
throw new Error('Invalid blendWeight: cannot be NaN');
|
|
493
|
-
}
|
|
494
|
-
if (!isFinite(args.blendWeight)) {
|
|
495
|
-
throw new Error('Invalid blendWeight: must be finite');
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// Optional savePath validation
|
|
500
|
-
if (args.savePath !== undefined && args.savePath !== null) {
|
|
501
|
-
if (typeof args.savePath !== 'string') {
|
|
502
|
-
throw new Error('Invalid savePath: must be a string');
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
mappedName = 'setup_ragdoll';
|
|
507
|
-
mappedArgs = {
|
|
508
|
-
skeletonPath: args.skeletonPath,
|
|
509
|
-
physicsAssetName: args.physicsAssetName,
|
|
510
|
-
blendWeight: args.blendWeight,
|
|
511
|
-
savePath: args.savePath
|
|
512
|
-
};
|
|
513
|
-
break;
|
|
514
|
-
|
|
211
|
+
if (typeof montagePath !== 'string' || montagePath.trim() === '') throw new Error('Invalid montagePath');
|
|
212
|
+
const res = await tools.animationTools.playAnimation({ actorName: args.actorName, animationType: 'Montage', animationPath: montagePath, playRate: args.playRate });
|
|
213
|
+
return cleanObject(res);
|
|
214
|
+
}
|
|
215
|
+
case 'setup_ragdoll': {
|
|
216
|
+
if (typeof args.skeletonPath !== 'string' || args.skeletonPath.trim() === '') throw new Error('Invalid skeletonPath');
|
|
217
|
+
if (typeof args.physicsAssetName !== 'string' || args.physicsAssetName.trim() === '') throw new Error('Invalid physicsAssetName');
|
|
218
|
+
const res = await tools.physicsTools.setupRagdoll({ skeletonPath: args.skeletonPath, physicsAssetName: args.physicsAssetName, blendWeight: args.blendWeight, savePath: args.savePath });
|
|
219
|
+
return cleanObject(res);
|
|
220
|
+
}
|
|
515
221
|
default:
|
|
516
222
|
throw new Error(`Unknown animation/physics action: ${args.action}`);
|
|
517
223
|
}
|
|
518
|
-
break;
|
|
519
224
|
|
|
520
|
-
|
|
225
|
+
// 6. EFFECTS SYSTEM
|
|
521
226
|
case 'create_effect':
|
|
522
227
|
switch (args.action) {
|
|
523
|
-
case 'particle':
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
228
|
+
case 'particle': {
|
|
229
|
+
const res = await tools.niagaraTools.createEffect({ effectType: args.effectType, name: args.name, location: args.location, scale: args.scale, customParameters: args.customParameters });
|
|
230
|
+
return cleanObject(res);
|
|
231
|
+
}
|
|
232
|
+
case 'niagara': {
|
|
233
|
+
if (typeof args.systemPath !== 'string' || args.systemPath.trim() === '') throw new Error('Invalid systemPath');
|
|
234
|
+
// Create or ensure system exists (spawning in editor is not universally supported via RC)
|
|
235
|
+
const name = args.name || args.systemPath.split('/').pop();
|
|
236
|
+
const res = await tools.niagaraTools.createSystem({ name, savePath: args.savePath || '/Game/Effects/Niagara' });
|
|
237
|
+
return cleanObject(res);
|
|
238
|
+
}
|
|
239
|
+
case 'debug_shape': {
|
|
240
|
+
const shape = String(args.shape || 'Sphere').toLowerCase();
|
|
241
|
+
const loc = args.location || { x: 0, y: 0, z: 0 };
|
|
242
|
+
const size = args.size || 100;
|
|
243
|
+
const color = args.color || [255, 0, 0, 255];
|
|
244
|
+
const duration = args.duration || 5;
|
|
245
|
+
if (shape === 'line') {
|
|
246
|
+
const end = args.end || { x: loc.x + size, y: loc.y, z: loc.z };
|
|
247
|
+
return cleanObject(await tools.debugTools.drawDebugLine({ start: [loc.x, loc.y, loc.z], end: [end.x, end.y, end.z], color, duration }));
|
|
248
|
+
} else if (shape === 'box') {
|
|
249
|
+
const extent = [size, size, size];
|
|
250
|
+
return cleanObject(await tools.debugTools.drawDebugBox({ center: [loc.x, loc.y, loc.z], extent, color, duration }));
|
|
251
|
+
} else if (shape === 'sphere') {
|
|
252
|
+
return cleanObject(await tools.debugTools.drawDebugSphere({ center: [loc.x, loc.y, loc.z], radius: size, color, duration }));
|
|
253
|
+
} else if (shape === 'capsule') {
|
|
254
|
+
return cleanObject(await tools.debugTools.drawDebugCapsule({ center: [loc.x, loc.y, loc.z], halfHeight: size, radius: Math.max(10, size/3), color, duration }));
|
|
255
|
+
} else if (shape === 'cone') {
|
|
256
|
+
return cleanObject(await tools.debugTools.drawDebugCone({ origin: [loc.x, loc.y, loc.z], direction: [0,0,1], length: size, angleWidth: 0.5, angleHeight: 0.5, color, duration }));
|
|
257
|
+
} else if (shape === 'arrow') {
|
|
258
|
+
const end = args.end || { x: loc.x + size, y: loc.y, z: loc.z };
|
|
259
|
+
return cleanObject(await tools.debugTools.drawDebugArrow({ start: [loc.x, loc.y, loc.z], end: [end.x, end.y, end.z], color, duration }));
|
|
260
|
+
} else if (shape === 'point') {
|
|
261
|
+
return cleanObject(await tools.debugTools.drawDebugPoint({ location: [loc.x, loc.y, loc.z], size, color, duration }));
|
|
262
|
+
} else if (shape === 'text' || shape === 'string') {
|
|
263
|
+
const text = args.text || 'Debug';
|
|
264
|
+
return cleanObject(await tools.debugTools.drawDebugString({ location: [loc.x, loc.y, loc.z], text, color, duration }));
|
|
265
|
+
}
|
|
266
|
+
// Default fallback
|
|
267
|
+
return cleanObject(await tools.debugTools.drawDebugSphere({ center: [loc.x, loc.y, loc.z], radius: size, color, duration }));
|
|
268
|
+
}
|
|
551
269
|
default:
|
|
552
270
|
throw new Error(`Unknown effect action: ${args.action}`);
|
|
553
271
|
}
|
|
554
|
-
break;
|
|
555
272
|
|
|
556
|
-
|
|
273
|
+
// 7. BLUEPRINT MANAGER
|
|
557
274
|
case 'manage_blueprint':
|
|
558
275
|
switch (args.action) {
|
|
559
|
-
case 'create':
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
case 'add_component':
|
|
568
|
-
mappedName = 'add_blueprint_component';
|
|
569
|
-
mappedArgs = {
|
|
570
|
-
blueprintName: args.name,
|
|
571
|
-
componentType: args.componentType,
|
|
572
|
-
componentName: args.componentName
|
|
573
|
-
};
|
|
574
|
-
break;
|
|
276
|
+
case 'create': {
|
|
277
|
+
const res = await tools.blueprintTools.createBlueprint({ name: args.name, blueprintType: args.blueprintType || 'Actor', savePath: args.savePath });
|
|
278
|
+
return cleanObject(res);
|
|
279
|
+
}
|
|
280
|
+
case 'add_component': {
|
|
281
|
+
const res = await tools.blueprintTools.addComponent({ blueprintName: args.name, componentType: args.componentType, componentName: args.componentName });
|
|
282
|
+
return cleanObject(res);
|
|
283
|
+
}
|
|
575
284
|
default:
|
|
576
285
|
throw new Error(`Unknown blueprint action: ${args.action}`);
|
|
577
286
|
}
|
|
578
|
-
break;
|
|
579
287
|
|
|
580
|
-
|
|
288
|
+
// 8. ENVIRONMENT BUILDER
|
|
581
289
|
case 'build_environment':
|
|
582
290
|
switch (args.action) {
|
|
583
|
-
case 'create_landscape':
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
291
|
+
case 'create_landscape': {
|
|
292
|
+
const res = await tools.landscapeTools.createLandscape({ name: args.name, sizeX: args.sizeX, sizeY: args.sizeY, materialPath: args.materialPath });
|
|
293
|
+
return cleanObject(res);
|
|
294
|
+
}
|
|
295
|
+
case 'sculpt': {
|
|
296
|
+
const res = await tools.landscapeTools.sculptLandscape({ landscapeName: args.name, tool: args.tool, brushSize: args.brushSize, strength: args.strength });
|
|
297
|
+
return cleanObject(res);
|
|
298
|
+
}
|
|
299
|
+
case 'add_foliage': {
|
|
300
|
+
const res = await tools.foliageTools.addFoliageType({ name: args.name, meshPath: args.meshPath, density: args.density });
|
|
301
|
+
return cleanObject(res);
|
|
302
|
+
}
|
|
303
|
+
case 'paint_foliage': {
|
|
304
|
+
const pos = args.position ? [args.position.x || 0, args.position.y || 0, args.position.z || 0] : [0,0,0];
|
|
305
|
+
const res = await tools.foliageTools.paintFoliage({ foliageType: args.foliageType, position: pos, brushSize: args.brushSize, paintDensity: args.paintDensity, eraseMode: args.eraseMode });
|
|
306
|
+
return cleanObject(res);
|
|
307
|
+
}
|
|
308
|
+
case 'create_procedural_terrain': {
|
|
309
|
+
const loc = args.location ? [args.location.x||0, args.location.y||0, args.location.z||0] : [0,0,0];
|
|
310
|
+
const res = await tools.buildEnvAdvanced.createProceduralTerrain({
|
|
311
|
+
name: args.name || 'ProceduralTerrain',
|
|
312
|
+
location: loc as [number,number,number],
|
|
587
313
|
sizeX: args.sizeX,
|
|
588
314
|
sizeY: args.sizeY,
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
315
|
+
subdivisions: args.subdivisions,
|
|
316
|
+
heightFunction: args.heightFunction,
|
|
317
|
+
material: args.materialPath
|
|
318
|
+
});
|
|
319
|
+
return cleanObject(res);
|
|
320
|
+
}
|
|
321
|
+
case 'create_procedural_foliage': {
|
|
322
|
+
if (!args.bounds || !args.bounds.location || !args.bounds.size) throw new Error('bounds.location and bounds.size are required');
|
|
323
|
+
const bounds = {
|
|
324
|
+
location: [args.bounds.location.x||0, args.bounds.location.y||0, args.bounds.location.z||0] as [number,number,number],
|
|
325
|
+
size: [args.bounds.size.x||1000, args.bounds.size.y||1000, args.bounds.size.z||100] as [number,number,number]
|
|
326
|
+
};
|
|
327
|
+
const res = await tools.buildEnvAdvanced.createProceduralFoliage({
|
|
328
|
+
name: args.name || 'ProceduralFoliage',
|
|
329
|
+
bounds,
|
|
330
|
+
foliageTypes: args.foliageTypes || [],
|
|
331
|
+
seed: args.seed
|
|
332
|
+
});
|
|
333
|
+
return cleanObject(res);
|
|
334
|
+
}
|
|
335
|
+
case 'add_foliage_instances': {
|
|
336
|
+
if (!args.foliageType) throw new Error('foliageType is required');
|
|
337
|
+
if (!Array.isArray(args.transforms)) throw new Error('transforms array is required');
|
|
338
|
+
const transforms = (args.transforms as any[]).map(t => ({
|
|
339
|
+
location: [t.location?.x||0, t.location?.y||0, t.location?.z||0] as [number,number,number],
|
|
340
|
+
rotation: t.rotation ? [t.rotation.pitch||0, t.rotation.yaw||0, t.rotation.roll||0] as [number,number,number] : undefined,
|
|
341
|
+
scale: t.scale ? [t.scale.x||1, t.scale.y||1, t.scale.z||1] as [number,number,number] : undefined
|
|
342
|
+
}));
|
|
343
|
+
const res = await tools.buildEnvAdvanced.addFoliageInstances({ foliageType: args.foliageType, transforms });
|
|
344
|
+
return cleanObject(res);
|
|
345
|
+
}
|
|
346
|
+
case 'create_landscape_grass_type': {
|
|
347
|
+
const res = await tools.buildEnvAdvanced.createLandscapeGrassType({
|
|
348
|
+
name: args.name || 'GrassType',
|
|
617
349
|
meshPath: args.meshPath,
|
|
618
|
-
density: args.density
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
throw new Error(`Invalid foliageType: '${args.foliageType}'`);
|
|
625
|
-
}
|
|
626
|
-
// Convert position object to array if needed
|
|
627
|
-
let positionArray;
|
|
628
|
-
if (args.position) {
|
|
629
|
-
if (Array.isArray(args.position)) {
|
|
630
|
-
positionArray = args.position;
|
|
631
|
-
} else if (typeof args.position === 'object') {
|
|
632
|
-
positionArray = [args.position.x || 0, args.position.y || 0, args.position.z || 0];
|
|
633
|
-
} else {
|
|
634
|
-
positionArray = [0, 0, 0];
|
|
635
|
-
}
|
|
636
|
-
} else {
|
|
637
|
-
positionArray = [0, 0, 0];
|
|
638
|
-
}
|
|
639
|
-
// Validate numbers in position
|
|
640
|
-
if (!Array.isArray(positionArray) || positionArray.length !== 3 || positionArray.some(v => typeof v !== 'number' || !isFinite(v))) {
|
|
641
|
-
throw new Error(`Invalid position: '${JSON.stringify(args.position)}'`);
|
|
642
|
-
}
|
|
643
|
-
if (args.brushSize !== undefined) {
|
|
644
|
-
if (typeof args.brushSize !== 'number' || !isFinite(args.brushSize) || args.brushSize < 0) {
|
|
645
|
-
throw new Error(`Invalid brushSize: '${args.brushSize}' (must be non-negative finite number)`);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
mappedName = 'paint_foliage';
|
|
649
|
-
mappedArgs = {
|
|
650
|
-
foliageType: args.foliageType,
|
|
651
|
-
position: positionArray,
|
|
652
|
-
brushSize: args.brushSize
|
|
653
|
-
};
|
|
654
|
-
break;
|
|
350
|
+
density: args.density,
|
|
351
|
+
minScale: args.minScale,
|
|
352
|
+
maxScale: args.maxScale
|
|
353
|
+
});
|
|
354
|
+
return cleanObject(res);
|
|
355
|
+
}
|
|
655
356
|
default:
|
|
656
357
|
throw new Error(`Unknown environment action: ${args.action}`);
|
|
657
358
|
}
|
|
658
|
-
break;
|
|
659
359
|
|
|
660
360
|
// 9. SYSTEM CONTROL
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
if (args === null || args === undefined) {
|
|
664
|
-
throw new Error('Invalid arguments: null or undefined');
|
|
665
|
-
}
|
|
666
|
-
if (typeof args !== 'object') {
|
|
667
|
-
throw new Error('Invalid arguments: must be an object');
|
|
668
|
-
}
|
|
669
|
-
// Validate action exists
|
|
670
|
-
if (!args.action) {
|
|
671
|
-
throw new Error('Missing required parameter: action');
|
|
672
|
-
}
|
|
673
|
-
|
|
361
|
+
case 'system_control':
|
|
362
|
+
if (!args.action) throw new Error('Missing required parameter: action');
|
|
674
363
|
switch (args.action) {
|
|
675
|
-
case 'profile':
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
};
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
case '
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
const map: Record<string, string> = {
|
|
717
|
-
ViewDistance: 'ViewDistance',
|
|
718
|
-
AntiAliasing: 'AntiAliasing',
|
|
719
|
-
PostProcessing: 'PostProcess',
|
|
720
|
-
PostProcess: 'PostProcess',
|
|
721
|
-
Shadows: 'Shadow',
|
|
722
|
-
Shadow: 'Shadow',
|
|
723
|
-
GlobalIllumination: 'GlobalIllumination',
|
|
724
|
-
Reflections: 'Reflection',
|
|
725
|
-
Reflection: 'Reflection',
|
|
726
|
-
Textures: 'Texture',
|
|
727
|
-
Texture: 'Texture',
|
|
728
|
-
Effects: 'Effects',
|
|
729
|
-
Foliage: 'Foliage',
|
|
730
|
-
Shading: 'Shading',
|
|
731
|
-
};
|
|
732
|
-
const categoryName = map[String(args.category)] || args.category;
|
|
733
|
-
mappedName = 'set_scalability';
|
|
734
|
-
mappedArgs = {
|
|
735
|
-
category: categoryName,
|
|
736
|
-
level: args.level
|
|
737
|
-
};
|
|
738
|
-
break;
|
|
739
|
-
|
|
740
|
-
case 'play_sound':
|
|
741
|
-
// Validate sound path
|
|
742
|
-
if (!args.soundPath || typeof args.soundPath !== 'string') {
|
|
743
|
-
throw new Error('Invalid soundPath: must be a non-empty string');
|
|
744
|
-
}
|
|
745
|
-
// Validate volume if provided
|
|
746
|
-
if (args.volume !== undefined) {
|
|
747
|
-
if (typeof args.volume !== 'number' || args.volume < 0 || args.volume > 1) {
|
|
748
|
-
throw new Error(`Invalid volume: must be 0-1, got ${args.volume}`);
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
mappedName = 'play_sound';
|
|
752
|
-
mappedArgs = {
|
|
753
|
-
soundPath: args.soundPath,
|
|
754
|
-
location: args.location,
|
|
755
|
-
volume: args.volume,
|
|
756
|
-
is3D: args.is3D
|
|
757
|
-
};
|
|
758
|
-
break;
|
|
759
|
-
|
|
760
|
-
case 'create_widget':
|
|
761
|
-
// Validate widget name
|
|
762
|
-
if (!args.widgetName || typeof args.widgetName !== 'string' || args.widgetName.trim() === '') {
|
|
763
|
-
throw new Error('Invalid widgetName: must be a non-empty string');
|
|
764
|
-
}
|
|
765
|
-
mappedName = 'create_widget';
|
|
766
|
-
mappedArgs = {
|
|
767
|
-
name: args.widgetName,
|
|
768
|
-
type: args.widgetType,
|
|
769
|
-
savePath: args.savePath
|
|
770
|
-
};
|
|
771
|
-
break;
|
|
772
|
-
|
|
773
|
-
case 'show_widget':
|
|
774
|
-
// Validate widget name
|
|
775
|
-
if (!args.widgetName || typeof args.widgetName !== 'string') {
|
|
776
|
-
throw new Error('Invalid widgetName: must be a non-empty string');
|
|
777
|
-
}
|
|
778
|
-
// Validate visible is boolean (default to true if not provided)
|
|
779
|
-
const isVisible = args.visible !== undefined ? args.visible : true;
|
|
780
|
-
if (typeof isVisible !== 'boolean') {
|
|
781
|
-
throw new Error(`Invalid visible: must be boolean, got ${typeof isVisible}`);
|
|
782
|
-
}
|
|
783
|
-
mappedName = 'show_widget';
|
|
784
|
-
mappedArgs = {
|
|
785
|
-
widgetName: args.widgetName,
|
|
786
|
-
visible: isVisible
|
|
787
|
-
};
|
|
788
|
-
break;
|
|
789
|
-
|
|
790
|
-
case 'screenshot':
|
|
791
|
-
mappedName = 'take_screenshot';
|
|
792
|
-
mappedArgs = { resolution: args.resolution };
|
|
793
|
-
break;
|
|
794
|
-
|
|
795
|
-
case 'engine_start':
|
|
796
|
-
mappedName = 'launch_editor';
|
|
797
|
-
mappedArgs = { editorExe: args.editorExe, projectPath: args.projectPath };
|
|
798
|
-
break;
|
|
799
|
-
|
|
800
|
-
case 'engine_quit':
|
|
801
|
-
mappedName = 'quit_editor';
|
|
802
|
-
mappedArgs = {};
|
|
803
|
-
break;
|
|
804
|
-
|
|
364
|
+
case 'profile': {
|
|
365
|
+
const res = await tools.performanceTools.startProfiling({ type: args.profileType, duration: args.duration });
|
|
366
|
+
return cleanObject(res);
|
|
367
|
+
}
|
|
368
|
+
case 'show_fps': {
|
|
369
|
+
const res = await tools.performanceTools.showFPS({ enabled: !!args.enabled, verbose: !!args.verbose });
|
|
370
|
+
return cleanObject(res);
|
|
371
|
+
}
|
|
372
|
+
case 'set_quality': {
|
|
373
|
+
const res = await tools.performanceTools.setScalability({ category: args.category, level: args.level });
|
|
374
|
+
return cleanObject(res);
|
|
375
|
+
}
|
|
376
|
+
case 'play_sound': {
|
|
377
|
+
if (args.location && typeof args.location === 'object') {
|
|
378
|
+
const loc = [args.location.x || 0, args.location.y || 0, args.location.z || 0];
|
|
379
|
+
const res = await tools.audioTools.playSoundAtLocation({ soundPath: args.soundPath, location: loc as [number, number, number], volume: args.volume, pitch: args.pitch, startTime: args.startTime });
|
|
380
|
+
return cleanObject(res);
|
|
381
|
+
}
|
|
382
|
+
const res = await tools.audioTools.playSound2D({ soundPath: args.soundPath, volume: args.volume, pitch: args.pitch, startTime: args.startTime });
|
|
383
|
+
return cleanObject(res);
|
|
384
|
+
}
|
|
385
|
+
case 'create_widget': {
|
|
386
|
+
const res = await tools.uiTools.createWidget({ name: args.widgetName, type: args.widgetType, savePath: args.savePath });
|
|
387
|
+
return cleanObject(res);
|
|
388
|
+
}
|
|
389
|
+
case 'show_widget': {
|
|
390
|
+
const res = await tools.uiTools.setWidgetVisibility({ widgetName: args.widgetName, visible: args.visible !== false });
|
|
391
|
+
return cleanObject(res);
|
|
392
|
+
}
|
|
393
|
+
case 'screenshot': {
|
|
394
|
+
const res = await tools.visualTools.takeScreenshot({ resolution: args.resolution });
|
|
395
|
+
return cleanObject(res);
|
|
396
|
+
}
|
|
397
|
+
case 'engine_start': {
|
|
398
|
+
const res = await tools.engineTools.launchEditor({ editorExe: args.editorExe, projectPath: args.projectPath });
|
|
399
|
+
return cleanObject(res);
|
|
400
|
+
}
|
|
401
|
+
case 'engine_quit': {
|
|
402
|
+
const res = await tools.engineTools.quitEditor();
|
|
403
|
+
return cleanObject(res);
|
|
404
|
+
}
|
|
805
405
|
default:
|
|
806
406
|
throw new Error(`Unknown system action: ${args.action}`);
|
|
807
407
|
}
|
|
808
|
-
break;
|
|
809
408
|
|
|
810
409
|
// 10. CONSOLE COMMAND - handle validation here
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
return {
|
|
815
|
-
content: [{
|
|
816
|
-
type: 'text',
|
|
817
|
-
text: 'Empty command ignored'
|
|
818
|
-
}],
|
|
819
|
-
isError: false,
|
|
820
|
-
success: true,
|
|
821
|
-
message: 'Empty command'
|
|
822
|
-
};
|
|
410
|
+
case 'console_command':
|
|
411
|
+
if (!args.command || typeof args.command !== 'string' || args.command.trim() === '') {
|
|
412
|
+
return { success: true, message: 'Empty command' } as any;
|
|
823
413
|
}
|
|
824
|
-
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
};
|
|
414
|
+
// Basic safety filter
|
|
415
|
+
const cmd = String(args.command).trim();
|
|
416
|
+
const blocked = [/\bquit\b/i, /\bexit\b/i, /debugcrash/i];
|
|
417
|
+
if (blocked.some(r => r.test(cmd))) {
|
|
418
|
+
return { success: false, error: 'Command blocked for safety' } as any;
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
const res = await tools.bridge.executeConsoleCommand(cmd);
|
|
422
|
+
return cleanObject({ success: true, command: cmd, result: res });
|
|
423
|
+
} catch (e: any) {
|
|
424
|
+
return cleanObject({ success: false, command: cmd, error: e?.message || String(e) });
|
|
836
425
|
}
|
|
837
426
|
|
|
838
|
-
mappedName = 'console_command';
|
|
839
|
-
mappedArgs = args;
|
|
840
|
-
break;
|
|
841
427
|
|
|
842
428
|
// 11. REMOTE CONTROL PRESETS - Direct implementation
|
|
843
429
|
case 'manage_rc':
|
|
@@ -1035,42 +621,27 @@ export async function handleConsolidatedToolCall(
|
|
|
1035
621
|
// Clean to prevent circular references
|
|
1036
622
|
return cleanObject(seqResult);
|
|
1037
623
|
// 13. INTROSPECTION
|
|
1038
|
-
|
|
624
|
+
case 'inspect':
|
|
1039
625
|
if (!args.action) throw new Error('Missing required parameter: action');
|
|
1040
626
|
switch (args.action) {
|
|
1041
|
-
case 'inspect_object':
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
case 'set_property':
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
627
|
+
case 'inspect_object': {
|
|
628
|
+
const res = await tools.introspectionTools.inspectObject({ objectPath: args.objectPath, detailed: args.detailed });
|
|
629
|
+
return cleanObject(res);
|
|
630
|
+
}
|
|
631
|
+
case 'set_property': {
|
|
632
|
+
const res = await tools.introspectionTools.setProperty({ objectPath: args.objectPath, propertyName: args.propertyName, value: args.value });
|
|
633
|
+
return cleanObject(res);
|
|
634
|
+
}
|
|
1049
635
|
default:
|
|
1050
636
|
throw new Error(`Unknown inspect action: ${args.action}`);
|
|
1051
637
|
}
|
|
1052
|
-
|
|
638
|
+
|
|
1053
639
|
|
|
1054
640
|
default:
|
|
1055
641
|
throw new Error(`Unknown consolidated tool: ${name}`);
|
|
1056
642
|
}
|
|
1057
643
|
|
|
1058
|
-
|
|
1059
|
-
const TOOL_TIMEOUT = 15000; // 15 seconds timeout for tool execution
|
|
1060
|
-
|
|
1061
|
-
const toolPromise = handleToolCall(mappedName, mappedArgs, tools);
|
|
1062
|
-
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
1063
|
-
setTimeout(() => {
|
|
1064
|
-
reject(new Error(`Tool execution timeout after ${TOOL_TIMEOUT}ms`));
|
|
1065
|
-
}, TOOL_TIMEOUT);
|
|
1066
|
-
});
|
|
1067
|
-
|
|
1068
|
-
const result = await Promise.race([toolPromise, timeoutPromise]);
|
|
1069
|
-
const duration = Date.now() - startTime;
|
|
1070
|
-
console.log(`[ConsolidatedToolHandler] Completed execution of ${name} in ${duration}ms`);
|
|
1071
|
-
|
|
1072
|
-
// Clean the result to prevent circular reference errors
|
|
1073
|
-
return cleanObject(result);
|
|
644
|
+
// All cases return (or throw) above; this is a type guard for exhaustiveness.
|
|
1074
645
|
|
|
1075
646
|
} catch (err: any) {
|
|
1076
647
|
const duration = Date.now() - startTime;
|