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,83 @@
1
+ export class ActorResources {
2
+ bridge;
3
+ cache = new Map();
4
+ CACHE_TTL_MS = 5000; // 5 seconds cache for actors (they change more frequently)
5
+ constructor(bridge) {
6
+ this.bridge = bridge;
7
+ }
8
+ getFromCache(key) {
9
+ const entry = this.cache.get(key);
10
+ if (entry && (Date.now() - entry.timestamp) < this.CACHE_TTL_MS) {
11
+ return entry.data;
12
+ }
13
+ this.cache.delete(key);
14
+ return null;
15
+ }
16
+ setCache(key, data) {
17
+ this.cache.set(key, { data, timestamp: Date.now() });
18
+ }
19
+ async listActors() {
20
+ // Check cache first
21
+ const cached = this.getFromCache('listActors');
22
+ if (cached !== null) {
23
+ return cached;
24
+ }
25
+ // Use Python to get actors via EditorActorSubsystem
26
+ try {
27
+ const pythonCode = `
28
+ import unreal
29
+ actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
30
+ actors = actor_subsystem.get_all_level_actors()
31
+ actor_list = []
32
+ for actor in actors:
33
+ if actor:
34
+ actor_list.append({
35
+ 'name': actor.get_name(),
36
+ 'class': actor.get_class().get_name(),
37
+ 'path': actor.get_path_name()
38
+ })
39
+ print(f"Found {len(actor_list)} actors")
40
+ `.trim();
41
+ const result = await this.bridge.executePython(pythonCode);
42
+ this.setCache('listActors', result);
43
+ return result;
44
+ }
45
+ catch (err) {
46
+ return { error: `Failed to list actors: ${err}` };
47
+ }
48
+ }
49
+ async getActorByName(actorName) {
50
+ // GetActorOfClass expects a class, not a name. Use Python to find by name
51
+ try {
52
+ const pythonCode = `
53
+ import unreal
54
+ actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
55
+ actors = actor_subsystem.get_all_level_actors()
56
+ for actor in actors:
57
+ if actor and actor.get_name() == "${actorName}":
58
+ print(f"Found actor: {actor.get_path_name()}")
59
+ break
60
+ else:
61
+ print(f"Actor not found: ${actorName}")
62
+ `.trim();
63
+ const result = await this.bridge.executePython(pythonCode);
64
+ return result;
65
+ }
66
+ catch (err) {
67
+ return { error: `Failed to get actor: ${err}` };
68
+ }
69
+ }
70
+ async getActorTransform(actorPath) {
71
+ try {
72
+ const res = await this.bridge.httpCall('/remote/object/property', 'GET', {
73
+ objectPath: actorPath,
74
+ propertyName: 'ActorTransform'
75
+ });
76
+ return res;
77
+ }
78
+ catch (err) {
79
+ return { error: `Failed to get transform: ${err}` };
80
+ }
81
+ }
82
+ }
83
+ //# sourceMappingURL=actors.js.map
@@ -0,0 +1,23 @@
1
+ import { UnrealBridge } from '../unreal-bridge.js';
2
+ export declare class AssetResources {
3
+ private bridge;
4
+ constructor(bridge: UnrealBridge);
5
+ private cache;
6
+ private get ttlMs();
7
+ private makeKey;
8
+ list(dir?: string, recursive?: boolean, limit?: number): Promise<any>;
9
+ /**
10
+ * List assets with pagination support
11
+ * @param dir Directory to list assets from
12
+ * @param page Page number (0-based)
13
+ * @param pageSize Number of assets per page (max 50 to avoid socket failures)
14
+ */
15
+ listPaged(dir?: string, page?: number, pageSize?: number, recursive?: boolean): Promise<any>;
16
+ /**
17
+ * Directory-based listing for paths with too many assets
18
+ * Shows only immediate children (folders and files) to avoid timeouts
19
+ */
20
+ private listDirectoryOnly;
21
+ find(assetPath: string): Promise<boolean>;
22
+ }
23
+ //# sourceMappingURL=assets.d.ts.map
@@ -0,0 +1,245 @@
1
+ export class AssetResources {
2
+ bridge;
3
+ constructor(bridge) {
4
+ this.bridge = bridge;
5
+ }
6
+ // Simple in-memory cache for asset listing
7
+ cache = new Map();
8
+ get ttlMs() { return Number(process.env.ASSET_LIST_TTL_MS || 10000); }
9
+ makeKey(dir, recursive, page) {
10
+ return page !== undefined ? `${dir}::${recursive ? 1 : 0}::${page}` : `${dir}::${recursive ? 1 : 0}`;
11
+ }
12
+ async list(dir = '/Game', recursive = false, limit = 50) {
13
+ // ALWAYS use non-recursive listing to show only immediate children
14
+ // This prevents timeouts and makes navigation clearer
15
+ recursive = false; // Force non-recursive
16
+ // Cache fast-path
17
+ try {
18
+ const key = this.makeKey(dir, false);
19
+ const entry = this.cache.get(key);
20
+ const now = Date.now();
21
+ if (entry && (now - entry.timestamp) < this.ttlMs) {
22
+ return entry.data;
23
+ }
24
+ }
25
+ catch { }
26
+ // Check if bridge is connected
27
+ if (!this.bridge.isConnected) {
28
+ return {
29
+ assets: [],
30
+ warning: 'Unreal Engine is not connected. Please ensure Unreal Engine is running with Remote Control enabled.',
31
+ connectionStatus: 'disconnected'
32
+ };
33
+ }
34
+ // Always use directory-only listing (immediate children)
35
+ return this.listDirectoryOnly(dir, false, limit);
36
+ // End of list method - all logic is now in listDirectoryOnly
37
+ }
38
+ /**
39
+ * List assets with pagination support
40
+ * @param dir Directory to list assets from
41
+ * @param page Page number (0-based)
42
+ * @param pageSize Number of assets per page (max 50 to avoid socket failures)
43
+ */
44
+ async listPaged(dir = '/Game', page = 0, pageSize = 30, recursive = false) {
45
+ // Ensure pageSize doesn't exceed safe limit
46
+ const safePageSize = Math.min(pageSize, 50);
47
+ const offset = page * safePageSize;
48
+ // Check cache for this specific page
49
+ const cacheKey = this.makeKey(dir, recursive, page);
50
+ const cached = this.cache.get(cacheKey);
51
+ if (cached && (Date.now() - cached.timestamp) < this.ttlMs) {
52
+ return cached.data;
53
+ }
54
+ if (!this.bridge.isConnected) {
55
+ return {
56
+ assets: [],
57
+ page,
58
+ pageSize: safePageSize,
59
+ warning: 'Unreal Engine is not connected.',
60
+ connectionStatus: 'disconnected'
61
+ };
62
+ }
63
+ try {
64
+ // Use search API with pagination
65
+ // Use the same directory listing approach but with pagination
66
+ const allAssets = await this.listDirectoryOnly(dir, false, 1000);
67
+ // Paginate the results
68
+ const start = offset;
69
+ const end = offset + safePageSize;
70
+ const pagedAssets = allAssets.assets ? allAssets.assets.slice(start, end) : [];
71
+ const result = {
72
+ assets: pagedAssets,
73
+ page,
74
+ pageSize: safePageSize,
75
+ count: pagedAssets.length,
76
+ totalCount: allAssets.assets ? allAssets.assets.length : 0,
77
+ hasMore: end < (allAssets.assets ? allAssets.assets.length : 0),
78
+ method: 'directory_listing_paged'
79
+ };
80
+ this.cache.set(cacheKey, { timestamp: Date.now(), data: result });
81
+ return result;
82
+ }
83
+ catch (err) {
84
+ console.warn(`Asset listing page ${page} failed:`, err.message);
85
+ }
86
+ return {
87
+ assets: [],
88
+ page,
89
+ pageSize: safePageSize,
90
+ error: 'Failed to fetch page'
91
+ };
92
+ }
93
+ /**
94
+ * Directory-based listing for paths with too many assets
95
+ * Shows only immediate children (folders and files) to avoid timeouts
96
+ */
97
+ async listDirectoryOnly(dir, recursive, limit) {
98
+ // Always return only immediate children to avoid timeout and improve navigation
99
+ try {
100
+ const py = `
101
+ import unreal
102
+ import json
103
+
104
+ _dir = r"${dir}"
105
+
106
+ try:
107
+ # ALWAYS non-recursive - get only immediate children
108
+ all_paths = unreal.EditorAssetLibrary.list_assets(_dir, False, False)
109
+
110
+ # Organize into immediate children only
111
+ immediate_folders = set()
112
+ immediate_assets = []
113
+
114
+ for path in all_paths:
115
+ # Remove the base directory to get relative path
116
+ relative = path.replace(_dir, '').strip('/')
117
+ if not relative:
118
+ continue
119
+
120
+ # Split to check depth
121
+ parts = relative.split('/')
122
+
123
+ if len(parts) == 1:
124
+ # This is an immediate child asset
125
+ immediate_assets.append(path)
126
+ elif len(parts) > 1:
127
+ # This indicates a subfolder exists
128
+ immediate_folders.add(parts[0])
129
+
130
+ result = []
131
+
132
+ # Add folders first
133
+ for folder in sorted(immediate_folders):
134
+ result.append({
135
+ 'n': folder,
136
+ 'p': _dir + '/' + folder,
137
+ 'c': 'Folder',
138
+ 'isFolder': True
139
+ })
140
+
141
+ # Add immediate assets (limit to prevent socket issues)
142
+ for asset_path in immediate_assets[:min(${limit}, len(immediate_assets))]:
143
+ name = asset_path.split('/')[-1].split('.')[0]
144
+ result.append({
145
+ 'n': name,
146
+ 'p': asset_path,
147
+ 'c': 'Asset'
148
+ })
149
+
150
+ # Always showing immediate children only
151
+ note = f'Showing immediate children of {_dir} ({len(immediate_folders)} folders, {len(immediate_assets)} files)'
152
+
153
+ print("RESULT:" + json.dumps({
154
+ 'success': True,
155
+ 'assets': result,
156
+ 'count': len(result),
157
+ 'folders': len(immediate_folders),
158
+ 'files': len(immediate_assets),
159
+ 'note': note
160
+ }))
161
+ except Exception as e:
162
+ print("RESULT:" + json.dumps({'success': False, 'error': str(e), 'assets': []}))
163
+ `.trim();
164
+ const resp = await this.bridge.executePython(py);
165
+ let output = '';
166
+ if (resp?.LogOutput && Array.isArray(resp.LogOutput)) {
167
+ output = resp.LogOutput.map((l) => l.Output || '').join('');
168
+ }
169
+ else if (typeof resp === 'string') {
170
+ output = resp;
171
+ }
172
+ else {
173
+ output = JSON.stringify(resp);
174
+ }
175
+ const m = output.match(/RESULT:({.*})/);
176
+ if (m) {
177
+ try {
178
+ const parsed = JSON.parse(m[1]);
179
+ if (parsed.success) {
180
+ // Transform to standard format
181
+ const assets = parsed.assets.map((a) => ({
182
+ Name: a.n,
183
+ Path: a.p,
184
+ Class: a.c,
185
+ isFolder: a.isFolder || false
186
+ }));
187
+ return {
188
+ assets,
189
+ count: parsed.count,
190
+ folders: parsed.folders,
191
+ files: parsed.files,
192
+ note: parsed.note,
193
+ method: 'directory_listing'
194
+ };
195
+ }
196
+ }
197
+ catch { }
198
+ }
199
+ }
200
+ catch (err) {
201
+ console.warn('Engine asset listing failed:', err.message);
202
+ }
203
+ // Fallback: return empty with explanation
204
+ return {
205
+ assets: [],
206
+ warning: 'Directory contains too many assets. Showing immediate children only.',
207
+ suggestion: 'Navigate to specific subdirectories for detailed listings.',
208
+ method: 'directory_timeout_fallback'
209
+ };
210
+ }
211
+ async find(assetPath) {
212
+ // Guard against invalid paths (trailing slash, empty, whitespace)
213
+ if (!assetPath || typeof assetPath !== 'string' || assetPath.trim() === '' || assetPath.endsWith('/')) {
214
+ return false;
215
+ }
216
+ const py = `
217
+ import unreal
218
+ apath = r"${assetPath}"
219
+ try:
220
+ exists = unreal.EditorAssetLibrary.does_asset_exist(apath)
221
+ print("RESULT:{'success': True, 'exists': %s}" % ('True' if exists else 'False'))
222
+ except Exception as e:
223
+ print("RESULT:{'success': False, 'error': '" + str(e) + "'}")
224
+ `.trim();
225
+ const resp = await this.bridge.executePython(py);
226
+ let output = '';
227
+ if (resp?.LogOutput && Array.isArray(resp.LogOutput))
228
+ output = resp.LogOutput.map((l) => l.Output || '').join('');
229
+ else if (typeof resp === 'string')
230
+ output = resp;
231
+ else
232
+ output = JSON.stringify(resp);
233
+ const m = output.match(/RESULT:({.*})/);
234
+ if (m) {
235
+ try {
236
+ const parsed = JSON.parse(m[1].replace(/'/g, '"'));
237
+ if (parsed.success)
238
+ return !!parsed.exists;
239
+ }
240
+ catch { }
241
+ }
242
+ return false;
243
+ }
244
+ }
245
+ //# sourceMappingURL=assets.js.map
@@ -0,0 +1,17 @@
1
+ import { UnrealBridge } from '../unreal-bridge.js';
2
+ export declare class LevelResources {
3
+ private bridge;
4
+ constructor(bridge: UnrealBridge);
5
+ getCurrentLevel(): Promise<any>;
6
+ getLevelName(): Promise<any>;
7
+ saveCurrentLevel(): Promise<{
8
+ success: boolean;
9
+ message: string;
10
+ error?: undefined;
11
+ } | {
12
+ error: string;
13
+ success: boolean;
14
+ message?: undefined;
15
+ }>;
16
+ }
17
+ //# sourceMappingURL=levels.d.ts.map
@@ -0,0 +1,94 @@
1
+ export class LevelResources {
2
+ bridge;
3
+ constructor(bridge) {
4
+ this.bridge = bridge;
5
+ }
6
+ async getCurrentLevel() {
7
+ // Use UnrealEditorSubsystem instead of deprecated EditorLevelLibrary
8
+ try {
9
+ const py = '\nimport unreal, json\ntry:\n # Use UnrealEditorSubsystem instead of deprecated EditorLevelLibrary\n editor_subsys = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)\n world = editor_subsys.get_editor_world()\n name = world.get_name() if world else \'None\'\n path = world.get_path_name() if world else \'None\'\n print(\'RESULT:\' + json.dumps({\'success\': True, \'name\': name, \'path\': path}))\nexcept Exception as e:\n print(\'RESULT:\' + json.dumps({\'success\': False, \'error\': str(e)}))\n'.trim();
10
+ const resp = await this.bridge.executePython(py);
11
+ // Handle LogOutput format from executePython
12
+ let out = '';
13
+ if (resp?.LogOutput && Array.isArray(resp.LogOutput)) {
14
+ out = resp.LogOutput.map((log) => log.Output || '').join('');
15
+ }
16
+ else if (typeof resp === 'string') {
17
+ out = resp;
18
+ }
19
+ else {
20
+ out = JSON.stringify(resp);
21
+ }
22
+ const m = out.match(/RESULT:({.*})/);
23
+ if (m) {
24
+ const parsed = JSON.parse(m[1]);
25
+ if (parsed.success)
26
+ return parsed;
27
+ }
28
+ // If Python failed, return error
29
+ return { error: 'Failed to get current level', success: false };
30
+ }
31
+ catch (err) {
32
+ return { error: `Failed to get current level: ${err}`, success: false };
33
+ }
34
+ }
35
+ async getLevelName() {
36
+ // Return camera/world info via Python first
37
+ try {
38
+ const py = '\nimport unreal, json\ntry:\n # Use UnrealEditorSubsystem instead of deprecated EditorLevelLibrary\n editor_subsys = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)\n world = editor_subsys.get_editor_world()\n path = world.get_path_name() if world else \'\'\n print(\'RESULT:\' + json.dumps({\'success\': True, \'path\': path}))\nexcept Exception as e:\n print(\'RESULT:\' + json.dumps({\'success\': False, \'error\': str(e)}))\n'.trim();
39
+ const resp = await this.bridge.executePython(py);
40
+ // Handle LogOutput format from executePython
41
+ let out = '';
42
+ if (resp?.LogOutput && Array.isArray(resp.LogOutput)) {
43
+ out = resp.LogOutput.map((log) => log.Output || '').join('');
44
+ }
45
+ else if (typeof resp === 'string') {
46
+ out = resp;
47
+ }
48
+ else {
49
+ out = JSON.stringify(resp);
50
+ }
51
+ const m = out.match(/RESULT:({.*})/);
52
+ if (m) {
53
+ const parsed = JSON.parse(m[1]);
54
+ if (parsed.success)
55
+ return parsed;
56
+ }
57
+ // If Python failed, return error
58
+ return { error: 'Failed to get level name', success: false };
59
+ }
60
+ catch (err) {
61
+ return { error: `Failed to get level name: ${err}`, success: false };
62
+ }
63
+ }
64
+ async saveCurrentLevel() {
65
+ // Prefer Python save (or LevelEditorSubsystem) then fallback
66
+ try {
67
+ const py = '\nimport unreal, json\ntry:\n les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)\n if les: les.save_current_level()\n else: unreal.EditorLevelLibrary.save_current_level()\n print(\'RESULT:\' + json.dumps({\'success\': True}))\nexcept Exception as e:\n print(\'RESULT:\' + json.dumps({\'success\': False, \'error\': str(e)}))\n'.trim();
68
+ const resp = await this.bridge.executePython(py);
69
+ // Handle LogOutput format from executePython
70
+ let out = '';
71
+ if (resp?.LogOutput && Array.isArray(resp.LogOutput)) {
72
+ out = resp.LogOutput.map((log) => log.Output || '').join('');
73
+ }
74
+ else if (typeof resp === 'string') {
75
+ out = resp;
76
+ }
77
+ else {
78
+ out = JSON.stringify(resp);
79
+ }
80
+ const m = out.match(/RESULT:({.*})/);
81
+ if (m) {
82
+ const parsed = JSON.parse(m[1]);
83
+ if (parsed.success)
84
+ return { success: true, message: 'Level saved' };
85
+ }
86
+ // If Python failed, return error
87
+ return { error: 'Failed to save level', success: false };
88
+ }
89
+ catch (err) {
90
+ return { error: `Failed to save level: ${err}`, success: false };
91
+ }
92
+ }
93
+ }
94
+ //# sourceMappingURL=levels.js.map
@@ -0,0 +1,51 @@
1
+ import { UnrealBridge } from '../unreal-bridge.js';
2
+ export declare class ActorTools {
3
+ private bridge;
4
+ constructor(bridge: UnrealBridge);
5
+ spawn(params: {
6
+ classPath: string;
7
+ location?: {
8
+ x: number;
9
+ y: number;
10
+ z: number;
11
+ };
12
+ rotation?: {
13
+ pitch: number;
14
+ yaw: number;
15
+ roll: number;
16
+ };
17
+ }): Promise<any>;
18
+ spawnViaPython(params: {
19
+ classPath: string;
20
+ location?: {
21
+ x: number;
22
+ y: number;
23
+ z: number;
24
+ };
25
+ rotation?: {
26
+ pitch: number;
27
+ yaw: number;
28
+ roll: number;
29
+ };
30
+ }): Promise<any>;
31
+ spawnViaConsole(params: {
32
+ classPath: string;
33
+ location?: {
34
+ x: number;
35
+ y: number;
36
+ z: number;
37
+ };
38
+ rotation?: {
39
+ pitch: number;
40
+ yaw: number;
41
+ roll: number;
42
+ };
43
+ }): Promise<{
44
+ success: boolean;
45
+ message: string;
46
+ note: string;
47
+ }>;
48
+ private resolveActorClass;
49
+ private getConsoleClassName;
50
+ }
51
+ //# sourceMappingURL=actors.d.ts.map