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