ue-mcp 1.0.37 → 1.0.39

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 (27) hide show
  1. package/README.md +2 -2
  2. package/dist/instructions.d.ts +1 -1
  3. package/dist/instructions.js +1 -1
  4. package/dist/pie/schema.d.ts +10 -10
  5. package/dist/tool-counts.json +6 -6
  6. package/dist/tools/animation.js +3 -0
  7. package/dist/tools/animation.js.map +1 -1
  8. package/dist/tools/gameplay.js +21 -0
  9. package/dist/tools/gameplay.js.map +1 -1
  10. package/dist/ue-mcp.default.yml +72 -0
  11. package/package.json +2 -2
  12. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerUtils.h +13 -0
  13. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +2 -1
  14. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +1 -0
  15. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +127 -0
  16. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +11 -0
  17. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +13 -0
  18. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_PIEObserve.cpp +495 -0
  19. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_PIERecord.cpp +0 -12
  20. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.cpp +236 -92
  21. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/PIE/MCPObservationProfile.h +59 -0
  22. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/PIE/PIEInputRecorder.cpp +0 -7
  23. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/PIE/PIEInputReplayer.cpp +0 -8
  24. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/PIE/PIEObserver.cpp +379 -0
  25. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/PIE/PIEObserver.h +109 -0
  26. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/PIE/PIESequenceFormat.h +5 -0
  27. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +5 -1
@@ -0,0 +1,379 @@
1
+ #include "PIEObserver.h"
2
+ #include "MCPObservationProfile.h"
3
+ #include "PIESequenceFormat.h"
4
+ #include "UE_MCP_BridgeModule.h"
5
+ #include "Editor.h"
6
+ #include "Engine/World.h"
7
+ #include "HAL/FileManager.h"
8
+ #include "Misc/FileHelper.h"
9
+ #include "Misc/Paths.h"
10
+
11
+ namespace UEMCPPIE
12
+ {
13
+ namespace
14
+ {
15
+ void SampleActors(UWorld* World,
16
+ const TArray<FString>& Ids,
17
+ TMap<FString, TWeakObjectPtr<AActor>>& Cache,
18
+ FTrackedActorRow& OutRow)
19
+ {
20
+ for (const FString& Id : Ids)
21
+ {
22
+ FActorState AS;
23
+ AActor* A = nullptr;
24
+ if (TWeakObjectPtr<AActor>* Cached = Cache.Find(Id))
25
+ {
26
+ A = Cached->Get();
27
+ }
28
+ if (!A)
29
+ {
30
+ A = FindActorById(World, Id);
31
+ if (A) Cache.Add(Id, A);
32
+ }
33
+ if (A)
34
+ {
35
+ AS.Location = A->GetActorLocation();
36
+ AS.Rotation = A->GetActorRotation();
37
+ AS.Velocity = A->GetVelocity();
38
+ AS.bResolved = true;
39
+ }
40
+ OutRow.Actors.Add(Id, AS);
41
+ }
42
+ }
43
+ }
44
+
45
+ FPIEObserver& FPIEObserver::Get()
46
+ {
47
+ static FPIEObserver Instance;
48
+ return Instance;
49
+ }
50
+
51
+ void FPIEObserver::Init()
52
+ {
53
+ if (BeginPIEHandle.IsValid()) return;
54
+ BeginPIEHandle = FEditorDelegates::BeginPIE.AddLambda([this](bool bSim)
55
+ {
56
+ this->OnBeginPIE(bSim);
57
+ });
58
+ EndPIEHandle = FEditorDelegates::EndPIE.AddLambda([this](bool bSim)
59
+ {
60
+ this->OnEndPIE(bSim);
61
+ });
62
+ }
63
+
64
+ void FPIEObserver::Shutdown()
65
+ {
66
+ if (BeginPIEHandle.IsValid()) FEditorDelegates::BeginPIE.Remove(BeginPIEHandle);
67
+ if (EndPIEHandle.IsValid()) FEditorDelegates::EndPIE.Remove(EndPIEHandle);
68
+ BeginPIEHandle.Reset();
69
+ EndPIEHandle.Reset();
70
+ if (bEndFrameBound && OnEndFrameHandle.IsValid())
71
+ {
72
+ FCoreDelegates::OnEndFrame.Remove(OnEndFrameHandle);
73
+ }
74
+ OnEndFrameHandle.Reset();
75
+ bEndFrameBound = false;
76
+ State = EObserverState::Idle;
77
+ bArmed = false;
78
+ }
79
+
80
+ bool FPIEObserver::Arm(const FObserverArmConfig& Cfg, FString& OutError, FString& OutMessage)
81
+ {
82
+ if (State == EObserverState::Observing || State == EObserverState::WaitingForPawn)
83
+ {
84
+ OutError = TEXT("Observation already in flight; pie_observe_stop first.");
85
+ return false;
86
+ }
87
+
88
+ UMCPObservationProfile* Profile = LoadObject<UMCPObservationProfile>(
89
+ nullptr, *Cfg.ProfilePath);
90
+ if (!Profile)
91
+ {
92
+ OutError = FString::Printf(TEXT("Profile not found: %s"), *Cfg.ProfilePath);
93
+ return false;
94
+ }
95
+
96
+ Pending = Cfg;
97
+
98
+ TrackedValuePaths.Reset();
99
+ TrackedThresholds.Reset();
100
+ for (const FMCPTrackedValueEntry& E : Profile->TrackedValues)
101
+ {
102
+ TrackedValuePaths.Add(E.Path);
103
+ if (E.DriftThreshold > 0.f)
104
+ {
105
+ TrackedThresholds.Add(E.Path, E.DriftThreshold);
106
+ }
107
+ }
108
+
109
+ TrackedActorIds.Reset();
110
+ for (const FMCPTrackedActorEntry& E : Profile->TrackedActors)
111
+ {
112
+ TrackedActorIds.Add(E.ActorId);
113
+ }
114
+
115
+ bCapturePawnState = Profile->bCapturePawnState;
116
+ bCaptureMontage = Profile->bCaptureMontage;
117
+ ThrPosCm = Profile->PositionThresholdCm;
118
+ ThrRotDeg = Profile->RotationThresholdDeg;
119
+ ThrVelCms = Profile->VelocityThresholdCms;
120
+ ThrTrackedDefault = Profile->TrackedValueDefaultThreshold;
121
+ CurrentProfilePath = Cfg.ProfilePath;
122
+
123
+ CurrentRunId = FString::Printf(TEXT("obs_%s"), *FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S")));
124
+ const FString Root = Cfg.OutputDir.IsEmpty()
125
+ ? (FPaths::ProjectSavedDir() / TEXT("MCPObservations"))
126
+ : Cfg.OutputDir;
127
+ CurrentOutputDir = Root / CurrentRunId;
128
+
129
+ ActorRows.Reset();
130
+ ActorCache.Reset();
131
+ CSVBody.Reset();
132
+ FramesSampled = 0;
133
+
134
+ bArmed = true;
135
+ State = EObserverState::Armed;
136
+ OutMessage = FString::Printf(TEXT("Armed: profile=%s run=%s values=%d actors=%d"),
137
+ *FPaths::GetBaseFilename(Cfg.ProfilePath),
138
+ *CurrentRunId,
139
+ TrackedValuePaths.Num(),
140
+ TrackedActorIds.Num());
141
+
142
+ if (GEditor && GEditor->PlayWorld)
143
+ {
144
+ OnBeginPIE(false);
145
+ }
146
+ return true;
147
+ }
148
+
149
+ bool FPIEObserver::Disarm(FString& OutError)
150
+ {
151
+ if (State == EObserverState::Observing || State == EObserverState::WaitingForPawn)
152
+ {
153
+ OutError = TEXT("Observation is in flight; pie_observe_stop to finalize.");
154
+ return false;
155
+ }
156
+ bArmed = false;
157
+ State = EObserverState::Idle;
158
+ return true;
159
+ }
160
+
161
+ void FPIEObserver::OnBeginPIE(bool /*bIsSimulating*/)
162
+ {
163
+ if (!bArmed) return;
164
+ bArmed = false;
165
+
166
+ FPIEFrameSampler::FConfig SC;
167
+ SC.AxisThreshold = 0.15f;
168
+ SC.bCapturePawnState = bCapturePawnState;
169
+ SC.bCaptureMontage = bCaptureMontage;
170
+ SC.TrackedValuePaths = TrackedValuePaths;
171
+ SC.ClientIndex = Pending.ClientId;
172
+ Sampler.Reset();
173
+ Sampler.SetConfig(SC);
174
+
175
+ State = EObserverState::WaitingForPawn;
176
+ AttachTime = 0.0;
177
+ StartedAt = ISOTimestampNow();
178
+
179
+ if (!bEndFrameBound)
180
+ {
181
+ OnEndFrameHandle = FCoreDelegates::OnEndFrame.AddLambda([this]()
182
+ {
183
+ this->OnEndFrame();
184
+ });
185
+ bEndFrameBound = true;
186
+ }
187
+
188
+ UE_LOG(LogMCPBridge, Log, TEXT("[PIE-OBS] Armed -> BeginPIE: profile=%s run=%s"),
189
+ *FPaths::GetBaseFilename(CurrentProfilePath), *CurrentRunId);
190
+ }
191
+
192
+ void FPIEObserver::OnEndFrame()
193
+ {
194
+ if (State == EObserverState::Idle || State == EObserverState::Completed) return;
195
+ UWorld* PIEWorld = nullptr;
196
+ if (GEditor) PIEWorld = GEditor->PlayWorld;
197
+ if (!PIEWorld) return;
198
+
199
+ if (State == EObserverState::WaitingForPawn)
200
+ {
201
+ if (Sampler.AttachToPIE(PIEWorld))
202
+ {
203
+ AttachTime = PIEWorld->GetTimeSeconds();
204
+
205
+ CSVHdr = FCSVHeader();
206
+ CSVHdr.RecordingId = CurrentRunId;
207
+ CSVHdr.SampleHz = Pending.SampleHz > 0 ? Pending.SampleHz : 60;
208
+ CSVHdr.Actions = Sampler.GetActions();
209
+ CSVHdr.TrackedValues = Sampler.GetTrackedValues();
210
+ CSVHeaderStr = BuildCSVHeader(CSVHdr);
211
+ CSVBody.Reset();
212
+
213
+ State = EObserverState::Observing;
214
+ UE_LOG(LogMCPBridge, Log, TEXT("[PIE-OBS] Pawn attached, observing"));
215
+ }
216
+ return;
217
+ }
218
+
219
+ if (State == EObserverState::Observing)
220
+ {
221
+ const double GameTime = PIEWorld->GetTimeSeconds();
222
+ const double Dt = PIEWorld->GetDeltaSeconds();
223
+ const uint64 FrameNum = static_cast<uint64>(FramesSampled);
224
+ FCSVRow Row = Sampler.SampleFrame(PIEWorld, FrameNum, GameTime, Dt);
225
+
226
+ if (TrackedActorIds.Num() > 0)
227
+ {
228
+ FTrackedActorRow AR;
229
+ AR.Frame = FrameNum;
230
+ AR.Time = GameTime;
231
+ SampleActors(PIEWorld, TrackedActorIds, ActorCache, AR);
232
+ ActorRows.Add(MoveTemp(AR));
233
+ }
234
+
235
+ AppendCSVRow(CSVBody, Row, CSVHdr);
236
+ FramesSampled++;
237
+ }
238
+ }
239
+
240
+ void FPIEObserver::OnEndPIE(bool /*bIsSimulating*/)
241
+ {
242
+ if (State == EObserverState::Idle) return;
243
+ FinaliseCurrent();
244
+ }
245
+
246
+ FObserverFinishResult FPIEObserver::FinaliseCurrent()
247
+ {
248
+ FObserverFinishResult R;
249
+ if (State == EObserverState::Idle)
250
+ {
251
+ R.Error = TEXT("Not observing");
252
+ return R;
253
+ }
254
+
255
+ R.RunId = CurrentRunId;
256
+ R.OutputDir = CurrentOutputDir;
257
+ R.FramesSampled = FramesSampled;
258
+
259
+ if (FramesSampled == 0)
260
+ {
261
+ R.bSuccess = true;
262
+ State = EObserverState::Idle;
263
+ return R;
264
+ }
265
+
266
+ IFileManager::Get().MakeDirectory(*CurrentOutputDir, true);
267
+
268
+ // Write observation CSV
269
+ {
270
+ const FString FullCSV = CSVHeaderStr + CSVBody;
271
+ FString Err;
272
+ if (SaveCSV(CurrentOutputDir / TEXT("observation.csv"), FullCSV, Err))
273
+ {
274
+ R.CSVPath = CurrentOutputDir / TEXT("observation.csv");
275
+ }
276
+ else
277
+ {
278
+ UE_LOG(LogMCPBridge, Warning, TEXT("[PIE-OBS] CSV write failed: %s"), *Err);
279
+ }
280
+ }
281
+
282
+ // Write tracked actors
283
+ if (ActorRows.Num() > 0)
284
+ {
285
+ FString Err;
286
+ if (SaveTrackedActorsJSONL(CurrentOutputDir / TEXT("tracked.jsonl"), ActorRows, Err))
287
+ {
288
+ R.TrackedActorsPath = CurrentOutputDir / TEXT("tracked.jsonl");
289
+ }
290
+ else
291
+ {
292
+ UE_LOG(LogMCPBridge, Warning, TEXT("[PIE-OBS] tracked.jsonl write failed: %s"), *Err);
293
+ }
294
+ }
295
+
296
+ // Write manifest
297
+ {
298
+ TSharedRef<FJsonObject> M = MakeShared<FJsonObject>();
299
+ M->SetNumberField(TEXT("version"), kFormatVersion);
300
+ M->SetStringField(TEXT("type"), TEXT("observation"));
301
+ M->SetStringField(TEXT("run_id"), CurrentRunId);
302
+ M->SetStringField(TEXT("profile"), CurrentProfilePath);
303
+ M->SetStringField(TEXT("started_at"), StartedAt);
304
+ M->SetStringField(TEXT("ended_at"), ISOTimestampNow());
305
+ M->SetNumberField(TEXT("frames_sampled"), FramesSampled);
306
+ M->SetNumberField(TEXT("sample_hz"), CSVHdr.SampleHz);
307
+
308
+ TArray<TSharedPtr<FJsonValue>> Vals;
309
+ for (const FString& P : TrackedValuePaths)
310
+ {
311
+ Vals.Add(MakeShared<FJsonValueString>(P));
312
+ }
313
+ M->SetArrayField(TEXT("tracked_values"), Vals);
314
+
315
+ TArray<TSharedPtr<FJsonValue>> Acts;
316
+ for (const FString& A : TrackedActorIds)
317
+ {
318
+ Acts.Add(MakeShared<FJsonValueString>(A));
319
+ }
320
+ M->SetArrayField(TEXT("tracked_actors"), Acts);
321
+
322
+ TSharedRef<FJsonObject> Thr = MakeShared<FJsonObject>();
323
+ Thr->SetNumberField(TEXT("position_cm"), ThrPosCm);
324
+ Thr->SetNumberField(TEXT("rotation_deg"), ThrRotDeg);
325
+ Thr->SetNumberField(TEXT("velocity_cms"), ThrVelCms);
326
+ Thr->SetNumberField(TEXT("tracked_default"), ThrTrackedDefault);
327
+ if (TrackedThresholds.Num() > 0)
328
+ {
329
+ TSharedRef<FJsonObject> PerPath = MakeShared<FJsonObject>();
330
+ for (const TPair<FString, float>& KV : TrackedThresholds)
331
+ {
332
+ PerPath->SetNumberField(KV.Key, KV.Value);
333
+ }
334
+ Thr->SetObjectField(TEXT("tracked"), PerPath);
335
+ }
336
+ M->SetObjectField(TEXT("thresholds"), Thr);
337
+
338
+ FString JsonStr;
339
+ TSharedRef<TJsonWriter<>> W = TJsonWriterFactory<>::Create(&JsonStr);
340
+ FJsonSerializer::Serialize(M, W);
341
+ FFileHelper::SaveStringToFile(JsonStr, *(CurrentOutputDir / TEXT("manifest.json")),
342
+ FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM);
343
+ }
344
+
345
+ R.bSuccess = true;
346
+ R.DurationSeconds = 0.0;
347
+
348
+ if (bEndFrameBound && OnEndFrameHandle.IsValid())
349
+ {
350
+ FCoreDelegates::OnEndFrame.Remove(OnEndFrameHandle);
351
+ OnEndFrameHandle.Reset();
352
+ bEndFrameBound = false;
353
+ }
354
+ State = EObserverState::Idle;
355
+
356
+ UE_LOG(LogMCPBridge, Log, TEXT("[PIE-OBS] Finalized: %d frames -> %s"),
357
+ FramesSampled, *CurrentOutputDir);
358
+ return R;
359
+ }
360
+
361
+ FObserverFinishResult FPIEObserver::ForceStop()
362
+ {
363
+ return FinaliseCurrent();
364
+ }
365
+
366
+ FObserverStatus FPIEObserver::GetStatus() const
367
+ {
368
+ FObserverStatus S;
369
+ S.State = State;
370
+ S.RunId = CurrentRunId;
371
+ S.ProfilePath = CurrentProfilePath;
372
+ S.FramesSampled = FramesSampled;
373
+ if (GEditor && GEditor->PlayWorld && AttachTime > 0.0)
374
+ {
375
+ S.ElapsedSeconds = GEditor->PlayWorld->GetTimeSeconds() - AttachTime;
376
+ }
377
+ return S;
378
+ }
379
+ }
@@ -0,0 +1,109 @@
1
+ #pragma once
2
+
3
+ #include "CoreMinimal.h"
4
+ #include "PIEFrameSampler.h"
5
+ #include "PIESequenceFormat.h"
6
+ #include "UObject/WeakObjectPtrTemplates.h"
7
+
8
+ class UMCPObservationProfile;
9
+ class UWorld;
10
+ class AActor;
11
+
12
+ namespace UEMCPPIE
13
+ {
14
+ enum class EObserverState : uint8
15
+ {
16
+ Idle,
17
+ Armed,
18
+ WaitingForPawn,
19
+ Observing,
20
+ Completed
21
+ };
22
+
23
+ struct FObserverArmConfig
24
+ {
25
+ FString ProfilePath;
26
+ FString OutputDir;
27
+ int32 SampleHz = 60;
28
+ int32 PinFPS = -1;
29
+ int32 ClientId = 0;
30
+ };
31
+
32
+ struct FObserverStatus
33
+ {
34
+ EObserverState State = EObserverState::Idle;
35
+ FString RunId;
36
+ FString ProfilePath;
37
+ int32 FramesSampled = 0;
38
+ double ElapsedSeconds = 0.0;
39
+ };
40
+
41
+ struct FObserverFinishResult
42
+ {
43
+ bool bSuccess = false;
44
+ FString Error;
45
+ FString RunId;
46
+ FString OutputDir;
47
+ FString CSVPath;
48
+ FString TrackedActorsPath;
49
+ int32 FramesSampled = 0;
50
+ double DurationSeconds = 0.0;
51
+ };
52
+
53
+ class FPIEObserver
54
+ {
55
+ public:
56
+ static FPIEObserver& Get();
57
+
58
+ void Init();
59
+ void Shutdown();
60
+
61
+ bool Arm(const FObserverArmConfig& Cfg, FString& OutError, FString& OutMessage);
62
+ bool Disarm(FString& OutError);
63
+ FObserverFinishResult ForceStop();
64
+ FObserverStatus GetStatus() const;
65
+ bool IsActive() const { return State != EObserverState::Idle && State != EObserverState::Completed; }
66
+
67
+ private:
68
+ void OnBeginPIE(bool bIsSimulating);
69
+ void OnEndPIE(bool bIsSimulating);
70
+ void OnEndFrame();
71
+ FObserverFinishResult FinaliseCurrent();
72
+
73
+ FObserverArmConfig Pending;
74
+ EObserverState State = EObserverState::Idle;
75
+ bool bArmed = false;
76
+
77
+ FString CurrentRunId;
78
+ FString CurrentOutputDir;
79
+ FString CurrentProfilePath;
80
+
81
+ FPIEFrameSampler Sampler;
82
+ FCSVHeader CSVHdr;
83
+ FString CSVHeaderStr;
84
+ FString CSVBody;
85
+
86
+ TArray<FString> TrackedActorIds;
87
+ TArray<FTrackedActorRow> ActorRows;
88
+ TMap<FString, TWeakObjectPtr<AActor>> ActorCache;
89
+
90
+ double AttachTime = 0.0;
91
+ int32 FramesSampled = 0;
92
+ FString StartedAt;
93
+
94
+ // Profile config extracted at arm time
95
+ TArray<FString> TrackedValuePaths;
96
+ bool bCapturePawnState = true;
97
+ bool bCaptureMontage = true;
98
+ float ThrPosCm = 5.f;
99
+ float ThrRotDeg = 2.f;
100
+ float ThrVelCms = 25.f;
101
+ float ThrTrackedDefault = 0.f;
102
+ TMap<FString, float> TrackedThresholds;
103
+
104
+ FDelegateHandle BeginPIEHandle;
105
+ FDelegateHandle EndPIEHandle;
106
+ FDelegateHandle OnEndFrameHandle;
107
+ bool bEndFrameBound = false;
108
+ };
109
+ }
@@ -247,4 +247,9 @@ namespace UEMCPPIE
247
247
  // recorder (initial resolve), the replayer (re-resolve in the new world),
248
248
  // and pie_snapshot.
249
249
  AActor* FindActorById(UWorld* World, const FString& Id);
250
+
251
+ inline FString ISOTimestampNow()
252
+ {
253
+ return FDateTime::Now().ToString(TEXT("%Y-%m-%dT%H:%M:%S"));
254
+ }
250
255
  }
@@ -5,6 +5,7 @@
5
5
  #include "PIE/PIEInputInjector.h"
6
6
  #include "PIE/PIEInputRecorder.h"
7
7
  #include "PIE/PIEInputReplayer.h"
8
+ #include "PIE/PIEObserver.h"
8
9
  #include "Editor.h"
9
10
  #include "Editor/EditorEngine.h"
10
11
  #include "Misc/ConfigCacheIni.h"
@@ -24,6 +25,7 @@ void FUE_MCP_BridgeModule::StartupModule()
24
25
  UEMCPPIE::FPIEInputInjector::Init();
25
26
  UEMCPPIE::FPIEInputRecorder::Get().Init();
26
27
  UEMCPPIE::FPIEInputReplayer::Get().Init();
28
+ UEMCPPIE::FPIEObserver::Get().Init();
27
29
  // Clear any leftover injections from a previous PIE session so a fresh
28
30
  // EndPIE-BeginPIE pair starts with no ghost holds in the queue.
29
31
  FEditorDelegates::EndPIE.AddLambda([](bool /*bIsSimulating*/)
@@ -100,7 +102,8 @@ void FUE_MCP_BridgeModule::StartupModule()
100
102
  Suppress.BindLambda([]() -> bool
101
103
  {
102
104
  return UEMCPPIE::FPIEInputRecorder::Get().IsActive()
103
- || UEMCPPIE::FPIEInputReplayer::Get().IsActive();
105
+ || UEMCPPIE::FPIEInputReplayer::Get().IsActive()
106
+ || UEMCPPIE::FPIEObserver::Get().IsActive();
104
107
  });
105
108
  GEditor->ShouldDisableCPUThrottlingDelegates.Add(Suppress);
106
109
 
@@ -113,6 +116,7 @@ void FUE_MCP_BridgeModule::ShutdownModule()
113
116
  {
114
117
  // Stop bridge server
115
118
  FDialogHandlers::RemoveDialogHook();
119
+ UEMCPPIE::FPIEObserver::Get().Shutdown();
116
120
  UEMCPPIE::FPIEInputReplayer::Get().Shutdown();
117
121
  UEMCPPIE::FPIEInputRecorder::Get().Shutdown();
118
122
  UEMCPPIE::FPIEInputInjector::Shutdown();