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.
Files changed (54) hide show
  1. package/dist/context-cli.js +16 -16
  2. package/dist/engine-observer.js +68 -68
  3. package/dist/flow/guard.d.ts +26 -37
  4. package/dist/flow/guard.js +40 -25
  5. package/dist/flow/guard.js.map +1 -1
  6. package/dist/flow/guarded-bridge.d.ts +0 -10
  7. package/dist/flow/guarded-bridge.js +18 -23
  8. package/dist/flow/guarded-bridge.js.map +1 -1
  9. package/dist/flow/schema.d.ts +276 -16
  10. package/dist/flow/task-guards.d.ts +1 -1
  11. package/dist/flow/task-guards.js +44 -68
  12. package/dist/flow/task-guards.js.map +1 -1
  13. package/dist/instructions.js +130 -130
  14. package/dist/plugin-cli.js +299 -299
  15. package/dist/tool-counts.json +2 -2
  16. package/package.json +2 -2
  17. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.cpp +76 -76
  18. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.h +27 -27
  19. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +128 -128
  20. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +63 -63
  21. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +230 -230
  22. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +323 -323
  23. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +20 -20
  24. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ChooserHandlers_Nested.cpp +537 -537
  25. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +77 -77
  26. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +353 -353
  27. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +18 -18
  28. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_InputRuntime.cpp +246 -246
  29. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +597 -597
  30. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +51 -51
  31. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +878 -878
  32. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +449 -449
  33. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Function.cpp +275 -275
  34. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Graph.cpp +551 -551
  35. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +531 -531
  36. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +28 -28
  37. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +677 -677
  38. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +822 -822
  39. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +22 -22
  40. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +218 -218
  41. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +17 -17
  42. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/VolumeHelpers_Internal.cpp +46 -46
  43. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/VolumeHelpers_Internal.h +23 -23
  44. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/MCPExternalRegistry.cpp +83 -83
  45. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -14
  46. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/UE_MCP_BridgeStatusModule.cpp +37 -37
  47. package/dist/manifest-signature.d.ts +0 -1
  48. package/dist/manifest-signature.js +0 -53
  49. package/dist/manifest-signature.js.map +0 -1
  50. package/dist/pie/schema.d.ts +0 -791
  51. package/dist/pie/schema.js +0 -151
  52. package/dist/pie/schema.js.map +0 -1
  53. package/dist/watch-cli.js.map +0 -1
  54. /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
+ }