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.
- package/.dockerignore +57 -0
- package/.env.production +25 -0
- package/.eslintrc.json +54 -0
- package/.github/workflows/publish-mcp.yml +75 -0
- package/Dockerfile +54 -0
- package/LICENSE +21 -0
- package/Public/icon.png +0 -0
- package/README.md +209 -0
- package/claude_desktop_config_example.json +13 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +7 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +484 -0
- package/dist/prompts/index.d.ts +14 -0
- package/dist/prompts/index.js +38 -0
- package/dist/python-utils.d.ts +29 -0
- package/dist/python-utils.js +54 -0
- package/dist/resources/actors.d.ts +13 -0
- package/dist/resources/actors.js +83 -0
- package/dist/resources/assets.d.ts +23 -0
- package/dist/resources/assets.js +245 -0
- package/dist/resources/levels.d.ts +17 -0
- package/dist/resources/levels.js +94 -0
- package/dist/tools/actors.d.ts +51 -0
- package/dist/tools/actors.js +459 -0
- package/dist/tools/animation.d.ts +196 -0
- package/dist/tools/animation.js +579 -0
- package/dist/tools/assets.d.ts +21 -0
- package/dist/tools/assets.js +304 -0
- package/dist/tools/audio.d.ts +170 -0
- package/dist/tools/audio.js +416 -0
- package/dist/tools/blueprint.d.ts +144 -0
- package/dist/tools/blueprint.js +652 -0
- package/dist/tools/build_environment_advanced.d.ts +66 -0
- package/dist/tools/build_environment_advanced.js +484 -0
- package/dist/tools/consolidated-tool-definitions.d.ts +2598 -0
- package/dist/tools/consolidated-tool-definitions.js +607 -0
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -0
- package/dist/tools/consolidated-tool-handlers.js +1050 -0
- package/dist/tools/debug.d.ts +185 -0
- package/dist/tools/debug.js +265 -0
- package/dist/tools/editor.d.ts +88 -0
- package/dist/tools/editor.js +365 -0
- package/dist/tools/engine.d.ts +30 -0
- package/dist/tools/engine.js +36 -0
- package/dist/tools/foliage.d.ts +155 -0
- package/dist/tools/foliage.js +525 -0
- package/dist/tools/introspection.d.ts +98 -0
- package/dist/tools/introspection.js +683 -0
- package/dist/tools/landscape.d.ts +158 -0
- package/dist/tools/landscape.js +375 -0
- package/dist/tools/level.d.ts +110 -0
- package/dist/tools/level.js +362 -0
- package/dist/tools/lighting.d.ts +159 -0
- package/dist/tools/lighting.js +1179 -0
- package/dist/tools/materials.d.ts +34 -0
- package/dist/tools/materials.js +146 -0
- package/dist/tools/niagara.d.ts +145 -0
- package/dist/tools/niagara.js +289 -0
- package/dist/tools/performance.d.ts +163 -0
- package/dist/tools/performance.js +412 -0
- package/dist/tools/physics.d.ts +189 -0
- package/dist/tools/physics.js +784 -0
- package/dist/tools/rc.d.ts +110 -0
- package/dist/tools/rc.js +363 -0
- package/dist/tools/sequence.d.ts +112 -0
- package/dist/tools/sequence.js +675 -0
- package/dist/tools/tool-definitions.d.ts +4919 -0
- package/dist/tools/tool-definitions.js +891 -0
- package/dist/tools/tool-handlers.d.ts +47 -0
- package/dist/tools/tool-handlers.js +830 -0
- package/dist/tools/ui.d.ts +171 -0
- package/dist/tools/ui.js +337 -0
- package/dist/tools/visual.d.ts +29 -0
- package/dist/tools/visual.js +67 -0
- package/dist/types/env.d.ts +10 -0
- package/dist/types/env.js +18 -0
- package/dist/types/index.d.ts +323 -0
- package/dist/types/index.js +28 -0
- package/dist/types/tool-types.d.ts +274 -0
- package/dist/types/tool-types.js +13 -0
- package/dist/unreal-bridge.d.ts +126 -0
- package/dist/unreal-bridge.js +992 -0
- package/dist/utils/cache-manager.d.ts +64 -0
- package/dist/utils/cache-manager.js +176 -0
- package/dist/utils/error-handler.d.ts +66 -0
- package/dist/utils/error-handler.js +243 -0
- package/dist/utils/errors.d.ts +133 -0
- package/dist/utils/errors.js +256 -0
- package/dist/utils/http.d.ts +26 -0
- package/dist/utils/http.js +135 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.js +32 -0
- package/dist/utils/normalize.d.ts +17 -0
- package/dist/utils/normalize.js +49 -0
- package/dist/utils/response-validator.d.ts +34 -0
- package/dist/utils/response-validator.js +121 -0
- package/dist/utils/safe-json.d.ts +4 -0
- package/dist/utils/safe-json.js +97 -0
- package/dist/utils/stdio-redirect.d.ts +2 -0
- package/dist/utils/stdio-redirect.js +20 -0
- package/dist/utils/validation.d.ts +50 -0
- package/dist/utils/validation.js +173 -0
- package/mcp-config-example.json +14 -0
- package/package.json +63 -0
- package/server.json +60 -0
- package/src/cli.ts +7 -0
- package/src/index.ts +543 -0
- package/src/prompts/index.ts +51 -0
- package/src/python/editor_compat.py +181 -0
- package/src/python-utils.ts +57 -0
- package/src/resources/actors.ts +92 -0
- package/src/resources/assets.ts +251 -0
- package/src/resources/levels.ts +83 -0
- package/src/tools/actors.ts +480 -0
- package/src/tools/animation.ts +713 -0
- package/src/tools/assets.ts +305 -0
- package/src/tools/audio.ts +548 -0
- package/src/tools/blueprint.ts +736 -0
- package/src/tools/build_environment_advanced.ts +526 -0
- package/src/tools/consolidated-tool-definitions.ts +619 -0
- package/src/tools/consolidated-tool-handlers.ts +1093 -0
- package/src/tools/debug.ts +368 -0
- package/src/tools/editor.ts +360 -0
- package/src/tools/engine.ts +32 -0
- package/src/tools/foliage.ts +652 -0
- package/src/tools/introspection.ts +778 -0
- package/src/tools/landscape.ts +523 -0
- package/src/tools/level.ts +410 -0
- package/src/tools/lighting.ts +1316 -0
- package/src/tools/materials.ts +148 -0
- package/src/tools/niagara.ts +312 -0
- package/src/tools/performance.ts +549 -0
- package/src/tools/physics.ts +924 -0
- package/src/tools/rc.ts +437 -0
- package/src/tools/sequence.ts +791 -0
- package/src/tools/tool-definitions.ts +907 -0
- package/src/tools/tool-handlers.ts +941 -0
- package/src/tools/ui.ts +499 -0
- package/src/tools/visual.ts +60 -0
- package/src/types/env.ts +27 -0
- package/src/types/index.ts +414 -0
- package/src/types/tool-types.ts +343 -0
- package/src/unreal-bridge.ts +1118 -0
- package/src/utils/cache-manager.ts +213 -0
- package/src/utils/error-handler.ts +320 -0
- package/src/utils/errors.ts +312 -0
- package/src/utils/http.ts +184 -0
- package/src/utils/logger.ts +30 -0
- package/src/utils/normalize.ts +54 -0
- package/src/utils/response-validator.ts +145 -0
- package/src/utils/safe-json.ts +112 -0
- package/src/utils/stdio-redirect.ts +18 -0
- package/src/utils/validation.ts +212 -0
- 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
|