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,548 @@
1
+ // Audio tools for Unreal Engine
2
+ import { UnrealBridge } from '../unreal-bridge.js';
3
+
4
+ export class AudioTools {
5
+ constructor(private bridge: UnrealBridge) {}
6
+
7
+ // Execute console command
8
+ private async _executeCommand(command: string) {
9
+ return this.bridge.httpCall('/remote/object/call', 'PUT', {
10
+ objectPath: '/Script/Engine.Default__KismetSystemLibrary',
11
+ functionName: 'ExecuteConsoleCommand',
12
+ parameters: {
13
+ WorldContextObject: null,
14
+ Command: command,
15
+ SpecificPlayer: null
16
+ },
17
+ generateTransaction: false
18
+ });
19
+ }
20
+
21
+ // Create sound cue
22
+ async createSoundCue(params: {
23
+ name: string;
24
+ wavePath?: string;
25
+ savePath?: string;
26
+ settings?: {
27
+ volume?: number;
28
+ pitch?: number;
29
+ looping?: boolean;
30
+ attenuationSettings?: string;
31
+ };
32
+ }) {
33
+ const path = params.savePath || '/Game/Audio/Cues';
34
+ const py = `
35
+ import unreal
36
+ import json
37
+ name = r"${params.name}"
38
+ path = r"${path}"
39
+ try:
40
+ asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
41
+ try:
42
+ factory = unreal.SoundCueFactoryNew()
43
+ except Exception:
44
+ factory = None
45
+ if not factory:
46
+ print('RESULT:' + json.dumps({'success': False, 'error': 'SoundCueFactoryNew unavailable'}))
47
+ else:
48
+ asset = asset_tools.create_asset(asset_name=name, package_path=path, asset_class=unreal.SoundCue, factory=factory)
49
+ if asset:
50
+ if ${params.wavePath !== undefined ? 'True' : 'False'}:
51
+ try:
52
+ wave_path = r"${params.wavePath || ''}"
53
+ if wave_path and unreal.EditorAssetLibrary.does_asset_exist(wave_path):
54
+ snd = unreal.EditorAssetLibrary.load_asset(wave_path)
55
+ # Simple node hookup via SoundCueGraph is non-trivial via Python; leave as empty cue
56
+ except Exception:
57
+ pass
58
+ unreal.EditorAssetLibrary.save_asset(f"{path}/{name}")
59
+ print('RESULT:' + json.dumps({'success': True}))
60
+ else:
61
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Failed to create SoundCue'}))
62
+ except Exception as e:
63
+ print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
64
+ `.trim();
65
+ try {
66
+ const resp = await this.bridge.executePython(py);
67
+ const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
68
+ const m = out.match(/RESULT:({.*})/);
69
+ if (m) { try { const parsed = JSON.parse(m[1]); return parsed.success ? { success: true, message: 'Sound cue created' } : { success: false, error: parsed.error }; } catch {} }
70
+ return { success: true, message: 'Sound cue creation attempted' };
71
+ } catch (e) {
72
+ return { success: false, error: `Failed to create sound cue: ${e}` };
73
+ }
74
+ }
75
+
76
+ // Play sound at location
77
+ async playSoundAtLocation(params: {
78
+ soundPath: string;
79
+ location: [number, number, number];
80
+ volume?: number;
81
+ pitch?: number;
82
+ startTime?: number;
83
+ }) {
84
+ const volume = params.volume ?? 1.0;
85
+ const pitch = params.pitch ?? 1.0;
86
+ const startTime = params.startTime ?? 0.0;
87
+
88
+ const py = `
89
+ import unreal
90
+ import json
91
+ loc = unreal.Vector(${params.location[0]}, ${params.location[1]}, ${params.location[2]})
92
+ path = r"${params.soundPath}"
93
+ try:
94
+ if not unreal.EditorAssetLibrary.does_asset_exist(path):
95
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Sound asset not found'}))
96
+ else:
97
+ snd = unreal.EditorAssetLibrary.load_asset(path)
98
+ # Get editor world via EditorSubsystem first to avoid deprecation
99
+ try:
100
+ world = unreal.EditorSubsystemLibrary.get_editor_world()
101
+ except Exception:
102
+ try:
103
+ world = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world()
104
+ except Exception:
105
+ world = unreal.EditorLevelLibrary.get_editor_world()
106
+ rot = unreal.Rotator(0.0, 0.0, 0.0)
107
+ # Use spawn_* variant with explicit rotation before optional floats
108
+ unreal.GameplayStatics.spawn_sound_at_location(world, snd, loc, rot, ${volume}, ${pitch}, ${startTime})
109
+ print('RESULT:' + json.dumps({'success': True}))
110
+ except Exception as e:
111
+ print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
112
+ `.trim();
113
+ try {
114
+ const resp = await this.bridge.executePython(py);
115
+ const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
116
+ const m = out.match(/RESULT:({.*})/);
117
+ if (m) { try { const parsed = JSON.parse(m[1]); return parsed.success ? { success: true, message: 'Sound played' } : { success: false, error: parsed.error }; } catch {} }
118
+ return { success: true, message: 'Sound play attempted' };
119
+ } catch (e) {
120
+ return { success: false, error: `Failed to play sound: ${e}` };
121
+ }
122
+ }
123
+
124
+ // Play sound 2D
125
+ async playSound2D(params: {
126
+ soundPath: string;
127
+ volume?: number;
128
+ pitch?: number;
129
+ startTime?: number;
130
+ }) {
131
+ const volume = params.volume ?? 1.0;
132
+ const pitch = params.pitch ?? 1.0;
133
+ const startTime = params.startTime ?? 0.0;
134
+
135
+ const py = `
136
+ import unreal
137
+ import json
138
+ path = r"${params.soundPath}"
139
+ try:
140
+ if not unreal.EditorAssetLibrary.does_asset_exist(path):
141
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Sound asset not found'}))
142
+ else:
143
+ snd = unreal.EditorAssetLibrary.load_asset(path)
144
+ # Get editor world via EditorSubsystem first to avoid deprecation
145
+ try:
146
+ world = unreal.EditorSubsystemLibrary.get_editor_world()
147
+ except Exception:
148
+ try:
149
+ world = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world()
150
+ except Exception:
151
+ world = unreal.EditorLevelLibrary.get_editor_world()
152
+ ok = False
153
+ try:
154
+ unreal.GameplayStatics.spawn_sound_2d(world, snd, ${volume}, ${pitch}, ${startTime})
155
+ ok = True
156
+ except AttributeError:
157
+ try:
158
+ unreal.GameplayStatics.play_sound_2d(world, snd, ${volume}, ${pitch}, ${startTime})
159
+ ok = True
160
+ except AttributeError:
161
+ # Fallback: play at camera location as 2D substitute
162
+ try:
163
+ info = unreal.EditorLevelLibrary.get_level_viewport_camera_info()
164
+ cam_loc = info[0] if isinstance(info, (list, tuple)) and len(info) > 0 else unreal.Vector(0.0, 0.0, 0.0)
165
+ except Exception:
166
+ cam_loc = unreal.Vector(0.0, 0.0, 0.0)
167
+ rot = unreal.Rotator(0.0, 0.0, 0.0)
168
+ unreal.GameplayStatics.spawn_sound_at_location(world, snd, cam_loc, rot, ${volume}, ${pitch}, ${startTime})
169
+ ok = True
170
+ print('RESULT:' + json.dumps({'success': True} if ok else {'success': False, 'error': 'No suitable 2D playback method found'}))
171
+ except Exception as e:
172
+ print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
173
+ `.trim();
174
+ try {
175
+ const resp = await this.bridge.executePython(py);
176
+ const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
177
+ const m = out.match(/RESULT:({.*})/);
178
+ if (m) { try { const parsed = JSON.parse(m[1]); return parsed.success ? { success: true, message: 'Sound2D played' } : { success: false, error: parsed.error }; } catch {} }
179
+ return { success: true, message: 'Sound2D play attempted' };
180
+ } catch (e) {
181
+ return { success: false, error: `Failed to play sound2D: ${e}` };
182
+ }
183
+ }
184
+
185
+ // Create audio component
186
+ async createAudioComponent(params: {
187
+ actorName: string;
188
+ componentName: string;
189
+ soundPath: string;
190
+ autoPlay?: boolean;
191
+ is3D?: boolean;
192
+ }) {
193
+ const commands = [];
194
+
195
+ commands.push(`AddAudioComponent ${params.actorName} ${params.componentName} ${params.soundPath}`);
196
+
197
+ if (params.autoPlay !== undefined) {
198
+ commands.push(`SetAudioComponentAutoPlay ${params.actorName}.${params.componentName} ${params.autoPlay}`);
199
+ }
200
+
201
+ if (params.is3D !== undefined) {
202
+ commands.push(`SetAudioComponent3D ${params.actorName}.${params.componentName} ${params.is3D}`);
203
+ }
204
+
205
+ for (const cmd of commands) {
206
+ await this.bridge.executeConsoleCommand(cmd);
207
+ }
208
+
209
+ return { success: true, message: `Audio component ${params.componentName} added to ${params.actorName}` };
210
+ }
211
+
212
+ // Set sound attenuation
213
+ async setSoundAttenuation(params: {
214
+ name: string;
215
+ innerRadius?: number;
216
+ falloffDistance?: number;
217
+ attenuationShape?: 'Sphere' | 'Capsule' | 'Box' | 'Cone';
218
+ falloffMode?: 'Linear' | 'Logarithmic' | 'Inverse' | 'LogReverse' | 'Natural';
219
+ }) {
220
+ const commands = [];
221
+
222
+ commands.push(`CreateAttenuationSettings ${params.name}`);
223
+
224
+ if (params.innerRadius !== undefined) {
225
+ commands.push(`SetAttenuationInnerRadius ${params.name} ${params.innerRadius}`);
226
+ }
227
+
228
+ if (params.falloffDistance !== undefined) {
229
+ commands.push(`SetAttenuationFalloffDistance ${params.name} ${params.falloffDistance}`);
230
+ }
231
+
232
+ if (params.attenuationShape) {
233
+ commands.push(`SetAttenuationShape ${params.name} ${params.attenuationShape}`);
234
+ }
235
+
236
+ if (params.falloffMode) {
237
+ commands.push(`SetAttenuationFalloffMode ${params.name} ${params.falloffMode}`);
238
+ }
239
+
240
+ for (const cmd of commands) {
241
+ await this.bridge.executeConsoleCommand(cmd);
242
+ }
243
+
244
+ return { success: true, message: `Attenuation settings ${params.name} configured` };
245
+ }
246
+
247
+ // Create sound class
248
+ async createSoundClass(params: {
249
+ name: string;
250
+ parentClass?: string;
251
+ properties?: {
252
+ volume?: number;
253
+ pitch?: number;
254
+ lowPassFilterFrequency?: number;
255
+ attenuationDistanceScale?: number;
256
+ };
257
+ }) {
258
+ const commands = [];
259
+ const parent = params.parentClass || 'Master';
260
+
261
+ commands.push(`CreateSoundClass ${params.name} ${parent}`);
262
+
263
+ if (params.properties) {
264
+ if (params.properties.volume !== undefined) {
265
+ commands.push(`SetSoundClassVolume ${params.name} ${params.properties.volume}`);
266
+ }
267
+ if (params.properties.pitch !== undefined) {
268
+ commands.push(`SetSoundClassPitch ${params.name} ${params.properties.pitch}`);
269
+ }
270
+ if (params.properties.lowPassFilterFrequency !== undefined) {
271
+ commands.push(`SetSoundClassLowPassFilter ${params.name} ${params.properties.lowPassFilterFrequency}`);
272
+ }
273
+ if (params.properties.attenuationDistanceScale !== undefined) {
274
+ commands.push(`SetSoundClassAttenuationScale ${params.name} ${params.properties.attenuationDistanceScale}`);
275
+ }
276
+ }
277
+
278
+ for (const cmd of commands) {
279
+ await this.bridge.executeConsoleCommand(cmd);
280
+ }
281
+
282
+ return { success: true, message: `Sound class ${params.name} created` };
283
+ }
284
+
285
+ // Create sound mix
286
+ async createSoundMix(params: {
287
+ name: string;
288
+ classAdjusters?: Array<{
289
+ soundClass: string;
290
+ volumeAdjuster?: number;
291
+ pitchAdjuster?: number;
292
+ fadeInTime?: number;
293
+ fadeOutTime?: number;
294
+ }>;
295
+ }) {
296
+ const commands = [];
297
+
298
+ commands.push(`CreateSoundMix ${params.name}`);
299
+
300
+ if (params.classAdjusters) {
301
+ for (const adjuster of params.classAdjusters) {
302
+ commands.push(`AddSoundMixClassAdjuster ${params.name} ${adjuster.soundClass}`);
303
+
304
+ if (adjuster.volumeAdjuster !== undefined) {
305
+ commands.push(`SetSoundMixVolume ${params.name} ${adjuster.soundClass} ${adjuster.volumeAdjuster}`);
306
+ }
307
+ if (adjuster.pitchAdjuster !== undefined) {
308
+ commands.push(`SetSoundMixPitch ${params.name} ${adjuster.soundClass} ${adjuster.pitchAdjuster}`);
309
+ }
310
+ if (adjuster.fadeInTime !== undefined) {
311
+ commands.push(`SetSoundMixFadeIn ${params.name} ${adjuster.soundClass} ${adjuster.fadeInTime}`);
312
+ }
313
+ if (adjuster.fadeOutTime !== undefined) {
314
+ commands.push(`SetSoundMixFadeOut ${params.name} ${adjuster.soundClass} ${adjuster.fadeOutTime}`);
315
+ }
316
+ }
317
+ }
318
+
319
+ for (const cmd of commands) {
320
+ await this.bridge.executeConsoleCommand(cmd);
321
+ }
322
+
323
+ return { success: true, message: `Sound mix ${params.name} created` };
324
+ }
325
+
326
+ // Push/Pop sound mix
327
+ async pushSoundMix(params: {
328
+ mixName: string;
329
+ }) {
330
+ const command = `PushSoundMix ${params.mixName}`;
331
+ return this.bridge.executeConsoleCommand(command);
332
+ }
333
+
334
+ async popSoundMix(params: {
335
+ mixName: string;
336
+ }) {
337
+ const command = `PopSoundMix ${params.mixName}`;
338
+ return this.bridge.executeConsoleCommand(command);
339
+ }
340
+
341
+ // Set master volume
342
+ async setMasterVolume(params: {
343
+ volume: number; // 0.0 to 1.0
344
+ }) {
345
+ // Clamp volume between 0 and 1
346
+ const vol = Math.max(0.0, Math.min(1.0, params.volume));
347
+
348
+ // Use the proper Unreal Engine audio command
349
+ // Note: au.Master.Volume is the correct console variable for master volume
350
+ const command = `au.Master.Volume ${vol}`;
351
+
352
+ try {
353
+ await this.bridge.executeConsoleCommand(command);
354
+ return { success: true, message: `Master volume set to ${vol}` };
355
+ } catch (e) {
356
+ // Fallback to Python method if console command fails
357
+ const py = `
358
+ import unreal
359
+ import json
360
+ try:
361
+ # Try using AudioMixerBlueprintLibrary if available
362
+ try:
363
+ unreal.AudioMixerBlueprintLibrary.set_overall_volume_multiplier(${vol})
364
+ print('RESULT:' + json.dumps({'success': True}))
365
+ except AttributeError:
366
+ # Fallback to GameplayStatics method
367
+ try:
368
+ world = unreal.EditorLevelLibrary.get_editor_world()
369
+ unreal.GameplayStatics.set_global_pitch_modulation(world, 1.0, 0.0) # Reset pitch
370
+ unreal.GameplayStatics.set_global_time_dilation(world, 1.0) # Reset time
371
+ # Note: There's no direct master volume in GameplayStatics, use sound class
372
+ print('RESULT:' + json.dumps({'success': False, 'error': 'Master volume control not available, use sound classes instead'}))
373
+ except Exception as e2:
374
+ print('RESULT:' + json.dumps({'success': False, 'error': str(e2)}))
375
+ except Exception as e:
376
+ print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
377
+ `.trim();
378
+
379
+ try {
380
+ const resp = await this.bridge.executePython(py);
381
+ const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
382
+ const m = out.match(/RESULT:({.*})/);
383
+ if (m) {
384
+ try {
385
+ const parsed = JSON.parse(m[1]);
386
+ return parsed.success
387
+ ? { success: true, message: `Master volume set to ${vol}` }
388
+ : { success: false, error: parsed.error };
389
+ } catch {}
390
+ }
391
+ return { success: true, message: 'Master volume set command executed' };
392
+ } catch {
393
+ return { success: false, error: `Failed to set master volume: ${e}` };
394
+ }
395
+ }
396
+ }
397
+
398
+ // Create ambient sound
399
+ async createAmbientSound(params: {
400
+ name: string;
401
+ location: [number, number, number];
402
+ soundPath: string;
403
+ volume?: number;
404
+ radius?: number;
405
+ autoPlay?: boolean;
406
+ }) {
407
+ const commands = [];
408
+
409
+ commands.push(`SpawnAmbientSound ${params.name} ${params.location.join(' ')} ${params.soundPath}`);
410
+
411
+ if (params.volume !== undefined) {
412
+ commands.push(`SetAmbientVolume ${params.name} ${params.volume}`);
413
+ }
414
+
415
+ if (params.radius !== undefined) {
416
+ commands.push(`SetAmbientRadius ${params.name} ${params.radius}`);
417
+ }
418
+
419
+ if (params.autoPlay !== undefined) {
420
+ commands.push(`SetAmbientAutoPlay ${params.name} ${params.autoPlay}`);
421
+ }
422
+
423
+ for (const cmd of commands) {
424
+ await this.bridge.executeConsoleCommand(cmd);
425
+ }
426
+
427
+ return { success: true, message: `Ambient sound ${params.name} created` };
428
+ }
429
+
430
+ // Create reverb zone
431
+ async createReverbZone(params: {
432
+ name: string;
433
+ location: [number, number, number];
434
+ size: [number, number, number];
435
+ reverbEffect?: string;
436
+ volume?: number;
437
+ fadeTime?: number;
438
+ }) {
439
+ const commands = [];
440
+
441
+ commands.push(`CreateReverbVolume ${params.name} ${params.location.join(' ')} ${params.size.join(' ')}`);
442
+
443
+ if (params.reverbEffect) {
444
+ commands.push(`SetReverbEffect ${params.name} ${params.reverbEffect}`);
445
+ }
446
+
447
+ if (params.volume !== undefined) {
448
+ commands.push(`SetReverbVolume ${params.name} ${params.volume}`);
449
+ }
450
+
451
+ if (params.fadeTime !== undefined) {
452
+ commands.push(`SetReverbFadeTime ${params.name} ${params.fadeTime}`);
453
+ }
454
+
455
+ for (const cmd of commands) {
456
+ await this.bridge.executeConsoleCommand(cmd);
457
+ }
458
+
459
+ return { success: true, message: `Reverb zone ${params.name} created` };
460
+ }
461
+
462
+ // Audio analysis
463
+ async enableAudioAnalysis(params: {
464
+ enabled: boolean;
465
+ fftSize?: number;
466
+ outputType?: 'Magnitude' | 'Decibel' | 'Normalized';
467
+ }) {
468
+ const commands = [];
469
+
470
+ commands.push(`EnableAudioAnalysis ${params.enabled}`);
471
+
472
+ if (params.enabled && params.fftSize) {
473
+ commands.push(`SetFFTSize ${params.fftSize}`);
474
+ }
475
+
476
+ if (params.enabled && params.outputType) {
477
+ commands.push(`SetAudioAnalysisOutput ${params.outputType}`);
478
+ }
479
+
480
+ for (const cmd of commands) {
481
+ await this.bridge.executeConsoleCommand(cmd);
482
+ }
483
+
484
+ return { success: true, message: `Audio analysis ${params.enabled ? 'enabled' : 'disabled'}` };
485
+ }
486
+
487
+ // Stop all sounds
488
+ async stopAllSounds() {
489
+ return this.bridge.executeConsoleCommand('StopAllSounds');
490
+ }
491
+
492
+ // Fade sound
493
+ async fadeSound(params: {
494
+ soundName: string;
495
+ targetVolume: number;
496
+ fadeTime: number;
497
+ fadeType?: 'FadeIn' | 'FadeOut' | 'FadeTo';
498
+ }) {
499
+ const type = params.fadeType || 'FadeTo';
500
+ const command = `${type}Sound ${params.soundName} ${params.targetVolume} ${params.fadeTime}`;
501
+ return this.bridge.executeConsoleCommand(command);
502
+ }
503
+
504
+ // Set doppler effect
505
+ async setDopplerEffect(params: {
506
+ enabled: boolean;
507
+ scale?: number;
508
+ }) {
509
+ const commands = [];
510
+
511
+ commands.push(`EnableDoppler ${params.enabled}`);
512
+
513
+ if (params.scale !== undefined) {
514
+ commands.push(`SetDopplerScale ${params.scale}`);
515
+ }
516
+
517
+ for (const cmd of commands) {
518
+ await this.bridge.executeConsoleCommand(cmd);
519
+ }
520
+
521
+ return { success: true, message: `Doppler effect ${params.enabled ? 'enabled' : 'disabled'}` };
522
+ }
523
+
524
+ // Audio occlusion
525
+ async setAudioOcclusion(params: {
526
+ enabled: boolean;
527
+ lowPassFilterFrequency?: number;
528
+ volumeAttenuation?: number;
529
+ }) {
530
+ const commands = [];
531
+
532
+ commands.push(`EnableAudioOcclusion ${params.enabled}`);
533
+
534
+ if (params.lowPassFilterFrequency !== undefined) {
535
+ commands.push(`SetOcclusionLowPassFilter ${params.lowPassFilterFrequency}`);
536
+ }
537
+
538
+ if (params.volumeAttenuation !== undefined) {
539
+ commands.push(`SetOcclusionVolumeAttenuation ${params.volumeAttenuation}`);
540
+ }
541
+
542
+ for (const cmd of commands) {
543
+ await this.bridge.executeConsoleCommand(cmd);
544
+ }
545
+
546
+ return { success: true, message: `Audio occlusion ${params.enabled ? 'enabled' : 'disabled'}` };
547
+ }
548
+ }