ue-mcp 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/context-cli.js +16 -16
- package/dist/engine-observer.js +68 -68
- package/dist/flow/guard.d.ts +26 -37
- package/dist/flow/guard.js +40 -25
- package/dist/flow/guard.js.map +1 -1
- package/dist/flow/guarded-bridge.d.ts +0 -10
- package/dist/flow/guarded-bridge.js +18 -23
- package/dist/flow/guarded-bridge.js.map +1 -1
- package/dist/flow/schema.d.ts +276 -16
- package/dist/flow/task-guards.d.ts +1 -1
- package/dist/flow/task-guards.js +44 -68
- package/dist/flow/task-guards.js.map +1 -1
- package/dist/instructions.js +130 -130
- package/dist/plugin-cli.js +299 -299
- package/dist/tool-counts.json +2 -2
- package/package.json +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.cpp +76 -76
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.h +27 -27
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +128 -128
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +63 -63
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +230 -230
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +323 -323
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +20 -20
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ChooserHandlers_Nested.cpp +537 -537
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +77 -77
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +353 -353
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +18 -18
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_InputRuntime.cpp +246 -246
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +597 -597
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +51 -51
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +878 -878
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +449 -449
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Function.cpp +275 -275
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Graph.cpp +551 -551
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +531 -531
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +28 -28
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +677 -677
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +822 -822
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +22 -22
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +218 -218
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +17 -17
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/VolumeHelpers_Internal.cpp +46 -46
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/VolumeHelpers_Internal.h +23 -23
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/MCPExternalRegistry.cpp +83 -83
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -14
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/UE_MCP_BridgeStatusModule.cpp +37 -37
- package/dist/manifest-signature.d.ts +0 -1
- package/dist/manifest-signature.js +0 -53
- package/dist/manifest-signature.js.map +0 -1
- package/dist/pie/schema.d.ts +0 -791
- package/dist/pie/schema.js +0 -151
- package/dist/pie/schema.js.map +0 -1
- package/dist/watch-cli.js.map +0 -1
- /package/plugin/ue_mcp_bridge/{ue_mcp_bridge.uplugin → UE_MCP_Bridge.uplugin} +0 -0
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
#include "CoreMinimal.h"
|
|
4
|
-
#include "Dom/JsonValue.h"
|
|
5
|
-
#include "Dom/JsonObject.h"
|
|
6
|
-
|
|
7
|
-
class FFoliageHandlers
|
|
8
|
-
{
|
|
9
|
-
public:
|
|
10
|
-
static void RegisterHandlers(class FMCPHandlerRegistry& Registry);
|
|
11
|
-
|
|
12
|
-
private:
|
|
13
|
-
static TSharedPtr<FJsonValue> ListFoliageTypes(const TSharedPtr<FJsonObject>& Params);
|
|
14
|
-
static TSharedPtr<FJsonValue> SampleFoliage(const TSharedPtr<FJsonObject>& Params);
|
|
15
|
-
static TSharedPtr<FJsonValue> GetFoliageSettings(const TSharedPtr<FJsonObject>& Params);
|
|
16
|
-
static TSharedPtr<FJsonValue> SetFoliageTypeSettings(const TSharedPtr<FJsonObject>& Params);
|
|
17
|
-
static TSharedPtr<FJsonValue> CreateFoliageType(const TSharedPtr<FJsonObject>& Params);
|
|
18
|
-
};
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "CoreMinimal.h"
|
|
4
|
+
#include "Dom/JsonValue.h"
|
|
5
|
+
#include "Dom/JsonObject.h"
|
|
6
|
+
|
|
7
|
+
class FFoliageHandlers
|
|
8
|
+
{
|
|
9
|
+
public:
|
|
10
|
+
static void RegisterHandlers(class FMCPHandlerRegistry& Registry);
|
|
11
|
+
|
|
12
|
+
private:
|
|
13
|
+
static TSharedPtr<FJsonValue> ListFoliageTypes(const TSharedPtr<FJsonObject>& Params);
|
|
14
|
+
static TSharedPtr<FJsonValue> SampleFoliage(const TSharedPtr<FJsonObject>& Params);
|
|
15
|
+
static TSharedPtr<FJsonValue> GetFoliageSettings(const TSharedPtr<FJsonObject>& Params);
|
|
16
|
+
static TSharedPtr<FJsonValue> SetFoliageTypeSettings(const TSharedPtr<FJsonObject>& Params);
|
|
17
|
+
static TSharedPtr<FJsonValue> CreateFoliageType(const TSharedPtr<FJsonObject>& Params);
|
|
18
|
+
};
|
package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_InputRuntime.cpp
CHANGED
|
@@ -1,246 +1,246 @@
|
|
|
1
|
-
// Per-player Enhanced Input runtime state (#778).
|
|
2
|
-
//
|
|
3
|
-
// The other half of #778: there was no way to ask "does player X in PIE
|
|
4
|
-
// instance N currently have mapping context Y applied?", which is what you need
|
|
5
|
-
// to verify input-authority gating across a multiplayer PIE session. The
|
|
6
|
-
// applied contexts live on the player's UEnhancedPlayerInput, reachable from
|
|
7
|
-
// the local player's Enhanced Input subsystem.
|
|
8
|
-
//
|
|
9
|
-
// Translation-unit partition of FGameplayHandlers; registration lives in
|
|
10
|
-
// GameplayHandlers.cpp.
|
|
11
|
-
|
|
12
|
-
#include "GameplayHandlers.h"
|
|
13
|
-
|
|
14
|
-
#include "HandlerUtils.h"
|
|
15
|
-
|
|
16
|
-
#include "EnhancedInputSubsystems.h"
|
|
17
|
-
#include "EnhancedPlayerInput.h"
|
|
18
|
-
#include "UObject/UnrealType.h"
|
|
19
|
-
#include "UObject/PropertyPortFlags.h"
|
|
20
|
-
#include "InputMappingContext.h"
|
|
21
|
-
#include "InputAction.h"
|
|
22
|
-
#include "Engine/Engine.h"
|
|
23
|
-
#include "Engine/LocalPlayer.h"
|
|
24
|
-
#include "Engine/World.h"
|
|
25
|
-
#include "GameFramework/PlayerController.h"
|
|
26
|
-
|
|
27
|
-
namespace
|
|
28
|
-
{
|
|
29
|
-
/** Applied mapping contexts for one player controller, or none. */
|
|
30
|
-
TSharedPtr<FJsonObject> DescribePlayer(APlayerController* PC, int32 PlayerIndex, bool bIncludeActions)
|
|
31
|
-
{
|
|
32
|
-
TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
|
|
33
|
-
Entry->SetNumberField(TEXT("playerIndex"), PlayerIndex);
|
|
34
|
-
Entry->SetStringField(TEXT("playerController"), PC ? PC->GetName() : FString());
|
|
35
|
-
if (!PC) return Entry;
|
|
36
|
-
|
|
37
|
-
Entry->SetBoolField(TEXT("isLocalController"), PC->IsLocalController());
|
|
38
|
-
if (APawn* Pawn = PC->GetPawn())
|
|
39
|
-
{
|
|
40
|
-
Entry->SetStringField(TEXT("pawn"), Pawn->GetName());
|
|
41
|
-
Entry->SetStringField(TEXT("pawnClass"), Pawn->GetClass()->GetName());
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
|
|
45
|
-
if (!LocalPlayer)
|
|
46
|
-
{
|
|
47
|
-
// A remote controller on the server has no local player, so it has
|
|
48
|
-
// no Enhanced Input subsystem. Say so rather than reporting an
|
|
49
|
-
// empty context list, which would read as "nothing is applied".
|
|
50
|
-
Entry->SetStringField(TEXT("note"),
|
|
51
|
-
TEXT("No LocalPlayer (remote controller); Enhanced Input state lives on the owning client."));
|
|
52
|
-
return Entry;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
UEnhancedInputLocalPlayerSubsystem* Subsystem =
|
|
56
|
-
LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
|
|
57
|
-
if (!Subsystem)
|
|
58
|
-
{
|
|
59
|
-
Entry->SetStringField(TEXT("note"), TEXT("Enhanced Input subsystem not active for this local player"));
|
|
60
|
-
return Entry;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
UEnhancedPlayerInput* PlayerInput = Subsystem->GetPlayerInput();
|
|
64
|
-
if (!PlayerInput)
|
|
65
|
-
{
|
|
66
|
-
Entry->SetStringField(TEXT("note"), TEXT("Local player has no UEnhancedPlayerInput yet"));
|
|
67
|
-
return Entry;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// GetAppliedInputContextData() is protected (only the subsystem
|
|
71
|
-
// interface is a friend), but AppliedInputContextData is a UPROPERTY -
|
|
72
|
-
// and FProperty reflection ignores C++ access specifiers. Read the map
|
|
73
|
-
// through reflection rather than giving up on enumeration and forcing
|
|
74
|
-
// the caller to already know which context to ask about.
|
|
75
|
-
TArray<TSharedPtr<FJsonValue>> Contexts;
|
|
76
|
-
FMapProperty* MapProp = CastField<FMapProperty>(
|
|
77
|
-
PlayerInput->GetClass()->FindPropertyByName(TEXT("AppliedInputContextData")));
|
|
78
|
-
if (!MapProp)
|
|
79
|
-
{
|
|
80
|
-
Entry->SetStringField(TEXT("note"),
|
|
81
|
-
TEXT("AppliedInputContextData not found by reflection on this engine version"));
|
|
82
|
-
return Entry;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
FScriptMapHelper MapHelper(MapProp, MapProp->ContainerPtrToValuePtr<void>(PlayerInput));
|
|
86
|
-
FObjectPropertyBase* KeyProp = CastField<FObjectPropertyBase>(MapProp->KeyProp);
|
|
87
|
-
FStructProperty* ValueProp = CastField<FStructProperty>(MapProp->ValueProp);
|
|
88
|
-
|
|
89
|
-
for (FScriptMapHelper::FIterator It = MapHelper.CreateIterator(); It; ++It)
|
|
90
|
-
{
|
|
91
|
-
const UInputMappingContext* Context = KeyProp
|
|
92
|
-
? Cast<UInputMappingContext>(KeyProp->GetObjectPropertyValue(MapHelper.GetKeyPtr(It)))
|
|
93
|
-
: nullptr;
|
|
94
|
-
if (!Context) continue;
|
|
95
|
-
|
|
96
|
-
int32 Priority = 0;
|
|
97
|
-
int32 RegistrationCount = 0;
|
|
98
|
-
if (ValueProp)
|
|
99
|
-
{
|
|
100
|
-
void* ValueAddr = MapHelper.GetValuePtr(It);
|
|
101
|
-
if (FIntProperty* P = CastField<FIntProperty>(ValueProp->Struct->FindPropertyByName(TEXT("Priority"))))
|
|
102
|
-
{
|
|
103
|
-
Priority = P->GetPropertyValue_InContainer(ValueAddr);
|
|
104
|
-
}
|
|
105
|
-
if (FIntProperty* R = CastField<FIntProperty>(ValueProp->Struct->FindPropertyByName(TEXT("RegistrationCount"))))
|
|
106
|
-
{
|
|
107
|
-
RegistrationCount = R->GetPropertyValue_InContainer(ValueAddr);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
TSharedPtr<FJsonObject> ContextObj = MakeShared<FJsonObject>();
|
|
112
|
-
ContextObj->SetStringField(TEXT("name"), Context->GetName());
|
|
113
|
-
ContextObj->SetStringField(TEXT("path"), Context->GetPathName());
|
|
114
|
-
ContextObj->SetNumberField(TEXT("priority"), Priority);
|
|
115
|
-
ContextObj->SetNumberField(TEXT("registrationCount"), RegistrationCount);
|
|
116
|
-
ContextObj->SetNumberField(TEXT("mappingCount"), Context->GetMappings().Num());
|
|
117
|
-
|
|
118
|
-
if (bIncludeActions)
|
|
119
|
-
{
|
|
120
|
-
TArray<TSharedPtr<FJsonValue>> Actions;
|
|
121
|
-
TSet<FString> Seen;
|
|
122
|
-
for (const FEnhancedActionKeyMapping& Mapping : Context->GetMappings())
|
|
123
|
-
{
|
|
124
|
-
if (!Mapping.Action) continue;
|
|
125
|
-
const FString ActionName = Mapping.Action->GetName();
|
|
126
|
-
if (Seen.Contains(ActionName)) continue;
|
|
127
|
-
Seen.Add(ActionName);
|
|
128
|
-
TSharedPtr<FJsonObject> ActionObj = MakeShared<FJsonObject>();
|
|
129
|
-
ActionObj->SetStringField(TEXT("action"), ActionName);
|
|
130
|
-
ActionObj->SetStringField(TEXT("actionPath"), Mapping.Action->GetPathName());
|
|
131
|
-
ActionObj->SetStringField(TEXT("key"), Mapping.Key.ToString());
|
|
132
|
-
Actions.Add(MakeShared<FJsonValueObject>(ActionObj));
|
|
133
|
-
}
|
|
134
|
-
ContextObj->SetArrayField(TEXT("actions"), Actions);
|
|
135
|
-
}
|
|
136
|
-
Contexts.Add(MakeShared<FJsonValueObject>(ContextObj));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Highest priority first: that is the order Enhanced Input resolves in,
|
|
140
|
-
// so a caller debugging a shadowed binding reads it top-down.
|
|
141
|
-
Contexts.Sort([](const TSharedPtr<FJsonValue>& A, const TSharedPtr<FJsonValue>& B)
|
|
142
|
-
{
|
|
143
|
-
double PA = 0.0, PB = 0.0;
|
|
144
|
-
A->AsObject()->TryGetNumberField(TEXT("priority"), PA);
|
|
145
|
-
B->AsObject()->TryGetNumberField(TEXT("priority"), PB);
|
|
146
|
-
return PA > PB;
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
Entry->SetArrayField(TEXT("mappingContexts"), Contexts);
|
|
150
|
-
Entry->SetNumberField(TEXT("mappingContextCount"), Contexts.Num());
|
|
151
|
-
return Entry;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
TSharedPtr<FJsonValue> FGameplayHandlers::GetInputMappingContexts(const TSharedPtr<FJsonObject>& Params)
|
|
156
|
-
{
|
|
157
|
-
if (!GEngine) return MCPError(TEXT("Engine not available"));
|
|
158
|
-
|
|
159
|
-
const bool bIncludeActions = OptionalBool(Params, TEXT("includeActions"), false);
|
|
160
|
-
const FString ContextFilter = OptionalString(Params, TEXT("mappingContext"));
|
|
161
|
-
|
|
162
|
-
int32 RequestedInstance = INDEX_NONE;
|
|
163
|
-
double RawInstance = 0.0;
|
|
164
|
-
const bool bHasInstance = Params->TryGetNumberField(TEXT("pieInstance"), RawInstance);
|
|
165
|
-
if (bHasInstance) RequestedInstance = FMath::RoundToInt(RawInstance);
|
|
166
|
-
|
|
167
|
-
int32 RequestedPlayer = INDEX_NONE;
|
|
168
|
-
double RawPlayer = 0.0;
|
|
169
|
-
if (Params->TryGetNumberField(TEXT("playerIndex"), RawPlayer)) RequestedPlayer = FMath::RoundToInt(RawPlayer);
|
|
170
|
-
|
|
171
|
-
TArray<TSharedPtr<FJsonValue>> Worlds;
|
|
172
|
-
int32 WorldsSeen = 0;
|
|
173
|
-
for (const FWorldContext& Ctx : GEngine->GetWorldContexts())
|
|
174
|
-
{
|
|
175
|
-
if (Ctx.WorldType != EWorldType::PIE && Ctx.WorldType != EWorldType::Game) continue;
|
|
176
|
-
if (bHasInstance && Ctx.PIEInstance != RequestedInstance) continue;
|
|
177
|
-
UWorld* World = Ctx.World();
|
|
178
|
-
if (!World) continue;
|
|
179
|
-
++WorldsSeen;
|
|
180
|
-
|
|
181
|
-
TSharedPtr<FJsonObject> WorldObj = MakeShared<FJsonObject>();
|
|
182
|
-
WorldObj->SetNumberField(TEXT("pieInstance"), Ctx.PIEInstance);
|
|
183
|
-
WorldObj->SetStringField(TEXT("worldPath"), World->GetPathName());
|
|
184
|
-
WorldObj->SetStringField(TEXT("netMode"), DescribePIENetMode(World));
|
|
185
|
-
|
|
186
|
-
TArray<TSharedPtr<FJsonValue>> Players;
|
|
187
|
-
int32 PlayerIndex = 0;
|
|
188
|
-
for (FConstPlayerControllerIterator It = World->GetPlayerControllerIterator(); It; ++It, ++PlayerIndex)
|
|
189
|
-
{
|
|
190
|
-
if (RequestedPlayer != INDEX_NONE && PlayerIndex != RequestedPlayer) continue;
|
|
191
|
-
APlayerController* PC = It->Get();
|
|
192
|
-
TSharedPtr<FJsonObject> Entry = DescribePlayer(PC, PlayerIndex, bIncludeActions);
|
|
193
|
-
|
|
194
|
-
// When asking about one specific context, answer the yes/no
|
|
195
|
-
// question directly instead of making the caller scan the list.
|
|
196
|
-
if (!ContextFilter.IsEmpty())
|
|
197
|
-
{
|
|
198
|
-
bool bApplied = false;
|
|
199
|
-
int32 Priority = 0;
|
|
200
|
-
const TArray<TSharedPtr<FJsonValue>>* Contexts = nullptr;
|
|
201
|
-
if (Entry->TryGetArrayField(TEXT("mappingContexts"), Contexts) && Contexts)
|
|
202
|
-
{
|
|
203
|
-
for (const TSharedPtr<FJsonValue>& Value : *Contexts)
|
|
204
|
-
{
|
|
205
|
-
const TSharedPtr<FJsonObject> Obj = Value->AsObject();
|
|
206
|
-
FString Name, Path;
|
|
207
|
-
Obj->TryGetStringField(TEXT("name"), Name);
|
|
208
|
-
Obj->TryGetStringField(TEXT("path"), Path);
|
|
209
|
-
if (Name.Equals(ContextFilter, ESearchCase::IgnoreCase) ||
|
|
210
|
-
Path.Equals(ContextFilter, ESearchCase::IgnoreCase) ||
|
|
211
|
-
Path.Contains(ContextFilter, ESearchCase::IgnoreCase))
|
|
212
|
-
{
|
|
213
|
-
bApplied = true;
|
|
214
|
-
double P = 0.0;
|
|
215
|
-
Obj->TryGetNumberField(TEXT("priority"), P);
|
|
216
|
-
Priority = (int32)P;
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
Entry->SetBoolField(TEXT("hasRequestedContext"), bApplied);
|
|
222
|
-
if (bApplied) Entry->SetNumberField(TEXT("requestedContextPriority"), Priority);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
Players.Add(MakeShared<FJsonValueObject>(Entry));
|
|
226
|
-
}
|
|
227
|
-
WorldObj->SetArrayField(TEXT("players"), Players);
|
|
228
|
-
// Reported = how many are in this response; total = how many the world
|
|
229
|
-
// actually has, which differs whenever playerIndex filtered the list.
|
|
230
|
-
WorldObj->SetNumberField(TEXT("playerCount"), Players.Num());
|
|
231
|
-
WorldObj->SetNumberField(TEXT("totalPlayerCount"), World->GetNumPlayerControllers());
|
|
232
|
-
Worlds.Add(MakeShared<FJsonValueObject>(WorldObj));
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
auto Result = MCPSuccess();
|
|
236
|
-
Result->SetArrayField(TEXT("worlds"), Worlds);
|
|
237
|
-
Result->SetNumberField(TEXT("worldCount"), Worlds.Num());
|
|
238
|
-
if (!ContextFilter.IsEmpty()) Result->SetStringField(TEXT("mappingContext"), ContextFilter);
|
|
239
|
-
if (WorldsSeen == 0)
|
|
240
|
-
{
|
|
241
|
-
Result->SetStringField(TEXT("note"), bHasInstance
|
|
242
|
-
? FString::Printf(TEXT("No running PIE world with instance %d. Use editor(list_pie_instances)."), RequestedInstance)
|
|
243
|
-
: TEXT("PIE is not running. Start it with editor(play_in_editor)."));
|
|
244
|
-
}
|
|
245
|
-
return MCPResult(Result);
|
|
246
|
-
}
|
|
1
|
+
// Per-player Enhanced Input runtime state (#778).
|
|
2
|
+
//
|
|
3
|
+
// The other half of #778: there was no way to ask "does player X in PIE
|
|
4
|
+
// instance N currently have mapping context Y applied?", which is what you need
|
|
5
|
+
// to verify input-authority gating across a multiplayer PIE session. The
|
|
6
|
+
// applied contexts live on the player's UEnhancedPlayerInput, reachable from
|
|
7
|
+
// the local player's Enhanced Input subsystem.
|
|
8
|
+
//
|
|
9
|
+
// Translation-unit partition of FGameplayHandlers; registration lives in
|
|
10
|
+
// GameplayHandlers.cpp.
|
|
11
|
+
|
|
12
|
+
#include "GameplayHandlers.h"
|
|
13
|
+
|
|
14
|
+
#include "HandlerUtils.h"
|
|
15
|
+
|
|
16
|
+
#include "EnhancedInputSubsystems.h"
|
|
17
|
+
#include "EnhancedPlayerInput.h"
|
|
18
|
+
#include "UObject/UnrealType.h"
|
|
19
|
+
#include "UObject/PropertyPortFlags.h"
|
|
20
|
+
#include "InputMappingContext.h"
|
|
21
|
+
#include "InputAction.h"
|
|
22
|
+
#include "Engine/Engine.h"
|
|
23
|
+
#include "Engine/LocalPlayer.h"
|
|
24
|
+
#include "Engine/World.h"
|
|
25
|
+
#include "GameFramework/PlayerController.h"
|
|
26
|
+
|
|
27
|
+
namespace
|
|
28
|
+
{
|
|
29
|
+
/** Applied mapping contexts for one player controller, or none. */
|
|
30
|
+
TSharedPtr<FJsonObject> DescribePlayer(APlayerController* PC, int32 PlayerIndex, bool bIncludeActions)
|
|
31
|
+
{
|
|
32
|
+
TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
|
|
33
|
+
Entry->SetNumberField(TEXT("playerIndex"), PlayerIndex);
|
|
34
|
+
Entry->SetStringField(TEXT("playerController"), PC ? PC->GetName() : FString());
|
|
35
|
+
if (!PC) return Entry;
|
|
36
|
+
|
|
37
|
+
Entry->SetBoolField(TEXT("isLocalController"), PC->IsLocalController());
|
|
38
|
+
if (APawn* Pawn = PC->GetPawn())
|
|
39
|
+
{
|
|
40
|
+
Entry->SetStringField(TEXT("pawn"), Pawn->GetName());
|
|
41
|
+
Entry->SetStringField(TEXT("pawnClass"), Pawn->GetClass()->GetName());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
|
|
45
|
+
if (!LocalPlayer)
|
|
46
|
+
{
|
|
47
|
+
// A remote controller on the server has no local player, so it has
|
|
48
|
+
// no Enhanced Input subsystem. Say so rather than reporting an
|
|
49
|
+
// empty context list, which would read as "nothing is applied".
|
|
50
|
+
Entry->SetStringField(TEXT("note"),
|
|
51
|
+
TEXT("No LocalPlayer (remote controller); Enhanced Input state lives on the owning client."));
|
|
52
|
+
return Entry;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
UEnhancedInputLocalPlayerSubsystem* Subsystem =
|
|
56
|
+
LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
|
|
57
|
+
if (!Subsystem)
|
|
58
|
+
{
|
|
59
|
+
Entry->SetStringField(TEXT("note"), TEXT("Enhanced Input subsystem not active for this local player"));
|
|
60
|
+
return Entry;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
UEnhancedPlayerInput* PlayerInput = Subsystem->GetPlayerInput();
|
|
64
|
+
if (!PlayerInput)
|
|
65
|
+
{
|
|
66
|
+
Entry->SetStringField(TEXT("note"), TEXT("Local player has no UEnhancedPlayerInput yet"));
|
|
67
|
+
return Entry;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// GetAppliedInputContextData() is protected (only the subsystem
|
|
71
|
+
// interface is a friend), but AppliedInputContextData is a UPROPERTY -
|
|
72
|
+
// and FProperty reflection ignores C++ access specifiers. Read the map
|
|
73
|
+
// through reflection rather than giving up on enumeration and forcing
|
|
74
|
+
// the caller to already know which context to ask about.
|
|
75
|
+
TArray<TSharedPtr<FJsonValue>> Contexts;
|
|
76
|
+
FMapProperty* MapProp = CastField<FMapProperty>(
|
|
77
|
+
PlayerInput->GetClass()->FindPropertyByName(TEXT("AppliedInputContextData")));
|
|
78
|
+
if (!MapProp)
|
|
79
|
+
{
|
|
80
|
+
Entry->SetStringField(TEXT("note"),
|
|
81
|
+
TEXT("AppliedInputContextData not found by reflection on this engine version"));
|
|
82
|
+
return Entry;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
FScriptMapHelper MapHelper(MapProp, MapProp->ContainerPtrToValuePtr<void>(PlayerInput));
|
|
86
|
+
FObjectPropertyBase* KeyProp = CastField<FObjectPropertyBase>(MapProp->KeyProp);
|
|
87
|
+
FStructProperty* ValueProp = CastField<FStructProperty>(MapProp->ValueProp);
|
|
88
|
+
|
|
89
|
+
for (FScriptMapHelper::FIterator It = MapHelper.CreateIterator(); It; ++It)
|
|
90
|
+
{
|
|
91
|
+
const UInputMappingContext* Context = KeyProp
|
|
92
|
+
? Cast<UInputMappingContext>(KeyProp->GetObjectPropertyValue(MapHelper.GetKeyPtr(It)))
|
|
93
|
+
: nullptr;
|
|
94
|
+
if (!Context) continue;
|
|
95
|
+
|
|
96
|
+
int32 Priority = 0;
|
|
97
|
+
int32 RegistrationCount = 0;
|
|
98
|
+
if (ValueProp)
|
|
99
|
+
{
|
|
100
|
+
void* ValueAddr = MapHelper.GetValuePtr(It);
|
|
101
|
+
if (FIntProperty* P = CastField<FIntProperty>(ValueProp->Struct->FindPropertyByName(TEXT("Priority"))))
|
|
102
|
+
{
|
|
103
|
+
Priority = P->GetPropertyValue_InContainer(ValueAddr);
|
|
104
|
+
}
|
|
105
|
+
if (FIntProperty* R = CastField<FIntProperty>(ValueProp->Struct->FindPropertyByName(TEXT("RegistrationCount"))))
|
|
106
|
+
{
|
|
107
|
+
RegistrationCount = R->GetPropertyValue_InContainer(ValueAddr);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
TSharedPtr<FJsonObject> ContextObj = MakeShared<FJsonObject>();
|
|
112
|
+
ContextObj->SetStringField(TEXT("name"), Context->GetName());
|
|
113
|
+
ContextObj->SetStringField(TEXT("path"), Context->GetPathName());
|
|
114
|
+
ContextObj->SetNumberField(TEXT("priority"), Priority);
|
|
115
|
+
ContextObj->SetNumberField(TEXT("registrationCount"), RegistrationCount);
|
|
116
|
+
ContextObj->SetNumberField(TEXT("mappingCount"), Context->GetMappings().Num());
|
|
117
|
+
|
|
118
|
+
if (bIncludeActions)
|
|
119
|
+
{
|
|
120
|
+
TArray<TSharedPtr<FJsonValue>> Actions;
|
|
121
|
+
TSet<FString> Seen;
|
|
122
|
+
for (const FEnhancedActionKeyMapping& Mapping : Context->GetMappings())
|
|
123
|
+
{
|
|
124
|
+
if (!Mapping.Action) continue;
|
|
125
|
+
const FString ActionName = Mapping.Action->GetName();
|
|
126
|
+
if (Seen.Contains(ActionName)) continue;
|
|
127
|
+
Seen.Add(ActionName);
|
|
128
|
+
TSharedPtr<FJsonObject> ActionObj = MakeShared<FJsonObject>();
|
|
129
|
+
ActionObj->SetStringField(TEXT("action"), ActionName);
|
|
130
|
+
ActionObj->SetStringField(TEXT("actionPath"), Mapping.Action->GetPathName());
|
|
131
|
+
ActionObj->SetStringField(TEXT("key"), Mapping.Key.ToString());
|
|
132
|
+
Actions.Add(MakeShared<FJsonValueObject>(ActionObj));
|
|
133
|
+
}
|
|
134
|
+
ContextObj->SetArrayField(TEXT("actions"), Actions);
|
|
135
|
+
}
|
|
136
|
+
Contexts.Add(MakeShared<FJsonValueObject>(ContextObj));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Highest priority first: that is the order Enhanced Input resolves in,
|
|
140
|
+
// so a caller debugging a shadowed binding reads it top-down.
|
|
141
|
+
Contexts.Sort([](const TSharedPtr<FJsonValue>& A, const TSharedPtr<FJsonValue>& B)
|
|
142
|
+
{
|
|
143
|
+
double PA = 0.0, PB = 0.0;
|
|
144
|
+
A->AsObject()->TryGetNumberField(TEXT("priority"), PA);
|
|
145
|
+
B->AsObject()->TryGetNumberField(TEXT("priority"), PB);
|
|
146
|
+
return PA > PB;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
Entry->SetArrayField(TEXT("mappingContexts"), Contexts);
|
|
150
|
+
Entry->SetNumberField(TEXT("mappingContextCount"), Contexts.Num());
|
|
151
|
+
return Entry;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
TSharedPtr<FJsonValue> FGameplayHandlers::GetInputMappingContexts(const TSharedPtr<FJsonObject>& Params)
|
|
156
|
+
{
|
|
157
|
+
if (!GEngine) return MCPError(TEXT("Engine not available"));
|
|
158
|
+
|
|
159
|
+
const bool bIncludeActions = OptionalBool(Params, TEXT("includeActions"), false);
|
|
160
|
+
const FString ContextFilter = OptionalString(Params, TEXT("mappingContext"));
|
|
161
|
+
|
|
162
|
+
int32 RequestedInstance = INDEX_NONE;
|
|
163
|
+
double RawInstance = 0.0;
|
|
164
|
+
const bool bHasInstance = Params->TryGetNumberField(TEXT("pieInstance"), RawInstance);
|
|
165
|
+
if (bHasInstance) RequestedInstance = FMath::RoundToInt(RawInstance);
|
|
166
|
+
|
|
167
|
+
int32 RequestedPlayer = INDEX_NONE;
|
|
168
|
+
double RawPlayer = 0.0;
|
|
169
|
+
if (Params->TryGetNumberField(TEXT("playerIndex"), RawPlayer)) RequestedPlayer = FMath::RoundToInt(RawPlayer);
|
|
170
|
+
|
|
171
|
+
TArray<TSharedPtr<FJsonValue>> Worlds;
|
|
172
|
+
int32 WorldsSeen = 0;
|
|
173
|
+
for (const FWorldContext& Ctx : GEngine->GetWorldContexts())
|
|
174
|
+
{
|
|
175
|
+
if (Ctx.WorldType != EWorldType::PIE && Ctx.WorldType != EWorldType::Game) continue;
|
|
176
|
+
if (bHasInstance && Ctx.PIEInstance != RequestedInstance) continue;
|
|
177
|
+
UWorld* World = Ctx.World();
|
|
178
|
+
if (!World) continue;
|
|
179
|
+
++WorldsSeen;
|
|
180
|
+
|
|
181
|
+
TSharedPtr<FJsonObject> WorldObj = MakeShared<FJsonObject>();
|
|
182
|
+
WorldObj->SetNumberField(TEXT("pieInstance"), Ctx.PIEInstance);
|
|
183
|
+
WorldObj->SetStringField(TEXT("worldPath"), World->GetPathName());
|
|
184
|
+
WorldObj->SetStringField(TEXT("netMode"), DescribePIENetMode(World));
|
|
185
|
+
|
|
186
|
+
TArray<TSharedPtr<FJsonValue>> Players;
|
|
187
|
+
int32 PlayerIndex = 0;
|
|
188
|
+
for (FConstPlayerControllerIterator It = World->GetPlayerControllerIterator(); It; ++It, ++PlayerIndex)
|
|
189
|
+
{
|
|
190
|
+
if (RequestedPlayer != INDEX_NONE && PlayerIndex != RequestedPlayer) continue;
|
|
191
|
+
APlayerController* PC = It->Get();
|
|
192
|
+
TSharedPtr<FJsonObject> Entry = DescribePlayer(PC, PlayerIndex, bIncludeActions);
|
|
193
|
+
|
|
194
|
+
// When asking about one specific context, answer the yes/no
|
|
195
|
+
// question directly instead of making the caller scan the list.
|
|
196
|
+
if (!ContextFilter.IsEmpty())
|
|
197
|
+
{
|
|
198
|
+
bool bApplied = false;
|
|
199
|
+
int32 Priority = 0;
|
|
200
|
+
const TArray<TSharedPtr<FJsonValue>>* Contexts = nullptr;
|
|
201
|
+
if (Entry->TryGetArrayField(TEXT("mappingContexts"), Contexts) && Contexts)
|
|
202
|
+
{
|
|
203
|
+
for (const TSharedPtr<FJsonValue>& Value : *Contexts)
|
|
204
|
+
{
|
|
205
|
+
const TSharedPtr<FJsonObject> Obj = Value->AsObject();
|
|
206
|
+
FString Name, Path;
|
|
207
|
+
Obj->TryGetStringField(TEXT("name"), Name);
|
|
208
|
+
Obj->TryGetStringField(TEXT("path"), Path);
|
|
209
|
+
if (Name.Equals(ContextFilter, ESearchCase::IgnoreCase) ||
|
|
210
|
+
Path.Equals(ContextFilter, ESearchCase::IgnoreCase) ||
|
|
211
|
+
Path.Contains(ContextFilter, ESearchCase::IgnoreCase))
|
|
212
|
+
{
|
|
213
|
+
bApplied = true;
|
|
214
|
+
double P = 0.0;
|
|
215
|
+
Obj->TryGetNumberField(TEXT("priority"), P);
|
|
216
|
+
Priority = (int32)P;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
Entry->SetBoolField(TEXT("hasRequestedContext"), bApplied);
|
|
222
|
+
if (bApplied) Entry->SetNumberField(TEXT("requestedContextPriority"), Priority);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
Players.Add(MakeShared<FJsonValueObject>(Entry));
|
|
226
|
+
}
|
|
227
|
+
WorldObj->SetArrayField(TEXT("players"), Players);
|
|
228
|
+
// Reported = how many are in this response; total = how many the world
|
|
229
|
+
// actually has, which differs whenever playerIndex filtered the list.
|
|
230
|
+
WorldObj->SetNumberField(TEXT("playerCount"), Players.Num());
|
|
231
|
+
WorldObj->SetNumberField(TEXT("totalPlayerCount"), World->GetNumPlayerControllers());
|
|
232
|
+
Worlds.Add(MakeShared<FJsonValueObject>(WorldObj));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
auto Result = MCPSuccess();
|
|
236
|
+
Result->SetArrayField(TEXT("worlds"), Worlds);
|
|
237
|
+
Result->SetNumberField(TEXT("worldCount"), Worlds.Num());
|
|
238
|
+
if (!ContextFilter.IsEmpty()) Result->SetStringField(TEXT("mappingContext"), ContextFilter);
|
|
239
|
+
if (WorldsSeen == 0)
|
|
240
|
+
{
|
|
241
|
+
Result->SetStringField(TEXT("note"), bHasInstance
|
|
242
|
+
? FString::Printf(TEXT("No running PIE world with instance %d. Use editor(list_pie_instances)."), RequestedInstance)
|
|
243
|
+
: TEXT("PIE is not running. Start it with editor(play_in_editor)."));
|
|
244
|
+
}
|
|
245
|
+
return MCPResult(Result);
|
|
246
|
+
}
|