unreal-engine-mcp-server 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/.dockerignore +57 -0
  2. package/.env.production +25 -0
  3. package/.eslintrc.json +54 -0
  4. package/.github/workflows/publish-mcp.yml +75 -0
  5. package/Dockerfile +54 -0
  6. package/LICENSE +21 -0
  7. package/Public/icon.png +0 -0
  8. package/README.md +209 -0
  9. package/claude_desktop_config_example.json +13 -0
  10. package/dist/cli.d.ts +3 -0
  11. package/dist/cli.js +7 -0
  12. package/dist/index.d.ts +31 -0
  13. package/dist/index.js +484 -0
  14. package/dist/prompts/index.d.ts +14 -0
  15. package/dist/prompts/index.js +38 -0
  16. package/dist/python-utils.d.ts +29 -0
  17. package/dist/python-utils.js +54 -0
  18. package/dist/resources/actors.d.ts +13 -0
  19. package/dist/resources/actors.js +83 -0
  20. package/dist/resources/assets.d.ts +23 -0
  21. package/dist/resources/assets.js +245 -0
  22. package/dist/resources/levels.d.ts +17 -0
  23. package/dist/resources/levels.js +94 -0
  24. package/dist/tools/actors.d.ts +51 -0
  25. package/dist/tools/actors.js +459 -0
  26. package/dist/tools/animation.d.ts +196 -0
  27. package/dist/tools/animation.js +579 -0
  28. package/dist/tools/assets.d.ts +21 -0
  29. package/dist/tools/assets.js +304 -0
  30. package/dist/tools/audio.d.ts +170 -0
  31. package/dist/tools/audio.js +416 -0
  32. package/dist/tools/blueprint.d.ts +144 -0
  33. package/dist/tools/blueprint.js +652 -0
  34. package/dist/tools/build_environment_advanced.d.ts +66 -0
  35. package/dist/tools/build_environment_advanced.js +484 -0
  36. package/dist/tools/consolidated-tool-definitions.d.ts +2598 -0
  37. package/dist/tools/consolidated-tool-definitions.js +607 -0
  38. package/dist/tools/consolidated-tool-handlers.d.ts +2 -0
  39. package/dist/tools/consolidated-tool-handlers.js +1050 -0
  40. package/dist/tools/debug.d.ts +185 -0
  41. package/dist/tools/debug.js +265 -0
  42. package/dist/tools/editor.d.ts +88 -0
  43. package/dist/tools/editor.js +365 -0
  44. package/dist/tools/engine.d.ts +30 -0
  45. package/dist/tools/engine.js +36 -0
  46. package/dist/tools/foliage.d.ts +155 -0
  47. package/dist/tools/foliage.js +525 -0
  48. package/dist/tools/introspection.d.ts +98 -0
  49. package/dist/tools/introspection.js +683 -0
  50. package/dist/tools/landscape.d.ts +158 -0
  51. package/dist/tools/landscape.js +375 -0
  52. package/dist/tools/level.d.ts +110 -0
  53. package/dist/tools/level.js +362 -0
  54. package/dist/tools/lighting.d.ts +159 -0
  55. package/dist/tools/lighting.js +1179 -0
  56. package/dist/tools/materials.d.ts +34 -0
  57. package/dist/tools/materials.js +146 -0
  58. package/dist/tools/niagara.d.ts +145 -0
  59. package/dist/tools/niagara.js +289 -0
  60. package/dist/tools/performance.d.ts +163 -0
  61. package/dist/tools/performance.js +412 -0
  62. package/dist/tools/physics.d.ts +189 -0
  63. package/dist/tools/physics.js +784 -0
  64. package/dist/tools/rc.d.ts +110 -0
  65. package/dist/tools/rc.js +363 -0
  66. package/dist/tools/sequence.d.ts +112 -0
  67. package/dist/tools/sequence.js +675 -0
  68. package/dist/tools/tool-definitions.d.ts +4919 -0
  69. package/dist/tools/tool-definitions.js +891 -0
  70. package/dist/tools/tool-handlers.d.ts +47 -0
  71. package/dist/tools/tool-handlers.js +830 -0
  72. package/dist/tools/ui.d.ts +171 -0
  73. package/dist/tools/ui.js +337 -0
  74. package/dist/tools/visual.d.ts +29 -0
  75. package/dist/tools/visual.js +67 -0
  76. package/dist/types/env.d.ts +10 -0
  77. package/dist/types/env.js +18 -0
  78. package/dist/types/index.d.ts +323 -0
  79. package/dist/types/index.js +28 -0
  80. package/dist/types/tool-types.d.ts +274 -0
  81. package/dist/types/tool-types.js +13 -0
  82. package/dist/unreal-bridge.d.ts +126 -0
  83. package/dist/unreal-bridge.js +992 -0
  84. package/dist/utils/cache-manager.d.ts +64 -0
  85. package/dist/utils/cache-manager.js +176 -0
  86. package/dist/utils/error-handler.d.ts +66 -0
  87. package/dist/utils/error-handler.js +243 -0
  88. package/dist/utils/errors.d.ts +133 -0
  89. package/dist/utils/errors.js +256 -0
  90. package/dist/utils/http.d.ts +26 -0
  91. package/dist/utils/http.js +135 -0
  92. package/dist/utils/logger.d.ts +12 -0
  93. package/dist/utils/logger.js +32 -0
  94. package/dist/utils/normalize.d.ts +17 -0
  95. package/dist/utils/normalize.js +49 -0
  96. package/dist/utils/response-validator.d.ts +34 -0
  97. package/dist/utils/response-validator.js +121 -0
  98. package/dist/utils/safe-json.d.ts +4 -0
  99. package/dist/utils/safe-json.js +97 -0
  100. package/dist/utils/stdio-redirect.d.ts +2 -0
  101. package/dist/utils/stdio-redirect.js +20 -0
  102. package/dist/utils/validation.d.ts +50 -0
  103. package/dist/utils/validation.js +173 -0
  104. package/mcp-config-example.json +14 -0
  105. package/package.json +63 -0
  106. package/server.json +60 -0
  107. package/src/cli.ts +7 -0
  108. package/src/index.ts +543 -0
  109. package/src/prompts/index.ts +51 -0
  110. package/src/python/editor_compat.py +181 -0
  111. package/src/python-utils.ts +57 -0
  112. package/src/resources/actors.ts +92 -0
  113. package/src/resources/assets.ts +251 -0
  114. package/src/resources/levels.ts +83 -0
  115. package/src/tools/actors.ts +480 -0
  116. package/src/tools/animation.ts +713 -0
  117. package/src/tools/assets.ts +305 -0
  118. package/src/tools/audio.ts +548 -0
  119. package/src/tools/blueprint.ts +736 -0
  120. package/src/tools/build_environment_advanced.ts +526 -0
  121. package/src/tools/consolidated-tool-definitions.ts +619 -0
  122. package/src/tools/consolidated-tool-handlers.ts +1093 -0
  123. package/src/tools/debug.ts +368 -0
  124. package/src/tools/editor.ts +360 -0
  125. package/src/tools/engine.ts +32 -0
  126. package/src/tools/foliage.ts +652 -0
  127. package/src/tools/introspection.ts +778 -0
  128. package/src/tools/landscape.ts +523 -0
  129. package/src/tools/level.ts +410 -0
  130. package/src/tools/lighting.ts +1316 -0
  131. package/src/tools/materials.ts +148 -0
  132. package/src/tools/niagara.ts +312 -0
  133. package/src/tools/performance.ts +549 -0
  134. package/src/tools/physics.ts +924 -0
  135. package/src/tools/rc.ts +437 -0
  136. package/src/tools/sequence.ts +791 -0
  137. package/src/tools/tool-definitions.ts +907 -0
  138. package/src/tools/tool-handlers.ts +941 -0
  139. package/src/tools/ui.ts +499 -0
  140. package/src/tools/visual.ts +60 -0
  141. package/src/types/env.ts +27 -0
  142. package/src/types/index.ts +414 -0
  143. package/src/types/tool-types.ts +343 -0
  144. package/src/unreal-bridge.ts +1118 -0
  145. package/src/utils/cache-manager.ts +213 -0
  146. package/src/utils/error-handler.ts +320 -0
  147. package/src/utils/errors.ts +312 -0
  148. package/src/utils/http.ts +184 -0
  149. package/src/utils/logger.ts +30 -0
  150. package/src/utils/normalize.ts +54 -0
  151. package/src/utils/response-validator.ts +145 -0
  152. package/src/utils/safe-json.ts +112 -0
  153. package/src/utils/stdio-redirect.ts +18 -0
  154. package/src/utils/validation.ts +212 -0
  155. package/tsconfig.json +33 -0
@@ -0,0 +1,525 @@
1
+ export class FoliageTools {
2
+ bridge;
3
+ constructor(bridge) {
4
+ this.bridge = bridge;
5
+ }
6
+ // NOTE: We intentionally avoid issuing Unreal console commands here because
7
+ // they have proven unreliable and generate engine warnings (failed FindConsoleObject).
8
+ // Instead, we validate inputs and return structured results. Actual foliage
9
+ // authoring should be implemented via Python APIs in future iterations.
10
+ // Add foliage type via Python (creates FoliageType asset properly)
11
+ async addFoliageType(params) {
12
+ // Basic validation to prevent bad inputs like 'undefined' and empty strings
13
+ const errors = [];
14
+ const name = String(params?.name ?? '').trim();
15
+ const meshPath = String(params?.meshPath ?? '').trim();
16
+ if (!name || name.toLowerCase() === 'undefined' || name.toLowerCase() === 'any') {
17
+ errors.push(`Invalid foliage type name: '${params?.name}'`);
18
+ }
19
+ if (!meshPath || meshPath.toLowerCase() === 'undefined') {
20
+ errors.push(`Invalid meshPath: '${params?.meshPath}'`);
21
+ }
22
+ if (params?.density !== undefined) {
23
+ if (typeof params.density !== 'number' || !isFinite(params.density) || params.density < 0) {
24
+ errors.push(`Invalid density: '${params.density}' (must be non-negative finite number)`);
25
+ }
26
+ }
27
+ if (params?.minScale !== undefined || params?.maxScale !== undefined) {
28
+ const minS = params?.minScale ?? 1;
29
+ const maxS = params?.maxScale ?? 1;
30
+ if (typeof minS !== 'number' || typeof maxS !== 'number' || minS <= 0 || maxS <= 0 || maxS < minS) {
31
+ errors.push(`Invalid scale range: min=${params?.minScale}, max=${params?.maxScale}`);
32
+ }
33
+ }
34
+ if (errors.length > 0) {
35
+ return { success: false, error: errors.join('; ') };
36
+ }
37
+ const py = `
38
+ import unreal, json
39
+
40
+ name = ${JSON.stringify(name)}
41
+ mesh_path = ${JSON.stringify(meshPath)}
42
+ fallback_mesh = '/Engine/EngineMeshes/Sphere'
43
+ package_path = '/Game/Foliage/Types'
44
+
45
+ res = {'success': False, 'created': False, 'asset_path': '', 'used_mesh': '', 'exists_after': False, 'method': '', 'note': ''}
46
+
47
+ try:
48
+ # Ensure package directory
49
+ try:
50
+ if not unreal.EditorAssetLibrary.does_directory_exist(package_path):
51
+ unreal.EditorAssetLibrary.make_directory(package_path)
52
+ except Exception as e:
53
+ res['note'] += f"; make_directory failed: {e}"
54
+
55
+ # Load mesh or fallback
56
+ mesh = None
57
+ try:
58
+ if unreal.EditorAssetLibrary.does_asset_exist(mesh_path):
59
+ mesh = unreal.EditorAssetLibrary.load_asset(mesh_path)
60
+ except Exception as e:
61
+ res['note'] += f"; could not check/load mesh_path: {e}"
62
+
63
+ if not mesh:
64
+ mesh = unreal.EditorAssetLibrary.load_asset(fallback_mesh)
65
+ res['note'] += '; fallback_mesh_used'
66
+ if mesh:
67
+ res['used_mesh'] = str(mesh.get_path_name())
68
+
69
+ # Create FoliageType asset using proper UE5 API
70
+ asset = None
71
+ try:
72
+ asset_path = f"{package_path}/{name}"
73
+
74
+ # Check if asset already exists
75
+ if unreal.EditorAssetLibrary.does_asset_exist(asset_path):
76
+ asset = unreal.EditorAssetLibrary.load_asset(asset_path)
77
+ res['note'] += '; loaded_existing'
78
+ else:
79
+ # Create FoliageType_InstancedStaticMesh using proper API
80
+ try:
81
+ asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
82
+
83
+ # Try to create factory and set mesh property
84
+ factory = None
85
+ try:
86
+ factory = unreal.FoliageType_InstancedStaticMeshFactory()
87
+ # Try different property names for different UE versions
88
+ try:
89
+ factory.set_editor_property('mesh', mesh)
90
+ except:
91
+ try:
92
+ factory.set_editor_property('static_mesh', mesh)
93
+ except:
94
+ try:
95
+ factory.set_editor_property('source_mesh', mesh)
96
+ except:
97
+ pass # Factory will use default or no mesh
98
+ except:
99
+ res['note'] += '; factory_creation_failed'
100
+ factory = None
101
+
102
+ # Create the asset with or without factory
103
+ if factory:
104
+ asset = asset_tools.create_asset(
105
+ asset_name=name,
106
+ package_path=package_path,
107
+ asset_class=unreal.FoliageType_InstancedStaticMesh,
108
+ factory=factory
109
+ )
110
+ else:
111
+ # Try without factory
112
+ asset = asset_tools.create_asset(
113
+ asset_name=name,
114
+ package_path=package_path,
115
+ asset_class=unreal.FoliageType_InstancedStaticMesh,
116
+ factory=None
117
+ )
118
+
119
+ if asset:
120
+ # Configure foliage properties
121
+ asset.set_editor_property('mesh', mesh)
122
+ if ${params.density !== undefined ? params.density : 1.0} >= 0:
123
+ asset.set_editor_property('density', ${params.density !== undefined ? params.density : 1.0})
124
+ if ${params.randomYaw === false ? 'False' : 'True'}:
125
+ asset.set_editor_property('random_yaw', True)
126
+ if ${params.alignToNormal === false ? 'False' : 'True'}:
127
+ asset.set_editor_property('align_to_normal', True)
128
+
129
+ # Set scale range
130
+ min_scale = ${params.minScale || 0.8}
131
+ max_scale = ${params.maxScale || 1.2}
132
+ asset.set_editor_property('scale_x', (min_scale, max_scale))
133
+ asset.set_editor_property('scale_y', (min_scale, max_scale))
134
+ asset.set_editor_property('scale_z', (min_scale, max_scale))
135
+
136
+ res['note'] += '; created_with_factory'
137
+ else:
138
+ res['note'] += '; factory_creation_failed'
139
+ except AttributeError:
140
+ # Fallback if factory doesn't exist - use base FoliageType
141
+ try:
142
+ asset = asset_tools.create_asset(
143
+ asset_name=name,
144
+ package_path=package_path,
145
+ asset_class=unreal.FoliageType,
146
+ factory=None
147
+ )
148
+ if asset:
149
+ res['note'] += '; created_base_foliage_type'
150
+ except Exception as e2:
151
+ res['note'] += f"; base_creation_failed: {e2}"
152
+ except Exception as e:
153
+ res['note'] += f"; factory_creation_failed: {e}"
154
+ asset = None
155
+ except Exception as e:
156
+ res['note'] += f"; create_asset failed: {e}"
157
+ asset = None
158
+
159
+ if asset and mesh:
160
+ try:
161
+ # Set the mesh property (different property names in different UE versions)
162
+ try:
163
+ asset.set_editor_property('mesh', mesh)
164
+ except:
165
+ try:
166
+ asset.set_editor_property('static_mesh', mesh)
167
+ except:
168
+ pass
169
+
170
+ # Save the asset
171
+ unreal.EditorAssetLibrary.save_asset(asset.get_path_name())
172
+ res['asset_path'] = str(asset.get_path_name())
173
+ res['created'] = True
174
+ res['method'] = 'FoliageType_InstancedStaticMesh'
175
+ except Exception as e:
176
+ res['note'] += f"; set/save asset failed: {e}"
177
+ elif not asset:
178
+ res['note'] += "; asset creation returned None"
179
+ elif not mesh:
180
+ res['note'] += "; mesh object is None, cannot assign to foliage type"
181
+
182
+ # Verify existence
183
+ res['exists_after'] = unreal.EditorAssetLibrary.does_asset_exist(res['asset_path']) if res['asset_path'] else False
184
+ res['success'] = res['exists_after'] or res['created']
185
+
186
+ except Exception as e:
187
+ res['success'] = False
188
+ res['note'] += f"; fatal: {e}"
189
+
190
+ print('RESULT:' + json.dumps(res))
191
+ `.trim();
192
+ const pyResp = await this.bridge.executePython(py);
193
+ let out = '';
194
+ if (pyResp?.LogOutput && Array.isArray(pyResp.LogOutput))
195
+ out = pyResp.LogOutput.map((l) => l.Output || '').join('');
196
+ else if (typeof pyResp === 'string')
197
+ out = pyResp;
198
+ else
199
+ out = JSON.stringify(pyResp);
200
+ const m = out.match(/RESULT:({.*})/);
201
+ if (m) {
202
+ try {
203
+ const parsed = JSON.parse(m[1]);
204
+ if (!parsed.success) {
205
+ return { success: false, error: parsed.note || 'Add foliage type failed' };
206
+ }
207
+ return {
208
+ success: true,
209
+ created: parsed.created,
210
+ exists: parsed.exists_after,
211
+ method: parsed.method,
212
+ assetPath: parsed.asset_path,
213
+ usedMesh: parsed.used_mesh,
214
+ note: parsed.note,
215
+ message: parsed.exists_after ? `Foliage type '${name}' ready (${parsed.method || 'Unknown'})` : `Created foliage '${name}' but verification did not find it yet`
216
+ };
217
+ }
218
+ catch {
219
+ return { success: false, error: 'Failed to parse Python result' };
220
+ }
221
+ }
222
+ return { success: false, error: 'No parseable result from Python' };
223
+ }
224
+ // Paint foliage by placing HISM instances (editor-only)
225
+ async paintFoliage(params) {
226
+ const errors = [];
227
+ const foliageType = String(params?.foliageType ?? '').trim();
228
+ const pos = Array.isArray(params?.position) ? params.position : [0, 0, 0];
229
+ if (!foliageType || foliageType.toLowerCase() === 'undefined' || foliageType.toLowerCase() === 'any') {
230
+ errors.push(`Invalid foliageType: '${params?.foliageType}'`);
231
+ }
232
+ if (!Array.isArray(pos) || pos.length !== 3 || pos.some(v => typeof v !== 'number' || !isFinite(v))) {
233
+ errors.push(`Invalid position: '${JSON.stringify(params?.position)}'`);
234
+ }
235
+ if (params?.brushSize !== undefined) {
236
+ if (typeof params.brushSize !== 'number' || !isFinite(params.brushSize) || params.brushSize < 0) {
237
+ errors.push(`Invalid brushSize: '${params.brushSize}' (must be non-negative finite number)`);
238
+ }
239
+ }
240
+ if (params?.paintDensity !== undefined) {
241
+ if (typeof params.paintDensity !== 'number' || !isFinite(params.paintDensity) || params.paintDensity < 0) {
242
+ errors.push(`Invalid paintDensity: '${params.paintDensity}' (must be non-negative finite number)`);
243
+ }
244
+ }
245
+ if (errors.length > 0) {
246
+ return { success: false, error: errors.join('; ') };
247
+ }
248
+ const brush = Number.isFinite(params.brushSize) ? params.brushSize : 300;
249
+ const py = `
250
+ import unreal, json, random, math
251
+
252
+ res = {'success': False, 'added': 0, 'actor': '', 'component': '', 'used_mesh': '', 'note': ''}
253
+ foliage_type_name = ${JSON.stringify(foliageType)}
254
+ px, py, pz = ${pos[0]}, ${pos[1]}, ${pos[2]}
255
+ radius = float(${brush}) / 2.0
256
+
257
+ try:
258
+ actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
259
+ all_actors = actor_sub.get_all_level_actors() if actor_sub else []
260
+
261
+ # Find or create a container actor
262
+ label = f"FoliageContainer_{foliage_type_name}"
263
+ container = None
264
+ for a in all_actors:
265
+ try:
266
+ if a.get_actor_label() == label:
267
+ container = a
268
+ break
269
+ except Exception:
270
+ pass
271
+ if not container:
272
+ # Spawn actor that can hold components
273
+ container = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.StaticMeshActor, unreal.Vector(px, py, pz))
274
+ try:
275
+ container.set_actor_label(label)
276
+ except Exception:
277
+ pass
278
+
279
+ # Resolve mesh from FoliageType asset
280
+ mesh = None
281
+ fol_asset_path = f"/Game/Foliage/Types/{foliage_type_name}.{foliage_type_name}"
282
+ if unreal.EditorAssetLibrary.does_asset_exist(fol_asset_path):
283
+ try:
284
+ ft_asset = unreal.EditorAssetLibrary.load_asset(fol_asset_path)
285
+ mesh = ft_asset.get_editor_property('mesh')
286
+ except Exception:
287
+ mesh = None
288
+
289
+ if not mesh:
290
+ mesh = unreal.EditorAssetLibrary.load_asset('/Engine/EngineMeshes/Sphere')
291
+ res['note'] += '; used_fallback_mesh'
292
+
293
+ if mesh:
294
+ res['used_mesh'] = str(mesh.get_path_name())
295
+
296
+ # Since HISM components and add_component don't work in this version,
297
+ # spawn individual StaticMeshActors for each instance
298
+ target_count = max(5, int(radius / 20.0))
299
+ added = 0
300
+ for i in range(target_count):
301
+ ang = random.random() * math.tau
302
+ r = random.random() * radius
303
+ x, y, z = px + math.cos(ang) * r, py + math.sin(ang) * r, pz
304
+ try:
305
+ # Spawn static mesh actor at position
306
+ inst_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(
307
+ unreal.StaticMeshActor,
308
+ unreal.Vector(x, y, z),
309
+ unreal.Rotator(0, random.random()*360.0, 0)
310
+ )
311
+ if inst_actor and mesh:
312
+ # Set mesh on the actor's component
313
+ try:
314
+ mesh_comp = inst_actor.static_mesh_component
315
+ if mesh_comp:
316
+ mesh_comp.set_static_mesh(mesh)
317
+ inst_actor.set_actor_label(f"{foliage_type_name}_instance_{i}")
318
+ # Group under the container for organization
319
+ inst_actor.attach_to_actor(container, "", unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, False)
320
+ added += 1
321
+ except Exception as e:
322
+ res['note'] += f"; instance_{i} setup failed: {e}"
323
+ except Exception as e:
324
+ res['note'] += f"; spawn instance_{i} failed: {e}"
325
+
326
+ res['added'] = added
327
+ res['actor'] = container.get_actor_label()
328
+ res['component'] = 'StaticMeshActors' # Using actors instead of components
329
+ res['success'] = True
330
+ except Exception as e:
331
+ res['success'] = False
332
+ res['note'] += f"; fatal: {e}"
333
+
334
+ print('RESULT:' + json.dumps(res))
335
+ `.trim();
336
+ const pyResp = await this.bridge.executePython(py);
337
+ let out = '';
338
+ if (pyResp?.LogOutput && Array.isArray(pyResp.LogOutput))
339
+ out = pyResp.LogOutput.map((l) => l.Output || '').join('');
340
+ else if (typeof pyResp === 'string')
341
+ out = pyResp;
342
+ else
343
+ out = JSON.stringify(pyResp);
344
+ const m = out.match(/RESULT:({.*})/);
345
+ if (m) {
346
+ try {
347
+ const parsed = JSON.parse(m[1]);
348
+ if (!parsed.success) {
349
+ return { success: false, error: parsed.note || 'Paint foliage failed' };
350
+ }
351
+ return {
352
+ success: true,
353
+ added: parsed.added,
354
+ actor: parsed.actor,
355
+ component: parsed.component,
356
+ usedMesh: parsed.used_mesh,
357
+ note: parsed.note,
358
+ message: `Painted ${parsed.added} instances for '${foliageType}' around (${pos[0]}, ${pos[1]}, ${pos[2]})`
359
+ };
360
+ }
361
+ catch {
362
+ return { success: false, error: 'Failed to parse Python result' };
363
+ }
364
+ }
365
+ return { success: false, error: 'No parseable result from Python' };
366
+ }
367
+ // Create instanced mesh
368
+ async createInstancedMesh(params) {
369
+ const commands = [];
370
+ commands.push(`CreateInstancedStaticMesh ${params.name} ${params.meshPath}`);
371
+ for (const instance of params.instances) {
372
+ const rot = instance.rotation || [0, 0, 0];
373
+ const scale = instance.scale || [1, 1, 1];
374
+ commands.push(`AddInstance ${params.name} ${instance.position.join(' ')} ${rot.join(' ')} ${scale.join(' ')}`);
375
+ }
376
+ if (params.enableCulling !== undefined) {
377
+ commands.push(`SetInstanceCulling ${params.name} ${params.enableCulling}`);
378
+ }
379
+ if (params.cullDistance !== undefined) {
380
+ commands.push(`SetInstanceCullDistance ${params.name} ${params.cullDistance}`);
381
+ }
382
+ for (const cmd of commands) {
383
+ await this.bridge.executeConsoleCommand(cmd);
384
+ }
385
+ return { success: true, message: `Instanced mesh ${params.name} created with ${params.instances.length} instances` };
386
+ }
387
+ // Set foliage LOD
388
+ async setFoliageLOD(params) {
389
+ const commands = [];
390
+ if (params.lodDistances) {
391
+ commands.push(`SetFoliageLODDistances ${params.foliageType} ${params.lodDistances.join(' ')}`);
392
+ }
393
+ if (params.screenSize) {
394
+ commands.push(`SetFoliageLODScreenSize ${params.foliageType} ${params.screenSize.join(' ')}`);
395
+ }
396
+ for (const cmd of commands) {
397
+ await this.bridge.executeConsoleCommand(cmd);
398
+ }
399
+ return { success: true, message: 'Foliage LOD settings updated' };
400
+ }
401
+ // Create procedural foliage
402
+ async createProceduralFoliage(params) {
403
+ const commands = [];
404
+ commands.push(`CreateProceduralFoliageVolume ${params.volumeName} ${params.position.join(' ')} ${params.size.join(' ')}`);
405
+ for (const type of params.foliageTypes) {
406
+ commands.push(`AddProceduralFoliageType ${params.volumeName} ${type}`);
407
+ }
408
+ if (params.seed !== undefined) {
409
+ commands.push(`SetProceduralSeed ${params.volumeName} ${params.seed}`);
410
+ }
411
+ if (params.tileSize !== undefined) {
412
+ commands.push(`SetProceduralTileSize ${params.volumeName} ${params.tileSize}`);
413
+ }
414
+ commands.push(`GenerateProceduralFoliage ${params.volumeName}`);
415
+ for (const cmd of commands) {
416
+ await this.bridge.executeConsoleCommand(cmd);
417
+ }
418
+ return { success: true, message: `Procedural foliage volume ${params.volumeName} created` };
419
+ }
420
+ // Set foliage collision
421
+ async setFoliageCollision(params) {
422
+ const commands = [];
423
+ if (params.collisionEnabled !== undefined) {
424
+ commands.push(`SetFoliageCollision ${params.foliageType} ${params.collisionEnabled}`);
425
+ }
426
+ if (params.collisionProfile) {
427
+ commands.push(`SetFoliageCollisionProfile ${params.foliageType} ${params.collisionProfile}`);
428
+ }
429
+ if (params.generateOverlapEvents !== undefined) {
430
+ commands.push(`SetFoliageOverlapEvents ${params.foliageType} ${params.generateOverlapEvents}`);
431
+ }
432
+ for (const cmd of commands) {
433
+ await this.bridge.executeConsoleCommand(cmd);
434
+ }
435
+ return { success: true, message: 'Foliage collision settings updated' };
436
+ }
437
+ // Create grass system
438
+ async createGrassSystem(params) {
439
+ const commands = [];
440
+ commands.push(`CreateGrassSystem ${params.name}`);
441
+ for (const grassType of params.grassTypes) {
442
+ const minScale = grassType.minScale || 0.8;
443
+ const maxScale = grassType.maxScale || 1.2;
444
+ commands.push(`AddGrassType ${params.name} ${grassType.meshPath} ${grassType.density} ${minScale} ${maxScale}`);
445
+ }
446
+ if (params.windStrength !== undefined) {
447
+ commands.push(`SetGrassWindStrength ${params.name} ${params.windStrength}`);
448
+ }
449
+ if (params.windSpeed !== undefined) {
450
+ commands.push(`SetGrassWindSpeed ${params.name} ${params.windSpeed}`);
451
+ }
452
+ for (const cmd of commands) {
453
+ await this.bridge.executeConsoleCommand(cmd);
454
+ }
455
+ return { success: true, message: `Grass system ${params.name} created` };
456
+ }
457
+ // Remove foliage instances
458
+ async removeFoliageInstances(params) {
459
+ const command = `RemoveFoliageInRadius ${params.foliageType} ${params.position.join(' ')} ${params.radius}`;
460
+ return this.bridge.executeConsoleCommand(command);
461
+ }
462
+ // Select foliage instances
463
+ async selectFoliageInstances(params) {
464
+ let command;
465
+ if (params.selectAll) {
466
+ command = `SelectAllFoliage ${params.foliageType}`;
467
+ }
468
+ else if (params.position && params.radius) {
469
+ command = `SelectFoliageInRadius ${params.foliageType} ${params.position.join(' ')} ${params.radius}`;
470
+ }
471
+ else {
472
+ command = `SelectFoliageType ${params.foliageType}`;
473
+ }
474
+ return this.bridge.executeConsoleCommand(command);
475
+ }
476
+ // Update foliage instances
477
+ async updateFoliageInstances(params) {
478
+ const commands = [];
479
+ if (params.updateTransforms) {
480
+ commands.push(`UpdateFoliageTransforms ${params.foliageType}`);
481
+ }
482
+ if (params.updateMesh && params.newMeshPath) {
483
+ commands.push(`UpdateFoliageMesh ${params.foliageType} ${params.newMeshPath}`);
484
+ }
485
+ commands.push(`RefreshFoliage ${params.foliageType}`);
486
+ for (const cmd of commands) {
487
+ await this.bridge.executeConsoleCommand(cmd);
488
+ }
489
+ return { success: true, message: 'Foliage instances updated' };
490
+ }
491
+ // Create foliage spawner
492
+ async createFoliageSpawner(params) {
493
+ const commands = [];
494
+ commands.push(`CreateFoliageSpawner ${params.name} ${params.spawnArea}`);
495
+ if (params.excludeAreas) {
496
+ for (const area of params.excludeAreas) {
497
+ commands.push(`AddFoliageExclusionArea ${params.name} ${area.join(' ')}`);
498
+ }
499
+ }
500
+ for (const cmd of commands) {
501
+ await this.bridge.executeConsoleCommand(cmd);
502
+ }
503
+ return { success: true, message: `Foliage spawner ${params.name} created` };
504
+ }
505
+ // Optimize foliage
506
+ async optimizeFoliage(params) {
507
+ const commands = [];
508
+ if (params.mergeInstances) {
509
+ commands.push('MergeFoliageInstances');
510
+ }
511
+ if (params.generateClusters) {
512
+ const size = params.clusterSize || 100;
513
+ commands.push(`GenerateFoliageClusters ${size}`);
514
+ }
515
+ if (params.reduceDrawCalls) {
516
+ commands.push('OptimizeFoliageDrawCalls');
517
+ }
518
+ commands.push('RebuildFoliageTree');
519
+ for (const cmd of commands) {
520
+ await this.bridge.executeConsoleCommand(cmd);
521
+ }
522
+ return { success: true, message: 'Foliage optimized' };
523
+ }
524
+ }
525
+ //# sourceMappingURL=foliage.js.map
@@ -0,0 +1,98 @@
1
+ import { UnrealBridge } from '../unreal-bridge.js';
2
+ export interface ObjectInfo {
3
+ class: string;
4
+ name: string;
5
+ path: string;
6
+ properties: PropertyInfo[];
7
+ functions?: FunctionInfo[];
8
+ parent?: string;
9
+ interfaces?: string[];
10
+ flags?: string[];
11
+ }
12
+ export interface PropertyInfo {
13
+ name: string;
14
+ type: string;
15
+ value?: any;
16
+ flags?: string[];
17
+ metadata?: Record<string, any>;
18
+ category?: string;
19
+ tooltip?: string;
20
+ }
21
+ export interface FunctionInfo {
22
+ name: string;
23
+ parameters: ParameterInfo[];
24
+ returnType?: string;
25
+ flags?: string[];
26
+ category?: string;
27
+ }
28
+ export interface ParameterInfo {
29
+ name: string;
30
+ type: string;
31
+ defaultValue?: any;
32
+ isOptional?: boolean;
33
+ }
34
+ export declare class IntrospectionTools {
35
+ private bridge;
36
+ private log;
37
+ private objectCache;
38
+ private retryAttempts;
39
+ private retryDelay;
40
+ constructor(bridge: UnrealBridge);
41
+ /**
42
+ * Execute with retry logic for transient failures
43
+ */
44
+ private executeWithRetry;
45
+ /**
46
+ * Parse Python execution result with better error handling
47
+ */
48
+ private parsePythonResult;
49
+ /**
50
+ * Convert Unreal property value to JavaScript-friendly format
51
+ */
52
+ private convertPropertyValue;
53
+ inspectObject(params: {
54
+ objectPath: string;
55
+ detailed?: boolean;
56
+ }): Promise<any>;
57
+ setProperty(params: {
58
+ objectPath: string;
59
+ propertyName: string;
60
+ value: any;
61
+ }): Promise<{
62
+ success: boolean;
63
+ result: any;
64
+ error?: undefined;
65
+ } | {
66
+ success: boolean;
67
+ error: any;
68
+ result?: undefined;
69
+ }>;
70
+ /**
71
+ * Get property value of an object
72
+ */
73
+ getProperty(params: {
74
+ objectPath: string;
75
+ propertyName: string;
76
+ }): Promise<any>;
77
+ /**
78
+ * Call a function on an object
79
+ */
80
+ callFunction(params: {
81
+ objectPath: string;
82
+ functionName: string;
83
+ parameters?: any[];
84
+ }): Promise<any>;
85
+ /**
86
+ * Get Class Default Object (CDO) for a class
87
+ */
88
+ getCDO(className: string): Promise<any>;
89
+ /**
90
+ * Search for objects by class
91
+ */
92
+ findObjectsByClass(className: string, limit?: number): Promise<any>;
93
+ /**
94
+ * Clear object cache
95
+ */
96
+ clearCache(): void;
97
+ }
98
+ //# sourceMappingURL=introspection.d.ts.map