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,683 @@
1
+ import { Logger } from '../utils/logger.js';
2
+ export class IntrospectionTools {
3
+ bridge;
4
+ log = new Logger('IntrospectionTools');
5
+ objectCache = new Map();
6
+ retryAttempts = 3;
7
+ retryDelay = 1000;
8
+ constructor(bridge) {
9
+ this.bridge = bridge;
10
+ }
11
+ /**
12
+ * Execute with retry logic for transient failures
13
+ */
14
+ async executeWithRetry(operation, operationName) {
15
+ let lastError;
16
+ for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
17
+ try {
18
+ return await operation();
19
+ }
20
+ catch (error) {
21
+ lastError = error;
22
+ this.log.warn(`${operationName} attempt ${attempt} failed: ${error.message || error}`);
23
+ if (attempt < this.retryAttempts) {
24
+ await new Promise(resolve => setTimeout(resolve, this.retryDelay * attempt));
25
+ }
26
+ }
27
+ }
28
+ throw lastError;
29
+ }
30
+ /**
31
+ * Parse Python execution result with better error handling
32
+ */
33
+ parsePythonResult(resp, operationName) {
34
+ let out = '';
35
+ if (resp?.LogOutput && Array.isArray(resp.LogOutput)) {
36
+ out = resp.LogOutput.map((l) => l.Output || '').join('');
37
+ }
38
+ else if (typeof resp === 'string') {
39
+ out = resp;
40
+ }
41
+ else {
42
+ out = JSON.stringify(resp);
43
+ }
44
+ const m = out.match(/RESULT:({.*})/);
45
+ if (m) {
46
+ try {
47
+ return JSON.parse(m[1]);
48
+ }
49
+ catch (e) {
50
+ this.log.error(`Failed to parse ${operationName} result: ${e}`);
51
+ }
52
+ }
53
+ // Check for common error patterns
54
+ if (out.includes('ModuleNotFoundError')) {
55
+ return { success: false, error: 'Reflection module not available.' };
56
+ }
57
+ if (out.includes('AttributeError')) {
58
+ return { success: false, error: 'Reflection API method not found. Check Unreal Engine version compatibility.' };
59
+ }
60
+ return { success: false, error: `${operationName} did not return a valid result: ${out.substring(0, 200)}` };
61
+ }
62
+ /**
63
+ * Convert Unreal property value to JavaScript-friendly format
64
+ */
65
+ convertPropertyValue(value, typeName) {
66
+ // Handle vectors, rotators, transforms
67
+ if (typeName.includes('Vector')) {
68
+ if (typeof value === 'object' && value !== null) {
69
+ return { x: value.X || 0, y: value.Y || 0, z: value.Z || 0 };
70
+ }
71
+ }
72
+ if (typeName.includes('Rotator')) {
73
+ if (typeof value === 'object' && value !== null) {
74
+ return { pitch: value.Pitch || 0, yaw: value.Yaw || 0, roll: value.Roll || 0 };
75
+ }
76
+ }
77
+ if (typeName.includes('Transform')) {
78
+ if (typeof value === 'object' && value !== null) {
79
+ return {
80
+ location: this.convertPropertyValue(value.Translation || value.Location, 'Vector'),
81
+ rotation: this.convertPropertyValue(value.Rotation, 'Rotator'),
82
+ scale: this.convertPropertyValue(value.Scale3D || value.Scale, 'Vector')
83
+ };
84
+ }
85
+ }
86
+ return value;
87
+ }
88
+ async inspectObject(params) {
89
+ // Check cache first if not requesting detailed info
90
+ if (!params.detailed && this.objectCache.has(params.objectPath)) {
91
+ const cached = this.objectCache.get(params.objectPath);
92
+ if (cached) {
93
+ return { success: true, info: cached };
94
+ }
95
+ }
96
+ const py = `
97
+ import unreal, json, inspect
98
+ path = r"${params.objectPath}"
99
+ detailed = ${params.detailed ? 'True' : 'False'}
100
+
101
+ def get_property_info(prop, obj=None):
102
+ """Extract detailed property information"""
103
+ try:
104
+ info = {
105
+ 'name': prop.get_name(),
106
+ 'type': prop.get_property_class_name() if hasattr(prop, 'get_property_class_name') else 'Unknown'
107
+ }
108
+
109
+ # Try to get property flags
110
+ flags = []
111
+ if hasattr(prop, 'has_any_property_flags'):
112
+ if prop.has_any_property_flags(unreal.PropertyFlags.CPF_EDIT_CONST):
113
+ flags.append('ReadOnly')
114
+ if prop.has_any_property_flags(unreal.PropertyFlags.CPF_BLUEPRINT_READ_ONLY):
115
+ flags.append('BlueprintReadOnly')
116
+ if prop.has_any_property_flags(unreal.PropertyFlags.CPF_TRANSIENT):
117
+ flags.append('Transient')
118
+ info['flags'] = flags
119
+
120
+ # Try to get metadata
121
+ if hasattr(prop, 'get_metadata'):
122
+ try:
123
+ info['category'] = prop.get_metadata('Category')
124
+ info['tooltip'] = prop.get_metadata('ToolTip')
125
+ except Exception:
126
+ pass
127
+
128
+ # Try to get current value if object provided
129
+ if obj and detailed:
130
+ try:
131
+ value = getattr(obj, prop.get_name())
132
+ # Convert complex types to serializable format
133
+ if hasattr(value, '__dict__'):
134
+ value = str(value)
135
+ info['value'] = value
136
+ except Exception:
137
+ pass
138
+
139
+ return info
140
+ except Exception as e:
141
+ return {'name': str(prop) if prop else 'Unknown', 'type': 'Unknown', 'error': str(e)}
142
+
143
+ try:
144
+ obj = unreal.load_object(None, path)
145
+ if not obj:
146
+ # Try as class if object load fails
147
+ try:
148
+ obj = unreal.load_class(None, path)
149
+ if not obj:
150
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Object or class not found'}))
151
+ raise SystemExit(0)
152
+ except Exception:
153
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Object not found'}))
154
+ raise SystemExit(0)
155
+
156
+ info = {
157
+ 'class': obj.get_class().get_name() if hasattr(obj, 'get_class') else str(type(obj)),
158
+ 'name': obj.get_name() if hasattr(obj, 'get_name') else '',
159
+ 'path': path,
160
+ 'properties': [],
161
+ 'functions': [],
162
+ 'flags': []
163
+ }
164
+
165
+ # Get parent class
166
+ try:
167
+ if hasattr(obj, 'get_class'):
168
+ cls = obj.get_class()
169
+ if hasattr(cls, 'get_super_class'):
170
+ super_cls = cls.get_super_class()
171
+ if super_cls:
172
+ info['parent'] = super_cls.get_name()
173
+ except Exception:
174
+ pass
175
+
176
+ # Get object flags
177
+ try:
178
+ if hasattr(obj, 'has_any_flags'):
179
+ flags = []
180
+ if obj.has_any_flags(unreal.ObjectFlags.RF_PUBLIC):
181
+ flags.append('Public')
182
+ if obj.has_any_flags(unreal.ObjectFlags.RF_TRANSIENT):
183
+ flags.append('Transient')
184
+ if obj.has_any_flags(unreal.ObjectFlags.RF_DEFAULT_SUB_OBJECT):
185
+ flags.append('DefaultSubObject')
186
+ info['flags'] = flags
187
+ except Exception:
188
+ pass
189
+
190
+ # Get properties - AVOID deprecated properties completely
191
+ props = []
192
+
193
+ # List of deprecated properties to completely skip
194
+ deprecated_props = [
195
+ 'life_span', 'on_actor_touch', 'on_actor_un_touch',
196
+ 'get_touching_actors', 'get_touching_components',
197
+ 'controller_class', 'look_up_scale', 'sound_wave_param',
198
+ 'material_substitute', 'texture_object'
199
+ ]
200
+
201
+ try:
202
+ cls = obj.get_class() if hasattr(obj, 'get_class') else obj
203
+
204
+ # Try UE5 reflection API with safe property access
205
+ if hasattr(cls, 'get_properties'):
206
+ for prop in cls.get_properties():
207
+ prop_name = None
208
+ try:
209
+ # Safe property name extraction
210
+ if hasattr(prop, 'get_name'):
211
+ prop_name = prop.get_name()
212
+ elif hasattr(prop, 'name'):
213
+ prop_name = prop.name
214
+
215
+ # Skip if deprecated
216
+ if prop_name and prop_name not in deprecated_props:
217
+ prop_info = get_property_info(prop, obj if detailed else None)
218
+ if prop_info.get('name') not in deprecated_props:
219
+ props.append(prop_info)
220
+ except Exception:
221
+ pass
222
+
223
+ # If reflection API didn't work, use a safe property list
224
+ if not props:
225
+ # Only access known safe properties
226
+ safe_properties = [
227
+ 'actor_guid', 'actor_instance_guid', 'always_relevant',
228
+ 'auto_destroy_when_finished', 'can_be_damaged',
229
+ 'content_bundle_guid', 'custom_time_dilation',
230
+ 'enable_auto_lod_generation', 'find_camera_component_when_view_target',
231
+ 'generate_overlap_events_during_level_streaming', 'hidden',
232
+ 'initial_life_span', # Use new name instead of life_span
233
+ 'instigator', 'is_spatially_loaded', 'min_net_update_frequency',
234
+ 'net_cull_distance_squared', 'net_dormancy', 'net_priority',
235
+ 'net_update_frequency', 'net_use_owner_relevancy',
236
+ 'only_relevant_to_owner', 'pivot_offset',
237
+ 'replicate_using_registered_sub_object_list', 'replicates',
238
+ 'root_component', 'runtime_grid', 'spawn_collision_handling_method',
239
+ 'sprite_scale', 'tags', 'location', 'rotation', 'scale'
240
+ ]
241
+
242
+ for prop_name in safe_properties:
243
+ try:
244
+ # Use get_editor_property for safer access
245
+ if hasattr(obj, 'get_editor_property'):
246
+ val = obj.get_editor_property(prop_name)
247
+ props.append({
248
+ 'name': prop_name,
249
+ 'type': type(val).__name__ if val is not None else 'None',
250
+ 'value': str(val)[:100] if detailed and val is not None else None
251
+ })
252
+ elif hasattr(obj, prop_name):
253
+ # Direct access only for safe properties
254
+ val = getattr(obj, prop_name)
255
+ if not callable(val):
256
+ props.append({
257
+ 'name': prop_name,
258
+ 'type': type(val).__name__,
259
+ 'value': str(val)[:100] if detailed else None
260
+ })
261
+ except Exception:
262
+ pass
263
+ except Exception as e:
264
+ # Minimal fallback with only essential safe properties
265
+ pass
266
+
267
+ info['properties'] = props
268
+
269
+ # Get functions/methods if detailed
270
+ if detailed:
271
+ funcs = []
272
+ try:
273
+ cls = obj.get_class() if hasattr(obj, 'get_class') else obj
274
+
275
+ # Try to get UFunctions
276
+ if hasattr(cls, 'get_functions'):
277
+ for func in cls.get_functions():
278
+ func_info = {
279
+ 'name': func.get_name(),
280
+ 'parameters': [],
281
+ 'flags': []
282
+ }
283
+ # Get parameters if possible
284
+ if hasattr(func, 'get_params'):
285
+ for param in func.get_params():
286
+ func_info['parameters'].append({
287
+ 'name': param.get_name() if hasattr(param, 'get_name') else str(param),
288
+ 'type': 'Unknown'
289
+ })
290
+ funcs.append(func_info)
291
+ else:
292
+ # Fallback: use known safe function names
293
+ safe_functions = [
294
+ 'get_actor_location', 'set_actor_location',
295
+ 'get_actor_rotation', 'set_actor_rotation',
296
+ 'get_actor_scale', 'set_actor_scale',
297
+ 'destroy_actor', 'destroy_component',
298
+ 'get_components', 'get_component_by_class',
299
+ 'add_actor_component', 'add_component',
300
+ 'get_world', 'get_name', 'get_path_name',
301
+ 'is_valid', 'is_a', 'has_authority'
302
+ ]
303
+ for func_name in safe_functions:
304
+ if hasattr(obj, func_name):
305
+ try:
306
+ attr_value = getattr(obj, func_name)
307
+ if callable(attr_value):
308
+ funcs.append({
309
+ 'name': func_name,
310
+ 'parameters': [],
311
+ 'flags': []
312
+ })
313
+ except Exception:
314
+ pass
315
+ except Exception:
316
+ pass
317
+
318
+ info['functions'] = funcs
319
+
320
+ print('RESULT:' + json.dumps({'success': True, 'info': info}))
321
+ except Exception as e:
322
+ print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
323
+ `.trim();
324
+ const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'inspectObject');
325
+ const result = this.parsePythonResult(resp, 'inspectObject');
326
+ // Cache the result if successful and not detailed
327
+ if (result.success && result.info && !params.detailed) {
328
+ this.objectCache.set(params.objectPath, result.info);
329
+ }
330
+ return result;
331
+ }
332
+ async setProperty(params) {
333
+ return this.executeWithRetry(async () => {
334
+ try {
335
+ // Validate and convert value type if needed
336
+ let processedValue = params.value;
337
+ // Handle special Unreal types
338
+ if (typeof params.value === 'object' && params.value !== null) {
339
+ // Vector conversion
340
+ if ('x' in params.value || 'X' in params.value) {
341
+ processedValue = {
342
+ X: params.value.x || params.value.X || 0,
343
+ Y: params.value.y || params.value.Y || 0,
344
+ Z: params.value.z || params.value.Z || 0
345
+ };
346
+ }
347
+ // Rotator conversion
348
+ else if ('pitch' in params.value || 'Pitch' in params.value) {
349
+ processedValue = {
350
+ Pitch: params.value.pitch || params.value.Pitch || 0,
351
+ Yaw: params.value.yaw || params.value.Yaw || 0,
352
+ Roll: params.value.roll || params.value.Roll || 0
353
+ };
354
+ }
355
+ // Transform conversion
356
+ else if ('location' in params.value || 'Location' in params.value) {
357
+ processedValue = {
358
+ Translation: this.convertPropertyValue(params.value.location || params.value.Location, 'Vector'),
359
+ Rotation: this.convertPropertyValue(params.value.rotation || params.value.Rotation, 'Rotator'),
360
+ Scale3D: this.convertPropertyValue(params.value.scale || params.value.Scale || { x: 1, y: 1, z: 1 }, 'Vector')
361
+ };
362
+ }
363
+ }
364
+ const res = await this.bridge.httpCall('/remote/object/property', 'PUT', {
365
+ objectPath: params.objectPath,
366
+ propertyName: params.propertyName,
367
+ propertyValue: processedValue
368
+ });
369
+ // Clear cache for this object
370
+ this.objectCache.delete(params.objectPath);
371
+ return { success: true, result: res };
372
+ }
373
+ catch (err) {
374
+ const errorMsg = err?.message || String(err);
375
+ if (errorMsg.includes('404')) {
376
+ return { success: false, error: `Property '${params.propertyName}' not found on object '${params.objectPath}'` };
377
+ }
378
+ if (errorMsg.includes('400')) {
379
+ return { success: false, error: `Invalid value type for property '${params.propertyName}'` };
380
+ }
381
+ return { success: false, error: errorMsg };
382
+ }
383
+ }, 'setProperty');
384
+ }
385
+ /**
386
+ * Get property value of an object
387
+ */
388
+ async getProperty(params) {
389
+ const py = `
390
+ import unreal, json
391
+ path = r"${params.objectPath}"
392
+ prop_name = r"${params.propertyName}"
393
+ try:
394
+ obj = unreal.load_object(None, path)
395
+ if not obj:
396
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Object not found'}))
397
+ else:
398
+ # Try different methods to get property
399
+ value = None
400
+ found = False
401
+
402
+ # Method 1: Direct attribute access
403
+ if hasattr(obj, prop_name):
404
+ try:
405
+ value = getattr(obj, prop_name)
406
+ found = True
407
+ except Exception:
408
+ pass
409
+
410
+ # Method 2: get_editor_property (UE4/5)
411
+ if not found and hasattr(obj, 'get_editor_property'):
412
+ try:
413
+ value = obj.get_editor_property(prop_name)
414
+ found = True
415
+ except Exception:
416
+ pass
417
+
418
+ # Method 3: Try with common property name variations
419
+ if not found:
420
+ # Try common property name variations
421
+ variations = [
422
+ prop_name,
423
+ prop_name.lower(),
424
+ prop_name.upper(),
425
+ prop_name.capitalize(),
426
+ # Convert snake_case to CamelCase
427
+ ''.join(word.capitalize() for word in prop_name.split('_')),
428
+ # Convert CamelCase to snake_case
429
+ ''.join(['_' + c.lower() if c.isupper() else c for c in prop_name]).lstrip('_')
430
+ ]
431
+ for variant in variations:
432
+ if hasattr(obj, variant):
433
+ try:
434
+ value = getattr(obj, variant)
435
+ found = True
436
+ break
437
+ except Exception:
438
+ pass
439
+
440
+ if found:
441
+ # Convert complex types to string
442
+ if hasattr(value, '__dict__'):
443
+ value = str(value)
444
+ elif isinstance(value, (list, tuple, dict)):
445
+ value = json.dumps(value)
446
+
447
+ print('RESULT:' + json.dumps({'success': True, 'value': value}))
448
+ else:
449
+ print('RESULT:' + json.dumps({'success': False, 'error': f'Property {prop_name} not found'}))
450
+ except Exception as e:
451
+ print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
452
+ `.trim();
453
+ const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'getProperty');
454
+ return this.parsePythonResult(resp, 'getProperty');
455
+ }
456
+ /**
457
+ * Call a function on an object
458
+ */
459
+ async callFunction(params) {
460
+ const py = `
461
+ import unreal, json
462
+ path = r"${params.objectPath}"
463
+ func_name = r"${params.functionName}"
464
+ params = ${JSON.stringify(params.parameters || [])}
465
+ try:
466
+ obj = unreal.load_object(None, path)
467
+ if not obj:
468
+ # Try loading as class if object fails
469
+ try:
470
+ obj = unreal.load_class(None, path)
471
+ except:
472
+ pass
473
+
474
+ if not obj:
475
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Object not found'}))
476
+ else:
477
+ # For KismetMathLibrary or similar utility classes, use static method call
478
+ if 'KismetMathLibrary' in path or 'MathLibrary' in path or 'GameplayStatics' in path:
479
+ try:
480
+ # Use Unreal's MathLibrary (KismetMathLibrary is exposed as MathLibrary in Python)
481
+ if func_name.lower() == 'abs':
482
+ # Use Unreal's MathLibrary.abs function
483
+ result = unreal.MathLibrary.abs(float(params[0])) if params else 0
484
+ print('RESULT:' + json.dumps({'success': True, 'result': result}))
485
+ elif func_name.lower() == 'sqrt':
486
+ # Use Unreal's MathLibrary.sqrt function
487
+ result = unreal.MathLibrary.sqrt(float(params[0])) if params else 0
488
+ print('RESULT:' + json.dumps({'success': True, 'result': result}))
489
+ else:
490
+ # Try to call as static method
491
+ if hasattr(obj, func_name):
492
+ func = getattr(obj, func_name)
493
+ if callable(func):
494
+ result = func(*params) if params else func()
495
+ if hasattr(result, '__dict__'):
496
+ result = str(result)
497
+ print('RESULT:' + json.dumps({'success': True, 'result': result}))
498
+ else:
499
+ print('RESULT:' + json.dumps({'success': False, 'error': f'{func_name} is not callable'}))
500
+ else:
501
+ # Try snake_case version
502
+ snake_case_name = ''.join(['_' + c.lower() if c.isupper() else c for c in func_name]).lstrip('_')
503
+ if hasattr(obj, snake_case_name):
504
+ func = getattr(obj, snake_case_name)
505
+ result = func(*params) if params else func()
506
+ print('RESULT:' + json.dumps({'success': True, 'result': result}))
507
+ else:
508
+ print('RESULT:' + json.dumps({'success': False, 'error': f'Function {func_name} not found'}))
509
+ except Exception as e:
510
+ print('RESULT:' + json.dumps({'success': False, 'error': f'Function call failed: {str(e)}'}))
511
+ else:
512
+ # Regular object method call
513
+ if hasattr(obj, func_name):
514
+ func = getattr(obj, func_name)
515
+ if callable(func):
516
+ try:
517
+ result = func(*params) if params else func()
518
+ # Convert result to serializable format
519
+ if hasattr(result, '__dict__'):
520
+ result = str(result)
521
+ print('RESULT:' + json.dumps({'success': True, 'result': result}))
522
+ except Exception as e:
523
+ print('RESULT:' + json.dumps({'success': False, 'error': f'Function call failed: {str(e)}'}))
524
+ else:
525
+ print('RESULT:' + json.dumps({'success': False, 'error': f'{func_name} is not callable'}))
526
+ else:
527
+ print('RESULT:' + json.dumps({'success': False, 'error': f'Function {func_name} not found'}))
528
+ except Exception as e:
529
+ print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
530
+ `.trim();
531
+ const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'callFunction');
532
+ return this.parsePythonResult(resp, 'callFunction');
533
+ }
534
+ /**
535
+ * Get Class Default Object (CDO) for a class
536
+ */
537
+ async getCDO(className) {
538
+ const py = `
539
+ import unreal, json
540
+ class_name = r"${className}"
541
+ try:
542
+ # Try to find the class
543
+ cls = None
544
+
545
+ # Method 1: Direct class load
546
+ try:
547
+ cls = unreal.load_class(None, class_name)
548
+ except Exception:
549
+ pass
550
+
551
+ # Method 2: Find class by name
552
+ if not cls:
553
+ try:
554
+ cls = unreal.find_class(class_name)
555
+ except Exception:
556
+ pass
557
+
558
+ # Method 3: Search in loaded classes
559
+ if not cls:
560
+ for obj in unreal.ObjectLibrary.get_all_objects():
561
+ if hasattr(obj, 'get_class'):
562
+ obj_cls = obj.get_class()
563
+ if obj_cls.get_name() == class_name:
564
+ cls = obj_cls
565
+ break
566
+
567
+ if not cls:
568
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Class not found'}))
569
+ else:
570
+ # Get CDO
571
+ cdo = cls.get_default_object() if hasattr(cls, 'get_default_object') else None
572
+
573
+ if cdo:
574
+ info = {
575
+ 'className': cls.get_name(),
576
+ 'cdoPath': cdo.get_path_name() if hasattr(cdo, 'get_path_name') else '',
577
+ 'properties': []
578
+ }
579
+
580
+ # Get default property values using safe property list
581
+ safe_cdo_properties = [
582
+ 'initial_life_span', 'hidden', 'can_be_damaged', 'replicates',
583
+ 'always_relevant', 'net_dormancy', 'net_priority',
584
+ 'net_update_frequency', 'replicate_movement',
585
+ 'actor_guid', 'tags', 'root_component',
586
+ 'auto_destroy_when_finished', 'enable_auto_lod_generation'
587
+ ]
588
+ for prop_name in safe_cdo_properties:
589
+ try:
590
+ if hasattr(cdo, 'get_editor_property'):
591
+ value = cdo.get_editor_property(prop_name)
592
+ info['properties'].append({
593
+ 'name': prop_name,
594
+ 'defaultValue': str(value)[:100]
595
+ })
596
+ elif hasattr(cdo, prop_name):
597
+ value = getattr(cdo, prop_name)
598
+ if not callable(value):
599
+ info['properties'].append({
600
+ 'name': prop_name,
601
+ 'defaultValue': str(value)[:100]
602
+ })
603
+ except Exception:
604
+ pass
605
+
606
+ print('RESULT:' + json.dumps({'success': True, 'cdo': info}))
607
+ else:
608
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Could not get CDO'}))
609
+ except Exception as e:
610
+ print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
611
+ `.trim();
612
+ const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'getCDO');
613
+ return this.parsePythonResult(resp, 'getCDO');
614
+ }
615
+ /**
616
+ * Search for objects by class
617
+ */
618
+ async findObjectsByClass(className, limit = 100) {
619
+ const py = `
620
+ import unreal, json
621
+ class_name = r"${className}"
622
+ limit = ${limit}
623
+ try:
624
+ objects = []
625
+ count = 0
626
+
627
+ # Use EditorAssetLibrary to find assets
628
+ try:
629
+ all_assets = unreal.EditorAssetLibrary.list_assets("/Game", recursive=True)
630
+ for asset_path in all_assets:
631
+ if count >= limit:
632
+ break
633
+ try:
634
+ asset = unreal.EditorAssetLibrary.load_asset(asset_path)
635
+ if asset:
636
+ asset_class = asset.get_class() if hasattr(asset, 'get_class') else None
637
+ if asset_class and class_name in asset_class.get_name():
638
+ objects.append({
639
+ 'path': asset_path,
640
+ 'name': asset.get_name() if hasattr(asset, 'get_name') else '',
641
+ 'class': asset_class.get_name()
642
+ })
643
+ count += 1
644
+ except Exception:
645
+ pass
646
+ except Exception as e:
647
+ print('RESULT:' + json.dumps({'success': False, 'error': f'Asset search failed: {str(e)}'}))
648
+ raise SystemExit(0)
649
+
650
+ # Also search in level actors
651
+ try:
652
+ actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
653
+ if actor_sub:
654
+ for actor in actor_sub.get_all_level_actors():
655
+ if count >= limit:
656
+ break
657
+ if actor:
658
+ actor_class = actor.get_class() if hasattr(actor, 'get_class') else None
659
+ if actor_class and class_name in actor_class.get_name():
660
+ objects.append({
661
+ 'path': actor.get_path_name() if hasattr(actor, 'get_path_name') else '',
662
+ 'name': actor.get_actor_label() if hasattr(actor, 'get_actor_label') else '',
663
+ 'class': actor_class.get_name()
664
+ })
665
+ count += 1
666
+ except Exception:
667
+ pass
668
+
669
+ print('RESULT:' + json.dumps({'success': True, 'objects': objects, 'count': len(objects)}))
670
+ except Exception as e:
671
+ print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
672
+ `.trim();
673
+ const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'findObjectsByClass');
674
+ return this.parsePythonResult(resp, 'findObjectsByClass');
675
+ }
676
+ /**
677
+ * Clear object cache
678
+ */
679
+ clearCache() {
680
+ this.objectCache.clear();
681
+ }
682
+ }
683
+ //# sourceMappingURL=introspection.js.map