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/dist/resources/assets.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { coerceBoolean, coerceString, interpretStandardResult } from '../utils/result-helpers.js';
|
|
1
2
|
export class AssetResources {
|
|
2
3
|
bridge;
|
|
3
4
|
constructor(bridge) {
|
|
@@ -9,10 +10,36 @@ export class AssetResources {
|
|
|
9
10
|
makeKey(dir, recursive, page) {
|
|
10
11
|
return page !== undefined ? `${dir}::${recursive ? 1 : 0}::${page}` : `${dir}::${recursive ? 1 : 0}`;
|
|
11
12
|
}
|
|
13
|
+
// Normalize UE content paths:
|
|
14
|
+
// - Map '/Content' -> '/Game'
|
|
15
|
+
// - Ensure forward slashes
|
|
16
|
+
normalizeDir(dir) {
|
|
17
|
+
try {
|
|
18
|
+
if (!dir || typeof dir !== 'string')
|
|
19
|
+
return '/Game';
|
|
20
|
+
let d = dir.replace(/\\/g, '/');
|
|
21
|
+
if (!d.startsWith('/'))
|
|
22
|
+
d = '/' + d;
|
|
23
|
+
if (d.toLowerCase().startsWith('/content')) {
|
|
24
|
+
d = '/Game' + d.substring('/Content'.length);
|
|
25
|
+
}
|
|
26
|
+
// Collapse multiple slashes
|
|
27
|
+
d = d.replace(/\/+/g, '/');
|
|
28
|
+
// Remove trailing slash except root
|
|
29
|
+
if (d.length > 1)
|
|
30
|
+
d = d.replace(/\/$/, '');
|
|
31
|
+
return d;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return '/Game';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
12
37
|
async list(dir = '/Game', _recursive = false, limit = 50) {
|
|
13
38
|
// ALWAYS use non-recursive listing to show only immediate children
|
|
14
39
|
// This prevents timeouts and makes navigation clearer
|
|
15
40
|
_recursive = false; // Force non-recursive
|
|
41
|
+
// Normalize directory first
|
|
42
|
+
dir = this.normalizeDir(dir);
|
|
16
43
|
// Cache fast-path
|
|
17
44
|
try {
|
|
18
45
|
const key = this.makeKey(dir, false);
|
|
@@ -45,7 +72,8 @@ export class AssetResources {
|
|
|
45
72
|
// Ensure pageSize doesn't exceed safe limit
|
|
46
73
|
const safePageSize = Math.min(pageSize, 50);
|
|
47
74
|
const offset = page * safePageSize;
|
|
48
|
-
//
|
|
75
|
+
// Normalize directory and check cache for this specific page
|
|
76
|
+
dir = this.normalizeDir(dir);
|
|
49
77
|
const cacheKey = this.makeKey(dir, recursive, page);
|
|
50
78
|
const cached = this.cache.get(cacheKey);
|
|
51
79
|
if (cached && (Date.now() - cached.timestamp) < this.ttlMs) {
|
|
@@ -91,8 +119,8 @@ export class AssetResources {
|
|
|
91
119
|
};
|
|
92
120
|
}
|
|
93
121
|
/**
|
|
94
|
-
* Directory-based listing
|
|
95
|
-
*
|
|
122
|
+
* Directory-based listing of immediate children using AssetRegistry.
|
|
123
|
+
* Returns both subfolders and assets at the given path.
|
|
96
124
|
*/
|
|
97
125
|
async listDirectoryOnly(dir, _recursive, limit) {
|
|
98
126
|
// Always return only immediate children to avoid timeout and improve navigation
|
|
@@ -101,100 +129,84 @@ export class AssetResources {
|
|
|
101
129
|
import unreal
|
|
102
130
|
import json
|
|
103
131
|
|
|
104
|
-
_dir = r"${dir}"
|
|
132
|
+
_dir = r"${this.normalizeDir(dir)}"
|
|
105
133
|
|
|
106
134
|
try:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
135
|
+
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
|
136
|
+
# Immediate subfolders
|
|
137
|
+
sub_paths = ar.get_sub_paths(_dir, False)
|
|
138
|
+
folders_list = []
|
|
139
|
+
for p in sub_paths:
|
|
140
|
+
try:
|
|
141
|
+
name = p.split('/')[-1]
|
|
142
|
+
folders_list.append({'n': name, 'p': p})
|
|
143
|
+
except Exception:
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
# Immediate assets at this path
|
|
147
|
+
assets_data = ar.get_assets_by_path(_dir, False)
|
|
148
|
+
assets = []
|
|
149
|
+
for a in assets_data[:${limit}]:
|
|
150
|
+
try:
|
|
151
|
+
assets.append({
|
|
152
|
+
'n': str(a.asset_name),
|
|
153
|
+
'p': str(a.object_path),
|
|
154
|
+
'c': str(a.asset_class)
|
|
155
|
+
})
|
|
156
|
+
except Exception:
|
|
157
|
+
pass
|
|
158
|
+
|
|
153
159
|
print("RESULT:" + json.dumps({
|
|
154
|
-
'success': True,
|
|
155
|
-
'
|
|
156
|
-
'
|
|
157
|
-
'
|
|
158
|
-
'
|
|
159
|
-
'
|
|
160
|
+
'success': True,
|
|
161
|
+
'path': _dir,
|
|
162
|
+
'folders': len(folders_list),
|
|
163
|
+
'files': len(assets),
|
|
164
|
+
'folders_list': folders_list,
|
|
165
|
+
'assets': assets
|
|
160
166
|
}))
|
|
161
167
|
except Exception as e:
|
|
162
|
-
print("RESULT:" + json.dumps({'success': False, 'error': str(e), '
|
|
168
|
+
print("RESULT:" + json.dumps({'success': False, 'error': str(e), 'path': _dir}))
|
|
163
169
|
`.trim();
|
|
164
170
|
const resp = await this.bridge.executePython(py);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
171
|
+
const interpreted = interpretStandardResult(resp, {
|
|
172
|
+
successMessage: 'Directory contents retrieved',
|
|
173
|
+
failureMessage: 'Failed to list directory contents'
|
|
174
|
+
});
|
|
175
|
+
if (interpreted.success) {
|
|
176
|
+
const payload = interpreted.payload;
|
|
177
|
+
const foldersArr = Array.isArray(payload.folders_list)
|
|
178
|
+
? payload.folders_list.map((f) => ({
|
|
179
|
+
Name: coerceString(f?.n) ?? '',
|
|
180
|
+
Path: coerceString(f?.p) ?? '',
|
|
181
|
+
Class: 'Folder',
|
|
182
|
+
isFolder: true
|
|
183
|
+
}))
|
|
184
|
+
: [];
|
|
185
|
+
const assetsArr = Array.isArray(payload.assets)
|
|
186
|
+
? payload.assets.map((a) => ({
|
|
187
|
+
Name: coerceString(a?.n) ?? '',
|
|
188
|
+
Path: coerceString(a?.p) ?? '',
|
|
189
|
+
Class: coerceString(a?.c) ?? 'Asset',
|
|
190
|
+
isFolder: false
|
|
191
|
+
}))
|
|
192
|
+
: [];
|
|
193
|
+
const total = foldersArr.length + assetsArr.length;
|
|
194
|
+
const summary = {
|
|
195
|
+
total,
|
|
196
|
+
folders: foldersArr.length,
|
|
197
|
+
assets: assetsArr.length
|
|
198
|
+
};
|
|
199
|
+
const resolvedPath = coerceString(payload.path) ?? this.normalizeDir(dir);
|
|
200
|
+
return {
|
|
201
|
+
success: true,
|
|
202
|
+
path: resolvedPath,
|
|
203
|
+
summary,
|
|
204
|
+
foldersList: foldersArr,
|
|
205
|
+
assets: assetsArr,
|
|
206
|
+
count: total,
|
|
207
|
+
note: `Immediate children of ${resolvedPath}: ${foldersArr.length} folder(s), ${assetsArr.length} asset(s)`,
|
|
208
|
+
method: 'asset_registry_listing'
|
|
209
|
+
};
|
|
198
210
|
}
|
|
199
211
|
}
|
|
200
212
|
catch (err) {
|
|
@@ -202,10 +214,13 @@ except Exception as e:
|
|
|
202
214
|
}
|
|
203
215
|
// Fallback: return empty with explanation
|
|
204
216
|
return {
|
|
217
|
+
success: true,
|
|
218
|
+
path: this.normalizeDir(dir),
|
|
219
|
+
summary: { total: 0, folders: 0, assets: 0 },
|
|
220
|
+
foldersList: [],
|
|
205
221
|
assets: [],
|
|
206
|
-
warning: '
|
|
207
|
-
|
|
208
|
-
method: 'directory_timeout_fallback'
|
|
222
|
+
warning: 'No items at this path or failed to query AssetRegistry.',
|
|
223
|
+
method: 'asset_registry_fallback'
|
|
209
224
|
};
|
|
210
225
|
}
|
|
211
226
|
async find(assetPath) {
|
|
@@ -213,9 +228,11 @@ except Exception as e:
|
|
|
213
228
|
if (!assetPath || typeof assetPath !== 'string' || assetPath.trim() === '' || assetPath.endsWith('/')) {
|
|
214
229
|
return false;
|
|
215
230
|
}
|
|
231
|
+
// Normalize asset path (support users passing /Content/...)
|
|
232
|
+
const ap = this.normalizeDir(assetPath);
|
|
216
233
|
const py = `
|
|
217
234
|
import unreal
|
|
218
|
-
apath = r"${
|
|
235
|
+
apath = r"${ap}"
|
|
219
236
|
try:
|
|
220
237
|
exists = unreal.EditorAssetLibrary.does_asset_exist(apath)
|
|
221
238
|
print("RESULT:{'success': True, 'exists': %s}" % ('True' if exists else 'False'))
|
|
@@ -223,21 +240,12 @@ except Exception as e:
|
|
|
223
240
|
print("RESULT:{'success': False, 'error': '" + str(e) + "'}")
|
|
224
241
|
`.trim();
|
|
225
242
|
const resp = await this.bridge.executePython(py);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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 { }
|
|
243
|
+
const interpreted = interpretStandardResult(resp, {
|
|
244
|
+
successMessage: 'Asset existence verified',
|
|
245
|
+
failureMessage: 'Failed to verify asset existence'
|
|
246
|
+
});
|
|
247
|
+
if (interpreted.success) {
|
|
248
|
+
return coerceBoolean(interpreted.payload.exists, false) ?? false;
|
|
241
249
|
}
|
|
242
250
|
return false;
|
|
243
251
|
}
|
|
@@ -2,15 +2,33 @@ import { UnrealBridge } from '../unreal-bridge.js';
|
|
|
2
2
|
export declare class LevelResources {
|
|
3
3
|
private bridge;
|
|
4
4
|
constructor(bridge: UnrealBridge);
|
|
5
|
-
getCurrentLevel(): Promise<
|
|
6
|
-
|
|
5
|
+
getCurrentLevel(): Promise<{
|
|
6
|
+
success: boolean;
|
|
7
|
+
name: string;
|
|
8
|
+
path: string;
|
|
9
|
+
error?: undefined;
|
|
10
|
+
} | {
|
|
11
|
+
success: boolean;
|
|
12
|
+
error: string;
|
|
13
|
+
name?: undefined;
|
|
14
|
+
path?: undefined;
|
|
15
|
+
}>;
|
|
16
|
+
getLevelName(): Promise<{
|
|
17
|
+
success: boolean;
|
|
18
|
+
path: string;
|
|
19
|
+
error?: undefined;
|
|
20
|
+
} | {
|
|
21
|
+
success: boolean;
|
|
22
|
+
error: string;
|
|
23
|
+
path?: undefined;
|
|
24
|
+
}>;
|
|
7
25
|
saveCurrentLevel(): Promise<{
|
|
8
26
|
success: boolean;
|
|
9
27
|
message: string;
|
|
10
28
|
error?: undefined;
|
|
11
29
|
} | {
|
|
12
|
-
error: string;
|
|
13
30
|
success: boolean;
|
|
31
|
+
error: string;
|
|
14
32
|
message?: undefined;
|
|
15
33
|
}>;
|
|
16
34
|
}
|
package/dist/resources/levels.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { coerceString, interpretStandardResult } from '../utils/result-helpers.js';
|
|
1
2
|
export class LevelResources {
|
|
2
3
|
bridge;
|
|
3
4
|
constructor(bridge) {
|
|
@@ -8,25 +9,18 @@ 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 = await this.bridge.executePython(py);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const interpreted = interpretStandardResult(resp, {
|
|
13
|
+
successMessage: 'Retrieved current level',
|
|
14
|
+
failureMessage: 'Failed to get current level'
|
|
15
|
+
});
|
|
16
|
+
if (interpreted.success) {
|
|
17
|
+
return {
|
|
18
|
+
success: true,
|
|
19
|
+
name: coerceString(interpreted.payload.name) ?? coerceString(interpreted.payload.level_name) ?? 'None',
|
|
20
|
+
path: coerceString(interpreted.payload.path) ?? 'None'
|
|
21
|
+
};
|
|
15
22
|
}
|
|
16
|
-
|
|
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 };
|
|
23
|
+
return { success: false, error: interpreted.error ?? interpreted.message };
|
|
30
24
|
}
|
|
31
25
|
catch (err) {
|
|
32
26
|
return { error: `Failed to get current level: ${err}`, success: false };
|
|
@@ -37,54 +31,35 @@ export class LevelResources {
|
|
|
37
31
|
try {
|
|
38
32
|
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
33
|
const resp = await this.bridge.executePython(py);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
out = JSON.stringify(resp);
|
|
34
|
+
const interpreted = interpretStandardResult(resp, {
|
|
35
|
+
successMessage: 'Retrieved level path',
|
|
36
|
+
failureMessage: 'Failed to get level name'
|
|
37
|
+
});
|
|
38
|
+
if (interpreted.success) {
|
|
39
|
+
return {
|
|
40
|
+
success: true,
|
|
41
|
+
path: coerceString(interpreted.payload.path) ?? ''
|
|
42
|
+
};
|
|
50
43
|
}
|
|
51
|
-
|
|
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 };
|
|
44
|
+
return { success: false, error: interpreted.error ?? interpreted.message };
|
|
59
45
|
}
|
|
60
46
|
catch (err) {
|
|
61
47
|
return { error: `Failed to get level name: ${err}`, success: false };
|
|
62
48
|
}
|
|
63
49
|
}
|
|
64
50
|
async saveCurrentLevel() {
|
|
65
|
-
//
|
|
51
|
+
// Strict modern API: require LevelEditorSubsystem
|
|
66
52
|
try {
|
|
67
|
-
const py = '\nimport unreal, json\ntry:\n les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)\n if les
|
|
53
|
+
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();
|
|
68
54
|
const resp = await this.bridge.executePython(py);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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' };
|
|
55
|
+
const interpreted = interpretStandardResult(resp, {
|
|
56
|
+
successMessage: 'Level saved',
|
|
57
|
+
failureMessage: 'Failed to save level'
|
|
58
|
+
});
|
|
59
|
+
if (interpreted.success) {
|
|
60
|
+
return { success: true, message: interpreted.message };
|
|
85
61
|
}
|
|
86
|
-
|
|
87
|
-
return { error: 'Failed to save level', success: false };
|
|
62
|
+
return { success: false, error: interpreted.error ?? interpreted.message };
|
|
88
63
|
}
|
|
89
64
|
catch (err) {
|
|
90
65
|
return { error: `Failed to save level: ${err}`, success: false };
|
package/dist/tools/actors.d.ts
CHANGED
|
@@ -14,20 +14,8 @@ export declare class ActorTools {
|
|
|
14
14
|
yaw: number;
|
|
15
15
|
roll: number;
|
|
16
16
|
};
|
|
17
|
-
|
|
18
|
-
|
|
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>;
|
|
17
|
+
actorName?: string;
|
|
18
|
+
}): Promise<Record<string, unknown>>;
|
|
31
19
|
spawnViaConsole(params: {
|
|
32
20
|
classPath: string;
|
|
33
21
|
location?: {
|
|
@@ -45,6 +33,7 @@ export declare class ActorTools {
|
|
|
45
33
|
message: string;
|
|
46
34
|
note: string;
|
|
47
35
|
}>;
|
|
36
|
+
private getPythonSpawnHelper;
|
|
48
37
|
private resolveActorClass;
|
|
49
38
|
private getConsoleClassName;
|
|
50
39
|
}
|