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,715 @@
|
|
|
1
|
+
// MCPGASCommands.cpp (Plan 25-01)
|
|
2
|
+
// Implements four Gameplay Ability System inspection command handlers for the MCP bridge:
|
|
3
|
+
// gas.abilities -- list all Gameplay Ability classes with tags, costs, and cooldowns (GAS-01)
|
|
4
|
+
// gas.effects -- inspect Gameplay Effect modifiers, duration policy, stacking, period (GAS-02)
|
|
5
|
+
// gas.attributes -- read Attribute Set definitions with base values and clamping info (GAS-03)
|
|
6
|
+
// gas.tags -- query Gameplay Tag hierarchy and find assets using specific tags (GAS-04)
|
|
7
|
+
//
|
|
8
|
+
// All handlers run on the game thread via FMCPCommandRouter::Dispatch.
|
|
9
|
+
// All operations are read-only -- no Modify() calls needed.
|
|
10
|
+
// asset_path is validated to start with "/Game/" or "/Engine/" before any
|
|
11
|
+
// StaticLoadObject call to prevent path traversal (T-25-01).
|
|
12
|
+
// gas.abilities results are capped at 500 to prevent DoS (T-25-02).
|
|
13
|
+
// gas.tags reverse lookup tagged_assets are capped at 200 to prevent DoS (T-25-03).
|
|
14
|
+
|
|
15
|
+
#include "MCPGASCommands.h"
|
|
16
|
+
|
|
17
|
+
// Gameplay Ability System headers
|
|
18
|
+
#include "Abilities/GameplayAbility.h"
|
|
19
|
+
#include "GameplayEffect.h"
|
|
20
|
+
#include "AttributeSet.h"
|
|
21
|
+
#include "GameplayEffectTypes.h"
|
|
22
|
+
|
|
23
|
+
// Gameplay Tags headers
|
|
24
|
+
#include "GameplayTagsManager.h"
|
|
25
|
+
#include "GameplayTagContainer.h"
|
|
26
|
+
|
|
27
|
+
// Asset Registry
|
|
28
|
+
#include "AssetRegistry/AssetRegistryModule.h"
|
|
29
|
+
#include "AssetRegistry/IAssetRegistry.h"
|
|
30
|
+
|
|
31
|
+
// UObject reflection
|
|
32
|
+
#include "UObject/Class.h"
|
|
33
|
+
#include "UObject/UnrealType.h"
|
|
34
|
+
#include "UObject/ObjectMacros.h"
|
|
35
|
+
|
|
36
|
+
// Engine
|
|
37
|
+
#include "Engine/Blueprint.h"
|
|
38
|
+
|
|
39
|
+
// JSON
|
|
40
|
+
#include "Serialization/JsonSerializer.h"
|
|
41
|
+
#include "Serialization/JsonWriter.h"
|
|
42
|
+
#include "Dom/JsonObject.h"
|
|
43
|
+
#include "Dom/JsonValue.h"
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Internal helpers
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
/** Returns a JSON success response string (without trailing newline). */
|
|
50
|
+
static FString BuildGASSuccessResponse(const FString& CorrId, TSharedPtr<FJsonObject> Data)
|
|
51
|
+
{
|
|
52
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
53
|
+
Obj->SetBoolField(TEXT("success"), true);
|
|
54
|
+
Obj->SetStringField(TEXT("correlationId"), CorrId);
|
|
55
|
+
if (Data.IsValid())
|
|
56
|
+
{
|
|
57
|
+
Obj->SetObjectField(TEXT("data"), Data);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
FString Output;
|
|
61
|
+
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
|
|
62
|
+
FJsonSerializer::Serialize(Obj.ToSharedRef(), Writer);
|
|
63
|
+
return Output;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Returns a JSON error response string (without trailing newline). */
|
|
67
|
+
static FString BuildGASErrorResponse(const FString& CorrId, const FString& Error)
|
|
68
|
+
{
|
|
69
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
70
|
+
Obj->SetBoolField(TEXT("success"), false);
|
|
71
|
+
Obj->SetStringField(TEXT("correlationId"), CorrId);
|
|
72
|
+
Obj->SetStringField(TEXT("error"), Error);
|
|
73
|
+
|
|
74
|
+
FString Output;
|
|
75
|
+
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
|
|
76
|
+
FJsonSerializer::Serialize(Obj.ToSharedRef(), Writer);
|
|
77
|
+
return Output;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validate that asset_path starts with "/Game/" or "/Engine/" to prevent
|
|
82
|
+
* path traversal attacks (T-25-01).
|
|
83
|
+
*/
|
|
84
|
+
static bool IsValidAssetPath(const FString& AssetPath)
|
|
85
|
+
{
|
|
86
|
+
return AssetPath.StartsWith(TEXT("/Game/")) || AssetPath.StartsWith(TEXT("/Engine/"));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Convert a FGameplayTagContainer to a JSON array of tag name strings.
|
|
91
|
+
*/
|
|
92
|
+
static TArray<TSharedPtr<FJsonValue>> GameplayTagContainerToJsonArray(const FGameplayTagContainer& Container)
|
|
93
|
+
{
|
|
94
|
+
TArray<TSharedPtr<FJsonValue>> TagArray;
|
|
95
|
+
for (const FGameplayTag& Tag : Container)
|
|
96
|
+
{
|
|
97
|
+
TagArray.Add(MakeShared<FJsonValueString>(Tag.ToString()));
|
|
98
|
+
}
|
|
99
|
+
return TagArray;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Extract the display name of an enum value from a UObject property using reflection.
|
|
104
|
+
* Returns "Unknown" if the property cannot be found or cast.
|
|
105
|
+
*/
|
|
106
|
+
static FString GetEnumPropertyDisplayName(const UObject* Obj, const FString& PropertyName)
|
|
107
|
+
{
|
|
108
|
+
if (!Obj)
|
|
109
|
+
{
|
|
110
|
+
return TEXT("Unknown");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
FProperty* Prop = Obj->GetClass()->FindPropertyByName(FName(*PropertyName));
|
|
114
|
+
if (!Prop)
|
|
115
|
+
{
|
|
116
|
+
return TEXT("Unknown");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Try FEnumProperty (UE5 preferred pattern for typed enums)
|
|
120
|
+
if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Prop))
|
|
121
|
+
{
|
|
122
|
+
const void* ValuePtr = EnumProp->ContainerPtrToValuePtr<void>(Obj);
|
|
123
|
+
int64 EnumValue = EnumProp->GetUnderlyingProperty()->GetSignedIntPropertyValue(ValuePtr);
|
|
124
|
+
UEnum* Enum = EnumProp->GetEnum();
|
|
125
|
+
if (Enum)
|
|
126
|
+
{
|
|
127
|
+
return Enum->GetNameStringByValue(EnumValue);
|
|
128
|
+
}
|
|
129
|
+
return FString::Printf(TEXT("%lld"), EnumValue);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Try FByteProperty (older TEnumAsByte<> pattern)
|
|
133
|
+
if (FByteProperty* ByteProp = CastField<FByteProperty>(Prop))
|
|
134
|
+
{
|
|
135
|
+
const void* ValuePtr = ByteProp->ContainerPtrToValuePtr<void>(Obj);
|
|
136
|
+
uint8 EnumValue = ByteProp->GetPropertyValue(ValuePtr);
|
|
137
|
+
UEnum* Enum = ByteProp->Enum;
|
|
138
|
+
if (Enum)
|
|
139
|
+
{
|
|
140
|
+
return Enum->GetNameStringByValue((int64)EnumValue);
|
|
141
|
+
}
|
|
142
|
+
return FString::Printf(TEXT("%d"), (int32)EnumValue);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return TEXT("Unknown");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// gas.abilities handler (GAS-01)
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
static void HandleGasAbilities(TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
153
|
+
{
|
|
154
|
+
FString CorrId;
|
|
155
|
+
Cmd->TryGetStringField(TEXT("correlationId"), CorrId);
|
|
156
|
+
|
|
157
|
+
// Optional class_filter payload field
|
|
158
|
+
FString ClassFilter;
|
|
159
|
+
const TSharedPtr<FJsonObject>* PayloadPtr = nullptr;
|
|
160
|
+
if (Cmd->TryGetObjectField(TEXT("payload"), PayloadPtr) && PayloadPtr && PayloadPtr->IsValid())
|
|
161
|
+
{
|
|
162
|
+
(*PayloadPtr)->TryGetStringField(TEXT("class_filter"), ClassFilter);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Discover all UGameplayAbility subclass assets via the asset registry
|
|
166
|
+
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
|
|
167
|
+
|
|
168
|
+
// Ensure registry is up to date
|
|
169
|
+
AssetRegistry.SearchAllAssets(/*bSynchronousSearch=*/false);
|
|
170
|
+
|
|
171
|
+
FTopLevelAssetPath AbilityClassPath(UGameplayAbility::StaticClass()->GetPathName());
|
|
172
|
+
TArray<FAssetData> AssetList;
|
|
173
|
+
AssetRegistry.GetAssetsByClass(AbilityClassPath, AssetList, /*bSearchSubClasses=*/true);
|
|
174
|
+
|
|
175
|
+
// Cap results at 500 (T-25-02)
|
|
176
|
+
constexpr int32 MaxAbilities = 500;
|
|
177
|
+
bool bCapped = AssetList.Num() > MaxAbilities;
|
|
178
|
+
if (bCapped)
|
|
179
|
+
{
|
|
180
|
+
AssetList.SetNum(MaxAbilities);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
TArray<TSharedPtr<FJsonValue>> AbilitiesArray;
|
|
184
|
+
for (const FAssetData& AssetData : AssetList)
|
|
185
|
+
{
|
|
186
|
+
// Apply optional class filter on asset name
|
|
187
|
+
if (!ClassFilter.IsEmpty() && !AssetData.AssetName.ToString().Contains(ClassFilter))
|
|
188
|
+
{
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Validate path prefix (T-25-01) -- skip assets outside /Game/ or /Engine/
|
|
193
|
+
FString AssetPath = AssetData.GetObjectPathString();
|
|
194
|
+
if (!IsValidAssetPath(AssetPath))
|
|
195
|
+
{
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Load the ability CDO
|
|
200
|
+
UGameplayAbility* AbilityCDO = Cast<UGameplayAbility>(
|
|
201
|
+
StaticLoadObject(UGameplayAbility::StaticClass(), nullptr, *AssetPath)
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// For Blueprint assets, try loading via Blueprint->GeneratedClass
|
|
205
|
+
if (!AbilityCDO)
|
|
206
|
+
{
|
|
207
|
+
UBlueprint* BP = Cast<UBlueprint>(
|
|
208
|
+
StaticLoadObject(UBlueprint::StaticClass(), nullptr, *AssetPath)
|
|
209
|
+
);
|
|
210
|
+
if (BP && BP->GeneratedClass)
|
|
211
|
+
{
|
|
212
|
+
AbilityCDO = Cast<UGameplayAbility>(BP->GeneratedClass->GetDefaultObject());
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!AbilityCDO)
|
|
217
|
+
{
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
TSharedPtr<FJsonObject> AbilityObj = MakeShared<FJsonObject>();
|
|
222
|
+
AbilityObj->SetStringField(TEXT("class_name"), AbilityCDO->GetClass()->GetName());
|
|
223
|
+
AbilityObj->SetStringField(TEXT("asset_path"), AssetPath);
|
|
224
|
+
|
|
225
|
+
// Ability tags
|
|
226
|
+
AbilityObj->SetArrayField(TEXT("ability_tags"),
|
|
227
|
+
GameplayTagContainerToJsonArray(AbilityCDO->AbilityTags));
|
|
228
|
+
|
|
229
|
+
// Cancel / block tags
|
|
230
|
+
AbilityObj->SetArrayField(TEXT("cancel_abilities_with_tag"),
|
|
231
|
+
GameplayTagContainerToJsonArray(AbilityCDO->CancelAbilitiesWithTag));
|
|
232
|
+
AbilityObj->SetArrayField(TEXT("block_abilities_with_tag"),
|
|
233
|
+
GameplayTagContainerToJsonArray(AbilityCDO->BlockAbilitiesWithTag));
|
|
234
|
+
|
|
235
|
+
// Cost and cooldown GE class references
|
|
236
|
+
FString CostGEName = AbilityCDO->CostGameplayEffectClass
|
|
237
|
+
? AbilityCDO->CostGameplayEffectClass->GetName()
|
|
238
|
+
: TEXT("None");
|
|
239
|
+
FString CooldownGEName = AbilityCDO->CooldownGameplayEffectClass
|
|
240
|
+
? AbilityCDO->CooldownGameplayEffectClass->GetName()
|
|
241
|
+
: TEXT("None");
|
|
242
|
+
AbilityObj->SetStringField(TEXT("cost_gameplay_effect_class"), CostGEName);
|
|
243
|
+
AbilityObj->SetStringField(TEXT("cooldown_gameplay_effect_class"), CooldownGEName);
|
|
244
|
+
|
|
245
|
+
// Instancing policy via reflection
|
|
246
|
+
FString InstancingPolicy = GetEnumPropertyDisplayName(AbilityCDO, TEXT("InstancingPolicy"));
|
|
247
|
+
AbilityObj->SetStringField(TEXT("instancing_policy"), InstancingPolicy);
|
|
248
|
+
|
|
249
|
+
AbilitiesArray.Add(MakeShared<FJsonValueObject>(AbilityObj));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
253
|
+
Data->SetArrayField(TEXT("abilities"), AbilitiesArray);
|
|
254
|
+
if (bCapped)
|
|
255
|
+
{
|
|
256
|
+
Data->SetBoolField(TEXT("capped"), true);
|
|
257
|
+
Data->SetNumberField(TEXT("cap_limit"), MaxAbilities);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
SendResponse(BuildGASSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
// gas.effects handler (GAS-02)
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
static void HandleGasEffects(TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
268
|
+
{
|
|
269
|
+
FString CorrId;
|
|
270
|
+
Cmd->TryGetStringField(TEXT("correlationId"), CorrId);
|
|
271
|
+
|
|
272
|
+
FString AssetPath;
|
|
273
|
+
const TSharedPtr<FJsonObject>* PayloadPtr = nullptr;
|
|
274
|
+
if (Cmd->TryGetObjectField(TEXT("payload"), PayloadPtr) && PayloadPtr && PayloadPtr->IsValid())
|
|
275
|
+
{
|
|
276
|
+
(*PayloadPtr)->TryGetStringField(TEXT("asset_path"), AssetPath);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (AssetPath.IsEmpty())
|
|
280
|
+
{
|
|
281
|
+
SendResponse(BuildGASErrorResponse(CorrId, TEXT("payload.asset_path is required")) + TEXT("\n"));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Validate path (T-25-01)
|
|
286
|
+
if (!IsValidAssetPath(AssetPath))
|
|
287
|
+
{
|
|
288
|
+
SendResponse(BuildGASErrorResponse(CorrId,
|
|
289
|
+
TEXT("asset_path must start with /Game/ or /Engine/")) + TEXT("\n"));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Load as UGameplayEffect CDO
|
|
294
|
+
UGameplayEffect* GEObj = Cast<UGameplayEffect>(
|
|
295
|
+
StaticLoadObject(UGameplayEffect::StaticClass(), nullptr, *AssetPath)
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
// For Blueprint-based GEs
|
|
299
|
+
if (!GEObj)
|
|
300
|
+
{
|
|
301
|
+
UBlueprint* BP = Cast<UBlueprint>(
|
|
302
|
+
StaticLoadObject(UBlueprint::StaticClass(), nullptr, *AssetPath)
|
|
303
|
+
);
|
|
304
|
+
if (BP && BP->GeneratedClass)
|
|
305
|
+
{
|
|
306
|
+
GEObj = Cast<UGameplayEffect>(BP->GeneratedClass->GetDefaultObject());
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!GEObj)
|
|
311
|
+
{
|
|
312
|
+
SendResponse(BuildGASErrorResponse(CorrId,
|
|
313
|
+
FString::Printf(TEXT("Failed to load UGameplayEffect at: %s"), *AssetPath)) + TEXT("\n"));
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
318
|
+
Data->SetStringField(TEXT("asset_path"), AssetPath);
|
|
319
|
+
|
|
320
|
+
// Duration policy via reflection
|
|
321
|
+
FString DurationPolicy = GetEnumPropertyDisplayName(GEObj, TEXT("DurationPolicy"));
|
|
322
|
+
Data->SetStringField(TEXT("duration_policy"), DurationPolicy);
|
|
323
|
+
|
|
324
|
+
// Modifiers array
|
|
325
|
+
TArray<TSharedPtr<FJsonValue>> ModifiersArray;
|
|
326
|
+
for (const FGameplayModifierInfo& ModInfo : GEObj->Modifiers)
|
|
327
|
+
{
|
|
328
|
+
TSharedPtr<FJsonObject> ModObj = MakeShared<FJsonObject>();
|
|
329
|
+
|
|
330
|
+
// Attribute name and owning set
|
|
331
|
+
ModObj->SetStringField(TEXT("attribute"), ModInfo.Attribute.GetName());
|
|
332
|
+
FString AttrSetName = ModInfo.Attribute.GetAttributeSetClass()
|
|
333
|
+
? ModInfo.Attribute.GetAttributeSetClass()->GetName()
|
|
334
|
+
: TEXT("None");
|
|
335
|
+
ModObj->SetStringField(TEXT("attribute_set"), AttrSetName);
|
|
336
|
+
|
|
337
|
+
// Modifier operation via reflection on FGameplayModifierInfo
|
|
338
|
+
// ModifierOp is an EGameplayModOp::Type inside FGameplayModifierInfo
|
|
339
|
+
// We reflect on the struct property directly
|
|
340
|
+
FString ModOpStr = TEXT("Unknown");
|
|
341
|
+
{
|
|
342
|
+
UScriptStruct* ModInfoStruct = FGameplayModifierInfo::StaticStruct();
|
|
343
|
+
if (ModInfoStruct)
|
|
344
|
+
{
|
|
345
|
+
FProperty* ModOpProp = ModInfoStruct->FindPropertyByName(TEXT("ModifierOp"));
|
|
346
|
+
if (FByteProperty* ByteProp = CastField<FByteProperty>(ModOpProp))
|
|
347
|
+
{
|
|
348
|
+
const void* ValuePtr = ByteProp->ContainerPtrToValuePtr<void>(&ModInfo);
|
|
349
|
+
uint8 EnumVal = ByteProp->GetPropertyValue(ValuePtr);
|
|
350
|
+
UEnum* Enum = ByteProp->Enum;
|
|
351
|
+
if (Enum)
|
|
352
|
+
{
|
|
353
|
+
ModOpStr = Enum->GetNameStringByValue((int64)EnumVal);
|
|
354
|
+
}
|
|
355
|
+
else
|
|
356
|
+
{
|
|
357
|
+
ModOpStr = FString::Printf(TEXT("%d"), (int32)EnumVal);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
else if (FEnumProperty* EnumProp = CastField<FEnumProperty>(ModOpProp))
|
|
361
|
+
{
|
|
362
|
+
const void* ValuePtr = EnumProp->ContainerPtrToValuePtr<void>(&ModInfo);
|
|
363
|
+
int64 EnumVal = EnumProp->GetUnderlyingProperty()->GetSignedIntPropertyValue(ValuePtr);
|
|
364
|
+
UEnum* Enum = EnumProp->GetEnum();
|
|
365
|
+
if (Enum)
|
|
366
|
+
{
|
|
367
|
+
ModOpStr = Enum->GetNameStringByValue(EnumVal);
|
|
368
|
+
}
|
|
369
|
+
else
|
|
370
|
+
{
|
|
371
|
+
ModOpStr = FString::Printf(TEXT("%lld"), EnumVal);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
ModObj->SetStringField(TEXT("modifier_op"), ModOpStr);
|
|
377
|
+
|
|
378
|
+
// Magnitude value
|
|
379
|
+
FString MagnitudeDesc;
|
|
380
|
+
{
|
|
381
|
+
const FGameplayEffectModifierMagnitude& Mag = ModInfo.ModifierMagnitude;
|
|
382
|
+
EGameplayEffectMagnitudeCalculation MagType = Mag.GetMagnitudeCalculationType();
|
|
383
|
+
switch (MagType)
|
|
384
|
+
{
|
|
385
|
+
case EGameplayEffectMagnitudeCalculation::ScalableFloat:
|
|
386
|
+
{
|
|
387
|
+
float ScalarValue = 0.0f;
|
|
388
|
+
Mag.GetStaticMagnitudeIfPossible(1.0f, ScalarValue);
|
|
389
|
+
MagnitudeDesc = FString::Printf(TEXT("%f"), ScalarValue);
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
case EGameplayEffectMagnitudeCalculation::AttributeBased:
|
|
393
|
+
MagnitudeDesc = TEXT("AttributeBased");
|
|
394
|
+
break;
|
|
395
|
+
case EGameplayEffectMagnitudeCalculation::CustomCalculationClass:
|
|
396
|
+
MagnitudeDesc = TEXT("Custom");
|
|
397
|
+
break;
|
|
398
|
+
case EGameplayEffectMagnitudeCalculation::SetByCaller:
|
|
399
|
+
MagnitudeDesc = TEXT("SetByCaller");
|
|
400
|
+
break;
|
|
401
|
+
default:
|
|
402
|
+
MagnitudeDesc = TEXT("Calculated");
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
ModObj->SetStringField(TEXT("magnitude_value"), MagnitudeDesc);
|
|
407
|
+
|
|
408
|
+
ModifiersArray.Add(MakeShared<FJsonValueObject>(ModObj));
|
|
409
|
+
}
|
|
410
|
+
Data->SetArrayField(TEXT("modifiers"), ModifiersArray);
|
|
411
|
+
|
|
412
|
+
// Stacking type and limit via reflection
|
|
413
|
+
FString StackingType = GetEnumPropertyDisplayName(GEObj, TEXT("StackingType"));
|
|
414
|
+
Data->SetStringField(TEXT("stacking_type"), StackingType);
|
|
415
|
+
Data->SetNumberField(TEXT("stack_limit_count"), (double)GEObj->StackLimitCount);
|
|
416
|
+
|
|
417
|
+
// Period interval (period is a FScalableFloat, extract base value)
|
|
418
|
+
float PeriodValue = 0.0f;
|
|
419
|
+
GEObj->Period.GetStaticValue(PeriodValue);
|
|
420
|
+
Data->SetNumberField(TEXT("period_interval"), (double)PeriodValue);
|
|
421
|
+
|
|
422
|
+
// Gameplay Cue tags
|
|
423
|
+
Data->SetArrayField(TEXT("gameplay_cue_tags"),
|
|
424
|
+
GameplayTagContainerToJsonArray(GEObj->GameplayCueTags));
|
|
425
|
+
|
|
426
|
+
SendResponse(BuildGASSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
430
|
+
// gas.attributes handler (GAS-03)
|
|
431
|
+
// ---------------------------------------------------------------------------
|
|
432
|
+
|
|
433
|
+
static void HandleGasAttributes(TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
434
|
+
{
|
|
435
|
+
FString CorrId;
|
|
436
|
+
Cmd->TryGetStringField(TEXT("correlationId"), CorrId);
|
|
437
|
+
|
|
438
|
+
FString AssetPath;
|
|
439
|
+
const TSharedPtr<FJsonObject>* PayloadPtr = nullptr;
|
|
440
|
+
if (Cmd->TryGetObjectField(TEXT("payload"), PayloadPtr) && PayloadPtr && PayloadPtr->IsValid())
|
|
441
|
+
{
|
|
442
|
+
(*PayloadPtr)->TryGetStringField(TEXT("asset_path"), AssetPath);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (AssetPath.IsEmpty())
|
|
446
|
+
{
|
|
447
|
+
SendResponse(BuildGASErrorResponse(CorrId, TEXT("payload.asset_path is required")) + TEXT("\n"));
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Validate path (T-25-01)
|
|
452
|
+
if (!IsValidAssetPath(AssetPath))
|
|
453
|
+
{
|
|
454
|
+
SendResponse(BuildGASErrorResponse(CorrId,
|
|
455
|
+
TEXT("asset_path must start with /Game/ or /Engine/")) + TEXT("\n"));
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Attempt to load as AttributeSet -- try Blueprint first, then direct CDO
|
|
460
|
+
UClass* AttrSetClass = nullptr;
|
|
461
|
+
UAttributeSet* AttrSetCDO = nullptr;
|
|
462
|
+
|
|
463
|
+
// Try Blueprint path
|
|
464
|
+
UBlueprint* BP = Cast<UBlueprint>(
|
|
465
|
+
StaticLoadObject(UBlueprint::StaticClass(), nullptr, *AssetPath)
|
|
466
|
+
);
|
|
467
|
+
if (BP && BP->GeneratedClass && BP->GeneratedClass->IsChildOf(UAttributeSet::StaticClass()))
|
|
468
|
+
{
|
|
469
|
+
AttrSetClass = BP->GeneratedClass;
|
|
470
|
+
AttrSetCDO = Cast<UAttributeSet>(AttrSetClass->GetDefaultObject());
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Try loading directly as a native class CDO
|
|
474
|
+
if (!AttrSetCDO)
|
|
475
|
+
{
|
|
476
|
+
AttrSetCDO = Cast<UAttributeSet>(
|
|
477
|
+
StaticLoadObject(UAttributeSet::StaticClass(), nullptr, *AssetPath)
|
|
478
|
+
);
|
|
479
|
+
if (AttrSetCDO)
|
|
480
|
+
{
|
|
481
|
+
AttrSetClass = AttrSetCDO->GetClass();
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Try StaticLoadClass for native classes
|
|
486
|
+
if (!AttrSetCDO)
|
|
487
|
+
{
|
|
488
|
+
AttrSetClass = StaticLoadClass(UAttributeSet::StaticClass(), nullptr, *AssetPath);
|
|
489
|
+
if (AttrSetClass)
|
|
490
|
+
{
|
|
491
|
+
AttrSetCDO = Cast<UAttributeSet>(AttrSetClass->GetDefaultObject());
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (!AttrSetCDO || !AttrSetClass)
|
|
496
|
+
{
|
|
497
|
+
SendResponse(BuildGASErrorResponse(CorrId,
|
|
498
|
+
FString::Printf(TEXT("Failed to load UAttributeSet at: %s"), *AssetPath)) + TEXT("\n"));
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
503
|
+
Data->SetStringField(TEXT("class_name"), AttrSetClass->GetName());
|
|
504
|
+
|
|
505
|
+
// Iterate numeric properties (float attributes on attribute sets)
|
|
506
|
+
TArray<TSharedPtr<FJsonValue>> AttributesArray;
|
|
507
|
+
for (TFieldIterator<FNumericProperty> PropIt(AttrSetClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt)
|
|
508
|
+
{
|
|
509
|
+
FNumericProperty* NumProp = *PropIt;
|
|
510
|
+
if (!NumProp)
|
|
511
|
+
{
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Only include replicated or BlueprintReadOnly properties (typical for GAS attributes)
|
|
516
|
+
// We include all numeric properties on AttributeSet subclasses as they are attributes
|
|
517
|
+
TSharedPtr<FJsonObject> AttrObj = MakeShared<FJsonObject>();
|
|
518
|
+
AttrObj->SetStringField(TEXT("attribute_name"), NumProp->GetName());
|
|
519
|
+
|
|
520
|
+
// Read base value from CDO
|
|
521
|
+
double BaseValue = 0.0;
|
|
522
|
+
if (NumProp->IsFloatingPoint())
|
|
523
|
+
{
|
|
524
|
+
const void* ValuePtr = NumProp->ContainerPtrToValuePtr<void>(AttrSetCDO);
|
|
525
|
+
BaseValue = NumProp->GetFloatingPointPropertyValue(ValuePtr);
|
|
526
|
+
}
|
|
527
|
+
else if (NumProp->IsInteger())
|
|
528
|
+
{
|
|
529
|
+
const void* ValuePtr = NumProp->ContainerPtrToValuePtr<void>(AttrSetCDO);
|
|
530
|
+
BaseValue = (double)NumProp->GetSignedIntPropertyValue(ValuePtr);
|
|
531
|
+
}
|
|
532
|
+
AttrObj->SetNumberField(TEXT("base_value"), BaseValue);
|
|
533
|
+
|
|
534
|
+
// Replication check
|
|
535
|
+
bool bReplicated = NumProp->HasAnyPropertyFlags(CPF_Net);
|
|
536
|
+
AttrObj->SetBoolField(TEXT("replicated"), bReplicated);
|
|
537
|
+
|
|
538
|
+
// Clamping note -- exact clamp values require inspecting PreAttributeBaseChange
|
|
539
|
+
// override implementation which is not reliably extractable at CDO level
|
|
540
|
+
bool bHasClamping = AttrSetClass->IsFunctionImplementedInScript(TEXT("PreAttributeBaseChange")) ||
|
|
541
|
+
AttrSetClass->IsFunctionImplementedInScript(TEXT("PostAttributeChange"));
|
|
542
|
+
AttrObj->SetBoolField(TEXT("has_clamping"), bHasClamping);
|
|
543
|
+
if (bHasClamping)
|
|
544
|
+
{
|
|
545
|
+
AttrObj->SetStringField(TEXT("clamp_note"), TEXT("check_implementation"));
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
AttributesArray.Add(MakeShared<FJsonValueObject>(AttrObj));
|
|
549
|
+
}
|
|
550
|
+
Data->SetArrayField(TEXT("attributes"), AttributesArray);
|
|
551
|
+
|
|
552
|
+
SendResponse(BuildGASSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ---------------------------------------------------------------------------
|
|
556
|
+
// gas.tags handler (GAS-04)
|
|
557
|
+
// ---------------------------------------------------------------------------
|
|
558
|
+
|
|
559
|
+
static void HandleGasTags(TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
560
|
+
{
|
|
561
|
+
FString CorrId;
|
|
562
|
+
Cmd->TryGetStringField(TEXT("correlationId"), CorrId);
|
|
563
|
+
|
|
564
|
+
FString TagFilter;
|
|
565
|
+
FString FindAssetsWithTag;
|
|
566
|
+
const TSharedPtr<FJsonObject>* PayloadPtr = nullptr;
|
|
567
|
+
if (Cmd->TryGetObjectField(TEXT("payload"), PayloadPtr) && PayloadPtr && PayloadPtr->IsValid())
|
|
568
|
+
{
|
|
569
|
+
(*PayloadPtr)->TryGetStringField(TEXT("tag_filter"), TagFilter);
|
|
570
|
+
(*PayloadPtr)->TryGetStringField(TEXT("find_assets_with_tag"), FindAssetsWithTag);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
UGameplayTagsManager& TagsManager = UGameplayTagsManager::Get();
|
|
574
|
+
|
|
575
|
+
// Gather all registered gameplay tags
|
|
576
|
+
FGameplayTagContainer AllTagsContainer;
|
|
577
|
+
TagsManager.RequestAllGameplayTags(AllTagsContainer, /*bOnlyIncludeDictionaryTags=*/false);
|
|
578
|
+
|
|
579
|
+
TArray<TSharedPtr<FJsonValue>> TagsArray;
|
|
580
|
+
for (const FGameplayTag& Tag : AllTagsContainer)
|
|
581
|
+
{
|
|
582
|
+
FString TagName = Tag.ToString();
|
|
583
|
+
|
|
584
|
+
// Apply filter if provided
|
|
585
|
+
if (!TagFilter.IsEmpty() && !TagName.StartsWith(TagFilter) && !TagName.Contains(TagFilter))
|
|
586
|
+
{
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
TagsArray.Add(MakeShared<FJsonValueString>(TagName));
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
594
|
+
Data->SetArrayField(TEXT("tags"), TagsArray);
|
|
595
|
+
Data->SetNumberField(TEXT("total_registered"), (double)AllTagsContainer.Num());
|
|
596
|
+
if (!TagFilter.IsEmpty())
|
|
597
|
+
{
|
|
598
|
+
Data->SetStringField(TEXT("tag_filter"), TagFilter);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Optional reverse lookup: find assets that reference a given tag (T-25-03: cap at 200)
|
|
602
|
+
if (!FindAssetsWithTag.IsEmpty())
|
|
603
|
+
{
|
|
604
|
+
// Validate the tag exists
|
|
605
|
+
FGameplayTag LookupTag = TagsManager.RequestGameplayTag(FName(*FindAssetsWithTag), /*ErrorIfNotFound=*/false);
|
|
606
|
+
|
|
607
|
+
TArray<TSharedPtr<FJsonValue>> TaggedAssetsArray;
|
|
608
|
+
|
|
609
|
+
if (LookupTag.IsValid())
|
|
610
|
+
{
|
|
611
|
+
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
|
|
612
|
+
|
|
613
|
+
// Search /Game/ for assets
|
|
614
|
+
TArray<FAssetData> AllAssets;
|
|
615
|
+
AssetRegistry.GetAllAssets(AllAssets, /*bSkipARFilteredAssets=*/false);
|
|
616
|
+
|
|
617
|
+
constexpr int32 MaxTaggedAssets = 200;
|
|
618
|
+
int32 Found = 0;
|
|
619
|
+
bool bTaggedCapped = false;
|
|
620
|
+
|
|
621
|
+
for (const FAssetData& AssetData : AllAssets)
|
|
622
|
+
{
|
|
623
|
+
if (Found >= MaxTaggedAssets)
|
|
624
|
+
{
|
|
625
|
+
bTaggedCapped = true;
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
FString AssetPath = AssetData.GetObjectPathString();
|
|
630
|
+
if (!IsValidAssetPath(AssetPath))
|
|
631
|
+
{
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Check if this asset has the tag in its tags metadata
|
|
636
|
+
// FAssetData::TagsAndValues contains gameplay-relevant tags
|
|
637
|
+
bool bHasTag = false;
|
|
638
|
+
FAssetDataTagMapSharedView::FFindTagResult TagResult =
|
|
639
|
+
AssetData.TagsAndValues.FindTag(TEXT("GameplayTags"));
|
|
640
|
+
if (TagResult.IsSet())
|
|
641
|
+
{
|
|
642
|
+
bHasTag = TagResult.GetValue().Contains(FindAssetsWithTag);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (!bHasTag)
|
|
646
|
+
{
|
|
647
|
+
// Also check AssetBundleData tag
|
|
648
|
+
FAssetDataTagMapSharedView::FFindTagResult BundleResult =
|
|
649
|
+
AssetData.TagsAndValues.FindTag(TEXT("AssetBundleData"));
|
|
650
|
+
if (BundleResult.IsSet())
|
|
651
|
+
{
|
|
652
|
+
bHasTag = BundleResult.GetValue().Contains(FindAssetsWithTag);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (bHasTag)
|
|
657
|
+
{
|
|
658
|
+
TaggedAssetsArray.Add(MakeShared<FJsonValueString>(AssetPath));
|
|
659
|
+
++Found;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (bTaggedCapped)
|
|
664
|
+
{
|
|
665
|
+
Data->SetBoolField(TEXT("tagged_assets_capped"), true);
|
|
666
|
+
Data->SetNumberField(TEXT("tagged_assets_cap_limit"), MaxTaggedAssets);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
else
|
|
670
|
+
{
|
|
671
|
+
UE_LOG(LogTemp, Warning, TEXT("[MCPGASCommands] gas.tags: tag '%s' not found in registry"),
|
|
672
|
+
*FindAssetsWithTag);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
Data->SetArrayField(TEXT("tagged_assets"), TaggedAssetsArray);
|
|
676
|
+
Data->SetStringField(TEXT("find_assets_with_tag"), FindAssetsWithTag);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
SendResponse(BuildGASSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// ---------------------------------------------------------------------------
|
|
683
|
+
// RegisterGASCommands -- wire all four handlers into the router
|
|
684
|
+
// ---------------------------------------------------------------------------
|
|
685
|
+
|
|
686
|
+
void RegisterGASCommands(FMCPCommandRouter& Router)
|
|
687
|
+
{
|
|
688
|
+
// gas.abilities -- list all Gameplay Ability classes with tags, costs, and cooldowns (GAS-01)
|
|
689
|
+
Router.RegisterHandler(TEXT("gas.abilities"),
|
|
690
|
+
[](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
691
|
+
{
|
|
692
|
+
HandleGasAbilities(Cmd, SendResponse);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// gas.effects -- inspect Gameplay Effect modifiers, duration policy, stacking, period (GAS-02)
|
|
696
|
+
Router.RegisterHandler(TEXT("gas.effects"),
|
|
697
|
+
[](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
698
|
+
{
|
|
699
|
+
HandleGasEffects(Cmd, SendResponse);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
// gas.attributes -- read Attribute Set definitions with base values and clamping info (GAS-03)
|
|
703
|
+
Router.RegisterHandler(TEXT("gas.attributes"),
|
|
704
|
+
[](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
705
|
+
{
|
|
706
|
+
HandleGasAttributes(Cmd, SendResponse);
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// gas.tags -- query Gameplay Tag hierarchy and find assets using specific tags (GAS-04)
|
|
710
|
+
Router.RegisterHandler(TEXT("gas.tags"),
|
|
711
|
+
[](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
712
|
+
{
|
|
713
|
+
HandleGasTags(Cmd, SendResponse);
|
|
714
|
+
});
|
|
715
|
+
}
|