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,34 @@
1
+ import { UnrealBridge } from '../unreal-bridge.js';
2
+ export declare class MaterialTools {
3
+ private bridge;
4
+ constructor(bridge: UnrealBridge);
5
+ createMaterial(name: string, path: string): Promise<{
6
+ success: boolean;
7
+ path: string;
8
+ message: string;
9
+ error?: undefined;
10
+ debug?: undefined;
11
+ } | {
12
+ success: boolean;
13
+ error: any;
14
+ path?: undefined;
15
+ message?: undefined;
16
+ debug?: undefined;
17
+ } | {
18
+ success: boolean;
19
+ error: string;
20
+ debug: string;
21
+ path?: undefined;
22
+ message?: undefined;
23
+ }>;
24
+ applyMaterialToActor(actorPath: string, materialPath: string, slotIndex?: number): Promise<{
25
+ success: boolean;
26
+ message: string;
27
+ error?: undefined;
28
+ } | {
29
+ success: boolean;
30
+ error: string;
31
+ message?: undefined;
32
+ }>;
33
+ }
34
+ //# sourceMappingURL=materials.d.ts.map
@@ -0,0 +1,146 @@
1
+ export class MaterialTools {
2
+ bridge;
3
+ constructor(bridge) {
4
+ this.bridge = bridge;
5
+ }
6
+ async createMaterial(name, path) {
7
+ try {
8
+ // Validate name
9
+ if (!name || name.trim() === '') {
10
+ return { success: false, error: 'Material name cannot be empty' };
11
+ }
12
+ // Check name length (Unreal has 260 char path limit)
13
+ if (name.length > 100) {
14
+ return { success: false, error: `Material name too long (${name.length} chars). Maximum is 100 characters.` };
15
+ }
16
+ // Validate name doesn't contain invalid characters
17
+ // Unreal Engine doesn't allow: spaces, dots, slashes, backslashes, pipes, angle brackets,
18
+ // curly braces, square brackets, parentheses, @, #, etc.
19
+ const invalidChars = /[\s./<>|{}[\]()@#\\]/;
20
+ if (invalidChars.test(name)) {
21
+ const foundChars = name.match(invalidChars);
22
+ return { success: false, error: `Material name contains invalid characters: '${foundChars?.[0]}'. Avoid spaces, dots, slashes, backslashes, brackets, and special symbols.` };
23
+ }
24
+ // Validate path type
25
+ if (typeof path !== 'string') {
26
+ return { success: false, error: `Invalid path type: expected string, got ${typeof path}` };
27
+ }
28
+ // Clean up path - remove trailing slashes
29
+ const cleanPath = path.replace(/\/$/, '');
30
+ // Validate path starts with /Game or /Engine
31
+ if (!cleanPath.startsWith('/Game') && !cleanPath.startsWith('/Engine')) {
32
+ return { success: false, error: `Invalid path: must start with /Game or /Engine, got ${cleanPath}` };
33
+ }
34
+ // Use Python API to create material
35
+ const materialPath = `${cleanPath}/${name}`;
36
+ // Use the correct Unreal Engine 5 Python API
37
+ const pythonCode = `
38
+ import unreal
39
+ import json
40
+
41
+ try:
42
+ # Check if material already exists
43
+ material_path = '${materialPath}'
44
+ if unreal.EditorAssetLibrary.does_asset_exist(material_path):
45
+ print(json.dumps({"success": True, "exists": True, "path": material_path}))
46
+ else:
47
+ # Get the AssetTools
48
+ asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
49
+
50
+ # Create a MaterialFactoryNew
51
+ factory = unreal.MaterialFactoryNew()
52
+
53
+ # Clean up the path - remove trailing slashes
54
+ clean_path = '${cleanPath}'.rstrip('/')
55
+
56
+ # Create the material asset at the specified path
57
+ # The path should be: /Game/FolderName and asset name separately
58
+ asset = asset_tools.create_asset(
59
+ asset_name='${name}',
60
+ package_path=clean_path,
61
+ asset_class=unreal.Material,
62
+ factory=factory
63
+ )
64
+
65
+ if asset:
66
+ # Save the package
67
+ unreal.EditorAssetLibrary.save_asset(material_path)
68
+ print(json.dumps({"success": True, "created": True, "path": material_path}))
69
+ else:
70
+ print(json.dumps({"success": False, "error": "Failed to create material"}))
71
+ except Exception as e:
72
+ print(json.dumps({"success": False, "error": str(e)}))
73
+ `.trim();
74
+ const pyResult = await this.bridge.executePython(pythonCode);
75
+ // Parse the Python response
76
+ let responseStr = '';
77
+ if (pyResult?.LogOutput && Array.isArray(pyResult.LogOutput)) {
78
+ responseStr = pyResult.LogOutput.map((log) => log.Output || '').join('');
79
+ }
80
+ else if (typeof pyResult === 'string') {
81
+ responseStr = pyResult;
82
+ }
83
+ else {
84
+ responseStr = JSON.stringify(pyResult);
85
+ }
86
+ // Try to extract JSON response
87
+ try {
88
+ // Look for JSON in the output
89
+ const jsonMatch = responseStr.match(/\{.*\}/s);
90
+ if (jsonMatch) {
91
+ const result = JSON.parse(jsonMatch[0]);
92
+ if (result.success) {
93
+ if (result.exists) {
94
+ return { success: true, path: materialPath, message: `Material ${name} already exists at ${path}` };
95
+ }
96
+ else if (result.created) {
97
+ return { success: true, path: materialPath, message: `Material ${name} created at ${path}` };
98
+ }
99
+ }
100
+ else {
101
+ return { success: false, error: result.error || 'Failed to create material' };
102
+ }
103
+ }
104
+ }
105
+ catch {
106
+ // JSON parsing failed, fall back to verification
107
+ }
108
+ // Fallback: Verify creation using EditorAssetLibrary
109
+ let verify = {};
110
+ try {
111
+ verify = await this.bridge.call({
112
+ objectPath: '/Script/EditorScriptingUtilities.Default__EditorAssetLibrary',
113
+ functionName: 'DoesAssetExist',
114
+ parameters: {
115
+ AssetPath: materialPath
116
+ }
117
+ });
118
+ }
119
+ catch { }
120
+ const exists = verify?.ReturnValue === true || verify?.Result === true;
121
+ if (exists) {
122
+ return { success: true, path: materialPath, message: `Material ${name} created at ${path}` };
123
+ }
124
+ else {
125
+ return { success: false, error: 'Material creation may have failed. Check Output Log for details.', debug: responseStr };
126
+ }
127
+ }
128
+ catch (err) {
129
+ return { success: false, error: `Failed to create material: ${err}` };
130
+ }
131
+ }
132
+ async applyMaterialToActor(actorPath, materialPath, slotIndex = 0) {
133
+ try {
134
+ await this.bridge.httpCall('/remote/object/property', 'PUT', {
135
+ objectPath: actorPath,
136
+ propertyName: `StaticMeshComponent.Materials[${slotIndex}]`,
137
+ propertyValue: materialPath
138
+ });
139
+ return { success: true, message: 'Material applied' };
140
+ }
141
+ catch (err) {
142
+ return { success: false, error: `Failed to apply material: ${err}` };
143
+ }
144
+ }
145
+ }
146
+ //# sourceMappingURL=materials.js.map
@@ -0,0 +1,145 @@
1
+ import { UnrealBridge } from '../unreal-bridge.js';
2
+ export declare class NiagaraTools {
3
+ private bridge;
4
+ constructor(bridge: UnrealBridge);
5
+ /**
6
+ * Create Niagara System (real asset via Python)
7
+ */
8
+ createSystem(params: {
9
+ name: string;
10
+ savePath?: string;
11
+ template?: 'Empty' | 'Fountain' | 'Ambient' | 'Projectile' | 'Custom';
12
+ emitters?: Array<{
13
+ name: string;
14
+ spawnRate?: number;
15
+ lifetime?: number;
16
+ shape?: 'Point' | 'Sphere' | 'Box' | 'Cylinder' | 'Cone';
17
+ shapeSize?: [number, number, number];
18
+ }>;
19
+ }): Promise<{
20
+ success: boolean;
21
+ path: any;
22
+ message: string;
23
+ error?: undefined;
24
+ } | {
25
+ success: boolean;
26
+ error: any;
27
+ path?: undefined;
28
+ message?: undefined;
29
+ }>;
30
+ /**
31
+ * Add Emitter to System (left as-is; console commands may be placeholders)
32
+ */
33
+ addEmitter(params: {
34
+ systemName: string;
35
+ emitterName: string;
36
+ emitterType: 'Sprite' | 'Mesh' | 'Ribbon' | 'Beam' | 'GPU';
37
+ properties?: {
38
+ spawnRate?: number;
39
+ lifetime?: number;
40
+ velocityMin?: [number, number, number];
41
+ velocityMax?: [number, number, number];
42
+ size?: number;
43
+ color?: [number, number, number, number];
44
+ material?: string;
45
+ mesh?: string;
46
+ };
47
+ }): Promise<{
48
+ success: boolean;
49
+ message: string;
50
+ error?: undefined;
51
+ } | {
52
+ success: boolean;
53
+ error: string;
54
+ message?: undefined;
55
+ }>;
56
+ setParameter(params: {
57
+ systemName: string;
58
+ parameterName: string;
59
+ parameterType: 'Float' | 'Vector' | 'Color' | 'Bool' | 'Int';
60
+ value: any;
61
+ isUserParameter?: boolean;
62
+ }): Promise<{
63
+ success: boolean;
64
+ message: string;
65
+ error?: undefined;
66
+ } | {
67
+ success: boolean;
68
+ error: string;
69
+ message?: undefined;
70
+ }>;
71
+ /**
72
+ * Create Preset Effect (now creates a real Niagara system asset)
73
+ */
74
+ createEffect(params: {
75
+ effectType: 'Fire' | 'Smoke' | 'Explosion' | 'Water' | 'Rain' | 'Snow' | 'Magic' | 'Lightning' | 'Dust' | 'Steam';
76
+ name: string;
77
+ location: [number, number, number] | {
78
+ x: number;
79
+ y: number;
80
+ z: number;
81
+ };
82
+ scale?: number;
83
+ intensity?: number;
84
+ customParameters?: {
85
+ [key: string]: any;
86
+ };
87
+ }): Promise<{
88
+ success: boolean;
89
+ error: any;
90
+ message?: undefined;
91
+ path?: undefined;
92
+ } | {
93
+ success: boolean;
94
+ message: string;
95
+ path: string;
96
+ error?: undefined;
97
+ }>;
98
+ createGPUSimulation(params: {
99
+ name: string;
100
+ simulationType: 'Fluid' | 'Hair' | 'Cloth' | 'Debris' | 'Crowd';
101
+ particleCount: number;
102
+ savePath?: string;
103
+ gpuSettings?: {
104
+ computeShader?: string;
105
+ textureFormat?: 'RGBA8' | 'RGBA16F' | 'RGBA32F';
106
+ gridResolution?: [number, number, number];
107
+ iterations?: number;
108
+ };
109
+ }): Promise<{
110
+ success: boolean;
111
+ message: string;
112
+ path: string;
113
+ error?: undefined;
114
+ } | {
115
+ success: boolean;
116
+ error: string;
117
+ message?: undefined;
118
+ path?: undefined;
119
+ }>;
120
+ /**
121
+ * Spawn Niagara Effect in Level using Python (NiagaraActor)
122
+ */
123
+ spawnEffect(params: {
124
+ systemPath: string;
125
+ location: [number, number, number] | {
126
+ x: number;
127
+ y: number;
128
+ z: number;
129
+ };
130
+ rotation?: [number, number, number];
131
+ scale?: [number, number, number] | number;
132
+ autoDestroy?: boolean;
133
+ attachToActor?: string;
134
+ }): Promise<{
135
+ success: boolean;
136
+ message: string;
137
+ error?: undefined;
138
+ } | {
139
+ success: boolean;
140
+ error: any;
141
+ message?: undefined;
142
+ }>;
143
+ private _executeCommand;
144
+ }
145
+ //# sourceMappingURL=niagara.d.ts.map
@@ -0,0 +1,289 @@
1
+ import { sanitizeAssetName, validateAssetParams } from '../utils/validation.js';
2
+ export class NiagaraTools {
3
+ bridge;
4
+ constructor(bridge) {
5
+ this.bridge = bridge;
6
+ }
7
+ /**
8
+ * Create Niagara System (real asset via Python)
9
+ */
10
+ async createSystem(params) {
11
+ try {
12
+ const path = params.savePath || '/Game/Effects/Niagara';
13
+ // const fullPath = `${path}/${params.name}`; // Currently unused
14
+ const python = `
15
+ import unreal
16
+ path = r"${path}"
17
+ name = r"${params.name}"
18
+ full_path = f"{path}/{name}"
19
+ # If already exists, just report success
20
+ if unreal.EditorAssetLibrary.does_asset_exist(full_path):
21
+ print("RESULT:{'success': True, 'path': '" + full_path + "', 'existing': True}")
22
+ else:
23
+ asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
24
+ factory = None
25
+ try:
26
+ factory = unreal.NiagaraSystemFactoryNew()
27
+ except Exception as e:
28
+ factory = None
29
+ if factory is None:
30
+ print("RESULT:{'success': False, 'error': 'NiagaraSystemFactoryNew unavailable'}")
31
+ else:
32
+ asset = asset_tools.create_asset(asset_name=name, package_path=path, asset_class=unreal.NiagaraSystem, factory=factory)
33
+ if asset:
34
+ unreal.EditorAssetLibrary.save_asset(full_path)
35
+ print("RESULT:{'success': True, 'path': '" + full_path + "'}")
36
+ else:
37
+ print("RESULT:{'success': False, 'error': 'AssetTools create_asset failed'}")
38
+ `.trim();
39
+ const resp = await this.bridge.executePython(python);
40
+ let output = '';
41
+ if (resp?.LogOutput && Array.isArray(resp.LogOutput)) {
42
+ output = resp.LogOutput.map((l) => l.Output || '').join('');
43
+ }
44
+ else if (typeof resp === 'string') {
45
+ output = resp;
46
+ }
47
+ else {
48
+ output = JSON.stringify(resp);
49
+ }
50
+ const m = output.match(/RESULT:({.*})/);
51
+ if (m) {
52
+ try {
53
+ const parsed = JSON.parse(m[1].replace(/'/g, '"'));
54
+ if (parsed.success) {
55
+ return { success: true, path: parsed.path, message: `Niagara system ${params.name} created` };
56
+ }
57
+ return { success: false, error: parsed.error || 'Unknown error creating Niagara system' };
58
+ }
59
+ catch {
60
+ // fallthrough
61
+ }
62
+ }
63
+ return { success: false, error: 'No RESULT from Python when creating Niagara system' };
64
+ }
65
+ catch (err) {
66
+ return { success: false, error: `Failed to create Niagara system: ${err}` };
67
+ }
68
+ }
69
+ /**
70
+ * Add Emitter to System (left as-is; console commands may be placeholders)
71
+ */
72
+ async addEmitter(params) {
73
+ try {
74
+ const commands = [
75
+ `AddNiagaraEmitter ${params.systemName} ${params.emitterName} ${params.emitterType}`
76
+ ];
77
+ if (params.properties) {
78
+ const props = params.properties;
79
+ if (props.spawnRate !== undefined) {
80
+ commands.push(`SetEmitterSpawnRate ${params.systemName} ${params.emitterName} ${props.spawnRate}`);
81
+ }
82
+ if (props.lifetime !== undefined) {
83
+ commands.push(`SetEmitterLifetime ${params.systemName} ${params.emitterName} ${props.lifetime}`);
84
+ }
85
+ if (props.velocityMin && props.velocityMax) {
86
+ const min = props.velocityMin;
87
+ const max = props.velocityMax;
88
+ commands.push(`SetEmitterVelocity ${params.systemName} ${params.emitterName} ${min[0]} ${min[1]} ${min[2]} ${max[0]} ${max[1]} ${max[2]}`);
89
+ }
90
+ if (props.size !== undefined) {
91
+ commands.push(`SetEmitterSize ${params.systemName} ${params.emitterName} ${props.size}`);
92
+ }
93
+ if (props.color) {
94
+ const color = props.color;
95
+ commands.push(`SetEmitterColor ${params.systemName} ${params.emitterName} ${color[0]} ${color[1]} ${color[2]} ${color[3]}`);
96
+ }
97
+ if (props.material) {
98
+ commands.push(`SetEmitterMaterial ${params.systemName} ${params.emitterName} ${props.material}`);
99
+ }
100
+ if (props.mesh && params.emitterType === 'Mesh') {
101
+ commands.push(`SetEmitterMesh ${params.systemName} ${params.emitterName} ${props.mesh}`);
102
+ }
103
+ }
104
+ for (const cmd of commands) {
105
+ await this.bridge.executeConsoleCommand(cmd);
106
+ }
107
+ return { success: true, message: `Emitter ${params.emitterName} added to ${params.systemName}` };
108
+ }
109
+ catch (err) {
110
+ return { success: false, error: `Failed to add emitter: ${err}` };
111
+ }
112
+ }
113
+ async setParameter(params) {
114
+ try {
115
+ const paramType = params.isUserParameter ? 'User' : 'System';
116
+ let valueStr = '';
117
+ switch (params.parameterType) {
118
+ case 'Float':
119
+ case 'Int':
120
+ case 'Bool':
121
+ valueStr = String(params.value);
122
+ break;
123
+ case 'Vector': {
124
+ const v = params.value;
125
+ valueStr = `${v[0]} ${v[1]} ${v[2]}`;
126
+ break;
127
+ }
128
+ case 'Color': {
129
+ const c = params.value;
130
+ valueStr = `${c[0]} ${c[1]} ${c[2]} ${c[3] || 1}`;
131
+ break;
132
+ }
133
+ }
134
+ const command = `SetNiagara${paramType}Parameter ${params.systemName} ${params.parameterName} ${params.parameterType} ${valueStr}`;
135
+ await this.bridge.executeConsoleCommand(command);
136
+ return { success: true, message: `Parameter ${params.parameterName} set on ${params.systemName}` };
137
+ }
138
+ catch (err) {
139
+ return { success: false, error: `Failed to set parameter: ${err}` };
140
+ }
141
+ }
142
+ /**
143
+ * Create Preset Effect (now creates a real Niagara system asset)
144
+ */
145
+ async createEffect(params) {
146
+ try {
147
+ // Validate effect type at runtime (inputs can come from JSON)
148
+ const allowedTypes = ['Fire', 'Smoke', 'Explosion', 'Water', 'Rain', 'Snow', 'Magic', 'Lightning', 'Dust', 'Steam'];
149
+ if (!params || !allowedTypes.includes(String(params.effectType))) {
150
+ return { success: false, error: `Invalid effectType: ${String(params?.effectType)}` };
151
+ }
152
+ // Sanitize and validate name and path
153
+ const defaultPath = '/Game/Effects/Niagara';
154
+ const nameToUse = sanitizeAssetName(params.name);
155
+ const validation = validateAssetParams({ name: nameToUse, savePath: defaultPath });
156
+ if (!validation.valid) {
157
+ return { success: false, error: validation.error || 'Invalid asset parameters' };
158
+ }
159
+ const safeName = validation.sanitized.name;
160
+ const savePath = validation.sanitized.savePath || defaultPath;
161
+ const fullPath = `${savePath}/${safeName}`;
162
+ // Create or ensure the Niagara system asset exists
163
+ const createRes = await this.createSystem({ name: safeName, savePath, template: 'Empty' });
164
+ if (!createRes.success) {
165
+ return { success: false, error: createRes.error || 'Failed creating Niagara system' };
166
+ }
167
+ // Verify existence via Python to avoid RC EditorAssetLibrary issues
168
+ const verifyPy = `
169
+ import unreal
170
+ p = r"${fullPath}"
171
+ print("RESULT:{'success': True, 'exists': %s}" % ('True' if unreal.EditorAssetLibrary.does_asset_exist(p) else 'False'))
172
+ `.trim();
173
+ const verifyResp = await this.bridge.executePython(verifyPy);
174
+ let vout = '';
175
+ if (verifyResp?.LogOutput && Array.isArray(verifyResp.LogOutput))
176
+ vout = verifyResp.LogOutput.map((l) => l.Output || '').join('');
177
+ else if (typeof verifyResp === 'string')
178
+ vout = verifyResp;
179
+ else
180
+ vout = JSON.stringify(verifyResp);
181
+ const m = vout.match(/RESULT:({.*})/);
182
+ if (m) {
183
+ try {
184
+ const parsed = JSON.parse(m[1].replace(/'/g, '"'));
185
+ if (!parsed.exists) {
186
+ return { success: false, error: `Asset not found after creation: ${fullPath}` };
187
+ }
188
+ }
189
+ catch { }
190
+ }
191
+ return { success: true, message: `${params.effectType} effect ${safeName} created`, path: fullPath };
192
+ }
193
+ catch (err) {
194
+ return { success: false, error: `Failed to create effect: ${err}` };
195
+ }
196
+ }
197
+ async createGPUSimulation(params) {
198
+ try {
199
+ const path = params.savePath || '/Game/Effects/GPUSimulations';
200
+ const commands = [`CreateGPUSimulation ${params.name} ${params.simulationType} ${params.particleCount} ${path}`];
201
+ if (params.gpuSettings) {
202
+ const s = params.gpuSettings;
203
+ if (s.computeShader)
204
+ commands.push(`SetGPUComputeShader ${params.name} ${s.computeShader}`);
205
+ if (s.textureFormat)
206
+ commands.push(`SetGPUTextureFormat ${params.name} ${s.textureFormat}`);
207
+ if (s.gridResolution) {
208
+ const r = s.gridResolution;
209
+ commands.push(`SetGPUGridResolution ${params.name} ${r[0]} ${r[1]} ${r[2]}`);
210
+ }
211
+ if (s.iterations !== undefined)
212
+ commands.push(`SetGPUIterations ${params.name} ${s.iterations}`);
213
+ }
214
+ for (const cmd of commands)
215
+ await this.bridge.executeConsoleCommand(cmd);
216
+ return { success: true, message: `GPU simulation ${params.name} created`, path: `${path}/${params.name}` };
217
+ }
218
+ catch (err) {
219
+ return { success: false, error: `Failed to create GPU simulation: ${err}` };
220
+ }
221
+ }
222
+ /**
223
+ * Spawn Niagara Effect in Level using Python (NiagaraActor)
224
+ */
225
+ async spawnEffect(params) {
226
+ try {
227
+ const loc = Array.isArray(params.location) ? { x: params.location[0], y: params.location[1], z: params.location[2] } : params.location;
228
+ const rot = params.rotation || [0, 0, 0];
229
+ const scl = Array.isArray(params.scale) ? params.scale : (typeof params.scale === 'number' ? [params.scale, params.scale, params.scale] : [1, 1, 1]);
230
+ const py = `
231
+ import unreal
232
+ loc = unreal.Vector(${loc.x || 0}, ${loc.y || 0}, ${loc.z || 0})
233
+ rot = unreal.Rotator(${rot[0]}, ${rot[1]}, ${rot[2]})
234
+ scale = unreal.Vector(${scl[0]}, ${scl[1]}, ${scl[2]})
235
+ sys_path = r"${params.systemPath}"
236
+ if unreal.EditorAssetLibrary.does_asset_exist(sys_path):
237
+ sys = unreal.EditorAssetLibrary.load_asset(sys_path)
238
+ actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
239
+ actor = actor_subsystem.spawn_actor_from_class(unreal.NiagaraActor, loc, rot)
240
+ if actor:
241
+ comp = actor.get_niagara_component()
242
+ try:
243
+ comp.set_asset(sys)
244
+ except Exception:
245
+ try:
246
+ comp.set_editor_property('asset', sys)
247
+ except Exception:
248
+ pass
249
+ comp.set_world_scale3d(scale)
250
+ comp.activate(True)
251
+ actor.set_actor_label(f"Niagara_{unreal.SystemLibrary.get_game_time_in_seconds(actor.get_world()):.0f}")
252
+ print("RESULT:{'success': True, 'actor': '" + actor.get_actor_label() + "'}")
253
+ else:
254
+ print("RESULT:{'success': False, 'error': 'Failed to spawn NiagaraActor'}")
255
+ else:
256
+ print("RESULT:{'success': False, 'error': 'System asset not found'}")
257
+ `.trim();
258
+ const resp = await this.bridge.executePython(py);
259
+ let output = '';
260
+ if (resp?.LogOutput && Array.isArray(resp.LogOutput))
261
+ output = resp.LogOutput.map((l) => l.Output || '').join('');
262
+ else if (typeof resp === 'string')
263
+ output = resp;
264
+ else
265
+ output = JSON.stringify(resp);
266
+ const m = output.match(/RESULT:({.*})/);
267
+ if (m) {
268
+ try {
269
+ const parsed = JSON.parse(m[1].replace(/'/g, '"'));
270
+ return parsed.success ? { success: true, message: 'Niagara effect spawned' } : { success: false, error: parsed.error || 'Spawn failed' };
271
+ }
272
+ catch { }
273
+ }
274
+ return { success: true, message: 'Niagara effect spawn attempted' };
275
+ }
276
+ catch (err) {
277
+ return { success: false, error: `Failed to spawn effect: ${err}` };
278
+ }
279
+ }
280
+ async _executeCommand(command) {
281
+ return this.bridge.httpCall('/remote/object/call', 'PUT', {
282
+ objectPath: '/Script/Engine.Default__KismetSystemLibrary',
283
+ functionName: 'ExecuteConsoleCommand',
284
+ parameters: { WorldContextObject: null, Command: command, SpecificPlayer: null },
285
+ generateTransaction: false
286
+ });
287
+ }
288
+ }
289
+ //# sourceMappingURL=niagara.js.map