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,110 @@
1
+ import { UnrealBridge } from '../unreal-bridge.js';
2
+ export interface RCPreset {
3
+ id: string;
4
+ name: string;
5
+ path: string;
6
+ description?: string;
7
+ exposedEntities?: RCExposedEntity[];
8
+ }
9
+ export interface RCExposedEntity {
10
+ id: string;
11
+ label: string;
12
+ type: 'property' | 'function' | 'actor';
13
+ objectPath?: string;
14
+ propertyName?: string;
15
+ functionName?: string;
16
+ metadata?: Record<string, any>;
17
+ }
18
+ export declare class RcTools {
19
+ private bridge;
20
+ private log;
21
+ private presetCache;
22
+ private retryAttempts;
23
+ private retryDelay;
24
+ constructor(bridge: UnrealBridge);
25
+ /**
26
+ * Execute with retry logic for transient failures
27
+ */
28
+ private executeWithRetry;
29
+ /**
30
+ * Parse Python execution result with better error handling
31
+ */
32
+ private parsePythonResult;
33
+ createPreset(params: {
34
+ name: string;
35
+ path?: string;
36
+ }): Promise<any>;
37
+ exposeActor(params: {
38
+ presetPath: string;
39
+ actorName: string;
40
+ }): Promise<any>;
41
+ exposeProperty(params: {
42
+ presetPath: string;
43
+ objectPath: string;
44
+ propertyName: string;
45
+ }): Promise<any>;
46
+ listFields(params: {
47
+ presetPath: string;
48
+ }): Promise<any>;
49
+ setProperty(params: {
50
+ objectPath: string;
51
+ propertyName: string;
52
+ value: any;
53
+ }): Promise<{
54
+ success: boolean;
55
+ result: any;
56
+ error?: undefined;
57
+ } | {
58
+ success: boolean;
59
+ error: any;
60
+ result?: undefined;
61
+ }>;
62
+ getProperty(params: {
63
+ objectPath: string;
64
+ propertyName: string;
65
+ }): Promise<{
66
+ success: boolean;
67
+ value: any;
68
+ error?: undefined;
69
+ } | {
70
+ success: boolean;
71
+ error: any;
72
+ value?: undefined;
73
+ }>;
74
+ /**
75
+ * List all available Remote Control presets
76
+ */
77
+ listPresets(): Promise<{
78
+ success: boolean;
79
+ presets?: RCPreset[];
80
+ error?: string;
81
+ }>;
82
+ /**
83
+ * Delete a Remote Control preset
84
+ */
85
+ deletePreset(presetId: string): Promise<{
86
+ success: boolean;
87
+ error?: string;
88
+ }>;
89
+ /**
90
+ * Call an exposed function through Remote Control
91
+ */
92
+ callFunction(params: {
93
+ presetPath: string;
94
+ functionName: string;
95
+ parameters?: Record<string, any>;
96
+ }): Promise<{
97
+ success: boolean;
98
+ result?: any;
99
+ error?: string;
100
+ }>;
101
+ /**
102
+ * Validate connection to Remote Control
103
+ */
104
+ validateConnection(): Promise<boolean>;
105
+ /**
106
+ * Clear preset cache
107
+ */
108
+ clearCache(): void;
109
+ }
110
+ //# sourceMappingURL=rc.d.ts.map
@@ -0,0 +1,363 @@
1
+ import { Logger } from '../utils/logger.js';
2
+ export class RcTools {
3
+ bridge;
4
+ log = new Logger('RcTools');
5
+ presetCache = 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: 'Remote Control module not available. Ensure Remote Control plugin is enabled.' };
56
+ }
57
+ if (out.includes('AttributeError')) {
58
+ return { success: false, error: 'Remote Control 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
+ // Create a Remote Control Preset asset
63
+ async createPreset(params) {
64
+ const name = params.name?.trim();
65
+ const path = (params.path || '/Game/RCPresets').replace(/\/$/, '');
66
+ if (!name)
67
+ return { success: false, error: 'Preset name is required' };
68
+ const python = `
69
+ import unreal, json
70
+ import time
71
+ name = r"${name}"
72
+ base_path = r"${path}"
73
+ full_path = f"{base_path}/{name}"
74
+ try:
75
+ # Check if asset already exists
76
+ if unreal.EditorAssetLibrary.does_asset_exist(full_path):
77
+ # If it exists, add a timestamp suffix to create a unique name
78
+ timestamp = str(int(time.time() * 1000))
79
+ unique_name = f"{name}_{timestamp}"
80
+ full_path = f"{base_path}/{unique_name}"
81
+ # Check again to ensure uniqueness
82
+ if unreal.EditorAssetLibrary.does_asset_exist(full_path):
83
+ print('RESULT:' + json.dumps({'success': True, 'presetPath': full_path, 'existing': True}))
84
+ else:
85
+ # Continue with creation using unique name
86
+ name = unique_name
87
+ # Now create the preset if it doesn't exist
88
+ if not unreal.EditorAssetLibrary.does_asset_exist(full_path):
89
+ # Ensure directory exists
90
+ if not unreal.EditorAssetLibrary.does_directory_exist(base_path):
91
+ unreal.EditorAssetLibrary.make_directory(base_path)
92
+
93
+ asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
94
+ factory = None
95
+ try:
96
+ factory = unreal.RemoteControlPresetFactory()
97
+ except Exception:
98
+ # Factory might not be available in older versions
99
+ factory = None
100
+
101
+ asset = None
102
+ try:
103
+ if factory is not None:
104
+ asset = asset_tools.create_asset(asset_name=name, package_path=base_path, asset_class=unreal.RemoteControlPreset, factory=factory)
105
+ else:
106
+ # Try alternative creation method
107
+ asset = asset_tools.create_asset(asset_name=name, package_path=base_path, asset_class=unreal.RemoteControlPreset, factory=None)
108
+ except Exception as e:
109
+ # If creation fails, try to provide helpful error
110
+ if "RemoteControlPreset" in str(e):
111
+ print('RESULT:' + json.dumps({'success': False, 'error': 'RemoteControlPreset class not available. Ensure Remote Control plugin is enabled.'}))
112
+ else:
113
+ print('RESULT:' + json.dumps({'success': False, 'error': f'Create asset failed: {str(e)}'}))
114
+ raise SystemExit(0)
115
+
116
+ if asset:
117
+ # Save with suppressed validation warnings
118
+ try:
119
+ unreal.EditorAssetLibrary.save_asset(full_path, only_if_is_dirty=False)
120
+ print('RESULT:' + json.dumps({'success': True, 'presetPath': full_path}))
121
+ except Exception as save_err:
122
+ # Asset was created but save had warnings - still consider success
123
+ print('RESULT:' + json.dumps({'success': True, 'presetPath': full_path, 'warning': 'Asset created with validation warnings'}))
124
+ else:
125
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Preset creation returned None'}))
126
+ except Exception as e:
127
+ print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
128
+ `.trim();
129
+ const resp = await this.executeWithRetry(() => this.bridge.executePython(python), 'createPreset');
130
+ const result = this.parsePythonResult(resp, 'createPreset');
131
+ // Cache the preset if successful
132
+ if (result.success && result.presetPath) {
133
+ const preset = {
134
+ id: result.presetPath,
135
+ name: name,
136
+ path: result.presetPath,
137
+ description: `Created at ${new Date().toISOString()}`
138
+ };
139
+ this.presetCache.set(preset.id, preset);
140
+ }
141
+ return result;
142
+ }
143
+ // Expose an actor by label/name into a preset
144
+ async exposeActor(params) {
145
+ const python = `\nimport unreal, json\npreset_path = r"${params.presetPath}"\nactor_name = r"${params.actorName}"\ntry:\n preset = unreal.EditorAssetLibrary.load_asset(preset_path)\n if not preset:\n print('RESULT:' + json.dumps({'success': False, 'error': 'Preset not found'}))\n else:\n actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)\n target = None\n for a in actor_sub.get_all_level_actors():\n if not a: continue\n try:\n if a.get_actor_label() == actor_name or a.get_name() == actor_name:\n target = a; break\n except Exception: pass\n if not target:\n print('RESULT:' + json.dumps({'success': False, 'error': 'Actor not found'}))\n else:\n try:\n unreal.RemoteControlFunctionLibrary.expose_actor(preset, target, None)\n unreal.EditorAssetLibrary.save_asset(preset_path)\n print('RESULT:' + json.dumps({'success': True}))\n except Exception as e:\n print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))\nexcept Exception as e:\n print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))\n`.trim();
146
+ const resp = await this.executeWithRetry(() => this.bridge.executePython(python), 'exposeActor');
147
+ const result = this.parsePythonResult(resp, 'exposeActor');
148
+ // Clear cache for this preset to force refresh
149
+ if (result.success) {
150
+ this.presetCache.delete(params.presetPath);
151
+ }
152
+ return result;
153
+ }
154
+ // Expose a property on an object into a preset
155
+ async exposeProperty(params) {
156
+ const python = `\nimport unreal, json\npreset_path = r"${params.presetPath}"\nobj_path = r"${params.objectPath}"\nprop_name = r"${params.propertyName}"\ntry:\n preset = unreal.EditorAssetLibrary.load_asset(preset_path)\n obj = unreal.load_object(None, obj_path)\n if not preset or not obj:\n print('RESULT:' + json.dumps({'success': False, 'error': 'Preset or object not found'}))\n else:\n try:\n unreal.RemoteControlFunctionLibrary.expose_property(preset, obj, prop_name, None)\n unreal.EditorAssetLibrary.save_asset(preset_path)\n print('RESULT:' + json.dumps({'success': True}))\n except Exception as e:\n print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))\nexcept Exception as e:\n print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))\n`.trim();
157
+ const resp = await this.executeWithRetry(() => this.bridge.executePython(python), 'exposeProperty');
158
+ const result = this.parsePythonResult(resp, 'exposeProperty');
159
+ // Clear cache for this preset to force refresh
160
+ if (result.success) {
161
+ this.presetCache.delete(params.presetPath);
162
+ }
163
+ return result;
164
+ }
165
+ // List exposed fields (best-effort)
166
+ async listFields(params) {
167
+ const python = `
168
+ import unreal, json
169
+ preset_path = r"${params.presetPath}"
170
+ try:
171
+ # First check if the asset exists
172
+ if not preset_path or not preset_path.startswith('/Game/'):
173
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Invalid preset path. Must start with /Game/'}))
174
+ elif not unreal.EditorAssetLibrary.does_asset_exist(preset_path):
175
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Preset not found at path: ' + preset_path}))
176
+ else:
177
+ preset = unreal.EditorAssetLibrary.load_asset(preset_path)
178
+ if not preset:
179
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Failed to load preset'}))
180
+ else:
181
+ fields = []
182
+ try:
183
+ # Try to get exposed entities
184
+ if hasattr(preset, 'get_exposed_entities'):
185
+ for entity in preset.get_exposed_entities():
186
+ try:
187
+ fields.append({
188
+ 'id': str(entity.id) if hasattr(entity, 'id') else '',
189
+ 'label': str(entity.label) if hasattr(entity, 'label') else '',
190
+ 'path': str(getattr(entity, 'path', ''))
191
+ })
192
+ except Exception:
193
+ pass
194
+ except Exception as e:
195
+ # Method might not exist or be accessible
196
+ pass
197
+ print('RESULT:' + json.dumps({'success': True, 'fields': fields}))
198
+ except Exception as e:
199
+ print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
200
+ `.trim();
201
+ const resp = await this.executeWithRetry(() => this.bridge.executePython(python), 'listFields');
202
+ return this.parsePythonResult(resp, 'listFields');
203
+ }
204
+ // Set a property value via Remote Control property endpoint
205
+ async setProperty(params) {
206
+ return this.executeWithRetry(async () => {
207
+ try {
208
+ // Validate value type and convert if needed
209
+ let processedValue = params.value;
210
+ // Handle special types
211
+ if (typeof params.value === 'object' && params.value !== null) {
212
+ // Check if it's a vector/rotator/transform
213
+ if ('x' in params.value || 'X' in params.value) {
214
+ processedValue = {
215
+ X: params.value.x || params.value.X || 0,
216
+ Y: params.value.y || params.value.Y || 0,
217
+ Z: params.value.z || params.value.Z || 0
218
+ };
219
+ }
220
+ }
221
+ const res = await this.bridge.httpCall('/remote/object/property', 'PUT', {
222
+ objectPath: params.objectPath,
223
+ propertyName: params.propertyName,
224
+ propertyValue: processedValue
225
+ });
226
+ return { success: true, result: res };
227
+ }
228
+ catch (err) {
229
+ // Check for specific error types
230
+ const errorMsg = err?.message || String(err);
231
+ if (errorMsg.includes('404')) {
232
+ return { success: false, error: `Property '${params.propertyName}' not found on object '${params.objectPath}'` };
233
+ }
234
+ if (errorMsg.includes('400')) {
235
+ return { success: false, error: `Invalid value type for property '${params.propertyName}'` };
236
+ }
237
+ return { success: false, error: errorMsg };
238
+ }
239
+ }, 'setProperty');
240
+ }
241
+ // Get a property value via Remote Control property endpoint
242
+ async getProperty(params) {
243
+ return this.executeWithRetry(async () => {
244
+ try {
245
+ const res = await this.bridge.httpCall('/remote/object/property', 'GET', {
246
+ objectPath: params.objectPath,
247
+ propertyName: params.propertyName
248
+ });
249
+ return { success: true, value: res };
250
+ }
251
+ catch (err) {
252
+ const errorMsg = err?.message || String(err);
253
+ if (errorMsg.includes('404')) {
254
+ return { success: false, error: `Property '${params.propertyName}' not found on object '${params.objectPath}'` };
255
+ }
256
+ return { success: false, error: errorMsg };
257
+ }
258
+ }, 'getProperty');
259
+ }
260
+ /**
261
+ * List all available Remote Control presets
262
+ */
263
+ async listPresets() {
264
+ const python = `
265
+ import unreal, json
266
+ try:
267
+ presets = []
268
+ # Try to list assets in common RC preset locations
269
+ for path in ["/Game/RCPresets", "/Game/RemoteControl", "/Game"]:
270
+ try:
271
+ assets = unreal.EditorAssetLibrary.list_assets(path, recursive=True)
272
+ for asset in assets:
273
+ if "RemoteControlPreset" in asset:
274
+ try:
275
+ preset = unreal.EditorAssetLibrary.load_asset(asset)
276
+ if preset:
277
+ presets.append({
278
+ "id": asset,
279
+ "name": preset.get_name(),
280
+ "path": asset,
281
+ "description": getattr(preset, 'description', '')
282
+ })
283
+ except Exception:
284
+ pass
285
+ except Exception:
286
+ pass
287
+ print('RESULT:' + json.dumps({'success': True, 'presets': presets}))
288
+ except Exception as e:
289
+ print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
290
+ `.trim();
291
+ const resp = await this.executeWithRetry(() => this.bridge.executePython(python), 'listPresets');
292
+ const result = this.parsePythonResult(resp, 'listPresets');
293
+ // Update cache
294
+ if (result.success && result.presets) {
295
+ result.presets.forEach((p) => {
296
+ this.presetCache.set(p.id, p);
297
+ });
298
+ }
299
+ return result;
300
+ }
301
+ /**
302
+ * Delete a Remote Control preset
303
+ */
304
+ async deletePreset(presetId) {
305
+ const python = `
306
+ import unreal, json
307
+ preset_id = r"${presetId}"
308
+ try:
309
+ if unreal.EditorAssetLibrary.does_asset_exist(preset_id):
310
+ success = unreal.EditorAssetLibrary.delete_asset(preset_id)
311
+ if success:
312
+ print('RESULT:' + json.dumps({'success': True}))
313
+ else:
314
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Failed to delete preset'}))
315
+ else:
316
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Preset not found'}))
317
+ except Exception as e:
318
+ print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
319
+ `.trim();
320
+ const resp = await this.executeWithRetry(() => this.bridge.executePython(python), 'deletePreset');
321
+ const result = this.parsePythonResult(resp, 'deletePreset');
322
+ // Remove from cache if successful
323
+ if (result.success) {
324
+ this.presetCache.delete(presetId);
325
+ }
326
+ return result;
327
+ }
328
+ /**
329
+ * Call an exposed function through Remote Control
330
+ */
331
+ async callFunction(params) {
332
+ try {
333
+ const res = await this.bridge.httpCall('/remote/object/call', 'PUT', {
334
+ objectPath: params.presetPath,
335
+ functionName: params.functionName,
336
+ parameters: params.parameters || {}
337
+ });
338
+ return { success: true, result: res };
339
+ }
340
+ catch (err) {
341
+ return { success: false, error: String(err?.message || err) };
342
+ }
343
+ }
344
+ /**
345
+ * Validate connection to Remote Control
346
+ */
347
+ async validateConnection() {
348
+ try {
349
+ await this.bridge.httpCall('/remote/info', 'GET', {});
350
+ return true;
351
+ }
352
+ catch {
353
+ return false;
354
+ }
355
+ }
356
+ /**
357
+ * Clear preset cache
358
+ */
359
+ clearCache() {
360
+ this.presetCache.clear();
361
+ }
362
+ }
363
+ //# sourceMappingURL=rc.js.map
@@ -0,0 +1,112 @@
1
+ import { UnrealBridge } from '../unreal-bridge.js';
2
+ export interface LevelSequence {
3
+ path: string;
4
+ name: string;
5
+ duration?: number;
6
+ frameRate?: number;
7
+ bindings?: SequenceBinding[];
8
+ }
9
+ export interface SequenceBinding {
10
+ id: string;
11
+ name: string;
12
+ type: 'actor' | 'camera' | 'spawnable';
13
+ tracks?: SequenceTrack[];
14
+ }
15
+ export interface SequenceTrack {
16
+ name: string;
17
+ type: string;
18
+ sections?: any[];
19
+ }
20
+ export declare class SequenceTools {
21
+ private bridge;
22
+ private log;
23
+ private sequenceCache;
24
+ private retryAttempts;
25
+ private retryDelay;
26
+ constructor(bridge: UnrealBridge);
27
+ /**
28
+ * Execute with retry logic for transient failures
29
+ */
30
+ private executeWithRetry;
31
+ /**
32
+ * Parse Python execution result with better error handling
33
+ */
34
+ private parsePythonResult;
35
+ create(params: {
36
+ name: string;
37
+ path?: string;
38
+ }): Promise<any>;
39
+ open(params: {
40
+ path: string;
41
+ }): Promise<any>;
42
+ addCamera(params: {
43
+ spawnable?: boolean;
44
+ }): Promise<any>;
45
+ addActor(params: {
46
+ actorName: string;
47
+ createBinding?: boolean;
48
+ }): Promise<any>;
49
+ /**
50
+ * Play the current level sequence
51
+ */
52
+ play(params?: {
53
+ startTime?: number;
54
+ loopMode?: 'once' | 'loop' | 'pingpong';
55
+ }): Promise<any>;
56
+ /**
57
+ * Pause the current level sequence
58
+ */
59
+ pause(): Promise<any>;
60
+ /**
61
+ * Stop/close the current level sequence
62
+ */
63
+ stop(): Promise<any>;
64
+ /**
65
+ * Set sequence properties including frame rate and length
66
+ */
67
+ setSequenceProperties(params: {
68
+ path?: string;
69
+ frameRate?: number;
70
+ lengthInFrames?: number;
71
+ playbackStart?: number;
72
+ playbackEnd?: number;
73
+ }): Promise<any>;
74
+ /**
75
+ * Get sequence properties
76
+ */
77
+ getSequenceProperties(params: {
78
+ path?: string;
79
+ }): Promise<any>;
80
+ /**
81
+ * Set playback speed/rate
82
+ */
83
+ setPlaybackSpeed(params: {
84
+ speed: number;
85
+ }): Promise<any>;
86
+ /**
87
+ * Get all bindings in the current sequence
88
+ */
89
+ getBindings(params?: {
90
+ path?: string;
91
+ }): Promise<any>;
92
+ /**
93
+ * Add multiple actors to sequence at once
94
+ */
95
+ addActors(params: {
96
+ actorNames: string[];
97
+ }): Promise<any>;
98
+ /**
99
+ * Remove actors from binding
100
+ */
101
+ removeActors(params: {
102
+ actorNames: string[];
103
+ }): Promise<any>;
104
+ /**
105
+ * Create a spawnable from an actor class
106
+ */
107
+ addSpawnableFromClass(params: {
108
+ className: string;
109
+ path?: string;
110
+ }): Promise<any>;
111
+ }
112
+ //# sourceMappingURL=sequence.d.ts.map