ultimate-unreal-engine-mcp 0.1.0
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/README.md +729 -0
- package/dist/build/error-parser.js +51 -0
- package/dist/build/fix-suggester.js +84 -0
- package/dist/build/ubt-runner.js +146 -0
- package/dist/cli.js +13 -0
- package/dist/config.js +8 -0
- package/dist/docs/data/ue57-api.js +228 -0
- package/dist/docs/doc-index.js +110 -0
- package/dist/docs/types.js +4 -0
- package/dist/generators/class-generator.js +363 -0
- package/dist/generators/file-modifier.js +276 -0
- package/dist/generators/uht-validator.js +177 -0
- package/dist/index.js +89 -0
- package/dist/parsers/cpp-class-index.js +230 -0
- package/dist/parsers/cpp-parser.js +369 -0
- package/dist/parsers/ini-parser.js +216 -0
- package/dist/parsers/uproject-parser.js +130 -0
- package/dist/plugin-bridge/client.js +217 -0
- package/dist/plugin-bridge/protocol.js +6 -0
- package/dist/plugin-bridge/retry.js +23 -0
- package/dist/setup.js +209 -0
- package/dist/tools/ai-systems/index.js +247 -0
- package/dist/tools/ai-systems/types.js +4 -0
- package/dist/tools/animation/index.js +241 -0
- package/dist/tools/animation/types.js +4 -0
- package/dist/tools/audio/index.js +204 -0
- package/dist/tools/audio/types.js +4 -0
- package/dist/tools/blueprint/index.js +495 -0
- package/dist/tools/blueprint/types.js +4 -0
- package/dist/tools/build/index.js +163 -0
- package/dist/tools/chaos/index.js +230 -0
- package/dist/tools/chaos/types.js +4 -0
- package/dist/tools/collision-physics/index.js +211 -0
- package/dist/tools/config/index.js +288 -0
- package/dist/tools/cpp/index.js +305 -0
- package/dist/tools/docs/index.js +251 -0
- package/dist/tools/editor/index.js +242 -0
- package/dist/tools/gas/index.js +222 -0
- package/dist/tools/gas/types.js +5 -0
- package/dist/tools/import-export/index.js +218 -0
- package/dist/tools/input/index.js +146 -0
- package/dist/tools/known-issues/index.js +88 -0
- package/dist/tools/known-issues/middleware.js +55 -0
- package/dist/tools/known-issues/store.js +125 -0
- package/dist/tools/livelink/index.js +203 -0
- package/dist/tools/livelink/types.js +4 -0
- package/dist/tools/material/index.js +190 -0
- package/dist/tools/motion-design/index.js +251 -0
- package/dist/tools/motion-design/types.js +6 -0
- package/dist/tools/movie-render/index.js +220 -0
- package/dist/tools/networking/index.js +149 -0
- package/dist/tools/pcg/index.js +164 -0
- package/dist/tools/selection/index.js +180 -0
- package/dist/tools/sequencer/index.js +218 -0
- package/dist/tools/validation/index.js +183 -0
- package/dist/tools/validation/types.js +4 -0
- package/dist/tools/viewport/index.js +310 -0
- package/dist/tools/worldpartition/index.js +226 -0
- package/dist/tools/worldpartition/types.js +4 -0
- package/dist/utils/execFileNoThrow.js +40 -0
- package/dist/utils/logger.js +27 -0
- package/dist/utils/path-guard.js +26 -0
- package/package.json +40 -0
- package/unreal-plugin/MCPBridge/MCPBridge.uplugin +29 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/MCPBridgeEditor.Build.cs +68 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAICommands.cpp +919 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAICommands.h +23 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPActorCommands.cpp +415 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPActorCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAnimationCommands.cpp +653 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAnimationCommands.h +24 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAssetCommands.cpp +290 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAssetCommands.h +17 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAudioCommands.cpp +624 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAudioCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintHandlers.cpp +616 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintHandlers.h +25 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintWriteHandlers.cpp +744 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintWriteHandlers.h +24 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeEditor.cpp +23 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeSubsystem.cpp +149 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeSubsystem.h +38 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPChaosCommands.cpp +771 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPChaosCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPCollisionPhysicsCommands.cpp +749 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPCollisionPhysicsCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPEditorStateCommands.cpp +172 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPEditorStateCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPGASCommands.cpp +715 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPGASCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPImportExportCommands.cpp +679 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPImportExportCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPInputHandlers.cpp +381 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPInputHandlers.h +24 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPLiveLinkCommands.cpp +504 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPLiveLinkCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMaterialCommands.cpp +511 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMaterialCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMotionDesignCommands.cpp +1110 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMotionDesignCommands.h +28 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMovieRenderCommands.cpp +590 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMovieRenderCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPNetworkingCommands.cpp +482 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPNetworkingCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPPieCommands.cpp +338 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPPieCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSelectionCommands.cpp +677 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSelectionCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSequencerCommands.cpp +721 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSequencerCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPValidationCommands.cpp +368 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPValidationCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPViewportCommands.cpp +1208 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPViewportCommands.h +29 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPWorldPartitionCommands.cpp +822 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPWorldPartitionCommands.h +23 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Public/MCPBridgeEditor.h +14 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/MCPBridgeRuntime.Build.cs +28 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPBridgeRuntime.cpp +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPCommandRouter.cpp +118 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPTcpServer.cpp +196 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPBridgeRuntime.h +15 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPCommandRouter.h +55 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPTcpServer.h +59 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
// MCPNetworkingCommands.cpp
|
|
2
|
+
// Implements four networking and replication command handlers for the MCP bridge:
|
|
3
|
+
// net.replication -- inspect actor replication settings (NET-01)
|
|
4
|
+
// net.properties -- list replicated properties with conditions (NET-02)
|
|
5
|
+
// net.driver -- read NetDriver config and connection info (NET-03)
|
|
6
|
+
// net.session -- inspect online subsystem session and players (NET-04)
|
|
7
|
+
//
|
|
8
|
+
// All handlers run on the game thread via FMCPCommandRouter::Dispatch.
|
|
9
|
+
// All operations are read-only -- no Modify() calls needed.
|
|
10
|
+
// actor_label is validated to exist in the editor world before property access (T-30-01).
|
|
11
|
+
|
|
12
|
+
#include "MCPNetworkingCommands.h"
|
|
13
|
+
|
|
14
|
+
#include "Editor.h"
|
|
15
|
+
#include "Engine/World.h"
|
|
16
|
+
#include "GameFramework/Actor.h"
|
|
17
|
+
#include "EngineUtils.h"
|
|
18
|
+
|
|
19
|
+
// Networking headers
|
|
20
|
+
#include "Engine/NetDriver.h"
|
|
21
|
+
#include "Engine/NetConnection.h"
|
|
22
|
+
#include "Net/UnrealNetwork.h"
|
|
23
|
+
|
|
24
|
+
// Online Subsystem headers
|
|
25
|
+
#include "OnlineSubsystem.h"
|
|
26
|
+
#include "OnlineSessionSettings.h"
|
|
27
|
+
#include "Interfaces/OnlineSessionInterface.h"
|
|
28
|
+
|
|
29
|
+
// JSON
|
|
30
|
+
#include "Serialization/JsonSerializer.h"
|
|
31
|
+
#include "Serialization/JsonWriter.h"
|
|
32
|
+
#include "Dom/JsonObject.h"
|
|
33
|
+
#include "Dom/JsonValue.h"
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Internal helpers
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/** Returns a JSON success response string (without trailing newline). */
|
|
40
|
+
static FString BuildNetSuccessResponse(const FString& CorrId, TSharedPtr<FJsonObject> Data)
|
|
41
|
+
{
|
|
42
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
43
|
+
Obj->SetBoolField(TEXT("success"), true);
|
|
44
|
+
Obj->SetStringField(TEXT("correlationId"), CorrId);
|
|
45
|
+
if (Data.IsValid())
|
|
46
|
+
{
|
|
47
|
+
Obj->SetObjectField(TEXT("data"), Data);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
FString Output;
|
|
51
|
+
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
|
|
52
|
+
FJsonSerializer::Serialize(Obj.ToSharedRef(), Writer);
|
|
53
|
+
return Output;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Returns a JSON error response string (without trailing newline). */
|
|
57
|
+
static FString BuildNetErrorResponse(const FString& CorrId, const FString& Error)
|
|
58
|
+
{
|
|
59
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
60
|
+
Obj->SetBoolField(TEXT("success"), false);
|
|
61
|
+
Obj->SetStringField(TEXT("correlationId"), CorrId);
|
|
62
|
+
Obj->SetStringField(TEXT("error"), Error);
|
|
63
|
+
|
|
64
|
+
FString Output;
|
|
65
|
+
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
|
|
66
|
+
FJsonSerializer::Serialize(Obj.ToSharedRef(), Writer);
|
|
67
|
+
return Output;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Map ENetDormancy to a human-readable string. */
|
|
71
|
+
static FString NetDormancyToString(ENetDormancy Dormancy)
|
|
72
|
+
{
|
|
73
|
+
switch (Dormancy)
|
|
74
|
+
{
|
|
75
|
+
case DORM_Never: return TEXT("DORM_Never");
|
|
76
|
+
case DORM_Awake: return TEXT("DORM_Awake");
|
|
77
|
+
case DORM_DormantAll: return TEXT("DORM_DormantAll");
|
|
78
|
+
case DORM_DormantPartial: return TEXT("DORM_DormantPartial");
|
|
79
|
+
case DORM_Initial: return TEXT("DORM_Initial");
|
|
80
|
+
default: return TEXT("DORM_Unknown");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Map ELifetimeCondition to a human-readable string. */
|
|
85
|
+
static FString LifetimeConditionToString(ELifetimeCondition Condition)
|
|
86
|
+
{
|
|
87
|
+
switch (Condition)
|
|
88
|
+
{
|
|
89
|
+
case COND_None: return TEXT("COND_None");
|
|
90
|
+
case COND_InitialOnly: return TEXT("COND_InitialOnly");
|
|
91
|
+
case COND_OwnerOnly: return TEXT("COND_OwnerOnly");
|
|
92
|
+
case COND_SkipOwner: return TEXT("COND_SkipOwner");
|
|
93
|
+
case COND_SimulatedOnly: return TEXT("COND_SimulatedOnly");
|
|
94
|
+
case COND_AutonomousOnly: return TEXT("COND_AutonomousOnly");
|
|
95
|
+
case COND_SimulatedOrPhysics: return TEXT("COND_SimulatedOrPhysics");
|
|
96
|
+
case COND_InitialOrOwner: return TEXT("COND_InitialOrOwner");
|
|
97
|
+
case COND_Custom: return TEXT("COND_Custom");
|
|
98
|
+
case COND_ReplayOrOwner: return TEXT("COND_ReplayOrOwner");
|
|
99
|
+
case COND_ReplayOnly: return TEXT("COND_ReplayOnly");
|
|
100
|
+
case COND_SimulatedOnlyNoReplay: return TEXT("COND_SimulatedOnlyNoReplay");
|
|
101
|
+
case COND_SimulatedOrPhysicsNoReplay: return TEXT("COND_SimulatedOrPhysicsNoReplay");
|
|
102
|
+
case COND_SkipReplay: return TEXT("COND_SkipReplay");
|
|
103
|
+
case COND_Dynamic: return TEXT("COND_Dynamic");
|
|
104
|
+
case COND_Never: return TEXT("COND_Never");
|
|
105
|
+
default: return TEXT("COND_Unknown");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Map ENetMode to a human-readable string. */
|
|
110
|
+
static FString NetModeToString(ENetMode NetMode)
|
|
111
|
+
{
|
|
112
|
+
switch (NetMode)
|
|
113
|
+
{
|
|
114
|
+
case NM_Standalone: return TEXT("NM_Standalone");
|
|
115
|
+
case NM_DedicatedServer: return TEXT("NM_DedicatedServer");
|
|
116
|
+
case NM_ListenServer: return TEXT("NM_ListenServer");
|
|
117
|
+
case NM_Client: return TEXT("NM_Client");
|
|
118
|
+
default: return TEXT("NM_Unknown");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// RegisterNetworkingCommands
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
void RegisterNetworkingCommands(FMCPCommandRouter& Router)
|
|
127
|
+
{
|
|
128
|
+
// -----------------------------------------------------------------------
|
|
129
|
+
// net.replication (NET-01)
|
|
130
|
+
// Returns actor replication settings: bReplicates, bAlwaysRelevant,
|
|
131
|
+
// NetUpdateFrequency, MinNetUpdateFrequency, NetPriority, NetDormancy, etc.
|
|
132
|
+
// Required payload: actor_label (string)
|
|
133
|
+
// -----------------------------------------------------------------------
|
|
134
|
+
Router.RegisterHandler(TEXT("net.replication"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
135
|
+
{
|
|
136
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
137
|
+
|
|
138
|
+
// Extract payload.
|
|
139
|
+
TSharedPtr<FJsonObject> Payload;
|
|
140
|
+
const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
|
|
141
|
+
if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
|
|
142
|
+
{
|
|
143
|
+
Payload = (*PayloadVal)->AsObject();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
FString ActorLabel;
|
|
147
|
+
if (!Payload.IsValid() || !Payload->TryGetStringField(TEXT("actor_label"), ActorLabel) || ActorLabel.IsEmpty())
|
|
148
|
+
{
|
|
149
|
+
SendResponse(BuildNetErrorResponse(CorrId, TEXT("missing_actor_label")) + TEXT("\n"));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Get the editor world.
|
|
154
|
+
if (!GEditor)
|
|
155
|
+
{
|
|
156
|
+
SendResponse(BuildNetErrorResponse(CorrId, TEXT("no_editor")) + TEXT("\n"));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
UWorld* World = GEditor->GetEditorWorldContext().World();
|
|
161
|
+
if (!World)
|
|
162
|
+
{
|
|
163
|
+
SendResponse(BuildNetErrorResponse(CorrId, TEXT("no_world_open")) + TEXT("\n"));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Find actor by label (T-30-01: validate actor exists before property access).
|
|
168
|
+
AActor* TargetActor = nullptr;
|
|
169
|
+
for (TActorIterator<AActor> It(World); It; ++It)
|
|
170
|
+
{
|
|
171
|
+
if ((*It)->GetActorLabel() == ActorLabel)
|
|
172
|
+
{
|
|
173
|
+
TargetActor = *It;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!TargetActor)
|
|
179
|
+
{
|
|
180
|
+
SendResponse(BuildNetErrorResponse(CorrId, TEXT("actor_not_found")) + TEXT("\n"));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Build response with replication settings.
|
|
185
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
186
|
+
Data->SetStringField(TEXT("actor_label"), ActorLabel);
|
|
187
|
+
Data->SetStringField(TEXT("actor_class"), TargetActor->GetClass()->GetName());
|
|
188
|
+
Data->SetBoolField(TEXT("bReplicates"), TargetActor->GetIsReplicated());
|
|
189
|
+
Data->SetBoolField(TEXT("bAlwaysRelevant"), TargetActor->bAlwaysRelevant);
|
|
190
|
+
Data->SetBoolField(TEXT("bNetUseOwnerRelevancy"), TargetActor->bNetUseOwnerRelevancy);
|
|
191
|
+
Data->SetBoolField(TEXT("bReplicateMovement"), TargetActor->IsReplicatingMovement());
|
|
192
|
+
Data->SetBoolField(TEXT("bOnlyRelevantToOwner"), TargetActor->bOnlyRelevantToOwner);
|
|
193
|
+
Data->SetNumberField(TEXT("NetUpdateFrequency"), static_cast<double>(TargetActor->NetUpdateFrequency));
|
|
194
|
+
Data->SetNumberField(TEXT("MinNetUpdateFrequency"), static_cast<double>(TargetActor->MinNetUpdateFrequency));
|
|
195
|
+
Data->SetNumberField(TEXT("NetPriority"), static_cast<double>(TargetActor->NetPriority));
|
|
196
|
+
Data->SetNumberField(TEXT("NetDormancy"), static_cast<double>(static_cast<int32>(TargetActor->NetDormancy)));
|
|
197
|
+
Data->SetStringField(TEXT("NetDormancyName"), NetDormancyToString(TargetActor->NetDormancy));
|
|
198
|
+
|
|
199
|
+
SendResponse(BuildNetSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// -----------------------------------------------------------------------
|
|
203
|
+
// net.properties (NET-02)
|
|
204
|
+
// Returns all replicated properties on an actor with their replication
|
|
205
|
+
// conditions and rep notify status.
|
|
206
|
+
// Required payload: actor_label (string)
|
|
207
|
+
// -----------------------------------------------------------------------
|
|
208
|
+
Router.RegisterHandler(TEXT("net.properties"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
209
|
+
{
|
|
210
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
211
|
+
|
|
212
|
+
// Extract payload.
|
|
213
|
+
TSharedPtr<FJsonObject> Payload;
|
|
214
|
+
const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
|
|
215
|
+
if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
|
|
216
|
+
{
|
|
217
|
+
Payload = (*PayloadVal)->AsObject();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
FString ActorLabel;
|
|
221
|
+
if (!Payload.IsValid() || !Payload->TryGetStringField(TEXT("actor_label"), ActorLabel) || ActorLabel.IsEmpty())
|
|
222
|
+
{
|
|
223
|
+
SendResponse(BuildNetErrorResponse(CorrId, TEXT("missing_actor_label")) + TEXT("\n"));
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Get the editor world.
|
|
228
|
+
if (!GEditor)
|
|
229
|
+
{
|
|
230
|
+
SendResponse(BuildNetErrorResponse(CorrId, TEXT("no_editor")) + TEXT("\n"));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
UWorld* World = GEditor->GetEditorWorldContext().World();
|
|
235
|
+
if (!World)
|
|
236
|
+
{
|
|
237
|
+
SendResponse(BuildNetErrorResponse(CorrId, TEXT("no_world_open")) + TEXT("\n"));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Find actor by label (T-30-01: validate actor exists before property access).
|
|
242
|
+
AActor* TargetActor = nullptr;
|
|
243
|
+
for (TActorIterator<AActor> It(World); It; ++It)
|
|
244
|
+
{
|
|
245
|
+
if ((*It)->GetActorLabel() == ActorLabel)
|
|
246
|
+
{
|
|
247
|
+
TargetActor = *It;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!TargetActor)
|
|
253
|
+
{
|
|
254
|
+
SendResponse(BuildNetErrorResponse(CorrId, TEXT("actor_not_found")) + TEXT("\n"));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Get lifetime replicated props to resolve conditions.
|
|
259
|
+
TArray<FLifetimeProperty> LifetimeProps;
|
|
260
|
+
TargetActor->GetLifetimeReplicatedProps(LifetimeProps);
|
|
261
|
+
|
|
262
|
+
// Build a map from RepIndex -> condition for fast lookup.
|
|
263
|
+
TMap<uint16, ELifetimeCondition> ConditionByRepIndex;
|
|
264
|
+
for (const FLifetimeProperty& LifeProp : LifetimeProps)
|
|
265
|
+
{
|
|
266
|
+
ConditionByRepIndex.Add(LifeProp.RepIndex, LifeProp.Condition);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Iterate properties on the actor's class and its parent classes.
|
|
270
|
+
TArray<TSharedPtr<FJsonValue>> PropsArray;
|
|
271
|
+
UClass* ActorClass = TargetActor->GetClass();
|
|
272
|
+
|
|
273
|
+
for (TFieldIterator<FProperty> PropIt(ActorClass); PropIt; ++PropIt)
|
|
274
|
+
{
|
|
275
|
+
FProperty* Property = *PropIt;
|
|
276
|
+
if (!Property)
|
|
277
|
+
{
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Check if this property is replicated.
|
|
282
|
+
if (!Property->HasAnyPropertyFlags(CPF_Net))
|
|
283
|
+
{
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const FString PropertyName = Property->GetName();
|
|
288
|
+
const FString PropertyType = Property->GetCPPType();
|
|
289
|
+
const bool bHasRepNotify = Property->HasAnyPropertyFlags(CPF_RepNotify);
|
|
290
|
+
|
|
291
|
+
// Resolve condition from lifetime props by RepIndex.
|
|
292
|
+
FString ConditionStr = TEXT("COND_None");
|
|
293
|
+
const ELifetimeCondition* FoundCondition = ConditionByRepIndex.Find(Property->RepIndex);
|
|
294
|
+
if (FoundCondition)
|
|
295
|
+
{
|
|
296
|
+
ConditionStr = LifetimeConditionToString(*FoundCondition);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
TSharedPtr<FJsonObject> PropObj = MakeShared<FJsonObject>();
|
|
300
|
+
PropObj->SetStringField(TEXT("name"), PropertyName);
|
|
301
|
+
PropObj->SetStringField(TEXT("type"), PropertyType);
|
|
302
|
+
PropObj->SetStringField(TEXT("condition"), ConditionStr);
|
|
303
|
+
PropObj->SetBoolField(TEXT("has_rep_notify"), bHasRepNotify);
|
|
304
|
+
|
|
305
|
+
PropsArray.Add(MakeShared<FJsonValueObject>(PropObj));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
309
|
+
Data->SetStringField(TEXT("actor_label"), ActorLabel);
|
|
310
|
+
Data->SetStringField(TEXT("actor_class"), ActorClass->GetName());
|
|
311
|
+
Data->SetNumberField(TEXT("property_count"), static_cast<double>(PropsArray.Num()));
|
|
312
|
+
Data->SetArrayField(TEXT("properties"), PropsArray);
|
|
313
|
+
|
|
314
|
+
SendResponse(BuildNetSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// -----------------------------------------------------------------------
|
|
318
|
+
// net.driver (NET-03)
|
|
319
|
+
// Returns NetDriver configuration and connection information.
|
|
320
|
+
// No required parameters. Returns informative message when no NetDriver
|
|
321
|
+
// is active (editor without PIE or no networked session).
|
|
322
|
+
// -----------------------------------------------------------------------
|
|
323
|
+
Router.RegisterHandler(TEXT("net.driver"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
324
|
+
{
|
|
325
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
326
|
+
|
|
327
|
+
if (!GEditor)
|
|
328
|
+
{
|
|
329
|
+
SendResponse(BuildNetErrorResponse(CorrId, TEXT("no_editor")) + TEXT("\n"));
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
UWorld* World = GEditor->GetEditorWorldContext().World();
|
|
334
|
+
if (!World)
|
|
335
|
+
{
|
|
336
|
+
SendResponse(BuildNetErrorResponse(CorrId, TEXT("no_world_open")) + TEXT("\n"));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
UNetDriver* NetDriver = World->GetNetDriver();
|
|
341
|
+
if (!NetDriver)
|
|
342
|
+
{
|
|
343
|
+
// No NetDriver is active. Return graceful status (not an error — this is expected in editor).
|
|
344
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
345
|
+
Data->SetStringField(TEXT("status"), TEXT("no_net_driver"));
|
|
346
|
+
Data->SetStringField(TEXT("message"), TEXT("No NetDriver is active. Start a PIE session in server or listen-server mode to activate a NetDriver."));
|
|
347
|
+
SendResponse(BuildNetSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Read NetDriver properties.
|
|
352
|
+
const FString DriverName = NetDriver->GetName();
|
|
353
|
+
const ENetMode NetMode = NetDriver->GetNetMode();
|
|
354
|
+
const bool bIsServer = (NetMode == NM_DedicatedServer || NetMode == NM_ListenServer);
|
|
355
|
+
const int32 ConnCount = NetDriver->ClientConnections.Num();
|
|
356
|
+
const int32 MaxChannels = NetDriver->MaxChannelsOverride > 0 ? NetDriver->MaxChannelsOverride : NetDriver->MaxChannels;
|
|
357
|
+
|
|
358
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
359
|
+
Data->SetStringField(TEXT("driver_name"), DriverName);
|
|
360
|
+
Data->SetStringField(TEXT("net_mode"), NetModeToString(NetMode));
|
|
361
|
+
Data->SetBoolField(TEXT("is_server"), bIsServer);
|
|
362
|
+
Data->SetNumberField(TEXT("connection_count"), static_cast<double>(ConnCount));
|
|
363
|
+
Data->SetNumberField(TEXT("max_channels"), static_cast<double>(MaxChannels));
|
|
364
|
+
Data->SetNumberField(TEXT("in_total_bytes"), static_cast<double>(NetDriver->InTotalBytes));
|
|
365
|
+
Data->SetNumberField(TEXT("out_total_bytes"), static_cast<double>(NetDriver->OutTotalBytes));
|
|
366
|
+
|
|
367
|
+
// Build connections array (first 10).
|
|
368
|
+
TArray<TSharedPtr<FJsonValue>> ConnsArray;
|
|
369
|
+
const int32 MaxConns = FMath::Min(ConnCount, 10);
|
|
370
|
+
for (int32 i = 0; i < MaxConns; ++i)
|
|
371
|
+
{
|
|
372
|
+
UNetConnection* Conn = NetDriver->ClientConnections[i];
|
|
373
|
+
if (!Conn)
|
|
374
|
+
{
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
TSharedPtr<FJsonObject> ConnObj = MakeShared<FJsonObject>();
|
|
379
|
+
ConnObj->SetStringField(TEXT("address"), Conn->RemoteAddressToString());
|
|
380
|
+
ConnObj->SetNumberField(TEXT("avg_latency"), static_cast<double>(Conn->AvgLag));
|
|
381
|
+
ConnObj->SetNumberField(TEXT("in_bytes_per_sec"), static_cast<double>(Conn->InBytesPerSecond));
|
|
382
|
+
ConnObj->SetNumberField(TEXT("out_bytes_per_sec"), static_cast<double>(Conn->OutBytesPerSecond));
|
|
383
|
+
ConnsArray.Add(MakeShared<FJsonValueObject>(ConnObj));
|
|
384
|
+
}
|
|
385
|
+
Data->SetArrayField(TEXT("connections"), ConnsArray);
|
|
386
|
+
|
|
387
|
+
SendResponse(BuildNetSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// -----------------------------------------------------------------------
|
|
391
|
+
// net.session (NET-04)
|
|
392
|
+
// Returns online subsystem session information and player list.
|
|
393
|
+
// No required parameters. Returns informative status when no online
|
|
394
|
+
// subsystem is configured or no active session exists.
|
|
395
|
+
// -----------------------------------------------------------------------
|
|
396
|
+
Router.RegisterHandler(TEXT("net.session"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
397
|
+
{
|
|
398
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
399
|
+
|
|
400
|
+
// Try to get the online subsystem.
|
|
401
|
+
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
|
|
402
|
+
if (!OnlineSub)
|
|
403
|
+
{
|
|
404
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
405
|
+
Data->SetStringField(TEXT("status"), TEXT("no_online_subsystem"));
|
|
406
|
+
Data->SetStringField(TEXT("message"), TEXT("No online subsystem is configured. Enable an online subsystem plugin (e.g. OnlineSubsystemNull for LAN testing) to use this command."));
|
|
407
|
+
SendResponse(BuildNetSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const FString SubsystemName = OnlineSub->GetSubsystemName().ToString();
|
|
412
|
+
|
|
413
|
+
// Get session interface.
|
|
414
|
+
IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();
|
|
415
|
+
if (!SessionInterface.IsValid())
|
|
416
|
+
{
|
|
417
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
418
|
+
Data->SetStringField(TEXT("status"), TEXT("no_session_interface"));
|
|
419
|
+
Data->SetStringField(TEXT("subsystem_name"), SubsystemName);
|
|
420
|
+
Data->SetStringField(TEXT("message"), TEXT("Online subsystem is configured but no session interface is available."));
|
|
421
|
+
SendResponse(BuildNetSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Check for a named session (game session).
|
|
426
|
+
FNamedOnlineSession* NamedSession = SessionInterface->GetNamedSession(NAME_GameSession);
|
|
427
|
+
if (!NamedSession)
|
|
428
|
+
{
|
|
429
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
430
|
+
Data->SetStringField(TEXT("status"), TEXT("no_active_session"));
|
|
431
|
+
Data->SetStringField(TEXT("subsystem_name"), SubsystemName);
|
|
432
|
+
Data->SetStringField(TEXT("message"), TEXT("No active game session found. Start or join a session to inspect it."));
|
|
433
|
+
SendResponse(BuildNetSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Read session settings.
|
|
438
|
+
const FOnlineSessionSettings& Settings = NamedSession->SessionSettings;
|
|
439
|
+
const int32 MaxPublic = Settings.NumPublicConnections;
|
|
440
|
+
const int32 MaxPrivate = Settings.NumPrivateConnections;
|
|
441
|
+
const int32 MaxPlayers = MaxPublic + MaxPrivate;
|
|
442
|
+
const int32 NumPlayers = NamedSession->RegisteredPlayers.Num();
|
|
443
|
+
const bool bIsLAN = Settings.bIsLANMatch;
|
|
444
|
+
const bool bIsDedicated = Settings.bIsDedicated;
|
|
445
|
+
|
|
446
|
+
// Map session state to string.
|
|
447
|
+
FString StateStr;
|
|
448
|
+
switch (NamedSession->SessionState)
|
|
449
|
+
{
|
|
450
|
+
case EOnlineSessionState::NoSession: StateStr = TEXT("NoSession"); break;
|
|
451
|
+
case EOnlineSessionState::Creating: StateStr = TEXT("Creating"); break;
|
|
452
|
+
case EOnlineSessionState::Pending: StateStr = TEXT("Pending"); break;
|
|
453
|
+
case EOnlineSessionState::Starting: StateStr = TEXT("Starting"); break;
|
|
454
|
+
case EOnlineSessionState::InProgress: StateStr = TEXT("InProgress"); break;
|
|
455
|
+
case EOnlineSessionState::Ending: StateStr = TEXT("Ending"); break;
|
|
456
|
+
case EOnlineSessionState::Ended: StateStr = TEXT("Ended"); break;
|
|
457
|
+
case EOnlineSessionState::Destroying: StateStr = TEXT("Destroying"); break;
|
|
458
|
+
default: StateStr = TEXT("Unknown"); break;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Build players array.
|
|
462
|
+
TArray<TSharedPtr<FJsonValue>> PlayersArray;
|
|
463
|
+
for (const FUniqueNetIdRef& PlayerId : NamedSession->RegisteredPlayers)
|
|
464
|
+
{
|
|
465
|
+
TSharedPtr<FJsonObject> PlayerObj = MakeShared<FJsonObject>();
|
|
466
|
+
PlayerObj->SetStringField(TEXT("player_id"), PlayerId->ToString());
|
|
467
|
+
PlayersArray.Add(MakeShared<FJsonValueObject>(PlayerObj));
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
471
|
+
Data->SetStringField(TEXT("subsystem_name"), SubsystemName);
|
|
472
|
+
Data->SetStringField(TEXT("session_name"), NAME_GameSession.ToString());
|
|
473
|
+
Data->SetStringField(TEXT("session_state"), StateStr);
|
|
474
|
+
Data->SetNumberField(TEXT("max_players"), static_cast<double>(MaxPlayers));
|
|
475
|
+
Data->SetNumberField(TEXT("current_players"), static_cast<double>(NumPlayers));
|
|
476
|
+
Data->SetBoolField(TEXT("is_lan"), bIsLAN);
|
|
477
|
+
Data->SetBoolField(TEXT("is_dedicated"), bIsDedicated);
|
|
478
|
+
Data->SetArrayField(TEXT("players"), PlayersArray);
|
|
479
|
+
|
|
480
|
+
SendResponse(BuildNetSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
481
|
+
});
|
|
482
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// MCPNetworkingCommands.h
|
|
2
|
+
// Declares the registration function for all networking and replication MCP command handlers.
|
|
3
|
+
// Handlers: net.replication, net.properties, net.driver, net.session
|
|
4
|
+
//
|
|
5
|
+
// Call RegisterNetworkingCommands(*Router) in MCPBridgeSubsystem::Initialize()
|
|
6
|
+
// BEFORE the TCP server starts accepting connections.
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "MCPCommandRouter.h"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Register all four networking command handlers into the given router.
|
|
14
|
+
* Must be called on the game thread before connections arrive.
|
|
15
|
+
*/
|
|
16
|
+
void RegisterNetworkingCommands(FMCPCommandRouter& Router);
|