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,597 +1,597 @@
|
|
|
1
|
-
#include "GasHandlers.h"
|
|
2
|
-
#include "UE_MCP_BridgeModule.h"
|
|
3
|
-
#include "HandlerRegistry.h"
|
|
4
|
-
#include "HandlerUtils.h"
|
|
5
|
-
#include "HandlerAssetCreate.h"
|
|
6
|
-
#include "Kismet2/KismetEditorUtilities.h"
|
|
7
|
-
#include "Engine/Blueprint.h"
|
|
8
|
-
#include "AssetToolsModule.h"
|
|
9
|
-
#include "IAssetTools.h"
|
|
10
|
-
#include "Factories/BlueprintFactory.h"
|
|
11
|
-
#include "UObject/UObjectGlobals.h"
|
|
12
|
-
#include "UObject/Package.h"
|
|
13
|
-
#include "Misc/PackageName.h"
|
|
14
|
-
#include "UObject/SavePackage.h"
|
|
15
|
-
#include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
|
|
16
|
-
#include "Dom/JsonObject.h"
|
|
17
|
-
#include "Dom/JsonValue.h"
|
|
18
|
-
#include "GameplayTagContainer.h"
|
|
19
|
-
#include "GameplayTagsManager.h"
|
|
20
|
-
#include "Kismet2/BlueprintEditorUtils.h"
|
|
21
|
-
#include "Engine/SimpleConstructionScript.h"
|
|
22
|
-
#include "Engine/SCS_Node.h"
|
|
23
|
-
#include "EdGraphSchema_K2.h"
|
|
24
|
-
#include "AbilitySystemComponent.h"
|
|
25
|
-
#include "AttributeSet.h"
|
|
26
|
-
#include "Engine/DataTable.h"
|
|
27
|
-
#include "GameplayEffect.h"
|
|
28
|
-
#include "GameplayEffectTypes.h"
|
|
29
|
-
#include "ScalableFloat.h"
|
|
30
|
-
#include "GameplayTagsManager.h"
|
|
31
|
-
|
|
32
|
-
// Resolve a FGameplayAttribute by name across every loaded AttributeSet
|
|
33
|
-
// subclass. Accepts a bare property name ("Health") or a qualified
|
|
34
|
-
// "SetClassSubstring.Attribute". Returns an invalid attribute on miss.
|
|
35
|
-
static FGameplayAttribute FindGameplayAttributeByName(const FString& Name)
|
|
36
|
-
{
|
|
37
|
-
FString SetFilter, AttrName = Name;
|
|
38
|
-
if (Name.Contains(TEXT(".")))
|
|
39
|
-
{
|
|
40
|
-
Name.Split(TEXT("."), &SetFilter, &AttrName);
|
|
41
|
-
}
|
|
42
|
-
for (TObjectIterator<UClass> It; It; ++It)
|
|
43
|
-
{
|
|
44
|
-
UClass* C = *It;
|
|
45
|
-
if (C == UAttributeSet::StaticClass() || !C->IsChildOf(UAttributeSet::StaticClass())) continue;
|
|
46
|
-
if (!SetFilter.IsEmpty() && !C->GetName().Contains(SetFilter)) continue;
|
|
47
|
-
for (TFieldIterator<FProperty> P(C); P; ++P)
|
|
48
|
-
{
|
|
49
|
-
FStructProperty* SP = CastField<FStructProperty>(*P);
|
|
50
|
-
if (SP && SP->Struct == FGameplayAttributeData::StaticStruct() && SP->GetName() == AttrName)
|
|
51
|
-
{
|
|
52
|
-
return FGameplayAttribute(SP);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return FGameplayAttribute();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
void FGasHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
60
|
-
{
|
|
61
|
-
Registry.RegisterHandler(TEXT("create_gameplay_effect"), &CreateGameplayEffect);
|
|
62
|
-
Registry.RegisterHandler(TEXT("get_gas_info"), &GetGasInfo);
|
|
63
|
-
Registry.RegisterHandler(TEXT("create_gameplay_ability"), &CreateGameplayAbility);
|
|
64
|
-
Registry.RegisterHandler(TEXT("create_attribute_set"), &CreateAttributeSet);
|
|
65
|
-
Registry.RegisterHandler(TEXT("create_gameplay_cue"), &CreateGameplayCue);
|
|
66
|
-
Registry.RegisterHandler(TEXT("add_ability_system_component"), &AddAbilitySystemComponent);
|
|
67
|
-
Registry.RegisterHandler(TEXT("add_attribute"), &AddAttribute);
|
|
68
|
-
Registry.RegisterHandler(TEXT("set_ability_tags"), &SetAbilityTags);
|
|
69
|
-
Registry.RegisterHandler(TEXT("set_effect_modifier"), &SetEffectModifier);
|
|
70
|
-
Registry.RegisterHandler(TEXT("set_asc_defaults"), &SetAscDefaults);
|
|
71
|
-
Registry.RegisterHandler(TEXT("apply_effect"), &ApplyEffect);
|
|
72
|
-
Registry.RegisterHandler(TEXT("set_attribute"), &SetAttribute);
|
|
73
|
-
Registry.RegisterHandler(TEXT("get_attribute"), &GetAttribute);
|
|
74
|
-
Registry.RegisterHandler(TEXT("init_asc"), &InitAsc);
|
|
75
|
-
Registry.RegisterHandler(TEXT("get_asc_state"), &GetAscState);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
TSharedPtr<FJsonValue> FGasHandlers::CreateGasBlueprint(
|
|
79
|
-
const TSharedPtr<FJsonObject>& Params,
|
|
80
|
-
const FString& DefaultPackagePath,
|
|
81
|
-
UClass* ParentClass,
|
|
82
|
-
const FString& FriendlyType,
|
|
83
|
-
TFunction<void(TSharedPtr<FJsonObject>&)> ExtraResultFields)
|
|
84
|
-
{
|
|
85
|
-
FString Name;
|
|
86
|
-
if (auto Err = RequireString(Params, TEXT("name"), Name)) return Err;
|
|
87
|
-
|
|
88
|
-
const FString PackagePath = OptionalString(Params, TEXT("packagePath"), DefaultPackagePath);
|
|
89
|
-
const FString OnConflict = OptionalString(Params, TEXT("onConflict"), TEXT("skip"));
|
|
90
|
-
|
|
91
|
-
if (!ParentClass)
|
|
92
|
-
{
|
|
93
|
-
return MCPError(FString::Printf(TEXT("%s parent class not found. Enable GameplayAbilities plugin."), *FriendlyType));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
UBlueprintFactory* BlueprintFactory = NewObject<UBlueprintFactory>();
|
|
97
|
-
BlueprintFactory->ParentClass = ParentClass;
|
|
98
|
-
|
|
99
|
-
auto Created = MCPCreateAssetIdempotent<UBlueprint>(Name, PackagePath, OnConflict, FriendlyType, BlueprintFactory);
|
|
100
|
-
if (Created.EarlyReturn) return Created.EarlyReturn;
|
|
101
|
-
UBlueprint* NewBlueprint = Created.Asset;
|
|
102
|
-
|
|
103
|
-
NewBlueprint->ParentClass = ParentClass;
|
|
104
|
-
FKismetEditorUtilities::CompileBlueprint(NewBlueprint);
|
|
105
|
-
|
|
106
|
-
SaveAssetPackage(NewBlueprint);
|
|
107
|
-
|
|
108
|
-
auto Result = MCPSuccess();
|
|
109
|
-
MCPSetCreated(Result);
|
|
110
|
-
Result->SetStringField(TEXT("path"), NewBlueprint->GetPathName());
|
|
111
|
-
Result->SetStringField(TEXT("name"), Name);
|
|
112
|
-
if (ExtraResultFields) ExtraResultFields(Result);
|
|
113
|
-
MCPSetDeleteAssetRollback(Result, NewBlueprint->GetPathName());
|
|
114
|
-
|
|
115
|
-
return MCPResult(Result);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
TSharedPtr<FJsonValue> FGasHandlers::CreateGameplayEffect(const TSharedPtr<FJsonObject>& Params)
|
|
119
|
-
{
|
|
120
|
-
UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] CreateGameplayEffect called"));
|
|
121
|
-
|
|
122
|
-
const FString DurationPolicy = OptionalString(Params, TEXT("durationPolicy"), TEXT("Instant"));
|
|
123
|
-
UClass* Cls = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.GameplayEffect"));
|
|
124
|
-
|
|
125
|
-
return CreateGasBlueprint(
|
|
126
|
-
Params, TEXT("/Game/GAS/Effects"), Cls, TEXT("GameplayEffect"),
|
|
127
|
-
[&DurationPolicy](TSharedPtr<FJsonObject>& R)
|
|
128
|
-
{
|
|
129
|
-
R->SetStringField(TEXT("durationPolicy"), DurationPolicy);
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
TSharedPtr<FJsonValue> FGasHandlers::GetGasInfo(const TSharedPtr<FJsonObject>& Params)
|
|
134
|
-
{
|
|
135
|
-
FString BlueprintPath;
|
|
136
|
-
if (auto Err = RequireString(Params, TEXT("blueprintPath"), BlueprintPath)) return Err;
|
|
137
|
-
|
|
138
|
-
UBlueprint* Blueprint = LoadObject<UBlueprint>(nullptr, *BlueprintPath);
|
|
139
|
-
if (!Blueprint || !Blueprint->GeneratedClass)
|
|
140
|
-
{
|
|
141
|
-
// Return success with empty info rather than crashing
|
|
142
|
-
auto Result = MCPSuccess();
|
|
143
|
-
Result->SetStringField(TEXT("blueprintPath"), BlueprintPath);
|
|
144
|
-
Result->SetBoolField(TEXT("hasGasComponents"), false);
|
|
145
|
-
Result->SetStringField(TEXT("info"), TEXT("Blueprint not found or has no generated class"));
|
|
146
|
-
return MCPResult(Result);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
UObject* CDO = Blueprint->GeneratedClass->GetDefaultObject();
|
|
150
|
-
if (!CDO)
|
|
151
|
-
{
|
|
152
|
-
auto Result = MCPSuccess();
|
|
153
|
-
Result->SetStringField(TEXT("blueprintPath"), BlueprintPath);
|
|
154
|
-
Result->SetBoolField(TEXT("hasGasComponents"), false);
|
|
155
|
-
Result->SetStringField(TEXT("info"), TEXT("No CDO available"));
|
|
156
|
-
return MCPResult(Result);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
auto Result = MCPSuccess();
|
|
160
|
-
Result->SetStringField(TEXT("blueprintPath"), BlueprintPath);
|
|
161
|
-
Result->SetStringField(TEXT("className"), Blueprint->GeneratedClass->GetName());
|
|
162
|
-
Result->SetStringField(TEXT("parentClass"), Blueprint->ParentClass ? Blueprint->ParentClass->GetName() : TEXT("None"));
|
|
163
|
-
|
|
164
|
-
// Check for GAS-related components
|
|
165
|
-
bool bHasGasComponents = false;
|
|
166
|
-
TArray<TSharedPtr<FJsonValue>> ComponentArray;
|
|
167
|
-
|
|
168
|
-
// Check if the class has an AbilitySystemComponent
|
|
169
|
-
UClass* ASCClass = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.AbilitySystemComponent"));
|
|
170
|
-
if (ASCClass && CDO->IsA(AActor::StaticClass()))
|
|
171
|
-
{
|
|
172
|
-
AActor* ActorCDO = Cast<AActor>(CDO);
|
|
173
|
-
if (ActorCDO)
|
|
174
|
-
{
|
|
175
|
-
TArray<UActorComponent*> Components;
|
|
176
|
-
ActorCDO->GetComponents(Components);
|
|
177
|
-
for (UActorComponent* Comp : Components)
|
|
178
|
-
{
|
|
179
|
-
if (Comp && Comp->IsA(ASCClass))
|
|
180
|
-
{
|
|
181
|
-
bHasGasComponents = true;
|
|
182
|
-
TSharedPtr<FJsonObject> CompObj = MakeShared<FJsonObject>();
|
|
183
|
-
CompObj->SetStringField(TEXT("name"), Comp->GetName());
|
|
184
|
-
CompObj->SetStringField(TEXT("class"), Comp->GetClass()->GetName());
|
|
185
|
-
ComponentArray.Add(MakeShared<FJsonValueObject>(CompObj));
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
Result->SetBoolField(TEXT("hasGasComponents"), bHasGasComponents);
|
|
192
|
-
Result->SetArrayField(TEXT("gasComponents"), ComponentArray);
|
|
193
|
-
|
|
194
|
-
// Check if this is a GameplayEffect subclass
|
|
195
|
-
UClass* GEClass = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.GameplayEffect"));
|
|
196
|
-
Result->SetBoolField(TEXT("isGameplayEffect"), GEClass && Blueprint->GeneratedClass->IsChildOf(GEClass));
|
|
197
|
-
|
|
198
|
-
// Check if this is a GameplayAbility subclass
|
|
199
|
-
UClass* GAClass = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.GameplayAbility"));
|
|
200
|
-
Result->SetBoolField(TEXT("isGameplayAbility"), GAClass && Blueprint->GeneratedClass->IsChildOf(GAClass));
|
|
201
|
-
|
|
202
|
-
// Check if this is an AttributeSet subclass
|
|
203
|
-
UClass* AttrSetClass = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.AttributeSet"));
|
|
204
|
-
Result->SetBoolField(TEXT("isAttributeSet"), AttrSetClass && Blueprint->GeneratedClass->IsChildOf(AttrSetClass));
|
|
205
|
-
|
|
206
|
-
return MCPResult(Result);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
TSharedPtr<FJsonValue> FGasHandlers::CreateGameplayAbility(const TSharedPtr<FJsonObject>& Params)
|
|
210
|
-
{
|
|
211
|
-
UClass* Cls = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.GameplayAbility"));
|
|
212
|
-
return CreateGasBlueprint(Params, TEXT("/Game/GAS/Abilities"), Cls, TEXT("GameplayAbility"));
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
TSharedPtr<FJsonValue> FGasHandlers::CreateAttributeSet(const TSharedPtr<FJsonObject>& Params)
|
|
216
|
-
{
|
|
217
|
-
UClass* Cls = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.AttributeSet"));
|
|
218
|
-
return CreateGasBlueprint(Params, TEXT("/Game/GAS/Attributes"), Cls, TEXT("AttributeSet"));
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
TSharedPtr<FJsonValue> FGasHandlers::CreateGameplayCue(const TSharedPtr<FJsonObject>& Params)
|
|
222
|
-
{
|
|
223
|
-
const FString CueType = OptionalString(Params, TEXT("cueType"), TEXT("Static"));
|
|
224
|
-
const TCHAR* ParentPath = CueType == TEXT("Actor")
|
|
225
|
-
? TEXT("/Script/GameplayAbilities.GameplayCueNotify_Actor")
|
|
226
|
-
: TEXT("/Script/GameplayAbilities.GameplayCueNotify_Static");
|
|
227
|
-
UClass* Cls = FindObject<UClass>(nullptr, ParentPath);
|
|
228
|
-
|
|
229
|
-
return CreateGasBlueprint(
|
|
230
|
-
Params, TEXT("/Game/GAS/Cues"), Cls, TEXT("GameplayCue"),
|
|
231
|
-
[&CueType](TSharedPtr<FJsonObject>& R)
|
|
232
|
-
{
|
|
233
|
-
R->SetStringField(TEXT("cueType"), CueType);
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
TSharedPtr<FJsonValue> FGasHandlers::AddAbilitySystemComponent(const TSharedPtr<FJsonObject>& Params)
|
|
238
|
-
{
|
|
239
|
-
FString BPPath;
|
|
240
|
-
if (auto Err = RequireString(Params, TEXT("blueprintPath"), BPPath)) return Err;
|
|
241
|
-
|
|
242
|
-
UBlueprint* BP = Cast<UBlueprint>(UEditorAssetLibrary::LoadAsset(BPPath));
|
|
243
|
-
if (!BP)
|
|
244
|
-
{
|
|
245
|
-
return MCPError(FString::Printf(TEXT("Blueprint not found: %s"), *BPPath));
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
UClass* ASCClass = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.AbilitySystemComponent"));
|
|
249
|
-
if (!ASCClass)
|
|
250
|
-
{
|
|
251
|
-
return MCPError(TEXT("AbilitySystemComponent not found. Enable GameplayAbilities plugin."));
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
FString CompName = OptionalString(Params, TEXT("componentName"), TEXT("AbilitySystemComp"));
|
|
255
|
-
|
|
256
|
-
// Idempotency: existing ASC on the blueprint?
|
|
257
|
-
if (BP->SimpleConstructionScript)
|
|
258
|
-
{
|
|
259
|
-
const FName CompFName(*CompName);
|
|
260
|
-
for (USCS_Node* N : BP->SimpleConstructionScript->GetAllNodes())
|
|
261
|
-
{
|
|
262
|
-
if (!N || !N->ComponentTemplate) continue;
|
|
263
|
-
if (N->ComponentTemplate->GetClass() == ASCClass || N->GetVariableName() == CompFName)
|
|
264
|
-
{
|
|
265
|
-
auto Existed = MCPSuccess();
|
|
266
|
-
MCPSetExisted(Existed);
|
|
267
|
-
Existed->SetStringField(TEXT("blueprintPath"), BPPath);
|
|
268
|
-
Existed->SetStringField(TEXT("component"), N->GetVariableName().ToString());
|
|
269
|
-
return MCPResult(Existed);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
USCS_Node* NewNode = BP->SimpleConstructionScript->CreateNode(ASCClass, *CompName);
|
|
275
|
-
if (NewNode)
|
|
276
|
-
{
|
|
277
|
-
BP->SimpleConstructionScript->AddNode(NewNode);
|
|
278
|
-
FKismetEditorUtilities::CompileBlueprint(BP);
|
|
279
|
-
|
|
280
|
-
SaveAssetPackage(BP);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
auto Result = MCPSuccess();
|
|
284
|
-
MCPSetCreated(Result);
|
|
285
|
-
Result->SetStringField(TEXT("blueprintPath"), BPPath);
|
|
286
|
-
Result->SetStringField(TEXT("component"), CompName);
|
|
287
|
-
|
|
288
|
-
// Rollback: remove_component
|
|
289
|
-
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
290
|
-
Payload->SetStringField(TEXT("path"), BPPath);
|
|
291
|
-
Payload->SetStringField(TEXT("componentName"), CompName);
|
|
292
|
-
MCPSetRollback(Result, TEXT("remove_component"), Payload);
|
|
293
|
-
|
|
294
|
-
return MCPResult(Result);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
TSharedPtr<FJsonValue> FGasHandlers::AddAttribute(const TSharedPtr<FJsonObject>& Params)
|
|
298
|
-
{
|
|
299
|
-
FString BPPath;
|
|
300
|
-
if (auto Err = RequireString(Params, TEXT("attributeSetPath"), BPPath)) return Err;
|
|
301
|
-
|
|
302
|
-
FString AttrName;
|
|
303
|
-
if (auto Err = RequireString(Params, TEXT("attributeName"), AttrName)) return Err;
|
|
304
|
-
|
|
305
|
-
UBlueprint* BP = Cast<UBlueprint>(UEditorAssetLibrary::LoadAsset(BPPath));
|
|
306
|
-
if (!BP)
|
|
307
|
-
{
|
|
308
|
-
return MCPError(FString::Printf(TEXT("AttributeSet Blueprint not found: %s"), *BPPath));
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Idempotency: member variable with this name already present?
|
|
312
|
-
const FName AttrFName(*AttrName);
|
|
313
|
-
for (const FBPVariableDescription& V : BP->NewVariables)
|
|
314
|
-
{
|
|
315
|
-
if (V.VarName == AttrFName)
|
|
316
|
-
{
|
|
317
|
-
auto Existed = MCPSuccess();
|
|
318
|
-
MCPSetExisted(Existed);
|
|
319
|
-
Existed->SetStringField(TEXT("attributeSetPath"), BPPath);
|
|
320
|
-
Existed->SetStringField(TEXT("attributeName"), AttrName);
|
|
321
|
-
return MCPResult(Existed);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Add a FGameplayAttributeData variable
|
|
326
|
-
FEdGraphPinType PinType;
|
|
327
|
-
PinType.PinCategory = UEdGraphSchema_K2::PC_Struct;
|
|
328
|
-
UScriptStruct* AttrStruct = FindObject<UScriptStruct>(nullptr, TEXT("/Script/GameplayAbilities.GameplayAttributeData"));
|
|
329
|
-
if (AttrStruct)
|
|
330
|
-
{
|
|
331
|
-
PinType.PinSubCategoryObject = AttrStruct;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
FBlueprintEditorUtils::AddMemberVariable(BP, AttrFName, PinType);
|
|
335
|
-
FKismetEditorUtilities::CompileBlueprint(BP);
|
|
336
|
-
|
|
337
|
-
SaveAssetPackage(BP);
|
|
338
|
-
|
|
339
|
-
auto Result = MCPSuccess();
|
|
340
|
-
MCPSetCreated(Result);
|
|
341
|
-
Result->SetStringField(TEXT("attributeSetPath"), BPPath);
|
|
342
|
-
Result->SetStringField(TEXT("attributeName"), AttrName);
|
|
343
|
-
|
|
344
|
-
// Rollback: delete_variable
|
|
345
|
-
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
346
|
-
Payload->SetStringField(TEXT("path"), BPPath);
|
|
347
|
-
Payload->SetStringField(TEXT("name"), AttrName);
|
|
348
|
-
MCPSetRollback(Result, TEXT("delete_variable"), Payload);
|
|
349
|
-
|
|
350
|
-
return MCPResult(Result);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
TSharedPtr<FJsonValue> FGasHandlers::SetAbilityTags(const TSharedPtr<FJsonObject>& Params)
|
|
354
|
-
{
|
|
355
|
-
FString AbilityPath;
|
|
356
|
-
if (auto Err = RequireString(Params, TEXT("abilityPath"), AbilityPath)) return Err;
|
|
357
|
-
|
|
358
|
-
TSharedPtr<FJsonValue> CdoErr;
|
|
359
|
-
UObject* CDO = LoadBlueprintCDO<UObject>(AbilityPath, CdoErr);
|
|
360
|
-
if (!CDO) return CdoErr;
|
|
361
|
-
|
|
362
|
-
// param name -> FGameplayTagContainer UPROPERTY on UGameplayAbility.
|
|
363
|
-
const TArray<TPair<FString, FString>> TagMap = {
|
|
364
|
-
{TEXT("ability_tags"), TEXT("AbilityTags")},
|
|
365
|
-
{TEXT("cancel_abilities_with_tag"), TEXT("CancelAbilitiesWithTag")},
|
|
366
|
-
{TEXT("block_abilities_with_tag"), TEXT("BlockAbilitiesWithTag")},
|
|
367
|
-
{TEXT("activation_required_tags"), TEXT("ActivationRequiredTags")},
|
|
368
|
-
{TEXT("activation_blocked_tags"), TEXT("ActivationBlockedTags")},
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
TSharedPtr<FJsonObject> Applied = MakeShared<FJsonObject>();
|
|
372
|
-
TArray<FString> Unsupported;
|
|
373
|
-
bool bAnyApplied = false;
|
|
374
|
-
|
|
375
|
-
for (const TPair<FString, FString>& Entry : TagMap)
|
|
376
|
-
{
|
|
377
|
-
const TArray<TSharedPtr<FJsonValue>>* TagArray = nullptr;
|
|
378
|
-
if (!Params->TryGetArrayField(*Entry.Key, TagArray) || !TagArray) continue;
|
|
379
|
-
|
|
380
|
-
FStructProperty* Prop = CastField<FStructProperty>(CDO->GetClass()->FindPropertyByName(*Entry.Value));
|
|
381
|
-
if (!Prop || Prop->Struct != FGameplayTagContainer::StaticStruct())
|
|
382
|
-
{
|
|
383
|
-
// Engine-version drift (e.g. AbilityTags deprecated): report, don't fake.
|
|
384
|
-
Unsupported.Add(Entry.Value);
|
|
385
|
-
continue;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
FGameplayTagContainer* Container = Prop->ContainerPtrToValuePtr<FGameplayTagContainer>(CDO);
|
|
389
|
-
Container->Reset();
|
|
390
|
-
TArray<TSharedPtr<FJsonValue>> Wrote;
|
|
391
|
-
for (const TSharedPtr<FJsonValue>& TagVal : *TagArray)
|
|
392
|
-
{
|
|
393
|
-
FString TagStr;
|
|
394
|
-
if (!TagVal.IsValid() || !TagVal->TryGetString(TagStr)) continue;
|
|
395
|
-
const FGameplayTag Tag = FGameplayTag::RequestGameplayTag(FName(*TagStr), /*ErrorIfNotFound*/ false);
|
|
396
|
-
if (Tag.IsValid())
|
|
397
|
-
{
|
|
398
|
-
Container->AddTag(Tag);
|
|
399
|
-
Wrote.Add(MakeShared<FJsonValueString>(TagStr));
|
|
400
|
-
}
|
|
401
|
-
else
|
|
402
|
-
{
|
|
403
|
-
Wrote.Add(MakeShared<FJsonValueString>(TagStr + TEXT(" (unregistered tag - skipped)")));
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
Applied->SetArrayField(Entry.Key, Wrote);
|
|
407
|
-
bAnyApplied = true;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if (bAnyApplied)
|
|
411
|
-
{
|
|
412
|
-
CDO->MarkPackageDirty();
|
|
413
|
-
SaveAssetPackage(CDO);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
auto Result = MCPSuccess();
|
|
417
|
-
MCPSetUpdated(Result);
|
|
418
|
-
Result->SetStringField(TEXT("abilityPath"), AbilityPath);
|
|
419
|
-
Result->SetObjectField(TEXT("applied"), Applied);
|
|
420
|
-
if (Unsupported.Num() > 0)
|
|
421
|
-
{
|
|
422
|
-
TArray<TSharedPtr<FJsonValue>> U;
|
|
423
|
-
for (const FString& S : Unsupported) U.Add(MakeShared<FJsonValueString>(S));
|
|
424
|
-
Result->SetArrayField(TEXT("unsupportedProperties"), U);
|
|
425
|
-
}
|
|
426
|
-
return MCPResult(Result);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
TSharedPtr<FJsonValue> FGasHandlers::SetEffectModifier(const TSharedPtr<FJsonObject>& Params)
|
|
430
|
-
{
|
|
431
|
-
FString EffectPath;
|
|
432
|
-
if (auto Err = RequireString(Params, TEXT("effectPath"), EffectPath)) return Err;
|
|
433
|
-
|
|
434
|
-
FString Attribute;
|
|
435
|
-
if (auto Err = RequireString(Params, TEXT("attribute"), Attribute)) return Err;
|
|
436
|
-
|
|
437
|
-
const FString Operation = OptionalString(Params, TEXT("operation"), TEXT("Additive"));
|
|
438
|
-
const double Magnitude = OptionalNumber(Params, TEXT("magnitude"), 0.0);
|
|
439
|
-
|
|
440
|
-
TSharedPtr<FJsonValue> CdoErr;
|
|
441
|
-
UGameplayEffect* GE = LoadBlueprintCDO<UGameplayEffect>(EffectPath, CdoErr);
|
|
442
|
-
if (!GE) return CdoErr;
|
|
443
|
-
|
|
444
|
-
const FGameplayAttribute GAttr = FindGameplayAttributeByName(Attribute);
|
|
445
|
-
if (!GAttr.IsValid())
|
|
446
|
-
{
|
|
447
|
-
return MCPError(FString::Printf(
|
|
448
|
-
TEXT("Attribute not found: %s. Use 'SetName.Attribute' (or a unique attribute name); the AttributeSet must be compiled/loaded."), *Attribute));
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
const FString Op = Operation.ToLower();
|
|
452
|
-
EGameplayModOp::Type ModOp;
|
|
453
|
-
if (Op == TEXT("additive") || Op == TEXT("add")) ModOp = EGameplayModOp::Additive;
|
|
454
|
-
else if (Op == TEXT("multiplicative") || Op == TEXT("multiply") || Op == TEXT("multiplicitive")) ModOp = EGameplayModOp::Multiplicitive;
|
|
455
|
-
else if (Op == TEXT("division") || Op == TEXT("divide")) ModOp = EGameplayModOp::Division;
|
|
456
|
-
else if (Op == TEXT("override")) ModOp = EGameplayModOp::Override;
|
|
457
|
-
else return MCPError(FString::Printf(TEXT("Unknown operation '%s' (Additive|Multiplicative|Division|Override)"), *Operation));
|
|
458
|
-
|
|
459
|
-
// Update an existing modifier for the same attribute+op, else append one.
|
|
460
|
-
bool bUpdated = false;
|
|
461
|
-
for (FGameplayModifierInfo& M : GE->Modifiers)
|
|
462
|
-
{
|
|
463
|
-
if (M.Attribute == GAttr && M.ModifierOp == ModOp)
|
|
464
|
-
{
|
|
465
|
-
M.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(static_cast<float>(Magnitude)));
|
|
466
|
-
bUpdated = true;
|
|
467
|
-
break;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
if (!bUpdated)
|
|
471
|
-
{
|
|
472
|
-
FGameplayModifierInfo ModInfo;
|
|
473
|
-
ModInfo.Attribute = GAttr;
|
|
474
|
-
ModInfo.ModifierOp = ModOp;
|
|
475
|
-
ModInfo.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(static_cast<float>(Magnitude)));
|
|
476
|
-
GE->Modifiers.Add(ModInfo);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
GE->MarkPackageDirty();
|
|
480
|
-
SaveAssetPackage(GE);
|
|
481
|
-
|
|
482
|
-
auto Result = MCPSuccess();
|
|
483
|
-
MCPSetUpdated(Result);
|
|
484
|
-
Result->SetStringField(TEXT("effectPath"), EffectPath);
|
|
485
|
-
Result->SetStringField(TEXT("attribute"), GAttr.GetName());
|
|
486
|
-
Result->SetStringField(TEXT("operation"), Operation);
|
|
487
|
-
Result->SetNumberField(TEXT("magnitude"), Magnitude);
|
|
488
|
-
Result->SetBoolField(TEXT("replacedExisting"), bUpdated);
|
|
489
|
-
Result->SetNumberField(TEXT("modifierCount"), GE->Modifiers.Num());
|
|
490
|
-
return MCPResult(Result);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
TSharedPtr<FJsonValue> FGasHandlers::SetAscDefaults(const TSharedPtr<FJsonObject>& Params)
|
|
494
|
-
{
|
|
495
|
-
FString BPPath;
|
|
496
|
-
if (auto Err = RequireString(Params, TEXT("blueprintPath"), BPPath)) return Err;
|
|
497
|
-
|
|
498
|
-
FString AttrSetSpec;
|
|
499
|
-
if (auto Err = RequireStringAlt(Params, TEXT("attributeSet"), TEXT("attributeSetPath"), AttrSetSpec)) return Err;
|
|
500
|
-
|
|
501
|
-
UBlueprint* BP = Cast<UBlueprint>(UEditorAssetLibrary::LoadAsset(BPPath));
|
|
502
|
-
if (!BP) return MCPError(FString::Printf(TEXT("Blueprint not found: %s"), *BPPath));
|
|
503
|
-
|
|
504
|
-
UClass* ASCClass = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.AbilitySystemComponent"));
|
|
505
|
-
if (!ASCClass) return MCPError(TEXT("AbilitySystemComponent not found. Enable GameplayAbilities plugin."));
|
|
506
|
-
|
|
507
|
-
// Resolve the AttributeSet class from a content path (BP generated class) or
|
|
508
|
-
// a native short name.
|
|
509
|
-
UClass* AttrSetClass = nullptr;
|
|
510
|
-
{
|
|
511
|
-
UClass* AttrBase = UAttributeSet::StaticClass();
|
|
512
|
-
auto Ok = [AttrBase](UClass* C) { return C && C->IsChildOf(AttrBase); };
|
|
513
|
-
if (AttrSetSpec.Contains(TEXT("/")))
|
|
514
|
-
{
|
|
515
|
-
if (UClass* C = LoadObject<UClass>(nullptr, *AttrSetSpec); Ok(C)) AttrSetClass = C;
|
|
516
|
-
if (!AttrSetClass)
|
|
517
|
-
{
|
|
518
|
-
FString AssetName;
|
|
519
|
-
AttrSetSpec.Split(TEXT("/"), nullptr, &AssetName, ESearchCase::CaseSensitive, ESearchDir::FromEnd);
|
|
520
|
-
if (UClass* C = LoadObject<UClass>(nullptr, *(AttrSetSpec + TEXT(".") + AssetName + TEXT("_C"))); Ok(C)) AttrSetClass = C;
|
|
521
|
-
}
|
|
522
|
-
if (!AttrSetClass)
|
|
523
|
-
{
|
|
524
|
-
if (UBlueprint* SetBP = Cast<UBlueprint>(UEditorAssetLibrary::LoadAsset(AttrSetSpec)))
|
|
525
|
-
{
|
|
526
|
-
if (Ok(SetBP->GeneratedClass)) AttrSetClass = SetBP->GeneratedClass;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
else if (UClass* C = FindClassByShortName(AttrSetSpec); Ok(C))
|
|
531
|
-
{
|
|
532
|
-
AttrSetClass = C;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
if (!AttrSetClass) return MCPError(FString::Printf(TEXT("AttributeSet class not found: %s"), *AttrSetSpec));
|
|
536
|
-
|
|
537
|
-
// Find the ASC component template on the blueprint's construction script.
|
|
538
|
-
const FString CompName = OptionalString(Params, TEXT("componentName"));
|
|
539
|
-
UAbilitySystemComponent* ASCTemplate = nullptr;
|
|
540
|
-
FString ResolvedComp;
|
|
541
|
-
if (BP->SimpleConstructionScript)
|
|
542
|
-
{
|
|
543
|
-
for (USCS_Node* N : BP->SimpleConstructionScript->GetAllNodes())
|
|
544
|
-
{
|
|
545
|
-
if (!N || !N->ComponentTemplate || !N->ComponentTemplate->IsA(ASCClass)) continue;
|
|
546
|
-
if (!CompName.IsEmpty() && N->GetVariableName() != FName(*CompName)) continue;
|
|
547
|
-
ASCTemplate = Cast<UAbilitySystemComponent>(N->ComponentTemplate);
|
|
548
|
-
ResolvedComp = N->GetVariableName().ToString();
|
|
549
|
-
break;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
if (!ASCTemplate)
|
|
553
|
-
{
|
|
554
|
-
return MCPError(TEXT("No AbilitySystemComponent on the blueprint - run add_ability_system_component first"));
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// Optional init DataTable (production path: starting values at ASC init).
|
|
558
|
-
UDataTable* InitTable = nullptr;
|
|
559
|
-
const FString TablePath = OptionalString(Params, TEXT("initDataTable"));
|
|
560
|
-
if (!TablePath.IsEmpty())
|
|
561
|
-
{
|
|
562
|
-
InitTable = LoadObject<UDataTable>(nullptr, *TablePath);
|
|
563
|
-
if (!InitTable) return MCPError(FString::Printf(TEXT("initDataTable not found: %s"), *TablePath));
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
// Idempotency: already wired for this attribute set?
|
|
567
|
-
for (const FAttributeDefaults& D : ASCTemplate->DefaultStartingData)
|
|
568
|
-
{
|
|
569
|
-
if (D.Attributes == AttrSetClass)
|
|
570
|
-
{
|
|
571
|
-
auto Existed = MCPSuccess();
|
|
572
|
-
MCPSetExisted(Existed);
|
|
573
|
-
Existed->SetStringField(TEXT("blueprintPath"), BPPath);
|
|
574
|
-
Existed->SetStringField(TEXT("component"), ResolvedComp);
|
|
575
|
-
Existed->SetStringField(TEXT("attributeSet"), AttrSetClass->GetName());
|
|
576
|
-
return MCPResult(Existed);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
FAttributeDefaults Def;
|
|
581
|
-
Def.Attributes = AttrSetClass;
|
|
582
|
-
Def.DefaultStartingTable = InitTable;
|
|
583
|
-
ASCTemplate->DefaultStartingData.Add(Def);
|
|
584
|
-
|
|
585
|
-
FKismetEditorUtilities::CompileBlueprint(BP);
|
|
586
|
-
SaveAssetPackage(BP);
|
|
587
|
-
|
|
588
|
-
auto Result = MCPSuccess();
|
|
589
|
-
MCPSetCreated(Result);
|
|
590
|
-
Result->SetStringField(TEXT("blueprintPath"), BPPath);
|
|
591
|
-
Result->SetStringField(TEXT("component"), ResolvedComp);
|
|
592
|
-
Result->SetStringField(TEXT("attributeSet"), AttrSetClass->GetName());
|
|
593
|
-
if (InitTable) Result->SetStringField(TEXT("initDataTable"), InitTable->GetPathName());
|
|
594
|
-
Result->SetStringField(TEXT("note"),
|
|
595
|
-
TEXT("Attribute set wired to the ASC's DefaultStartingData. If attributes aren't live at runtime, call gas(action=\"init_asc\", attributeSet=...) after PIE starts."));
|
|
596
|
-
return MCPResult(Result);
|
|
597
|
-
}
|
|
1
|
+
#include "GasHandlers.h"
|
|
2
|
+
#include "UE_MCP_BridgeModule.h"
|
|
3
|
+
#include "HandlerRegistry.h"
|
|
4
|
+
#include "HandlerUtils.h"
|
|
5
|
+
#include "HandlerAssetCreate.h"
|
|
6
|
+
#include "Kismet2/KismetEditorUtilities.h"
|
|
7
|
+
#include "Engine/Blueprint.h"
|
|
8
|
+
#include "AssetToolsModule.h"
|
|
9
|
+
#include "IAssetTools.h"
|
|
10
|
+
#include "Factories/BlueprintFactory.h"
|
|
11
|
+
#include "UObject/UObjectGlobals.h"
|
|
12
|
+
#include "UObject/Package.h"
|
|
13
|
+
#include "Misc/PackageName.h"
|
|
14
|
+
#include "UObject/SavePackage.h"
|
|
15
|
+
#include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
|
|
16
|
+
#include "Dom/JsonObject.h"
|
|
17
|
+
#include "Dom/JsonValue.h"
|
|
18
|
+
#include "GameplayTagContainer.h"
|
|
19
|
+
#include "GameplayTagsManager.h"
|
|
20
|
+
#include "Kismet2/BlueprintEditorUtils.h"
|
|
21
|
+
#include "Engine/SimpleConstructionScript.h"
|
|
22
|
+
#include "Engine/SCS_Node.h"
|
|
23
|
+
#include "EdGraphSchema_K2.h"
|
|
24
|
+
#include "AbilitySystemComponent.h"
|
|
25
|
+
#include "AttributeSet.h"
|
|
26
|
+
#include "Engine/DataTable.h"
|
|
27
|
+
#include "GameplayEffect.h"
|
|
28
|
+
#include "GameplayEffectTypes.h"
|
|
29
|
+
#include "ScalableFloat.h"
|
|
30
|
+
#include "GameplayTagsManager.h"
|
|
31
|
+
|
|
32
|
+
// Resolve a FGameplayAttribute by name across every loaded AttributeSet
|
|
33
|
+
// subclass. Accepts a bare property name ("Health") or a qualified
|
|
34
|
+
// "SetClassSubstring.Attribute". Returns an invalid attribute on miss.
|
|
35
|
+
static FGameplayAttribute FindGameplayAttributeByName(const FString& Name)
|
|
36
|
+
{
|
|
37
|
+
FString SetFilter, AttrName = Name;
|
|
38
|
+
if (Name.Contains(TEXT(".")))
|
|
39
|
+
{
|
|
40
|
+
Name.Split(TEXT("."), &SetFilter, &AttrName);
|
|
41
|
+
}
|
|
42
|
+
for (TObjectIterator<UClass> It; It; ++It)
|
|
43
|
+
{
|
|
44
|
+
UClass* C = *It;
|
|
45
|
+
if (C == UAttributeSet::StaticClass() || !C->IsChildOf(UAttributeSet::StaticClass())) continue;
|
|
46
|
+
if (!SetFilter.IsEmpty() && !C->GetName().Contains(SetFilter)) continue;
|
|
47
|
+
for (TFieldIterator<FProperty> P(C); P; ++P)
|
|
48
|
+
{
|
|
49
|
+
FStructProperty* SP = CastField<FStructProperty>(*P);
|
|
50
|
+
if (SP && SP->Struct == FGameplayAttributeData::StaticStruct() && SP->GetName() == AttrName)
|
|
51
|
+
{
|
|
52
|
+
return FGameplayAttribute(SP);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return FGameplayAttribute();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
void FGasHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
60
|
+
{
|
|
61
|
+
Registry.RegisterHandler(TEXT("create_gameplay_effect"), &CreateGameplayEffect);
|
|
62
|
+
Registry.RegisterHandler(TEXT("get_gas_info"), &GetGasInfo);
|
|
63
|
+
Registry.RegisterHandler(TEXT("create_gameplay_ability"), &CreateGameplayAbility);
|
|
64
|
+
Registry.RegisterHandler(TEXT("create_attribute_set"), &CreateAttributeSet);
|
|
65
|
+
Registry.RegisterHandler(TEXT("create_gameplay_cue"), &CreateGameplayCue);
|
|
66
|
+
Registry.RegisterHandler(TEXT("add_ability_system_component"), &AddAbilitySystemComponent);
|
|
67
|
+
Registry.RegisterHandler(TEXT("add_attribute"), &AddAttribute);
|
|
68
|
+
Registry.RegisterHandler(TEXT("set_ability_tags"), &SetAbilityTags);
|
|
69
|
+
Registry.RegisterHandler(TEXT("set_effect_modifier"), &SetEffectModifier);
|
|
70
|
+
Registry.RegisterHandler(TEXT("set_asc_defaults"), &SetAscDefaults);
|
|
71
|
+
Registry.RegisterHandler(TEXT("apply_effect"), &ApplyEffect);
|
|
72
|
+
Registry.RegisterHandler(TEXT("set_attribute"), &SetAttribute);
|
|
73
|
+
Registry.RegisterHandler(TEXT("get_attribute"), &GetAttribute);
|
|
74
|
+
Registry.RegisterHandler(TEXT("init_asc"), &InitAsc);
|
|
75
|
+
Registry.RegisterHandler(TEXT("get_asc_state"), &GetAscState);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
TSharedPtr<FJsonValue> FGasHandlers::CreateGasBlueprint(
|
|
79
|
+
const TSharedPtr<FJsonObject>& Params,
|
|
80
|
+
const FString& DefaultPackagePath,
|
|
81
|
+
UClass* ParentClass,
|
|
82
|
+
const FString& FriendlyType,
|
|
83
|
+
TFunction<void(TSharedPtr<FJsonObject>&)> ExtraResultFields)
|
|
84
|
+
{
|
|
85
|
+
FString Name;
|
|
86
|
+
if (auto Err = RequireString(Params, TEXT("name"), Name)) return Err;
|
|
87
|
+
|
|
88
|
+
const FString PackagePath = OptionalString(Params, TEXT("packagePath"), DefaultPackagePath);
|
|
89
|
+
const FString OnConflict = OptionalString(Params, TEXT("onConflict"), TEXT("skip"));
|
|
90
|
+
|
|
91
|
+
if (!ParentClass)
|
|
92
|
+
{
|
|
93
|
+
return MCPError(FString::Printf(TEXT("%s parent class not found. Enable GameplayAbilities plugin."), *FriendlyType));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
UBlueprintFactory* BlueprintFactory = NewObject<UBlueprintFactory>();
|
|
97
|
+
BlueprintFactory->ParentClass = ParentClass;
|
|
98
|
+
|
|
99
|
+
auto Created = MCPCreateAssetIdempotent<UBlueprint>(Name, PackagePath, OnConflict, FriendlyType, BlueprintFactory);
|
|
100
|
+
if (Created.EarlyReturn) return Created.EarlyReturn;
|
|
101
|
+
UBlueprint* NewBlueprint = Created.Asset;
|
|
102
|
+
|
|
103
|
+
NewBlueprint->ParentClass = ParentClass;
|
|
104
|
+
FKismetEditorUtilities::CompileBlueprint(NewBlueprint);
|
|
105
|
+
|
|
106
|
+
SaveAssetPackage(NewBlueprint);
|
|
107
|
+
|
|
108
|
+
auto Result = MCPSuccess();
|
|
109
|
+
MCPSetCreated(Result);
|
|
110
|
+
Result->SetStringField(TEXT("path"), NewBlueprint->GetPathName());
|
|
111
|
+
Result->SetStringField(TEXT("name"), Name);
|
|
112
|
+
if (ExtraResultFields) ExtraResultFields(Result);
|
|
113
|
+
MCPSetDeleteAssetRollback(Result, NewBlueprint->GetPathName());
|
|
114
|
+
|
|
115
|
+
return MCPResult(Result);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
TSharedPtr<FJsonValue> FGasHandlers::CreateGameplayEffect(const TSharedPtr<FJsonObject>& Params)
|
|
119
|
+
{
|
|
120
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] CreateGameplayEffect called"));
|
|
121
|
+
|
|
122
|
+
const FString DurationPolicy = OptionalString(Params, TEXT("durationPolicy"), TEXT("Instant"));
|
|
123
|
+
UClass* Cls = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.GameplayEffect"));
|
|
124
|
+
|
|
125
|
+
return CreateGasBlueprint(
|
|
126
|
+
Params, TEXT("/Game/GAS/Effects"), Cls, TEXT("GameplayEffect"),
|
|
127
|
+
[&DurationPolicy](TSharedPtr<FJsonObject>& R)
|
|
128
|
+
{
|
|
129
|
+
R->SetStringField(TEXT("durationPolicy"), DurationPolicy);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
TSharedPtr<FJsonValue> FGasHandlers::GetGasInfo(const TSharedPtr<FJsonObject>& Params)
|
|
134
|
+
{
|
|
135
|
+
FString BlueprintPath;
|
|
136
|
+
if (auto Err = RequireString(Params, TEXT("blueprintPath"), BlueprintPath)) return Err;
|
|
137
|
+
|
|
138
|
+
UBlueprint* Blueprint = LoadObject<UBlueprint>(nullptr, *BlueprintPath);
|
|
139
|
+
if (!Blueprint || !Blueprint->GeneratedClass)
|
|
140
|
+
{
|
|
141
|
+
// Return success with empty info rather than crashing
|
|
142
|
+
auto Result = MCPSuccess();
|
|
143
|
+
Result->SetStringField(TEXT("blueprintPath"), BlueprintPath);
|
|
144
|
+
Result->SetBoolField(TEXT("hasGasComponents"), false);
|
|
145
|
+
Result->SetStringField(TEXT("info"), TEXT("Blueprint not found or has no generated class"));
|
|
146
|
+
return MCPResult(Result);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
UObject* CDO = Blueprint->GeneratedClass->GetDefaultObject();
|
|
150
|
+
if (!CDO)
|
|
151
|
+
{
|
|
152
|
+
auto Result = MCPSuccess();
|
|
153
|
+
Result->SetStringField(TEXT("blueprintPath"), BlueprintPath);
|
|
154
|
+
Result->SetBoolField(TEXT("hasGasComponents"), false);
|
|
155
|
+
Result->SetStringField(TEXT("info"), TEXT("No CDO available"));
|
|
156
|
+
return MCPResult(Result);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
auto Result = MCPSuccess();
|
|
160
|
+
Result->SetStringField(TEXT("blueprintPath"), BlueprintPath);
|
|
161
|
+
Result->SetStringField(TEXT("className"), Blueprint->GeneratedClass->GetName());
|
|
162
|
+
Result->SetStringField(TEXT("parentClass"), Blueprint->ParentClass ? Blueprint->ParentClass->GetName() : TEXT("None"));
|
|
163
|
+
|
|
164
|
+
// Check for GAS-related components
|
|
165
|
+
bool bHasGasComponents = false;
|
|
166
|
+
TArray<TSharedPtr<FJsonValue>> ComponentArray;
|
|
167
|
+
|
|
168
|
+
// Check if the class has an AbilitySystemComponent
|
|
169
|
+
UClass* ASCClass = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.AbilitySystemComponent"));
|
|
170
|
+
if (ASCClass && CDO->IsA(AActor::StaticClass()))
|
|
171
|
+
{
|
|
172
|
+
AActor* ActorCDO = Cast<AActor>(CDO);
|
|
173
|
+
if (ActorCDO)
|
|
174
|
+
{
|
|
175
|
+
TArray<UActorComponent*> Components;
|
|
176
|
+
ActorCDO->GetComponents(Components);
|
|
177
|
+
for (UActorComponent* Comp : Components)
|
|
178
|
+
{
|
|
179
|
+
if (Comp && Comp->IsA(ASCClass))
|
|
180
|
+
{
|
|
181
|
+
bHasGasComponents = true;
|
|
182
|
+
TSharedPtr<FJsonObject> CompObj = MakeShared<FJsonObject>();
|
|
183
|
+
CompObj->SetStringField(TEXT("name"), Comp->GetName());
|
|
184
|
+
CompObj->SetStringField(TEXT("class"), Comp->GetClass()->GetName());
|
|
185
|
+
ComponentArray.Add(MakeShared<FJsonValueObject>(CompObj));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
Result->SetBoolField(TEXT("hasGasComponents"), bHasGasComponents);
|
|
192
|
+
Result->SetArrayField(TEXT("gasComponents"), ComponentArray);
|
|
193
|
+
|
|
194
|
+
// Check if this is a GameplayEffect subclass
|
|
195
|
+
UClass* GEClass = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.GameplayEffect"));
|
|
196
|
+
Result->SetBoolField(TEXT("isGameplayEffect"), GEClass && Blueprint->GeneratedClass->IsChildOf(GEClass));
|
|
197
|
+
|
|
198
|
+
// Check if this is a GameplayAbility subclass
|
|
199
|
+
UClass* GAClass = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.GameplayAbility"));
|
|
200
|
+
Result->SetBoolField(TEXT("isGameplayAbility"), GAClass && Blueprint->GeneratedClass->IsChildOf(GAClass));
|
|
201
|
+
|
|
202
|
+
// Check if this is an AttributeSet subclass
|
|
203
|
+
UClass* AttrSetClass = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.AttributeSet"));
|
|
204
|
+
Result->SetBoolField(TEXT("isAttributeSet"), AttrSetClass && Blueprint->GeneratedClass->IsChildOf(AttrSetClass));
|
|
205
|
+
|
|
206
|
+
return MCPResult(Result);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
TSharedPtr<FJsonValue> FGasHandlers::CreateGameplayAbility(const TSharedPtr<FJsonObject>& Params)
|
|
210
|
+
{
|
|
211
|
+
UClass* Cls = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.GameplayAbility"));
|
|
212
|
+
return CreateGasBlueprint(Params, TEXT("/Game/GAS/Abilities"), Cls, TEXT("GameplayAbility"));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
TSharedPtr<FJsonValue> FGasHandlers::CreateAttributeSet(const TSharedPtr<FJsonObject>& Params)
|
|
216
|
+
{
|
|
217
|
+
UClass* Cls = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.AttributeSet"));
|
|
218
|
+
return CreateGasBlueprint(Params, TEXT("/Game/GAS/Attributes"), Cls, TEXT("AttributeSet"));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
TSharedPtr<FJsonValue> FGasHandlers::CreateGameplayCue(const TSharedPtr<FJsonObject>& Params)
|
|
222
|
+
{
|
|
223
|
+
const FString CueType = OptionalString(Params, TEXT("cueType"), TEXT("Static"));
|
|
224
|
+
const TCHAR* ParentPath = CueType == TEXT("Actor")
|
|
225
|
+
? TEXT("/Script/GameplayAbilities.GameplayCueNotify_Actor")
|
|
226
|
+
: TEXT("/Script/GameplayAbilities.GameplayCueNotify_Static");
|
|
227
|
+
UClass* Cls = FindObject<UClass>(nullptr, ParentPath);
|
|
228
|
+
|
|
229
|
+
return CreateGasBlueprint(
|
|
230
|
+
Params, TEXT("/Game/GAS/Cues"), Cls, TEXT("GameplayCue"),
|
|
231
|
+
[&CueType](TSharedPtr<FJsonObject>& R)
|
|
232
|
+
{
|
|
233
|
+
R->SetStringField(TEXT("cueType"), CueType);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
TSharedPtr<FJsonValue> FGasHandlers::AddAbilitySystemComponent(const TSharedPtr<FJsonObject>& Params)
|
|
238
|
+
{
|
|
239
|
+
FString BPPath;
|
|
240
|
+
if (auto Err = RequireString(Params, TEXT("blueprintPath"), BPPath)) return Err;
|
|
241
|
+
|
|
242
|
+
UBlueprint* BP = Cast<UBlueprint>(UEditorAssetLibrary::LoadAsset(BPPath));
|
|
243
|
+
if (!BP)
|
|
244
|
+
{
|
|
245
|
+
return MCPError(FString::Printf(TEXT("Blueprint not found: %s"), *BPPath));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
UClass* ASCClass = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.AbilitySystemComponent"));
|
|
249
|
+
if (!ASCClass)
|
|
250
|
+
{
|
|
251
|
+
return MCPError(TEXT("AbilitySystemComponent not found. Enable GameplayAbilities plugin."));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
FString CompName = OptionalString(Params, TEXT("componentName"), TEXT("AbilitySystemComp"));
|
|
255
|
+
|
|
256
|
+
// Idempotency: existing ASC on the blueprint?
|
|
257
|
+
if (BP->SimpleConstructionScript)
|
|
258
|
+
{
|
|
259
|
+
const FName CompFName(*CompName);
|
|
260
|
+
for (USCS_Node* N : BP->SimpleConstructionScript->GetAllNodes())
|
|
261
|
+
{
|
|
262
|
+
if (!N || !N->ComponentTemplate) continue;
|
|
263
|
+
if (N->ComponentTemplate->GetClass() == ASCClass || N->GetVariableName() == CompFName)
|
|
264
|
+
{
|
|
265
|
+
auto Existed = MCPSuccess();
|
|
266
|
+
MCPSetExisted(Existed);
|
|
267
|
+
Existed->SetStringField(TEXT("blueprintPath"), BPPath);
|
|
268
|
+
Existed->SetStringField(TEXT("component"), N->GetVariableName().ToString());
|
|
269
|
+
return MCPResult(Existed);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
USCS_Node* NewNode = BP->SimpleConstructionScript->CreateNode(ASCClass, *CompName);
|
|
275
|
+
if (NewNode)
|
|
276
|
+
{
|
|
277
|
+
BP->SimpleConstructionScript->AddNode(NewNode);
|
|
278
|
+
FKismetEditorUtilities::CompileBlueprint(BP);
|
|
279
|
+
|
|
280
|
+
SaveAssetPackage(BP);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
auto Result = MCPSuccess();
|
|
284
|
+
MCPSetCreated(Result);
|
|
285
|
+
Result->SetStringField(TEXT("blueprintPath"), BPPath);
|
|
286
|
+
Result->SetStringField(TEXT("component"), CompName);
|
|
287
|
+
|
|
288
|
+
// Rollback: remove_component
|
|
289
|
+
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
290
|
+
Payload->SetStringField(TEXT("path"), BPPath);
|
|
291
|
+
Payload->SetStringField(TEXT("componentName"), CompName);
|
|
292
|
+
MCPSetRollback(Result, TEXT("remove_component"), Payload);
|
|
293
|
+
|
|
294
|
+
return MCPResult(Result);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
TSharedPtr<FJsonValue> FGasHandlers::AddAttribute(const TSharedPtr<FJsonObject>& Params)
|
|
298
|
+
{
|
|
299
|
+
FString BPPath;
|
|
300
|
+
if (auto Err = RequireString(Params, TEXT("attributeSetPath"), BPPath)) return Err;
|
|
301
|
+
|
|
302
|
+
FString AttrName;
|
|
303
|
+
if (auto Err = RequireString(Params, TEXT("attributeName"), AttrName)) return Err;
|
|
304
|
+
|
|
305
|
+
UBlueprint* BP = Cast<UBlueprint>(UEditorAssetLibrary::LoadAsset(BPPath));
|
|
306
|
+
if (!BP)
|
|
307
|
+
{
|
|
308
|
+
return MCPError(FString::Printf(TEXT("AttributeSet Blueprint not found: %s"), *BPPath));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Idempotency: member variable with this name already present?
|
|
312
|
+
const FName AttrFName(*AttrName);
|
|
313
|
+
for (const FBPVariableDescription& V : BP->NewVariables)
|
|
314
|
+
{
|
|
315
|
+
if (V.VarName == AttrFName)
|
|
316
|
+
{
|
|
317
|
+
auto Existed = MCPSuccess();
|
|
318
|
+
MCPSetExisted(Existed);
|
|
319
|
+
Existed->SetStringField(TEXT("attributeSetPath"), BPPath);
|
|
320
|
+
Existed->SetStringField(TEXT("attributeName"), AttrName);
|
|
321
|
+
return MCPResult(Existed);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Add a FGameplayAttributeData variable
|
|
326
|
+
FEdGraphPinType PinType;
|
|
327
|
+
PinType.PinCategory = UEdGraphSchema_K2::PC_Struct;
|
|
328
|
+
UScriptStruct* AttrStruct = FindObject<UScriptStruct>(nullptr, TEXT("/Script/GameplayAbilities.GameplayAttributeData"));
|
|
329
|
+
if (AttrStruct)
|
|
330
|
+
{
|
|
331
|
+
PinType.PinSubCategoryObject = AttrStruct;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
FBlueprintEditorUtils::AddMemberVariable(BP, AttrFName, PinType);
|
|
335
|
+
FKismetEditorUtilities::CompileBlueprint(BP);
|
|
336
|
+
|
|
337
|
+
SaveAssetPackage(BP);
|
|
338
|
+
|
|
339
|
+
auto Result = MCPSuccess();
|
|
340
|
+
MCPSetCreated(Result);
|
|
341
|
+
Result->SetStringField(TEXT("attributeSetPath"), BPPath);
|
|
342
|
+
Result->SetStringField(TEXT("attributeName"), AttrName);
|
|
343
|
+
|
|
344
|
+
// Rollback: delete_variable
|
|
345
|
+
TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
|
|
346
|
+
Payload->SetStringField(TEXT("path"), BPPath);
|
|
347
|
+
Payload->SetStringField(TEXT("name"), AttrName);
|
|
348
|
+
MCPSetRollback(Result, TEXT("delete_variable"), Payload);
|
|
349
|
+
|
|
350
|
+
return MCPResult(Result);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
TSharedPtr<FJsonValue> FGasHandlers::SetAbilityTags(const TSharedPtr<FJsonObject>& Params)
|
|
354
|
+
{
|
|
355
|
+
FString AbilityPath;
|
|
356
|
+
if (auto Err = RequireString(Params, TEXT("abilityPath"), AbilityPath)) return Err;
|
|
357
|
+
|
|
358
|
+
TSharedPtr<FJsonValue> CdoErr;
|
|
359
|
+
UObject* CDO = LoadBlueprintCDO<UObject>(AbilityPath, CdoErr);
|
|
360
|
+
if (!CDO) return CdoErr;
|
|
361
|
+
|
|
362
|
+
// param name -> FGameplayTagContainer UPROPERTY on UGameplayAbility.
|
|
363
|
+
const TArray<TPair<FString, FString>> TagMap = {
|
|
364
|
+
{TEXT("ability_tags"), TEXT("AbilityTags")},
|
|
365
|
+
{TEXT("cancel_abilities_with_tag"), TEXT("CancelAbilitiesWithTag")},
|
|
366
|
+
{TEXT("block_abilities_with_tag"), TEXT("BlockAbilitiesWithTag")},
|
|
367
|
+
{TEXT("activation_required_tags"), TEXT("ActivationRequiredTags")},
|
|
368
|
+
{TEXT("activation_blocked_tags"), TEXT("ActivationBlockedTags")},
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
TSharedPtr<FJsonObject> Applied = MakeShared<FJsonObject>();
|
|
372
|
+
TArray<FString> Unsupported;
|
|
373
|
+
bool bAnyApplied = false;
|
|
374
|
+
|
|
375
|
+
for (const TPair<FString, FString>& Entry : TagMap)
|
|
376
|
+
{
|
|
377
|
+
const TArray<TSharedPtr<FJsonValue>>* TagArray = nullptr;
|
|
378
|
+
if (!Params->TryGetArrayField(*Entry.Key, TagArray) || !TagArray) continue;
|
|
379
|
+
|
|
380
|
+
FStructProperty* Prop = CastField<FStructProperty>(CDO->GetClass()->FindPropertyByName(*Entry.Value));
|
|
381
|
+
if (!Prop || Prop->Struct != FGameplayTagContainer::StaticStruct())
|
|
382
|
+
{
|
|
383
|
+
// Engine-version drift (e.g. AbilityTags deprecated): report, don't fake.
|
|
384
|
+
Unsupported.Add(Entry.Value);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
FGameplayTagContainer* Container = Prop->ContainerPtrToValuePtr<FGameplayTagContainer>(CDO);
|
|
389
|
+
Container->Reset();
|
|
390
|
+
TArray<TSharedPtr<FJsonValue>> Wrote;
|
|
391
|
+
for (const TSharedPtr<FJsonValue>& TagVal : *TagArray)
|
|
392
|
+
{
|
|
393
|
+
FString TagStr;
|
|
394
|
+
if (!TagVal.IsValid() || !TagVal->TryGetString(TagStr)) continue;
|
|
395
|
+
const FGameplayTag Tag = FGameplayTag::RequestGameplayTag(FName(*TagStr), /*ErrorIfNotFound*/ false);
|
|
396
|
+
if (Tag.IsValid())
|
|
397
|
+
{
|
|
398
|
+
Container->AddTag(Tag);
|
|
399
|
+
Wrote.Add(MakeShared<FJsonValueString>(TagStr));
|
|
400
|
+
}
|
|
401
|
+
else
|
|
402
|
+
{
|
|
403
|
+
Wrote.Add(MakeShared<FJsonValueString>(TagStr + TEXT(" (unregistered tag - skipped)")));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
Applied->SetArrayField(Entry.Key, Wrote);
|
|
407
|
+
bAnyApplied = true;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (bAnyApplied)
|
|
411
|
+
{
|
|
412
|
+
CDO->MarkPackageDirty();
|
|
413
|
+
SaveAssetPackage(CDO);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
auto Result = MCPSuccess();
|
|
417
|
+
MCPSetUpdated(Result);
|
|
418
|
+
Result->SetStringField(TEXT("abilityPath"), AbilityPath);
|
|
419
|
+
Result->SetObjectField(TEXT("applied"), Applied);
|
|
420
|
+
if (Unsupported.Num() > 0)
|
|
421
|
+
{
|
|
422
|
+
TArray<TSharedPtr<FJsonValue>> U;
|
|
423
|
+
for (const FString& S : Unsupported) U.Add(MakeShared<FJsonValueString>(S));
|
|
424
|
+
Result->SetArrayField(TEXT("unsupportedProperties"), U);
|
|
425
|
+
}
|
|
426
|
+
return MCPResult(Result);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
TSharedPtr<FJsonValue> FGasHandlers::SetEffectModifier(const TSharedPtr<FJsonObject>& Params)
|
|
430
|
+
{
|
|
431
|
+
FString EffectPath;
|
|
432
|
+
if (auto Err = RequireString(Params, TEXT("effectPath"), EffectPath)) return Err;
|
|
433
|
+
|
|
434
|
+
FString Attribute;
|
|
435
|
+
if (auto Err = RequireString(Params, TEXT("attribute"), Attribute)) return Err;
|
|
436
|
+
|
|
437
|
+
const FString Operation = OptionalString(Params, TEXT("operation"), TEXT("Additive"));
|
|
438
|
+
const double Magnitude = OptionalNumber(Params, TEXT("magnitude"), 0.0);
|
|
439
|
+
|
|
440
|
+
TSharedPtr<FJsonValue> CdoErr;
|
|
441
|
+
UGameplayEffect* GE = LoadBlueprintCDO<UGameplayEffect>(EffectPath, CdoErr);
|
|
442
|
+
if (!GE) return CdoErr;
|
|
443
|
+
|
|
444
|
+
const FGameplayAttribute GAttr = FindGameplayAttributeByName(Attribute);
|
|
445
|
+
if (!GAttr.IsValid())
|
|
446
|
+
{
|
|
447
|
+
return MCPError(FString::Printf(
|
|
448
|
+
TEXT("Attribute not found: %s. Use 'SetName.Attribute' (or a unique attribute name); the AttributeSet must be compiled/loaded."), *Attribute));
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const FString Op = Operation.ToLower();
|
|
452
|
+
EGameplayModOp::Type ModOp;
|
|
453
|
+
if (Op == TEXT("additive") || Op == TEXT("add")) ModOp = EGameplayModOp::Additive;
|
|
454
|
+
else if (Op == TEXT("multiplicative") || Op == TEXT("multiply") || Op == TEXT("multiplicitive")) ModOp = EGameplayModOp::Multiplicitive;
|
|
455
|
+
else if (Op == TEXT("division") || Op == TEXT("divide")) ModOp = EGameplayModOp::Division;
|
|
456
|
+
else if (Op == TEXT("override")) ModOp = EGameplayModOp::Override;
|
|
457
|
+
else return MCPError(FString::Printf(TEXT("Unknown operation '%s' (Additive|Multiplicative|Division|Override)"), *Operation));
|
|
458
|
+
|
|
459
|
+
// Update an existing modifier for the same attribute+op, else append one.
|
|
460
|
+
bool bUpdated = false;
|
|
461
|
+
for (FGameplayModifierInfo& M : GE->Modifiers)
|
|
462
|
+
{
|
|
463
|
+
if (M.Attribute == GAttr && M.ModifierOp == ModOp)
|
|
464
|
+
{
|
|
465
|
+
M.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(static_cast<float>(Magnitude)));
|
|
466
|
+
bUpdated = true;
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (!bUpdated)
|
|
471
|
+
{
|
|
472
|
+
FGameplayModifierInfo ModInfo;
|
|
473
|
+
ModInfo.Attribute = GAttr;
|
|
474
|
+
ModInfo.ModifierOp = ModOp;
|
|
475
|
+
ModInfo.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(static_cast<float>(Magnitude)));
|
|
476
|
+
GE->Modifiers.Add(ModInfo);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
GE->MarkPackageDirty();
|
|
480
|
+
SaveAssetPackage(GE);
|
|
481
|
+
|
|
482
|
+
auto Result = MCPSuccess();
|
|
483
|
+
MCPSetUpdated(Result);
|
|
484
|
+
Result->SetStringField(TEXT("effectPath"), EffectPath);
|
|
485
|
+
Result->SetStringField(TEXT("attribute"), GAttr.GetName());
|
|
486
|
+
Result->SetStringField(TEXT("operation"), Operation);
|
|
487
|
+
Result->SetNumberField(TEXT("magnitude"), Magnitude);
|
|
488
|
+
Result->SetBoolField(TEXT("replacedExisting"), bUpdated);
|
|
489
|
+
Result->SetNumberField(TEXT("modifierCount"), GE->Modifiers.Num());
|
|
490
|
+
return MCPResult(Result);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
TSharedPtr<FJsonValue> FGasHandlers::SetAscDefaults(const TSharedPtr<FJsonObject>& Params)
|
|
494
|
+
{
|
|
495
|
+
FString BPPath;
|
|
496
|
+
if (auto Err = RequireString(Params, TEXT("blueprintPath"), BPPath)) return Err;
|
|
497
|
+
|
|
498
|
+
FString AttrSetSpec;
|
|
499
|
+
if (auto Err = RequireStringAlt(Params, TEXT("attributeSet"), TEXT("attributeSetPath"), AttrSetSpec)) return Err;
|
|
500
|
+
|
|
501
|
+
UBlueprint* BP = Cast<UBlueprint>(UEditorAssetLibrary::LoadAsset(BPPath));
|
|
502
|
+
if (!BP) return MCPError(FString::Printf(TEXT("Blueprint not found: %s"), *BPPath));
|
|
503
|
+
|
|
504
|
+
UClass* ASCClass = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.AbilitySystemComponent"));
|
|
505
|
+
if (!ASCClass) return MCPError(TEXT("AbilitySystemComponent not found. Enable GameplayAbilities plugin."));
|
|
506
|
+
|
|
507
|
+
// Resolve the AttributeSet class from a content path (BP generated class) or
|
|
508
|
+
// a native short name.
|
|
509
|
+
UClass* AttrSetClass = nullptr;
|
|
510
|
+
{
|
|
511
|
+
UClass* AttrBase = UAttributeSet::StaticClass();
|
|
512
|
+
auto Ok = [AttrBase](UClass* C) { return C && C->IsChildOf(AttrBase); };
|
|
513
|
+
if (AttrSetSpec.Contains(TEXT("/")))
|
|
514
|
+
{
|
|
515
|
+
if (UClass* C = LoadObject<UClass>(nullptr, *AttrSetSpec); Ok(C)) AttrSetClass = C;
|
|
516
|
+
if (!AttrSetClass)
|
|
517
|
+
{
|
|
518
|
+
FString AssetName;
|
|
519
|
+
AttrSetSpec.Split(TEXT("/"), nullptr, &AssetName, ESearchCase::CaseSensitive, ESearchDir::FromEnd);
|
|
520
|
+
if (UClass* C = LoadObject<UClass>(nullptr, *(AttrSetSpec + TEXT(".") + AssetName + TEXT("_C"))); Ok(C)) AttrSetClass = C;
|
|
521
|
+
}
|
|
522
|
+
if (!AttrSetClass)
|
|
523
|
+
{
|
|
524
|
+
if (UBlueprint* SetBP = Cast<UBlueprint>(UEditorAssetLibrary::LoadAsset(AttrSetSpec)))
|
|
525
|
+
{
|
|
526
|
+
if (Ok(SetBP->GeneratedClass)) AttrSetClass = SetBP->GeneratedClass;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
else if (UClass* C = FindClassByShortName(AttrSetSpec); Ok(C))
|
|
531
|
+
{
|
|
532
|
+
AttrSetClass = C;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (!AttrSetClass) return MCPError(FString::Printf(TEXT("AttributeSet class not found: %s"), *AttrSetSpec));
|
|
536
|
+
|
|
537
|
+
// Find the ASC component template on the blueprint's construction script.
|
|
538
|
+
const FString CompName = OptionalString(Params, TEXT("componentName"));
|
|
539
|
+
UAbilitySystemComponent* ASCTemplate = nullptr;
|
|
540
|
+
FString ResolvedComp;
|
|
541
|
+
if (BP->SimpleConstructionScript)
|
|
542
|
+
{
|
|
543
|
+
for (USCS_Node* N : BP->SimpleConstructionScript->GetAllNodes())
|
|
544
|
+
{
|
|
545
|
+
if (!N || !N->ComponentTemplate || !N->ComponentTemplate->IsA(ASCClass)) continue;
|
|
546
|
+
if (!CompName.IsEmpty() && N->GetVariableName() != FName(*CompName)) continue;
|
|
547
|
+
ASCTemplate = Cast<UAbilitySystemComponent>(N->ComponentTemplate);
|
|
548
|
+
ResolvedComp = N->GetVariableName().ToString();
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (!ASCTemplate)
|
|
553
|
+
{
|
|
554
|
+
return MCPError(TEXT("No AbilitySystemComponent on the blueprint - run add_ability_system_component first"));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Optional init DataTable (production path: starting values at ASC init).
|
|
558
|
+
UDataTable* InitTable = nullptr;
|
|
559
|
+
const FString TablePath = OptionalString(Params, TEXT("initDataTable"));
|
|
560
|
+
if (!TablePath.IsEmpty())
|
|
561
|
+
{
|
|
562
|
+
InitTable = LoadObject<UDataTable>(nullptr, *TablePath);
|
|
563
|
+
if (!InitTable) return MCPError(FString::Printf(TEXT("initDataTable not found: %s"), *TablePath));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Idempotency: already wired for this attribute set?
|
|
567
|
+
for (const FAttributeDefaults& D : ASCTemplate->DefaultStartingData)
|
|
568
|
+
{
|
|
569
|
+
if (D.Attributes == AttrSetClass)
|
|
570
|
+
{
|
|
571
|
+
auto Existed = MCPSuccess();
|
|
572
|
+
MCPSetExisted(Existed);
|
|
573
|
+
Existed->SetStringField(TEXT("blueprintPath"), BPPath);
|
|
574
|
+
Existed->SetStringField(TEXT("component"), ResolvedComp);
|
|
575
|
+
Existed->SetStringField(TEXT("attributeSet"), AttrSetClass->GetName());
|
|
576
|
+
return MCPResult(Existed);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
FAttributeDefaults Def;
|
|
581
|
+
Def.Attributes = AttrSetClass;
|
|
582
|
+
Def.DefaultStartingTable = InitTable;
|
|
583
|
+
ASCTemplate->DefaultStartingData.Add(Def);
|
|
584
|
+
|
|
585
|
+
FKismetEditorUtilities::CompileBlueprint(BP);
|
|
586
|
+
SaveAssetPackage(BP);
|
|
587
|
+
|
|
588
|
+
auto Result = MCPSuccess();
|
|
589
|
+
MCPSetCreated(Result);
|
|
590
|
+
Result->SetStringField(TEXT("blueprintPath"), BPPath);
|
|
591
|
+
Result->SetStringField(TEXT("component"), ResolvedComp);
|
|
592
|
+
Result->SetStringField(TEXT("attributeSet"), AttrSetClass->GetName());
|
|
593
|
+
if (InitTable) Result->SetStringField(TEXT("initDataTable"), InitTable->GetPathName());
|
|
594
|
+
Result->SetStringField(TEXT("note"),
|
|
595
|
+
TEXT("Attribute set wired to the ASC's DefaultStartingData. If attributes aren't live at runtime, call gas(action=\"init_asc\", attributeSet=...) after PIE starts."));
|
|
596
|
+
return MCPResult(Result);
|
|
597
|
+
}
|