unreal-engine-mcp-server 0.3.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.production +1 -1
- package/.github/copilot-instructions.md +45 -0
- package/.github/workflows/publish-mcp.yml +1 -1
- package/README.md +22 -7
- package/dist/index.js +137 -46
- package/dist/prompts/index.d.ts +10 -3
- package/dist/prompts/index.js +186 -7
- package/dist/resources/actors.d.ts +19 -1
- package/dist/resources/actors.js +55 -64
- package/dist/resources/assets.d.ts +3 -2
- package/dist/resources/assets.js +117 -109
- package/dist/resources/levels.d.ts +21 -3
- package/dist/resources/levels.js +31 -56
- package/dist/tools/actors.d.ts +3 -14
- package/dist/tools/actors.js +246 -302
- package/dist/tools/animation.d.ts +57 -102
- package/dist/tools/animation.js +429 -450
- package/dist/tools/assets.d.ts +13 -2
- package/dist/tools/assets.js +58 -46
- package/dist/tools/audio.d.ts +22 -13
- package/dist/tools/audio.js +467 -121
- package/dist/tools/blueprint.d.ts +32 -13
- package/dist/tools/blueprint.js +699 -448
- package/dist/tools/build_environment_advanced.d.ts +0 -1
- package/dist/tools/build_environment_advanced.js +236 -87
- package/dist/tools/consolidated-tool-definitions.d.ts +232 -15
- package/dist/tools/consolidated-tool-definitions.js +124 -255
- package/dist/tools/consolidated-tool-handlers.js +749 -766
- package/dist/tools/debug.d.ts +72 -10
- package/dist/tools/debug.js +170 -36
- package/dist/tools/editor.d.ts +9 -2
- package/dist/tools/editor.js +30 -44
- package/dist/tools/foliage.d.ts +34 -15
- package/dist/tools/foliage.js +97 -107
- package/dist/tools/introspection.js +19 -21
- package/dist/tools/landscape.d.ts +1 -2
- package/dist/tools/landscape.js +311 -168
- package/dist/tools/level.d.ts +3 -28
- package/dist/tools/level.js +642 -192
- package/dist/tools/lighting.d.ts +14 -3
- package/dist/tools/lighting.js +236 -123
- package/dist/tools/materials.d.ts +25 -7
- package/dist/tools/materials.js +102 -79
- package/dist/tools/niagara.d.ts +10 -12
- package/dist/tools/niagara.js +74 -94
- package/dist/tools/performance.d.ts +12 -4
- package/dist/tools/performance.js +38 -79
- package/dist/tools/physics.d.ts +34 -10
- package/dist/tools/physics.js +364 -292
- package/dist/tools/rc.js +98 -24
- package/dist/tools/sequence.d.ts +1 -0
- package/dist/tools/sequence.js +146 -24
- package/dist/tools/ui.d.ts +31 -4
- package/dist/tools/ui.js +83 -66
- package/dist/tools/visual.d.ts +11 -0
- package/dist/tools/visual.js +245 -30
- package/dist/types/tool-types.d.ts +0 -6
- package/dist/types/tool-types.js +1 -8
- package/dist/unreal-bridge.d.ts +32 -2
- package/dist/unreal-bridge.js +621 -127
- package/dist/utils/elicitation.d.ts +57 -0
- package/dist/utils/elicitation.js +104 -0
- package/dist/utils/error-handler.d.ts +0 -33
- package/dist/utils/error-handler.js +4 -111
- package/dist/utils/http.d.ts +2 -22
- package/dist/utils/http.js +12 -75
- package/dist/utils/normalize.d.ts +4 -4
- package/dist/utils/normalize.js +15 -7
- package/dist/utils/python-output.d.ts +18 -0
- package/dist/utils/python-output.js +290 -0
- package/dist/utils/python.d.ts +2 -0
- package/dist/utils/python.js +4 -0
- package/dist/utils/response-validator.d.ts +6 -1
- package/dist/utils/response-validator.js +66 -13
- package/dist/utils/result-helpers.d.ts +27 -0
- package/dist/utils/result-helpers.js +147 -0
- package/dist/utils/safe-json.d.ts +0 -2
- package/dist/utils/safe-json.js +0 -43
- package/dist/utils/validation.d.ts +16 -0
- package/dist/utils/validation.js +70 -7
- package/mcp-config-example.json +2 -2
- package/package.json +11 -10
- package/server.json +37 -14
- package/src/index.ts +146 -50
- package/src/prompts/index.ts +211 -13
- package/src/resources/actors.ts +59 -44
- package/src/resources/assets.ts +123 -102
- package/src/resources/levels.ts +37 -47
- package/src/tools/actors.ts +269 -313
- package/src/tools/animation.ts +556 -539
- package/src/tools/assets.ts +59 -45
- package/src/tools/audio.ts +507 -113
- package/src/tools/blueprint.ts +778 -462
- package/src/tools/build_environment_advanced.ts +312 -106
- package/src/tools/consolidated-tool-definitions.ts +136 -267
- package/src/tools/consolidated-tool-handlers.ts +871 -795
- package/src/tools/debug.ts +179 -38
- package/src/tools/editor.ts +35 -37
- package/src/tools/foliage.ts +110 -104
- package/src/tools/introspection.ts +24 -22
- package/src/tools/landscape.ts +334 -181
- package/src/tools/level.ts +683 -182
- package/src/tools/lighting.ts +244 -123
- package/src/tools/materials.ts +114 -83
- package/src/tools/niagara.ts +87 -81
- package/src/tools/performance.ts +49 -88
- package/src/tools/physics.ts +393 -299
- package/src/tools/rc.ts +103 -25
- package/src/tools/sequence.ts +157 -30
- package/src/tools/ui.ts +101 -70
- package/src/tools/visual.ts +250 -29
- package/src/types/tool-types.ts +0 -9
- package/src/unreal-bridge.ts +658 -140
- package/src/utils/elicitation.ts +129 -0
- package/src/utils/error-handler.ts +4 -159
- package/src/utils/http.ts +16 -115
- package/src/utils/normalize.ts +20 -10
- package/src/utils/python-output.ts +351 -0
- package/src/utils/python.ts +3 -0
- package/src/utils/response-validator.ts +68 -17
- package/src/utils/result-helpers.ts +193 -0
- package/src/utils/safe-json.ts +0 -50
- package/src/utils/validation.ts +94 -7
- package/tests/run-unreal-tool-tests.mjs +720 -0
- package/tsconfig.json +2 -2
- package/dist/python-utils.d.ts +0 -29
- package/dist/python-utils.js +0 -54
- package/dist/tools/tool-definitions.d.ts +0 -4919
- package/dist/tools/tool-definitions.js +0 -1065
- package/dist/tools/tool-handlers.d.ts +0 -47
- package/dist/tools/tool-handlers.js +0 -863
- package/dist/types/index.d.ts +0 -323
- package/dist/types/index.js +0 -28
- package/dist/utils/cache-manager.d.ts +0 -64
- package/dist/utils/cache-manager.js +0 -176
- package/dist/utils/errors.d.ts +0 -133
- package/dist/utils/errors.js +0 -256
- package/src/python/editor_compat.py +0 -181
- package/src/python-utils.ts +0 -57
- package/src/tools/tool-definitions.ts +0 -1081
- package/src/tools/tool-handlers.ts +0 -973
- package/src/types/index.ts +0 -414
- package/src/utils/cache-manager.ts +0 -213
- package/src/utils/errors.ts +0 -312
package/src/resources/assets.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
|
+
import { coerceBoolean, coerceString, interpretStandardResult } from '../utils/result-helpers.js';
|
|
2
3
|
|
|
3
4
|
export class AssetResources {
|
|
4
5
|
constructor(private bridge: UnrealBridge) {}
|
|
@@ -10,11 +11,35 @@ export class AssetResources {
|
|
|
10
11
|
return page !== undefined ? `${dir}::${recursive ? 1 : 0}::${page}` : `${dir}::${recursive ? 1 : 0}`;
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
// Normalize UE content paths:
|
|
15
|
+
// - Map '/Content' -> '/Game'
|
|
16
|
+
// - Ensure forward slashes
|
|
17
|
+
private normalizeDir(dir: string): string {
|
|
18
|
+
try {
|
|
19
|
+
if (!dir || typeof dir !== 'string') return '/Game';
|
|
20
|
+
let d = dir.replace(/\\/g, '/');
|
|
21
|
+
if (!d.startsWith('/')) d = '/' + d;
|
|
22
|
+
if (d.toLowerCase().startsWith('/content')) {
|
|
23
|
+
d = '/Game' + d.substring('/Content'.length);
|
|
24
|
+
}
|
|
25
|
+
// Collapse multiple slashes
|
|
26
|
+
d = d.replace(/\/+/g, '/');
|
|
27
|
+
// Remove trailing slash except root
|
|
28
|
+
if (d.length > 1) d = d.replace(/\/$/, '');
|
|
29
|
+
return d;
|
|
30
|
+
} catch {
|
|
31
|
+
return '/Game';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
13
35
|
async list(dir = '/Game', _recursive = false, limit = 50) {
|
|
14
36
|
// ALWAYS use non-recursive listing to show only immediate children
|
|
15
37
|
// This prevents timeouts and makes navigation clearer
|
|
16
38
|
_recursive = false; // Force non-recursive
|
|
17
39
|
|
|
40
|
+
// Normalize directory first
|
|
41
|
+
dir = this.normalizeDir(dir);
|
|
42
|
+
|
|
18
43
|
// Cache fast-path
|
|
19
44
|
try {
|
|
20
45
|
const key = this.makeKey(dir, false);
|
|
@@ -50,7 +75,8 @@ export class AssetResources {
|
|
|
50
75
|
const safePageSize = Math.min(pageSize, 50);
|
|
51
76
|
const offset = page * safePageSize;
|
|
52
77
|
|
|
53
|
-
//
|
|
78
|
+
// Normalize directory and check cache for this specific page
|
|
79
|
+
dir = this.normalizeDir(dir);
|
|
54
80
|
const cacheKey = this.makeKey(dir, recursive, page);
|
|
55
81
|
const cached = this.cache.get(cacheKey);
|
|
56
82
|
if (cached && (Date.now() - cached.timestamp) < this.ttlMs) {
|
|
@@ -102,8 +128,8 @@ export class AssetResources {
|
|
|
102
128
|
}
|
|
103
129
|
|
|
104
130
|
/**
|
|
105
|
-
* Directory-based listing
|
|
106
|
-
*
|
|
131
|
+
* Directory-based listing of immediate children using AssetRegistry.
|
|
132
|
+
* Returns both subfolders and assets at the given path.
|
|
107
133
|
*/
|
|
108
134
|
private async listDirectoryOnly(dir: string, _recursive: boolean, limit: number) {
|
|
109
135
|
// Always return only immediate children to avoid timeout and improve navigation
|
|
@@ -112,100 +138,91 @@ export class AssetResources {
|
|
|
112
138
|
import unreal
|
|
113
139
|
import json
|
|
114
140
|
|
|
115
|
-
_dir = r"${dir}"
|
|
141
|
+
_dir = r"${this.normalizeDir(dir)}"
|
|
116
142
|
|
|
117
143
|
try:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
# Add folders first
|
|
144
|
-
for folder in sorted(immediate_folders):
|
|
145
|
-
result.append({
|
|
146
|
-
'n': folder,
|
|
147
|
-
'p': _dir + '/' + folder,
|
|
148
|
-
'c': 'Folder',
|
|
149
|
-
'isFolder': True
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
# Add immediate assets (limit to prevent socket issues)
|
|
153
|
-
for asset_path in immediate_assets[:min(${limit}, len(immediate_assets))]:
|
|
154
|
-
name = asset_path.split('/')[-1].split('.')[0]
|
|
155
|
-
result.append({
|
|
156
|
-
'n': name,
|
|
157
|
-
'p': asset_path,
|
|
158
|
-
'c': 'Asset'
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
# Always showing immediate children only
|
|
162
|
-
note = f'Showing immediate children of {_dir} ({len(immediate_folders)} folders, {len(immediate_assets)} files)'
|
|
163
|
-
|
|
144
|
+
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
|
145
|
+
# Immediate subfolders
|
|
146
|
+
sub_paths = ar.get_sub_paths(_dir, False)
|
|
147
|
+
folders_list = []
|
|
148
|
+
for p in sub_paths:
|
|
149
|
+
try:
|
|
150
|
+
name = p.split('/')[-1]
|
|
151
|
+
folders_list.append({'n': name, 'p': p})
|
|
152
|
+
except Exception:
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
# Immediate assets at this path
|
|
156
|
+
assets_data = ar.get_assets_by_path(_dir, False)
|
|
157
|
+
assets = []
|
|
158
|
+
for a in assets_data[:${limit}]:
|
|
159
|
+
try:
|
|
160
|
+
assets.append({
|
|
161
|
+
'n': str(a.asset_name),
|
|
162
|
+
'p': str(a.object_path),
|
|
163
|
+
'c': str(a.asset_class)
|
|
164
|
+
})
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
|
|
164
168
|
print("RESULT:" + json.dumps({
|
|
165
|
-
'success': True,
|
|
166
|
-
'
|
|
167
|
-
'
|
|
168
|
-
'
|
|
169
|
-
'
|
|
170
|
-
'
|
|
169
|
+
'success': True,
|
|
170
|
+
'path': _dir,
|
|
171
|
+
'folders': len(folders_list),
|
|
172
|
+
'files': len(assets),
|
|
173
|
+
'folders_list': folders_list,
|
|
174
|
+
'assets': assets
|
|
171
175
|
}))
|
|
172
176
|
except Exception as e:
|
|
173
|
-
print("RESULT:" + json.dumps({'success': False, 'error': str(e), '
|
|
177
|
+
print("RESULT:" + json.dumps({'success': False, 'error': str(e), 'path': _dir}))
|
|
174
178
|
`.trim();
|
|
175
179
|
|
|
176
180
|
const resp = await this.bridge.executePython(py);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
181
|
+
const interpreted = interpretStandardResult(resp, {
|
|
182
|
+
successMessage: 'Directory contents retrieved',
|
|
183
|
+
failureMessage: 'Failed to list directory contents'
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (interpreted.success) {
|
|
187
|
+
const payload = interpreted.payload as Record<string, unknown>;
|
|
188
|
+
|
|
189
|
+
const foldersArr = Array.isArray(payload.folders_list)
|
|
190
|
+
? payload.folders_list.map((f: any) => ({
|
|
191
|
+
Name: coerceString(f?.n) ?? '',
|
|
192
|
+
Path: coerceString(f?.p) ?? '',
|
|
193
|
+
Class: 'Folder',
|
|
194
|
+
isFolder: true
|
|
195
|
+
}))
|
|
196
|
+
: [];
|
|
197
|
+
|
|
198
|
+
const assetsArr = Array.isArray(payload.assets)
|
|
199
|
+
? payload.assets.map((a: any) => ({
|
|
200
|
+
Name: coerceString(a?.n) ?? '',
|
|
201
|
+
Path: coerceString(a?.p) ?? '',
|
|
202
|
+
Class: coerceString(a?.c) ?? 'Asset',
|
|
203
|
+
isFolder: false
|
|
204
|
+
}))
|
|
205
|
+
: [];
|
|
206
|
+
|
|
207
|
+
const total = foldersArr.length + assetsArr.length;
|
|
208
|
+
const summary = {
|
|
209
|
+
total,
|
|
210
|
+
folders: foldersArr.length,
|
|
211
|
+
assets: assetsArr.length
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const resolvedPath = coerceString(payload.path) ?? this.normalizeDir(dir);
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
success: true,
|
|
218
|
+
path: resolvedPath,
|
|
219
|
+
summary,
|
|
220
|
+
foldersList: foldersArr,
|
|
221
|
+
assets: assetsArr,
|
|
222
|
+
count: total,
|
|
223
|
+
note: `Immediate children of ${resolvedPath}: ${foldersArr.length} folder(s), ${assetsArr.length} asset(s)`,
|
|
224
|
+
method: 'asset_registry_listing'
|
|
225
|
+
};
|
|
209
226
|
}
|
|
210
227
|
} catch (err: any) {
|
|
211
228
|
console.warn('Engine asset listing failed:', err.message);
|
|
@@ -213,10 +230,13 @@ except Exception as e:
|
|
|
213
230
|
|
|
214
231
|
// Fallback: return empty with explanation
|
|
215
232
|
return {
|
|
233
|
+
success: true,
|
|
234
|
+
path: this.normalizeDir(dir),
|
|
235
|
+
summary: { total: 0, folders: 0, assets: 0 },
|
|
236
|
+
foldersList: [],
|
|
216
237
|
assets: [],
|
|
217
|
-
warning: '
|
|
218
|
-
|
|
219
|
-
method: 'directory_timeout_fallback'
|
|
238
|
+
warning: 'No items at this path or failed to query AssetRegistry.',
|
|
239
|
+
method: 'asset_registry_fallback'
|
|
220
240
|
};
|
|
221
241
|
}
|
|
222
242
|
|
|
@@ -226,9 +246,11 @@ except Exception as e:
|
|
|
226
246
|
return false;
|
|
227
247
|
}
|
|
228
248
|
|
|
249
|
+
// Normalize asset path (support users passing /Content/...)
|
|
250
|
+
const ap = this.normalizeDir(assetPath);
|
|
229
251
|
const py = `
|
|
230
252
|
import unreal
|
|
231
|
-
apath = r"${
|
|
253
|
+
apath = r"${ap}"
|
|
232
254
|
try:
|
|
233
255
|
exists = unreal.EditorAssetLibrary.does_asset_exist(apath)
|
|
234
256
|
print("RESULT:{'success': True, 'exists': %s}" % ('True' if exists else 'False'))
|
|
@@ -236,16 +258,15 @@ except Exception as e:
|
|
|
236
258
|
print("RESULT:{'success': False, 'error': '" + str(e) + "'}")
|
|
237
259
|
`.trim();
|
|
238
260
|
const resp = await this.bridge.executePython(py);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (parsed.success) return !!parsed.exists;
|
|
247
|
-
} catch {}
|
|
261
|
+
const interpreted = interpretStandardResult(resp, {
|
|
262
|
+
successMessage: 'Asset existence verified',
|
|
263
|
+
failureMessage: 'Failed to verify asset existence'
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
if (interpreted.success) {
|
|
267
|
+
return coerceBoolean(interpreted.payload.exists, false) ?? false;
|
|
248
268
|
}
|
|
269
|
+
|
|
249
270
|
return false;
|
|
250
271
|
}
|
|
251
272
|
}
|
package/src/resources/levels.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
|
+
import { coerceString, interpretStandardResult } from '../utils/result-helpers.js';
|
|
2
3
|
|
|
3
4
|
export class LevelResources {
|
|
4
5
|
constructor(private bridge: UnrealBridge) {}
|
|
@@ -8,22 +9,20 @@ export class LevelResources {
|
|
|
8
9
|
try {
|
|
9
10
|
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
11
|
const resp: any = await this.bridge.executePython(py);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const parsed = JSON.parse(m[1]);
|
|
23
|
-
if (parsed.success) return parsed;
|
|
12
|
+
const interpreted = interpretStandardResult(resp, {
|
|
13
|
+
successMessage: 'Retrieved current level',
|
|
14
|
+
failureMessage: 'Failed to get current level'
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
if (interpreted.success) {
|
|
18
|
+
return {
|
|
19
|
+
success: true,
|
|
20
|
+
name: coerceString(interpreted.payload.name) ?? coerceString(interpreted.payload.level_name) ?? 'None',
|
|
21
|
+
path: coerceString(interpreted.payload.path) ?? 'None'
|
|
22
|
+
};
|
|
24
23
|
}
|
|
25
|
-
|
|
26
|
-
return {
|
|
24
|
+
|
|
25
|
+
return { success: false, error: interpreted.error ?? interpreted.message };
|
|
27
26
|
} catch (err) {
|
|
28
27
|
return { error: `Failed to get current level: ${err}`, success: false };
|
|
29
28
|
}
|
|
@@ -34,48 +33,39 @@ export class LevelResources {
|
|
|
34
33
|
try {
|
|
35
34
|
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();
|
|
36
35
|
const resp: any = await this.bridge.executePython(py);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (m) {
|
|
48
|
-
const parsed = JSON.parse(m[1]);
|
|
49
|
-
if (parsed.success) return parsed;
|
|
36
|
+
const interpreted = interpretStandardResult(resp, {
|
|
37
|
+
successMessage: 'Retrieved level path',
|
|
38
|
+
failureMessage: 'Failed to get level name'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (interpreted.success) {
|
|
42
|
+
return {
|
|
43
|
+
success: true,
|
|
44
|
+
path: coerceString(interpreted.payload.path) ?? ''
|
|
45
|
+
};
|
|
50
46
|
}
|
|
51
|
-
|
|
52
|
-
return {
|
|
47
|
+
|
|
48
|
+
return { success: false, error: interpreted.error ?? interpreted.message };
|
|
53
49
|
} catch (err) {
|
|
54
50
|
return { error: `Failed to get level name: ${err}`, success: false };
|
|
55
51
|
}
|
|
56
52
|
}
|
|
57
53
|
|
|
58
54
|
async saveCurrentLevel() {
|
|
59
|
-
//
|
|
55
|
+
// Strict modern API: require LevelEditorSubsystem
|
|
60
56
|
try {
|
|
61
|
-
const py = '\nimport unreal, json\ntry:\n les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)\n if les
|
|
57
|
+
const py = '\nimport unreal, json\ntry:\n les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)\n if not les:\n print(\'RESULT:\' + json.dumps({\'success\': False, \'error\': \'LevelEditorSubsystem not available\'}))\n else:\n les.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();
|
|
62
58
|
const resp: any = await this.bridge.executePython(py);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
out = JSON.stringify(resp);
|
|
71
|
-
}
|
|
72
|
-
const m = out.match(/RESULT:({.*})/);
|
|
73
|
-
if (m) {
|
|
74
|
-
const parsed = JSON.parse(m[1]);
|
|
75
|
-
if (parsed.success) return { success: true, message: 'Level saved' };
|
|
59
|
+
const interpreted = interpretStandardResult(resp, {
|
|
60
|
+
successMessage: 'Level saved',
|
|
61
|
+
failureMessage: 'Failed to save level'
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (interpreted.success) {
|
|
65
|
+
return { success: true, message: interpreted.message };
|
|
76
66
|
}
|
|
77
|
-
|
|
78
|
-
return {
|
|
67
|
+
|
|
68
|
+
return { success: false, error: interpreted.error ?? interpreted.message };
|
|
79
69
|
} catch (err) {
|
|
80
70
|
return { error: `Failed to save level: ${err}`, success: false };
|
|
81
71
|
}
|