ue-mcp 0.4.0-alpha

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 (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +89 -0
  3. package/dist/bridge.d.ts +25 -0
  4. package/dist/bridge.js +124 -0
  5. package/dist/bridge.js.map +1 -0
  6. package/dist/deployer.d.ts +18 -0
  7. package/dist/deployer.js +175 -0
  8. package/dist/deployer.js.map +1 -0
  9. package/dist/editor-control.d.ts +15 -0
  10. package/dist/editor-control.js +181 -0
  11. package/dist/editor-control.js.map +1 -0
  12. package/dist/github-app.d.ts +4 -0
  13. package/dist/github-app.js +94 -0
  14. package/dist/github-app.js.map +1 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +125 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/init.d.ts +2 -0
  19. package/dist/init.js +290 -0
  20. package/dist/init.js.map +1 -0
  21. package/dist/instructions.d.ts +1 -0
  22. package/dist/instructions.js +150 -0
  23. package/dist/instructions.js.map +1 -0
  24. package/dist/project.d.ts +32 -0
  25. package/dist/project.js +175 -0
  26. package/dist/project.js.map +1 -0
  27. package/dist/tools/animation.d.ts +2 -0
  28. package/dist/tools/animation.js +62 -0
  29. package/dist/tools/animation.js.map +1 -0
  30. package/dist/tools/asset.d.ts +2 -0
  31. package/dist/tools/asset.js +128 -0
  32. package/dist/tools/asset.js.map +1 -0
  33. package/dist/tools/audio.d.ts +2 -0
  34. package/dist/tools/audio.js +24 -0
  35. package/dist/tools/audio.js.map +1 -0
  36. package/dist/tools/blueprint.d.ts +2 -0
  37. package/dist/tools/blueprint.js +64 -0
  38. package/dist/tools/blueprint.js.map +1 -0
  39. package/dist/tools/demo.d.ts +2 -0
  40. package/dist/tools/demo.js +10 -0
  41. package/dist/tools/demo.js.map +1 -0
  42. package/dist/tools/editor.d.ts +2 -0
  43. package/dist/tools/editor.js +133 -0
  44. package/dist/tools/editor.js.map +1 -0
  45. package/dist/tools/feedback.d.ts +2 -0
  46. package/dist/tools/feedback.js +44 -0
  47. package/dist/tools/feedback.js.map +1 -0
  48. package/dist/tools/foliage.d.ts +2 -0
  49. package/dist/tools/foliage.js +29 -0
  50. package/dist/tools/foliage.js.map +1 -0
  51. package/dist/tools/gameplay.d.ts +2 -0
  52. package/dist/tools/gameplay.js +101 -0
  53. package/dist/tools/gameplay.js.map +1 -0
  54. package/dist/tools/gas.d.ts +2 -0
  55. package/dist/tools/gas.js +43 -0
  56. package/dist/tools/gas.js.map +1 -0
  57. package/dist/tools/landscape.d.ts +2 -0
  58. package/dist/tools/landscape.js +32 -0
  59. package/dist/tools/landscape.js.map +1 -0
  60. package/dist/tools/level.d.ts +2 -0
  61. package/dist/tools/level.js +66 -0
  62. package/dist/tools/level.js.map +1 -0
  63. package/dist/tools/material.d.ts +2 -0
  64. package/dist/tools/material.js +59 -0
  65. package/dist/tools/material.js.map +1 -0
  66. package/dist/tools/networking.d.ts +2 -0
  67. package/dist/tools/networking.js +42 -0
  68. package/dist/tools/networking.js.map +1 -0
  69. package/dist/tools/niagara.d.ts +2 -0
  70. package/dist/tools/niagara.js +42 -0
  71. package/dist/tools/niagara.js.map +1 -0
  72. package/dist/tools/pcg.d.ts +2 -0
  73. package/dist/tools/pcg.js +39 -0
  74. package/dist/tools/pcg.js.map +1 -0
  75. package/dist/tools/project.d.ts +2 -0
  76. package/dist/tools/project.js +282 -0
  77. package/dist/tools/project.js.map +1 -0
  78. package/dist/tools/reflection.d.ts +2 -0
  79. package/dist/tools/reflection.js +26 -0
  80. package/dist/tools/reflection.js.map +1 -0
  81. package/dist/tools/widget.d.ts +2 -0
  82. package/dist/tools/widget.js +34 -0
  83. package/dist/tools/widget.js.map +1 -0
  84. package/dist/types.d.ts +21 -0
  85. package/dist/types.js +38 -0
  86. package/dist/types.js.map +1 -0
  87. package/package.json +76 -0
  88. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +746 -0
  89. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.h +81 -0
  90. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +49 -0
  91. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +37 -0
  92. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +75 -0
  93. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +50 -0
  94. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +1418 -0
  95. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +43 -0
  96. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +1773 -0
  97. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +48 -0
  98. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.cpp +289 -0
  99. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.h +18 -0
  100. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +1982 -0
  101. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +44 -0
  102. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +1217 -0
  103. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +71 -0
  104. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.cpp +465 -0
  105. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.h +49 -0
  106. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +1676 -0
  107. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +53 -0
  108. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +876 -0
  109. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +22 -0
  110. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +2222 -0
  111. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +54 -0
  112. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +783 -0
  113. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +24 -0
  114. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +898 -0
  115. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +24 -0
  116. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +1270 -0
  117. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +34 -0
  118. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +2190 -0
  119. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +53 -0
  120. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +554 -0
  121. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +29 -0
  122. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +601 -0
  123. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.h +25 -0
  124. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +1024 -0
  125. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.h +25 -0
  126. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +370 -0
  127. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.h +19 -0
  128. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.cpp +499 -0
  129. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.h +27 -0
  130. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +426 -0
  131. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +19 -0
  132. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +303 -0
  133. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +18 -0
  134. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +985 -0
  135. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +27 -0
  136. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.cpp +181 -0
  137. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.h +42 -0
  138. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +39 -0
  139. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -0
  140. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
  141. package/plugin/ue_mcp_bridge/UE_MCP_Bridge.uplugin +55 -0
@@ -0,0 +1,1217 @@
1
+ #include "DemoHandlers.h"
2
+ #include "HandlerRegistry.h"
3
+
4
+ // Core / Editor
5
+ #include "Editor.h"
6
+ #include "Editor/EditorEngine.h"
7
+ #include "Engine/World.h"
8
+ #include "Engine/Engine.h"
9
+ #include "EngineUtils.h"
10
+ #include "GameFramework/Actor.h"
11
+ #include "LevelEditorSubsystem.h"
12
+ #include "FileHelpers.h"
13
+
14
+ // Assets / Packages
15
+ #include "AssetToolsModule.h"
16
+ #include "IAssetTools.h"
17
+ #include "AssetRegistry/AssetRegistryModule.h"
18
+ #include "EditorAssetLibrary.h"
19
+ #include "UObject/UObjectGlobals.h"
20
+ #include "UObject/Package.h"
21
+ #include "UObject/SavePackage.h"
22
+ #include "Misc/PackageName.h"
23
+
24
+ // Static mesh actors / components
25
+ #include "Engine/StaticMeshActor.h"
26
+ #include "Components/StaticMeshComponent.h"
27
+ #include "Engine/StaticMesh.h"
28
+
29
+ // Lights
30
+ #include "Engine/PointLight.h"
31
+ #include "Engine/DirectionalLight.h"
32
+ #include "Engine/SkyLight.h"
33
+ #include "Components/PointLightComponent.h"
34
+ #include "Components/DirectionalLightComponent.h"
35
+ #include "Components/SkyLightComponent.h"
36
+
37
+ // Atmosphere / Post-process
38
+ #include "Engine/ExponentialHeightFog.h"
39
+ #include "Components/ExponentialHeightFogComponent.h"
40
+ #include "Engine/PostProcessVolume.h"
41
+
42
+ // Materials
43
+ #include "Factories/MaterialFactoryNew.h"
44
+ #include "Materials/Material.h"
45
+ #include "Materials/MaterialInterface.h"
46
+ #include "Materials/MaterialExpressionConstant4Vector.h"
47
+ #include "Materials/MaterialExpressionConstant.h"
48
+ #include "Materials/MaterialExpressionMultiply.h"
49
+
50
+ // Niagara
51
+ #include "NiagaraSystem.h"
52
+ #include "NiagaraComponent.h"
53
+ #include "NiagaraFunctionLibrary.h"
54
+ #include "NiagaraSystemFactoryNew.h"
55
+ #include "NiagaraEmitter.h"
56
+
57
+ // PCG
58
+ #include "PCGGraph.h"
59
+ #include "PCGComponent.h"
60
+ #include "PCGVolume.h"
61
+
62
+ // Sequencer
63
+ #include "LevelSequence.h"
64
+ #include "LevelSequenceActor.h"
65
+ #include "MovieScene.h"
66
+ #include "Tracks/MovieScene3DTransformTrack.h"
67
+
68
+ // Movement
69
+ #include "GameFramework/RotatingMovementComponent.h"
70
+
71
+ // Widget / Blutility (kept for potential future use)
72
+ // EditorUtilityWidget creation moved to interactive widget tool
73
+
74
+ // JSON
75
+ #include "Dom/JsonObject.h"
76
+ #include "Dom/JsonValue.h"
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // Constants
80
+ // ---------------------------------------------------------------------------
81
+ namespace DemoConstants
82
+ {
83
+ static const FString FOLDER = TEXT("Demo_Scene");
84
+ static const FString MAT_DIR = TEXT("/Game/Demo");
85
+ static const FString DEMO_LEVEL = TEXT("/Game/Demo/DemoLevel");
86
+ static const FString CUBE_MESH = TEXT("/Engine/BasicShapes/Cube.Cube");
87
+ static const FString SPHERE_MESH = TEXT("/Engine/BasicShapes/Sphere.Sphere");
88
+ static const FString CYLINDER_MESH = TEXT("/Engine/BasicShapes/Cylinder.Cylinder");
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Registration
93
+ // ---------------------------------------------------------------------------
94
+ void FDemoHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
95
+ {
96
+ Registry.RegisterHandler(TEXT("demo_step"), &DemoStep);
97
+ Registry.RegisterHandler(TEXT("demo_get_steps"), &DemoGetSteps);
98
+ Registry.RegisterHandler(TEXT("demo_cleanup"), &DemoCleanup);
99
+ }
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Step definitions
103
+ // ---------------------------------------------------------------------------
104
+ TArray<FDemoHandlers::FDemoStep> FDemoHandlers::GetStepDefinitions()
105
+ {
106
+ TArray<FDemoStep> Steps;
107
+ Steps.Add({ 1, TEXT("create_level"), TEXT("Create new level at /Game/Demo/DemoLevel") });
108
+ Steps.Add({ 2, TEXT("materials"), TEXT("Create 3 materials: floor, glow, pillar") });
109
+ Steps.Add({ 3, TEXT("floor"), TEXT("60m dark reflective floor") });
110
+ Steps.Add({ 4, TEXT("pedestal"), TEXT("Central pedestal cylinder") });
111
+ Steps.Add({ 5, TEXT("hero_sphere"), TEXT("Emissive gold hero sphere") });
112
+ Steps.Add({ 6, TEXT("pillars"), TEXT("4 corner pillar cylinders") });
113
+ Steps.Add({ 7, TEXT("orbs"), TEXT("4 glowing orbs at pillar bases") });
114
+ Steps.Add({ 8, TEXT("neon_lights"), TEXT("4 coloured point lights") });
115
+ Steps.Add({ 9, TEXT("hero_light"), TEXT("Warm point light above hero") });
116
+ Steps.Add({ 10, TEXT("moonlight"), TEXT("Directional moon light") });
117
+ Steps.Add({ 11, TEXT("sky_light"), TEXT("SkyLight ambient fill") });
118
+ Steps.Add({ 12, TEXT("fog"), TEXT("ExponentialHeightFog atmosphere") });
119
+ Steps.Add({ 13, TEXT("post_process"), TEXT("PostProcessVolume bloom/vignette") });
120
+ Steps.Add({ 14, TEXT("niagara_vfx"), TEXT("Niagara particle system above hero") });
121
+ Steps.Add({ 15, TEXT("pcg_scatter"), TEXT("PCG scatter volume on floor") });
122
+ Steps.Add({ 16, TEXT("orbit_rings"), TEXT("8 orbiting emissive spheres + rotation") });
123
+ Steps.Add({ 17, TEXT("level_sequence"), TEXT("LevelSequence with hero binding") });
124
+ Steps.Add({ 18, TEXT("tuning_panel"), TEXT("EditorUtilityWidget tuning panel") });
125
+ Steps.Add({ 19, TEXT("save"), TEXT("Save current level") });
126
+ return Steps;
127
+ }
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // Handler: demo_get_steps
131
+ // ---------------------------------------------------------------------------
132
+ TSharedPtr<FJsonValue> FDemoHandlers::DemoGetSteps(const TSharedPtr<FJsonObject>& Params)
133
+ {
134
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
135
+ TArray<FDemoStep> Steps = GetStepDefinitions();
136
+
137
+ TArray<TSharedPtr<FJsonValue>> StepsArray;
138
+ for (const FDemoStep& S : Steps)
139
+ {
140
+ TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
141
+ Obj->SetNumberField(TEXT("index"), S.Index);
142
+ Obj->SetStringField(TEXT("id"), S.Id);
143
+ Obj->SetStringField(TEXT("description"), S.Description);
144
+ StepsArray.Add(MakeShared<FJsonValueObject>(Obj));
145
+ }
146
+
147
+ Result->SetArrayField(TEXT("steps"), StepsArray);
148
+ Result->SetNumberField(TEXT("count"), StepsArray.Num());
149
+ Result->SetBoolField(TEXT("success"), true);
150
+ return MakeShared<FJsonValueObject>(Result);
151
+ }
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // Handler: demo_step
155
+ // ---------------------------------------------------------------------------
156
+ TSharedPtr<FJsonValue> FDemoHandlers::DemoStep(const TSharedPtr<FJsonObject>& Params)
157
+ {
158
+ // If no step param, return step list
159
+ double StepNum = -1;
160
+ if (!Params->TryGetNumberField(TEXT("step"), StepNum))
161
+ {
162
+ return DemoGetSteps(Params);
163
+ }
164
+
165
+ int32 StepIndex = static_cast<int32>(StepNum);
166
+
167
+ // Dispatch to step implementation
168
+ TSharedPtr<FJsonObject> StepResult;
169
+ switch (StepIndex)
170
+ {
171
+ case 1: StepResult = StepCreateLevel(); break;
172
+ case 2: StepResult = StepMaterials(); break;
173
+ case 3: StepResult = StepFloor(); break;
174
+ case 4: StepResult = StepPedestal(); break;
175
+ case 5: StepResult = StepHeroSphere(); break;
176
+ case 6: StepResult = StepPillars(); break;
177
+ case 7: StepResult = StepOrbs(); break;
178
+ case 8: StepResult = StepNeonLights(); break;
179
+ case 9: StepResult = StepHeroLight(); break;
180
+ case 10: StepResult = StepMoonlight(); break;
181
+ case 11: StepResult = StepSkyLight(); break;
182
+ case 12: StepResult = StepFog(); break;
183
+ case 13: StepResult = StepPostProcess(); break;
184
+ case 14: StepResult = StepNiagaraVfx(); break;
185
+ case 15: StepResult = StepPcgScatter(); break;
186
+ case 16: StepResult = StepOrbitRings(); break;
187
+ case 17: StepResult = StepLevelSequence(); break;
188
+ case 18: StepResult = StepTuningPanel(); break;
189
+ case 19: StepResult = StepSave(); break;
190
+ default:
191
+ {
192
+ TSharedPtr<FJsonObject> Err = MakeShared<FJsonObject>();
193
+ Err->SetStringField(TEXT("error"), FString::Printf(TEXT("Invalid step index %d. Valid range: 1-19"), StepIndex));
194
+ Err->SetBoolField(TEXT("success"), false);
195
+ return MakeShared<FJsonValueObject>(Err);
196
+ }
197
+ }
198
+
199
+ // Tag the result with step metadata
200
+ if (StepResult.IsValid())
201
+ {
202
+ StepResult->SetNumberField(TEXT("step"), StepIndex);
203
+ TArray<FDemoStep> Defs = GetStepDefinitions();
204
+ if (StepIndex >= 1 && StepIndex <= Defs.Num())
205
+ {
206
+ StepResult->SetStringField(TEXT("stepId"), Defs[StepIndex - 1].Id);
207
+ }
208
+ }
209
+
210
+ return MakeShared<FJsonValueObject>(StepResult);
211
+ }
212
+
213
+ // ---------------------------------------------------------------------------
214
+ // Handler: demo_cleanup
215
+ // ---------------------------------------------------------------------------
216
+ TSharedPtr<FJsonValue> FDemoHandlers::DemoCleanup(const TSharedPtr<FJsonObject>& Params)
217
+ {
218
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
219
+
220
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
221
+
222
+ // 1) Destroy actors whose label starts with "Demo_"
223
+ int32 ActorsDeleted = 0;
224
+ if (World)
225
+ {
226
+ TArray<AActor*> ToDelete;
227
+ for (TActorIterator<AActor> It(World); It; ++It)
228
+ {
229
+ AActor* Actor = *It;
230
+ if (Actor && Actor->GetActorLabel().StartsWith(TEXT("Demo_")))
231
+ {
232
+ ToDelete.Add(Actor);
233
+ }
234
+ }
235
+ for (AActor* Actor : ToDelete)
236
+ {
237
+ World->DestroyActor(Actor);
238
+ ++ActorsDeleted;
239
+ }
240
+ }
241
+
242
+ // 2) Delete demo assets
243
+ TArray<FString> AssetsToDelete = {
244
+ DemoConstants::MAT_DIR / TEXT("M_Demo_Floor"),
245
+ DemoConstants::MAT_DIR / TEXT("M_Demo_Glow"),
246
+ DemoConstants::MAT_DIR / TEXT("M_Demo_Pillar"),
247
+ DemoConstants::MAT_DIR / TEXT("NS_Demo_Aura"),
248
+ DemoConstants::MAT_DIR / TEXT("PCG_Demo_Scatter"),
249
+ DemoConstants::MAT_DIR / TEXT("SEQ_Demo_Showcase"),
250
+ DemoConstants::MAT_DIR / TEXT("EUW_DemoTuning"),
251
+ };
252
+
253
+ int32 AssetsDeleted = 0;
254
+ for (const FString& AssetPath : AssetsToDelete)
255
+ {
256
+ if (UEditorAssetLibrary::DoesAssetExist(AssetPath))
257
+ {
258
+ if (UEditorAssetLibrary::DeleteAsset(AssetPath))
259
+ {
260
+ ++AssetsDeleted;
261
+ }
262
+ }
263
+ }
264
+
265
+ // 3) Delete DemoLevel
266
+ if (UEditorAssetLibrary::DoesAssetExist(DemoConstants::DEMO_LEVEL))
267
+ {
268
+ UEditorAssetLibrary::DeleteAsset(DemoConstants::DEMO_LEVEL);
269
+ ++AssetsDeleted;
270
+ }
271
+
272
+ // 4) Delete /Game/Demo directory if empty
273
+ if (UEditorAssetLibrary::DoesDirectoryExist(DemoConstants::MAT_DIR))
274
+ {
275
+ TArray<FString> Remaining = UEditorAssetLibrary::ListAssets(DemoConstants::MAT_DIR, true);
276
+ if (Remaining.Num() == 0)
277
+ {
278
+ UEditorAssetLibrary::DeleteDirectory(DemoConstants::MAT_DIR);
279
+ }
280
+ }
281
+
282
+ Result->SetNumberField(TEXT("actorsDeleted"), ActorsDeleted);
283
+ Result->SetNumberField(TEXT("assetsDeleted"), AssetsDeleted);
284
+ Result->SetBoolField(TEXT("success"), true);
285
+ return MakeShared<FJsonValueObject>(Result);
286
+ }
287
+
288
+ // ===========================================================================
289
+ // Utility helpers
290
+ // ===========================================================================
291
+ AActor* FDemoHandlers::SpawnMesh(const FString& Label, const FString& MeshPath,
292
+ FVector Location, FRotator Rotation, FVector Scale)
293
+ {
294
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
295
+ if (!World) return nullptr;
296
+
297
+ FTransform SpawnTransform(Rotation, Location);
298
+ AStaticMeshActor* MeshActor = World->SpawnActor<AStaticMeshActor>(
299
+ AStaticMeshActor::StaticClass(), SpawnTransform);
300
+ if (!MeshActor) return nullptr;
301
+
302
+ MeshActor->SetActorLabel(Label);
303
+ MeshActor->SetFolderPath(*DemoConstants::FOLDER);
304
+ MeshActor->SetActorScale3D(Scale);
305
+
306
+ // Assign mesh
307
+ UStaticMesh* Mesh = LoadObject<UStaticMesh>(nullptr, *MeshPath);
308
+ if (Mesh && MeshActor->GetStaticMeshComponent())
309
+ {
310
+ MeshActor->GetStaticMeshComponent()->SetStaticMesh(Mesh);
311
+ }
312
+
313
+ return MeshActor;
314
+ }
315
+
316
+ AActor* FDemoHandlers::SpawnPointLight(const FString& Label, FVector Location,
317
+ FColor Color, float Intensity)
318
+ {
319
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
320
+ if (!World) return nullptr;
321
+
322
+ FTransform SpawnTransform(FRotator::ZeroRotator, Location);
323
+ APointLight* Light = World->SpawnActor<APointLight>(APointLight::StaticClass(), SpawnTransform);
324
+ if (!Light) return nullptr;
325
+
326
+ Light->SetActorLabel(Label);
327
+ Light->SetFolderPath(*DemoConstants::FOLDER);
328
+
329
+ UPointLightComponent* Comp = Light->PointLightComponent;
330
+ if (Comp)
331
+ {
332
+ Comp->SetIntensity(Intensity);
333
+ Comp->SetLightColor(FLinearColor(Color));
334
+ }
335
+
336
+ return Light;
337
+ }
338
+
339
+ UMaterialInterface* FDemoHandlers::LoadDemoMat(const FString& Name)
340
+ {
341
+ FString FullPath = DemoConstants::MAT_DIR / Name + TEXT(".") + Name;
342
+ return LoadObject<UMaterialInterface>(nullptr, *FullPath);
343
+ }
344
+
345
+ void FDemoHandlers::ApplyMat(AActor* Actor, UMaterialInterface* Mat)
346
+ {
347
+ if (!Actor || !Mat) return;
348
+
349
+ AStaticMeshActor* MeshActor = Cast<AStaticMeshActor>(Actor);
350
+ if (MeshActor && MeshActor->GetStaticMeshComponent())
351
+ {
352
+ MeshActor->GetStaticMeshComponent()->SetMaterial(0, Mat);
353
+ }
354
+ }
355
+
356
+ // ===========================================================================
357
+ // Helper: create a simple material with a constant base color
358
+ // ===========================================================================
359
+ static UMaterial* CreateSimpleMaterial(const FString& Name, const FString& PackagePath,
360
+ FLinearColor BaseColor, float Metallic, float Roughness,
361
+ bool bEmissive = false, FLinearColor EmissiveColor = FLinearColor::Black, float EmissiveStrength = 1.0f)
362
+ {
363
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
364
+ IAssetTools& AssetTools = AssetToolsModule.Get();
365
+
366
+ // Delete existing
367
+ FString FullPath = PackagePath / Name;
368
+ if (UEditorAssetLibrary::DoesAssetExist(FullPath))
369
+ {
370
+ UEditorAssetLibrary::DeleteAsset(FullPath);
371
+ }
372
+
373
+ UMaterialFactoryNew* Factory = NewObject<UMaterialFactoryNew>();
374
+ UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UMaterial::StaticClass(), Factory);
375
+ UMaterial* Mat = Cast<UMaterial>(NewAsset);
376
+ if (!Mat) return nullptr;
377
+
378
+ Mat->PreEditChange(nullptr);
379
+
380
+ // Base color expression
381
+ UMaterialExpressionConstant4Vector* ColorExpr = NewObject<UMaterialExpressionConstant4Vector>(Mat);
382
+ ColorExpr->Constant = BaseColor;
383
+ Mat->GetExpressionCollection().AddExpression(ColorExpr);
384
+ Mat->GetEditorOnlyData()->BaseColor.Connect(0, ColorExpr);
385
+
386
+ // Metallic
387
+ UMaterialExpressionConstant* MetallicExpr = NewObject<UMaterialExpressionConstant>(Mat);
388
+ MetallicExpr->R = Metallic;
389
+ Mat->GetExpressionCollection().AddExpression(MetallicExpr);
390
+ Mat->GetEditorOnlyData()->Metallic.Connect(0, MetallicExpr);
391
+
392
+ // Roughness
393
+ UMaterialExpressionConstant* RoughnessExpr = NewObject<UMaterialExpressionConstant>(Mat);
394
+ RoughnessExpr->R = Roughness;
395
+ Mat->GetExpressionCollection().AddExpression(RoughnessExpr);
396
+ Mat->GetEditorOnlyData()->Roughness.Connect(0, RoughnessExpr);
397
+
398
+ // Emissive
399
+ if (bEmissive)
400
+ {
401
+ UMaterialExpressionConstant4Vector* EmissiveExpr = NewObject<UMaterialExpressionConstant4Vector>(Mat);
402
+ EmissiveExpr->Constant = EmissiveColor;
403
+ Mat->GetExpressionCollection().AddExpression(EmissiveExpr);
404
+
405
+ UMaterialExpressionConstant* StrengthExpr = NewObject<UMaterialExpressionConstant>(Mat);
406
+ StrengthExpr->R = EmissiveStrength;
407
+ Mat->GetExpressionCollection().AddExpression(StrengthExpr);
408
+
409
+ UMaterialExpressionMultiply* MulExpr = NewObject<UMaterialExpressionMultiply>(Mat);
410
+ Mat->GetExpressionCollection().AddExpression(MulExpr);
411
+ MulExpr->A.Connect(0, EmissiveExpr);
412
+ MulExpr->B.Connect(0, StrengthExpr);
413
+
414
+ Mat->GetEditorOnlyData()->EmissiveColor.Connect(0, MulExpr);
415
+ }
416
+
417
+ Mat->PostEditChange();
418
+ Mat->MarkPackageDirty();
419
+
420
+ // Save
421
+ UPackage* Package = Mat->GetOutermost();
422
+ if (Package)
423
+ {
424
+ FString FileName = FPackageName::LongPackageNameToFilename(
425
+ Package->GetName(), FPackageName::GetAssetPackageExtension());
426
+ FSavePackageArgs SaveArgs;
427
+ SaveArgs.TopLevelFlags = RF_Standalone;
428
+ UPackage::SavePackage(Package, nullptr, *FileName, SaveArgs);
429
+ }
430
+
431
+ return Mat;
432
+ }
433
+
434
+ // ===========================================================================
435
+ // Step implementations
436
+ // ===========================================================================
437
+
438
+ // Step 1: Create level
439
+ TSharedPtr<FJsonObject> FDemoHandlers::StepCreateLevel()
440
+ {
441
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
442
+
443
+ // Ensure /Game/Demo directory exists by creating the level there
444
+ ULevelEditorSubsystem* LevelSub = GEditor ? GEditor->GetEditorSubsystem<ULevelEditorSubsystem>() : nullptr;
445
+ if (!LevelSub)
446
+ {
447
+ Result->SetStringField(TEXT("error"), TEXT("LevelEditorSubsystem not available"));
448
+ Result->SetBoolField(TEXT("success"), false);
449
+ return Result;
450
+ }
451
+
452
+ // Create a new map via the editor
453
+ UEditorAssetLibrary::MakeDirectory(DemoConstants::MAT_DIR);
454
+ bool bCreated = LevelSub->NewLevel(DemoConstants::DEMO_LEVEL);
455
+
456
+ Result->SetStringField(TEXT("levelPath"), DemoConstants::DEMO_LEVEL);
457
+ Result->SetBoolField(TEXT("created"), bCreated);
458
+ Result->SetBoolField(TEXT("success"), bCreated);
459
+ return Result;
460
+ }
461
+
462
+ // Step 2: Materials
463
+ TSharedPtr<FJsonObject> FDemoHandlers::StepMaterials()
464
+ {
465
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
466
+
467
+ UEditorAssetLibrary::MakeDirectory(DemoConstants::MAT_DIR);
468
+
469
+ // M_Demo_Floor - dark reflective
470
+ UMaterial* Floor = CreateSimpleMaterial(
471
+ TEXT("M_Demo_Floor"), DemoConstants::MAT_DIR,
472
+ FLinearColor(0.02f, 0.02f, 0.025f, 1.0f), // near-black
473
+ 0.9f, // high metallic
474
+ 0.15f // low roughness (reflective)
475
+ );
476
+
477
+ // M_Demo_Glow - emissive gold
478
+ UMaterial* Glow = CreateSimpleMaterial(
479
+ TEXT("M_Demo_Glow"), DemoConstants::MAT_DIR,
480
+ FLinearColor(0.8f, 0.6f, 0.1f, 1.0f), // gold base
481
+ 0.5f,
482
+ 0.3f,
483
+ true, // emissive
484
+ FLinearColor(1.0f, 0.75f, 0.1f, 1.0f), // emissive gold
485
+ 10.0f // strength
486
+ );
487
+
488
+ // M_Demo_Pillar - dark matte
489
+ UMaterial* Pillar = CreateSimpleMaterial(
490
+ TEXT("M_Demo_Pillar"), DemoConstants::MAT_DIR,
491
+ FLinearColor(0.05f, 0.05f, 0.06f, 1.0f),
492
+ 0.0f, // no metallic
493
+ 0.85f // high roughness (matte)
494
+ );
495
+
496
+ TArray<TSharedPtr<FJsonValue>> MatArray;
497
+ if (Floor) MatArray.Add(MakeShared<FJsonValueString>(Floor->GetPathName()));
498
+ if (Glow) MatArray.Add(MakeShared<FJsonValueString>(Glow->GetPathName()));
499
+ if (Pillar) MatArray.Add(MakeShared<FJsonValueString>(Pillar->GetPathName()));
500
+
501
+ Result->SetArrayField(TEXT("materials"), MatArray);
502
+ Result->SetNumberField(TEXT("count"), MatArray.Num());
503
+ Result->SetBoolField(TEXT("success"), MatArray.Num() == 3);
504
+ return Result;
505
+ }
506
+
507
+ // Step 3: Floor
508
+ TSharedPtr<FJsonObject> FDemoHandlers::StepFloor()
509
+ {
510
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
511
+
512
+ AActor* FloorActor = SpawnMesh(
513
+ TEXT("Demo_Floor"), DemoConstants::CUBE_MESH,
514
+ FVector(0.0, 0.0, -5.0),
515
+ FRotator::ZeroRotator,
516
+ FVector(60.0, 60.0, 0.1)
517
+ );
518
+
519
+ if (!FloorActor)
520
+ {
521
+ Result->SetStringField(TEXT("error"), TEXT("Failed to spawn floor"));
522
+ Result->SetBoolField(TEXT("success"), false);
523
+ return Result;
524
+ }
525
+
526
+ ApplyMat(FloorActor, LoadDemoMat(TEXT("M_Demo_Floor")));
527
+
528
+ Result->SetStringField(TEXT("actorLabel"), FloorActor->GetActorLabel());
529
+ Result->SetBoolField(TEXT("success"), true);
530
+ return Result;
531
+ }
532
+
533
+ // Step 4: Pedestal
534
+ TSharedPtr<FJsonObject> FDemoHandlers::StepPedestal()
535
+ {
536
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
537
+
538
+ AActor* Ped = SpawnMesh(
539
+ TEXT("Demo_Pedestal"), DemoConstants::CYLINDER_MESH,
540
+ FVector(0.0, 0.0, 75.0),
541
+ FRotator::ZeroRotator,
542
+ FVector(2.5, 2.5, 1.5)
543
+ );
544
+
545
+ if (!Ped)
546
+ {
547
+ Result->SetStringField(TEXT("error"), TEXT("Failed to spawn pedestal"));
548
+ Result->SetBoolField(TEXT("success"), false);
549
+ return Result;
550
+ }
551
+
552
+ ApplyMat(Ped, LoadDemoMat(TEXT("M_Demo_Pillar")));
553
+
554
+ Result->SetStringField(TEXT("actorLabel"), Ped->GetActorLabel());
555
+ Result->SetBoolField(TEXT("success"), true);
556
+ return Result;
557
+ }
558
+
559
+ // Step 5: Hero sphere
560
+ TSharedPtr<FJsonObject> FDemoHandlers::StepHeroSphere()
561
+ {
562
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
563
+
564
+ AActor* Hero = SpawnMesh(
565
+ TEXT("Demo_HeroSphere"), DemoConstants::SPHERE_MESH,
566
+ FVector(0.0, 0.0, 260.0),
567
+ FRotator::ZeroRotator,
568
+ FVector(1.8, 1.8, 1.8)
569
+ );
570
+
571
+ if (!Hero)
572
+ {
573
+ Result->SetStringField(TEXT("error"), TEXT("Failed to spawn hero sphere"));
574
+ Result->SetBoolField(TEXT("success"), false);
575
+ return Result;
576
+ }
577
+
578
+ ApplyMat(Hero, LoadDemoMat(TEXT("M_Demo_Glow")));
579
+
580
+ // Must be Movable for RotatingMovementComponent (added in orbit_rings step)
581
+ if (Hero->GetRootComponent())
582
+ {
583
+ Hero->GetRootComponent()->SetMobility(EComponentMobility::Movable);
584
+ }
585
+
586
+ Result->SetStringField(TEXT("actorLabel"), Hero->GetActorLabel());
587
+ Result->SetBoolField(TEXT("success"), true);
588
+ return Result;
589
+ }
590
+
591
+ // Step 6: 4 corner pillars
592
+ TSharedPtr<FJsonObject> FDemoHandlers::StepPillars()
593
+ {
594
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
595
+ UMaterialInterface* PillarMat = LoadDemoMat(TEXT("M_Demo_Pillar"));
596
+
597
+ struct FPillarDef { FString Label; double X; double Y; };
598
+ TArray<FPillarDef> Defs = {
599
+ { TEXT("Demo_Pillar_NE"), 600.0, 600.0 },
600
+ { TEXT("Demo_Pillar_NW"), -600.0, 600.0 },
601
+ { TEXT("Demo_Pillar_SE"), 600.0, -600.0 },
602
+ { TEXT("Demo_Pillar_SW"), -600.0, -600.0 },
603
+ };
604
+
605
+ TArray<TSharedPtr<FJsonValue>> Labels;
606
+ for (const FPillarDef& D : Defs)
607
+ {
608
+ AActor* P = SpawnMesh(D.Label, DemoConstants::CYLINDER_MESH,
609
+ FVector(D.X, D.Y, 200.0),
610
+ FRotator::ZeroRotator,
611
+ FVector(0.6, 0.6, 4.0));
612
+ if (P)
613
+ {
614
+ ApplyMat(P, PillarMat);
615
+ Labels.Add(MakeShared<FJsonValueString>(P->GetActorLabel()));
616
+ }
617
+ }
618
+
619
+ Result->SetArrayField(TEXT("pillars"), Labels);
620
+ Result->SetNumberField(TEXT("count"), Labels.Num());
621
+ Result->SetBoolField(TEXT("success"), Labels.Num() == 4);
622
+ return Result;
623
+ }
624
+
625
+ // Step 7: 4 orbs at pillar bases
626
+ TSharedPtr<FJsonObject> FDemoHandlers::StepOrbs()
627
+ {
628
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
629
+ UMaterialInterface* GlowMat = LoadDemoMat(TEXT("M_Demo_Glow"));
630
+
631
+ struct FOrbDef { FString Label; double X; double Y; };
632
+ TArray<FOrbDef> Defs = {
633
+ { TEXT("Demo_Orb_NE"), 600.0, 600.0 },
634
+ { TEXT("Demo_Orb_NW"), -600.0, 600.0 },
635
+ { TEXT("Demo_Orb_SE"), 600.0, -600.0 },
636
+ { TEXT("Demo_Orb_SW"), -600.0, -600.0 },
637
+ };
638
+
639
+ TArray<TSharedPtr<FJsonValue>> Labels;
640
+ for (const FOrbDef& D : Defs)
641
+ {
642
+ AActor* O = SpawnMesh(D.Label, DemoConstants::SPHERE_MESH,
643
+ FVector(D.X, D.Y, 30.0),
644
+ FRotator::ZeroRotator,
645
+ FVector(0.4, 0.4, 0.4));
646
+ if (O)
647
+ {
648
+ ApplyMat(O, GlowMat);
649
+ Labels.Add(MakeShared<FJsonValueString>(O->GetActorLabel()));
650
+ }
651
+ }
652
+
653
+ Result->SetArrayField(TEXT("orbs"), Labels);
654
+ Result->SetNumberField(TEXT("count"), Labels.Num());
655
+ Result->SetBoolField(TEXT("success"), Labels.Num() == 4);
656
+ return Result;
657
+ }
658
+
659
+ // Step 8: 4 coloured neon point lights
660
+ TSharedPtr<FJsonObject> FDemoHandlers::StepNeonLights()
661
+ {
662
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
663
+
664
+ struct FNeonDef { FString Label; FVector Pos; FColor Color; };
665
+ TArray<FNeonDef> Defs = {
666
+ { TEXT("Demo_Neon_Cyan"), FVector( 600.0, 600.0, 350.0), FColor(0, 220, 255) },
667
+ { TEXT("Demo_Neon_Magenta"), FVector(-600.0, 600.0, 350.0), FColor(255, 0, 180) },
668
+ { TEXT("Demo_Neon_Amber"), FVector( 600.0, -600.0, 350.0), FColor(255, 170, 0) },
669
+ { TEXT("Demo_Neon_Violet"), FVector(-600.0, -600.0, 350.0), FColor(130, 0, 255) },
670
+ };
671
+
672
+ TArray<TSharedPtr<FJsonValue>> Labels;
673
+ for (const FNeonDef& D : Defs)
674
+ {
675
+ AActor* L = SpawnPointLight(D.Label, D.Pos, D.Color, 80000.0f);
676
+ if (L)
677
+ {
678
+ Labels.Add(MakeShared<FJsonValueString>(L->GetActorLabel()));
679
+ }
680
+ }
681
+
682
+ Result->SetArrayField(TEXT("lights"), Labels);
683
+ Result->SetNumberField(TEXT("count"), Labels.Num());
684
+ Result->SetBoolField(TEXT("success"), Labels.Num() == 4);
685
+ return Result;
686
+ }
687
+
688
+ // Step 9: Hero light
689
+ TSharedPtr<FJsonObject> FDemoHandlers::StepHeroLight()
690
+ {
691
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
692
+
693
+ AActor* L = SpawnPointLight(
694
+ TEXT("Demo_HeroLight"),
695
+ FVector(80.0, -80.0, 500.0),
696
+ FColor(255, 225, 190),
697
+ 120000.0f
698
+ );
699
+
700
+ if (!L)
701
+ {
702
+ Result->SetStringField(TEXT("error"), TEXT("Failed to spawn hero light"));
703
+ Result->SetBoolField(TEXT("success"), false);
704
+ return Result;
705
+ }
706
+
707
+ Result->SetStringField(TEXT("actorLabel"), L->GetActorLabel());
708
+ Result->SetBoolField(TEXT("success"), true);
709
+ return Result;
710
+ }
711
+
712
+ // Step 10: Moonlight (directional)
713
+ TSharedPtr<FJsonObject> FDemoHandlers::StepMoonlight()
714
+ {
715
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
716
+
717
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
718
+ if (!World)
719
+ {
720
+ Result->SetStringField(TEXT("error"), TEXT("Editor world not available"));
721
+ Result->SetBoolField(TEXT("success"), false);
722
+ return Result;
723
+ }
724
+
725
+ FTransform SpawnTransform(FRotator(-30.0, 210.0, 0.0), FVector::ZeroVector);
726
+ ADirectionalLight* DirLight = World->SpawnActor<ADirectionalLight>(
727
+ ADirectionalLight::StaticClass(), SpawnTransform);
728
+ if (!DirLight)
729
+ {
730
+ Result->SetStringField(TEXT("error"), TEXT("Failed to spawn directional light"));
731
+ Result->SetBoolField(TEXT("success"), false);
732
+ return Result;
733
+ }
734
+
735
+ DirLight->SetActorLabel(TEXT("Demo_Moonlight"));
736
+ DirLight->SetFolderPath(*DemoConstants::FOLDER);
737
+
738
+ UDirectionalLightComponent* Comp = DirLight->GetComponent();
739
+ if (Comp)
740
+ {
741
+ Comp->SetIntensity(3.0f);
742
+ Comp->SetLightColor(FLinearColor(FColor(100, 120, 200)));
743
+ }
744
+
745
+ Result->SetStringField(TEXT("actorLabel"), DirLight->GetActorLabel());
746
+ Result->SetBoolField(TEXT("success"), true);
747
+ return Result;
748
+ }
749
+
750
+ // Step 11: SkyLight
751
+ TSharedPtr<FJsonObject> FDemoHandlers::StepSkyLight()
752
+ {
753
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
754
+
755
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
756
+ if (!World)
757
+ {
758
+ Result->SetStringField(TEXT("error"), TEXT("Editor world not available"));
759
+ Result->SetBoolField(TEXT("success"), false);
760
+ return Result;
761
+ }
762
+
763
+ FTransform SpawnTransform(FRotator::ZeroRotator, FVector(0.0, 0.0, 500.0));
764
+ ASkyLight* Sky = World->SpawnActor<ASkyLight>(ASkyLight::StaticClass(), SpawnTransform);
765
+ if (!Sky)
766
+ {
767
+ Result->SetStringField(TEXT("error"), TEXT("Failed to spawn sky light"));
768
+ Result->SetBoolField(TEXT("success"), false);
769
+ return Result;
770
+ }
771
+
772
+ Sky->SetActorLabel(TEXT("Demo_SkyLight"));
773
+ Sky->SetFolderPath(*DemoConstants::FOLDER);
774
+
775
+ USkyLightComponent* Comp = Sky->GetLightComponent();
776
+ if (Comp)
777
+ {
778
+ Comp->SetIntensity(0.3f);
779
+ }
780
+
781
+ Result->SetStringField(TEXT("actorLabel"), Sky->GetActorLabel());
782
+ Result->SetBoolField(TEXT("success"), true);
783
+ return Result;
784
+ }
785
+
786
+ // Step 12: ExponentialHeightFog
787
+ TSharedPtr<FJsonObject> FDemoHandlers::StepFog()
788
+ {
789
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
790
+
791
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
792
+ if (!World)
793
+ {
794
+ Result->SetStringField(TEXT("error"), TEXT("Editor world not available"));
795
+ Result->SetBoolField(TEXT("success"), false);
796
+ return Result;
797
+ }
798
+
799
+ FTransform SpawnTransform(FRotator::ZeroRotator, FVector::ZeroVector);
800
+ AExponentialHeightFog* Fog = World->SpawnActor<AExponentialHeightFog>(
801
+ AExponentialHeightFog::StaticClass(), SpawnTransform);
802
+ if (!Fog)
803
+ {
804
+ Result->SetStringField(TEXT("error"), TEXT("Failed to spawn fog"));
805
+ Result->SetBoolField(TEXT("success"), false);
806
+ return Result;
807
+ }
808
+
809
+ Fog->SetActorLabel(TEXT("Demo_Fog"));
810
+ Fog->SetFolderPath(*DemoConstants::FOLDER);
811
+
812
+ UExponentialHeightFogComponent* Comp = Fog->GetComponent();
813
+ if (Comp)
814
+ {
815
+ Comp->SetFogDensity(0.035f);
816
+ Comp->SetFogHeightFalloff(0.5f);
817
+ Comp->SetFogMaxOpacity(0.85f);
818
+ }
819
+
820
+ Result->SetStringField(TEXT("actorLabel"), Fog->GetActorLabel());
821
+ Result->SetBoolField(TEXT("success"), true);
822
+ return Result;
823
+ }
824
+
825
+ // Step 13: PostProcessVolume
826
+ TSharedPtr<FJsonObject> FDemoHandlers::StepPostProcess()
827
+ {
828
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
829
+
830
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
831
+ if (!World)
832
+ {
833
+ Result->SetStringField(TEXT("error"), TEXT("Editor world not available"));
834
+ Result->SetBoolField(TEXT("success"), false);
835
+ return Result;
836
+ }
837
+
838
+ FTransform SpawnTransform(FRotator::ZeroRotator, FVector::ZeroVector);
839
+ APostProcessVolume* PP = World->SpawnActor<APostProcessVolume>(
840
+ APostProcessVolume::StaticClass(), SpawnTransform);
841
+ if (!PP)
842
+ {
843
+ Result->SetStringField(TEXT("error"), TEXT("Failed to spawn post process volume"));
844
+ Result->SetBoolField(TEXT("success"), false);
845
+ return Result;
846
+ }
847
+
848
+ PP->SetActorLabel(TEXT("Demo_PostProcess"));
849
+ PP->SetFolderPath(*DemoConstants::FOLDER);
850
+ PP->bUnbound = true;
851
+
852
+ // Bloom
853
+ PP->Settings.bOverride_BloomIntensity = true;
854
+ PP->Settings.BloomIntensity = 2.0f;
855
+
856
+ // Vignette
857
+ PP->Settings.bOverride_VignetteIntensity = true;
858
+ PP->Settings.VignetteIntensity = 0.6f;
859
+
860
+ // Exposure bias
861
+ PP->Settings.bOverride_AutoExposureBias = true;
862
+ PP->Settings.AutoExposureBias = -1.0f;
863
+
864
+ Result->SetStringField(TEXT("actorLabel"), PP->GetActorLabel());
865
+ Result->SetBoolField(TEXT("success"), true);
866
+ return Result;
867
+ }
868
+
869
+ // Step 14: Niagara VFX — continuous particle aura above hero sphere
870
+ TSharedPtr<FJsonObject> FDemoHandlers::StepNiagaraVfx()
871
+ {
872
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
873
+
874
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
875
+ if (!World)
876
+ {
877
+ Result->SetStringField(TEXT("error"), TEXT("Editor world not available"));
878
+ Result->SetBoolField(TEXT("success"), false);
879
+ return Result;
880
+ }
881
+
882
+ // Clean up any existing asset
883
+ FString NiagaraAssetPath = DemoConstants::MAT_DIR / TEXT("NS_Demo_Aura");
884
+ if (UEditorAssetLibrary::DoesAssetExist(NiagaraAssetPath))
885
+ {
886
+ UEditorAssetLibrary::DeleteAsset(NiagaraAssetPath);
887
+ }
888
+
889
+ // Load the Fountain emitter template from engine content — a fully configured
890
+ // continuous-spawn emitter with sprite renderer, velocity, lifetime, etc.
891
+ UNiagaraEmitter* FountainEmitter = LoadObject<UNiagaraEmitter>(
892
+ nullptr, TEXT("/Niagara/DefaultAssets/Templates/Emitters/Fountain.Fountain"));
893
+ if (!FountainEmitter)
894
+ {
895
+ Result->SetStringField(TEXT("error"), TEXT("Could not load engine Fountain emitter template"));
896
+ Result->SetBoolField(TEXT("success"), false);
897
+ return Result;
898
+ }
899
+
900
+ // Create the system using the factory with the Fountain emitter pre-configured
901
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
902
+ IAssetTools& AssetTools = AssetToolsModule.Get();
903
+
904
+ UNiagaraSystemFactoryNew* Factory = NewObject<UNiagaraSystemFactoryNew>();
905
+ FVersionedNiagaraEmitter VersionedEmitter;
906
+ VersionedEmitter.Emitter = FountainEmitter;
907
+ VersionedEmitter.Version = FountainEmitter->GetExposedVersion().VersionGuid;
908
+ Factory->EmittersToAddToNewSystem.Add(VersionedEmitter);
909
+
910
+ UObject* NSAsset = AssetTools.CreateAsset(
911
+ TEXT("NS_Demo_Aura"), DemoConstants::MAT_DIR,
912
+ UNiagaraSystem::StaticClass(), Factory);
913
+ UNiagaraSystem* NiagaraSys = Cast<UNiagaraSystem>(NSAsset);
914
+ if (!NiagaraSys)
915
+ {
916
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create NiagaraSystem from Fountain emitter"));
917
+ Result->SetBoolField(TEXT("success"), false);
918
+ return Result;
919
+ }
920
+
921
+ // Initialize and save
922
+ UNiagaraSystemFactoryNew::InitializeSystem(NiagaraSys, true);
923
+ UEditorAssetLibrary::SaveAsset(NiagaraSys->GetPathName());
924
+
925
+ // Spawn a dedicated actor and attach a NiagaraComponent with the system
926
+ FTransform SpawnTransform(FRotator::ZeroRotator, FVector(0.0, 0.0, 380.0));
927
+ AActor* NiagaraActor = World->SpawnActor<AActor>(AActor::StaticClass(), SpawnTransform);
928
+ if (!NiagaraActor)
929
+ {
930
+ Result->SetStringField(TEXT("error"), TEXT("Failed to spawn Niagara actor"));
931
+ Result->SetBoolField(TEXT("success"), false);
932
+ return Result;
933
+ }
934
+
935
+ NiagaraActor->SetActorLabel(TEXT("Demo_NiagaraVFX"));
936
+ NiagaraActor->SetFolderPath(*DemoConstants::FOLDER);
937
+
938
+ // Create and register a scene root so the Niagara component has something to attach to
939
+ USceneComponent* SceneRoot = NewObject<USceneComponent>(NiagaraActor, TEXT("DefaultSceneRoot"));
940
+ SceneRoot->RegisterComponent();
941
+ NiagaraActor->SetRootComponent(SceneRoot);
942
+ NiagaraActor->AddInstanceComponent(SceneRoot);
943
+
944
+ // Create the Niagara component with our system asset
945
+ UNiagaraComponent* NiagaraComp = NewObject<UNiagaraComponent>(NiagaraActor, TEXT("DemoNiagaraComp"));
946
+ NiagaraComp->SetAsset(NiagaraSys);
947
+ NiagaraComp->SetAutoActivate(true);
948
+ NiagaraComp->RegisterComponent();
949
+ NiagaraComp->AttachToComponent(SceneRoot, FAttachmentTransformRules::KeepRelativeTransform);
950
+ NiagaraActor->AddInstanceComponent(NiagaraComp);
951
+
952
+ // Activate the component to start emitting
953
+ NiagaraComp->Activate(true);
954
+
955
+ Result->SetStringField(TEXT("actorLabel"), NiagaraActor->GetActorLabel());
956
+ Result->SetStringField(TEXT("assetPath"), NiagaraSys->GetPathName());
957
+ Result->SetBoolField(TEXT("success"), true);
958
+ return Result;
959
+ }
960
+
961
+ // Step 15: PCG scatter
962
+ TSharedPtr<FJsonObject> FDemoHandlers::StepPcgScatter()
963
+ {
964
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
965
+
966
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
967
+ if (!World)
968
+ {
969
+ Result->SetStringField(TEXT("error"), TEXT("Editor world not available"));
970
+ Result->SetBoolField(TEXT("success"), false);
971
+ return Result;
972
+ }
973
+
974
+ // Spawn PCG Volume on floor
975
+ FTransform SpawnTransform(FRotator::ZeroRotator, FVector::ZeroVector);
976
+ APCGVolume* PCGVol = World->SpawnActor<APCGVolume>(APCGVolume::StaticClass(), SpawnTransform);
977
+ if (!PCGVol)
978
+ {
979
+ Result->SetStringField(TEXT("error"), TEXT("Failed to spawn PCG volume"));
980
+ Result->SetBoolField(TEXT("success"), false);
981
+ return Result;
982
+ }
983
+
984
+ PCGVol->SetActorLabel(TEXT("Demo_PCGScatter"));
985
+ PCGVol->SetFolderPath(*DemoConstants::FOLDER);
986
+ PCGVol->SetActorScale3D(FVector(30.0, 30.0, 3.0));
987
+
988
+ // Create a PCG graph directly (no factory needed)
989
+ UPCGComponent* PCGComp = PCGVol->FindComponentByClass<UPCGComponent>();
990
+ if (PCGComp)
991
+ {
992
+ UPCGGraph* Graph = NewObject<UPCGGraph>(PCGComp, TEXT("PCG_Demo_Scatter"));
993
+ if (Graph)
994
+ {
995
+ PCGComp->SetGraph(Graph);
996
+ Result->SetBoolField(TEXT("graphAssigned"), true);
997
+ }
998
+ }
999
+
1000
+ Result->SetStringField(TEXT("actorLabel"), PCGVol->GetActorLabel());
1001
+ Result->SetStringField(TEXT("note"), TEXT("PCG volume placed. Configure the graph for scatter behavior."));
1002
+ Result->SetBoolField(TEXT("success"), true);
1003
+ return Result;
1004
+ }
1005
+
1006
+ // Step 16: Orbit rings
1007
+ TSharedPtr<FJsonObject> FDemoHandlers::StepOrbitRings()
1008
+ {
1009
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1010
+
1011
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
1012
+ if (!World)
1013
+ {
1014
+ Result->SetStringField(TEXT("error"), TEXT("Editor world not available"));
1015
+ Result->SetBoolField(TEXT("success"), false);
1016
+ return Result;
1017
+ }
1018
+
1019
+ UMaterialInterface* GlowMat = LoadDemoMat(TEXT("M_Demo_Glow"));
1020
+
1021
+ const float Radius = 220.0f;
1022
+ const float Height = 280.0f;
1023
+ const int32 NumOrbs = 8;
1024
+
1025
+ // Spawn an invisible pivot actor at the hero sphere's height — all orbs attach to this
1026
+ FTransform PivotTransform(FRotator::ZeroRotator, FVector(0.0, 0.0, 0.0));
1027
+ AActor* PivotActor = World->SpawnActor<AActor>(AActor::StaticClass(), PivotTransform);
1028
+ if (!PivotActor)
1029
+ {
1030
+ Result->SetStringField(TEXT("error"), TEXT("Failed to spawn orbit pivot"));
1031
+ Result->SetBoolField(TEXT("success"), false);
1032
+ return Result;
1033
+ }
1034
+ PivotActor->SetActorLabel(TEXT("Demo_OrbitPivot"));
1035
+ PivotActor->SetFolderPath(*DemoConstants::FOLDER);
1036
+
1037
+ // Give it a scene root and make it movable
1038
+ USceneComponent* PivotRoot = NewObject<USceneComponent>(PivotActor, TEXT("PivotRoot"));
1039
+ PivotRoot->SetMobility(EComponentMobility::Movable);
1040
+ PivotRoot->RegisterComponent();
1041
+ PivotActor->SetRootComponent(PivotRoot);
1042
+ PivotActor->AddInstanceComponent(PivotRoot);
1043
+
1044
+ // Add RotatingMovementComponent to the pivot
1045
+ URotatingMovementComponent* RotComp = NewObject<URotatingMovementComponent>(
1046
+ PivotActor, TEXT("DemoRotation"));
1047
+ if (RotComp)
1048
+ {
1049
+ RotComp->RotationRate = FRotator(0.0, 45.0, 0.0);
1050
+ RotComp->RegisterComponent();
1051
+ PivotActor->AddInstanceComponent(RotComp);
1052
+ Result->SetBoolField(TEXT("rotationAdded"), true);
1053
+ }
1054
+
1055
+ // Spawn orbs and attach them to the pivot
1056
+ TArray<TSharedPtr<FJsonValue>> Labels;
1057
+ for (int32 i = 0; i < NumOrbs; ++i)
1058
+ {
1059
+ float Angle = (2.0f * PI * i) / NumOrbs;
1060
+ float X = Radius * FMath::Cos(Angle);
1061
+ float Y = Radius * FMath::Sin(Angle);
1062
+
1063
+ FString Label = FString::Printf(TEXT("Demo_OrbitOrb_%d"), i);
1064
+ AActor* Orb = SpawnMesh(Label, DemoConstants::SPHERE_MESH,
1065
+ FVector(X, Y, Height),
1066
+ FRotator::ZeroRotator,
1067
+ FVector(0.2, 0.2, 0.2));
1068
+ if (Orb)
1069
+ {
1070
+ // Make movable so it can be attached and rotate
1071
+ if (Orb->GetRootComponent())
1072
+ {
1073
+ Orb->GetRootComponent()->SetMobility(EComponentMobility::Movable);
1074
+ }
1075
+ Orb->AttachToActor(PivotActor, FAttachmentTransformRules::KeepWorldTransform);
1076
+ ApplyMat(Orb, GlowMat);
1077
+ Labels.Add(MakeShared<FJsonValueString>(Orb->GetActorLabel()));
1078
+ }
1079
+ }
1080
+
1081
+ Result->SetArrayField(TEXT("orbitOrbs"), Labels);
1082
+ Result->SetNumberField(TEXT("count"), Labels.Num());
1083
+ Result->SetBoolField(TEXT("success"), Labels.Num() == NumOrbs);
1084
+ return Result;
1085
+ }
1086
+
1087
+ // Step 17: LevelSequence
1088
+ TSharedPtr<FJsonObject> FDemoHandlers::StepLevelSequence()
1089
+ {
1090
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1091
+
1092
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
1093
+ if (!World)
1094
+ {
1095
+ Result->SetStringField(TEXT("error"), TEXT("Editor world not available"));
1096
+ Result->SetBoolField(TEXT("success"), false);
1097
+ return Result;
1098
+ }
1099
+
1100
+ // Create LevelSequence asset
1101
+ FString SeqName = TEXT("SEQ_Demo_Showcase");
1102
+ FString FullPackagePath = DemoConstants::MAT_DIR / SeqName;
1103
+
1104
+ if (UEditorAssetLibrary::DoesAssetExist(FullPackagePath))
1105
+ {
1106
+ UEditorAssetLibrary::DeleteAsset(FullPackagePath);
1107
+ }
1108
+
1109
+ UPackage* Package = CreatePackage(*FullPackagePath);
1110
+ if (!Package)
1111
+ {
1112
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create sequence package"));
1113
+ Result->SetBoolField(TEXT("success"), false);
1114
+ return Result;
1115
+ }
1116
+
1117
+ ULevelSequence* Seq = NewObject<ULevelSequence>(Package, FName(*SeqName), RF_Public | RF_Standalone);
1118
+ Seq->Initialize();
1119
+
1120
+ if (!Seq)
1121
+ {
1122
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create LevelSequence"));
1123
+ Result->SetBoolField(TEXT("success"), false);
1124
+ return Result;
1125
+ }
1126
+
1127
+ FAssetRegistryModule::AssetCreated(Seq);
1128
+ Package->MarkPackageDirty();
1129
+
1130
+ // Bind the hero sphere as a possessable
1131
+ UMovieScene* MovieScene = Seq->GetMovieScene();
1132
+ if (MovieScene)
1133
+ {
1134
+ AActor* HeroSphere = nullptr;
1135
+ for (TActorIterator<AActor> It(World); It; ++It)
1136
+ {
1137
+ if ((*It)->GetActorLabel() == TEXT("Demo_HeroSphere"))
1138
+ {
1139
+ HeroSphere = *It;
1140
+ break;
1141
+ }
1142
+ }
1143
+
1144
+ if (HeroSphere)
1145
+ {
1146
+ FGuid BindingGuid = MovieScene->AddPossessable(
1147
+ TEXT("Demo_HeroSphere"), HeroSphere->GetClass());
1148
+ Seq->BindPossessableObject(BindingGuid, *HeroSphere, World);
1149
+
1150
+ // Add a transform track
1151
+ MovieScene->AddTrack(UMovieScene3DTransformTrack::StaticClass(), BindingGuid);
1152
+
1153
+ Result->SetStringField(TEXT("boundActor"), TEXT("Demo_HeroSphere"));
1154
+ Result->SetStringField(TEXT("bindingGuid"), BindingGuid.ToString());
1155
+ }
1156
+ }
1157
+
1158
+ UEditorAssetLibrary::SaveAsset(Seq->GetPathName());
1159
+
1160
+ // Place a LevelSequenceActor in the level
1161
+ FTransform SeqTransform(FRotator::ZeroRotator, FVector::ZeroVector);
1162
+ ALevelSequenceActor* SeqActor = World->SpawnActor<ALevelSequenceActor>(
1163
+ ALevelSequenceActor::StaticClass(), SeqTransform);
1164
+ if (SeqActor)
1165
+ {
1166
+ SeqActor->SetActorLabel(TEXT("Demo_SequenceActor"));
1167
+ SeqActor->SetFolderPath(*DemoConstants::FOLDER);
1168
+ SeqActor->SetSequence(Seq);
1169
+ Result->SetStringField(TEXT("sequenceActorLabel"), SeqActor->GetActorLabel());
1170
+ }
1171
+
1172
+ Result->SetStringField(TEXT("sequencePath"), Seq->GetPathName());
1173
+ Result->SetBoolField(TEXT("success"), true);
1174
+ return Result;
1175
+ }
1176
+
1177
+ // Step 18: Tuning panel (EditorUtilityWidget)
1178
+ TSharedPtr<FJsonObject> FDemoHandlers::StepTuningPanel()
1179
+ {
1180
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1181
+
1182
+ // This step is a placeholder - creating EditorUtilityWidgets programmatically
1183
+ // requires careful factory setup. Mark as success with a note.
1184
+ Result->SetStringField(TEXT("note"),
1185
+ TEXT("Tuning panel step skipped - create EUW_DemoTuning manually in /Game/Demo/ for a custom control panel. "
1186
+ "Use the widget tool's create_widget action to build one interactively."));
1187
+ Result->SetBoolField(TEXT("success"), true);
1188
+ return Result;
1189
+ }
1190
+
1191
+ // Step 19: Save
1192
+ TSharedPtr<FJsonObject> FDemoHandlers::StepSave()
1193
+ {
1194
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1195
+
1196
+ ULevelEditorSubsystem* LevelSub = GEditor ?
1197
+ GEditor->GetEditorSubsystem<ULevelEditorSubsystem>() : nullptr;
1198
+ if (!LevelSub)
1199
+ {
1200
+ Result->SetStringField(TEXT("error"), TEXT("LevelEditorSubsystem not available"));
1201
+ Result->SetBoolField(TEXT("success"), false);
1202
+ return Result;
1203
+ }
1204
+
1205
+ bool bSaved = LevelSub->SaveCurrentLevel();
1206
+
1207
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
1208
+ if (World)
1209
+ {
1210
+ Result->SetStringField(TEXT("levelName"), World->GetName());
1211
+ Result->SetStringField(TEXT("levelPath"), World->GetPathName());
1212
+ }
1213
+
1214
+ Result->SetBoolField(TEXT("saved"), bSaved);
1215
+ Result->SetBoolField(TEXT("success"), bSaved);
1216
+ return Result;
1217
+ }