ue-mcp 1.0.69 → 1.0.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/tool-counts.json +5 -5
- package/dist/tools/gas.js +14 -0
- package/dist/tools/gas.js.map +1 -1
- package/dist/ue-mcp.default.yml +30 -0
- package/package.json +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +114 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +13 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Runtime.cpp +360 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# UE-MCP
|
|
2
2
|
|
|
3
|
-
**Unreal Engine Model Context Protocol Server** - gives AI assistants deep read/write access to the Unreal Editor through <!-- count:tools -->21<!-- /count --> category tools covering <!-- count:actions -->
|
|
3
|
+
**Unreal Engine Model Context Protocol Server** - gives AI assistants deep read/write access to the Unreal Editor through <!-- count:tools -->21<!-- /count --> category tools covering <!-- count:actions -->547+<!-- /count --> actions, plus a YAML flow engine for multi-step workflows.
|
|
4
4
|
|
|
5
5
|
```mermaid
|
|
6
6
|
flowchart LR
|
|
@@ -57,7 +57,7 @@ If you prefer to configure manually, add to your MCP client config:
|
|
|
57
57
|
|
|
58
58
|
- [Getting Started](https://db-lyon.github.io/ue-mcp/getting-started/) — Installation, configuration, first run
|
|
59
59
|
- [Architecture](https://db-lyon.github.io/ue-mcp/architecture/) — How the pieces fit together
|
|
60
|
-
- [Tool Reference](https://db-lyon.github.io/ue-mcp/tool-reference/) - All <!-- count:tools -->21<!-- /count --> tools with <!-- count:actions -->
|
|
60
|
+
- [Tool Reference](https://db-lyon.github.io/ue-mcp/tool-reference/) - All <!-- count:tools -->21<!-- /count --> tools with <!-- count:actions -->547+<!-- /count --> actions
|
|
61
61
|
- [Flows](https://db-lyon.github.io/ue-mcp/flows/) - YAML flow engine, custom tasks, rollback, hooks
|
|
62
62
|
- [Configuration](https://db-lyon.github.io/ue-mcp/configuration/) — `.ue-mcp.json` and MCP client config
|
|
63
63
|
- [Neon Shrine Demo](https://db-lyon.github.io/ue-mcp/neon-shrine-demo/) — Interactive guided demo
|
package/dist/tool-counts.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"tools": 21,
|
|
3
|
-
"actions":
|
|
4
|
-
"bridgeActions":
|
|
3
|
+
"actions": 547,
|
|
4
|
+
"bridgeActions": 519,
|
|
5
5
|
"localActions": 28,
|
|
6
6
|
"perTool": {
|
|
7
7
|
"project": 25,
|
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
"editor": 54,
|
|
20
20
|
"reflection": 8,
|
|
21
21
|
"gameplay": 53,
|
|
22
|
-
"gas":
|
|
22
|
+
"gas": 14,
|
|
23
23
|
"networking": 11,
|
|
24
24
|
"demo": 3,
|
|
25
25
|
"feedback": 1,
|
|
26
26
|
"statetree": 35,
|
|
27
27
|
"plugins": 2
|
|
28
28
|
},
|
|
29
|
-
"generatedAt": "2026-05-29T20:
|
|
30
|
-
"version": "1.0.
|
|
29
|
+
"generatedAt": "2026-05-29T20:48:42.832Z",
|
|
30
|
+
"version": "1.0.71"
|
|
31
31
|
}
|
package/dist/tools/gas.js
CHANGED
|
@@ -10,6 +10,11 @@ export const gasTool = categoryTool("gas", "Gameplay Ability System: abilities,
|
|
|
10
10
|
set_effect_modifier: bp("Add modifier. Params: effectPath, attribute, operation?, magnitude?", "set_effect_modifier"),
|
|
11
11
|
create_cue: bp("Create GameplayCue. Params: name, packagePath?, cueType?", "create_gameplay_cue"),
|
|
12
12
|
get_info: bp("Inspect GAS setup. Params: blueprintPath", "get_gas_info"),
|
|
13
|
+
set_asc_defaults: bp("Wire an AttributeSet onto a Blueprint's ASC component (DefaultStartingData) so attributes exist at runtime. Params: blueprintPath, attributeSet (content path or class name), componentName?, initDataTable? (starting values). Run add_ability_system_component first.", "set_asc_defaults"),
|
|
14
|
+
apply_effect: bp("Apply a GameplayEffect to a live actor's ASC (agnostic stat/damage stimulus - uses the game's own effect). Params: actorLabel, effectClass (content path or class name), level?, setByCaller? ({tag-or-name: magnitude}), world? (auto|pie|editor, default auto)", "apply_effect"),
|
|
15
|
+
set_attribute: bp("Set a gameplay attribute's base value on a live actor's ASC (recalculates CurrentValue through the aggregator). Params: actorLabel, attribute (Health | SetName.Health), value, world?", "set_attribute"),
|
|
16
|
+
get_attribute: bp("Read gameplay attribute base + current values on a live actor's ASC. Omit attribute to list all. Params: actorLabel, attribute?, world?", "get_attribute"),
|
|
17
|
+
init_asc: bp("Initialize a live actor's ASC (InitAbilityActorInfo) and optionally instantiate an AttributeSet so attributes are live - the runtime setup step for testing a bridge-authored GAS actor. Params: actorLabel, attributeSet? (content path or class name), world?", "init_asc"),
|
|
13
18
|
}, undefined, {
|
|
14
19
|
blueprintPath: z.string().optional(),
|
|
15
20
|
name: z.string().optional(),
|
|
@@ -31,5 +36,14 @@ export const gasTool = categoryTool("gas", "Gameplay Ability System: abilities,
|
|
|
31
36
|
magnitude: z.number().optional(),
|
|
32
37
|
durationPolicy: z.string().optional(),
|
|
33
38
|
cueType: z.string().optional(),
|
|
39
|
+
// Runtime GAS control (apply_effect / set_attribute / get_attribute)
|
|
40
|
+
actorLabel: z.string().optional().describe("Live actor label/name for runtime GAS actions"),
|
|
41
|
+
effectClass: z.string().optional().describe("apply_effect: GameplayEffect content path or class name"),
|
|
42
|
+
level: z.number().optional().describe("apply_effect: effect level (default 1)"),
|
|
43
|
+
setByCaller: z.record(z.number()).optional().describe("apply_effect: SetByCaller magnitudes keyed by gameplay tag or name"),
|
|
44
|
+
value: z.number().optional().describe("set_attribute: new base value"),
|
|
45
|
+
world: z.string().optional().describe("Runtime world scope: auto (default) | pie | editor"),
|
|
46
|
+
attributeSet: z.string().optional().describe("set_asc_defaults / init_asc: AttributeSet content path or class name"),
|
|
47
|
+
initDataTable: z.string().optional().describe("set_asc_defaults: optional DataTable of starting attribute values"),
|
|
34
48
|
});
|
|
35
49
|
//# sourceMappingURL=gas.js.map
|
package/dist/tools/gas.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gas.js","sourceRoot":"","sources":["../../src/tools/gas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,EAAE,EAAgB,MAAM,aAAa,CAAC;AAE7D,MAAM,CAAC,MAAM,OAAO,GAAY,YAAY,CAC1C,KAAK,EACL,oEAAoE,EACpE;IACE,OAAO,EAAc,EAAE,CAAC,mEAAmE,EAAE,8BAA8B,CAAC;IAC5H,oBAAoB,EAAE,EAAE,CAAC,oDAAoD,EAAE,sBAAsB,CAAC;IACtG,aAAa,EAAQ,EAAE,CAAC,8EAA8E,EAAE,eAAe,CAAC;IACxH,cAAc,EAAO,EAAE,CAAC,qEAAqE,EAAE,yBAAyB,CAAC;IACzH,gBAAgB,EAAK,EAAE,CAAC,0IAA0I,EAAE,kBAAkB,CAAC;IACvL,aAAa,EAAQ,EAAE,CAAC,uEAAuE,EAAE,wBAAwB,CAAC;IAC1H,mBAAmB,EAAE,EAAE,CAAC,qEAAqE,EAAE,qBAAqB,CAAC;IACrH,UAAU,EAAW,EAAE,CAAC,0DAA0D,EAAE,qBAAqB,CAAC;IAC1G,QAAQ,EAAa,EAAE,CAAC,0CAA0C,EAAE,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"gas.js","sourceRoot":"","sources":["../../src/tools/gas.ts"],"names":[],"mappings":"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"}
|
package/dist/ue-mcp.default.yml
CHANGED
|
@@ -2927,6 +2927,36 @@ tasks:
|
|
|
2927
2927
|
description: "Inspect GAS setup. Params: blueprintPath"
|
|
2928
2928
|
options:
|
|
2929
2929
|
method: get_gas_info
|
|
2930
|
+
gas.set_asc_defaults:
|
|
2931
|
+
class_path: ue-mcp.bridge
|
|
2932
|
+
group: gas
|
|
2933
|
+
description: "Wire an AttributeSet onto a Blueprint's ASC component (DefaultStartingData) so attributes exist at runtime. Params: blueprintPath, attributeSet (content path or class name), componentName?, initDataTable? (starting values). Run add_ability_system_component first."
|
|
2934
|
+
options:
|
|
2935
|
+
method: set_asc_defaults
|
|
2936
|
+
gas.apply_effect:
|
|
2937
|
+
class_path: ue-mcp.bridge
|
|
2938
|
+
group: gas
|
|
2939
|
+
description: "Apply a GameplayEffect to a live actor's ASC (agnostic stat/damage stimulus - uses the game's own effect). Params: actorLabel, effectClass (content path or class name), level?, setByCaller? ({tag-or-name: magnitude}), world? (auto|pie|editor, default auto)"
|
|
2940
|
+
options:
|
|
2941
|
+
method: apply_effect
|
|
2942
|
+
gas.set_attribute:
|
|
2943
|
+
class_path: ue-mcp.bridge
|
|
2944
|
+
group: gas
|
|
2945
|
+
description: "Set a gameplay attribute's base value on a live actor's ASC (recalculates CurrentValue through the aggregator). Params: actorLabel, attribute (Health | SetName.Health), value, world?"
|
|
2946
|
+
options:
|
|
2947
|
+
method: set_attribute
|
|
2948
|
+
gas.get_attribute:
|
|
2949
|
+
class_path: ue-mcp.bridge
|
|
2950
|
+
group: gas
|
|
2951
|
+
description: "Read gameplay attribute base + current values on a live actor's ASC. Omit attribute to list all. Params: actorLabel, attribute?, world?"
|
|
2952
|
+
options:
|
|
2953
|
+
method: get_attribute
|
|
2954
|
+
gas.init_asc:
|
|
2955
|
+
class_path: ue-mcp.bridge
|
|
2956
|
+
group: gas
|
|
2957
|
+
description: "Initialize a live actor's ASC (InitAbilityActorInfo) and optionally instantiate an AttributeSet so attributes are live - the runtime setup step for testing a bridge-authored GAS actor. Params: actorLabel, attributeSet? (content path or class name), world?"
|
|
2958
|
+
options:
|
|
2959
|
+
method: init_asc
|
|
2930
2960
|
|
|
2931
2961
|
# ── networking ──
|
|
2932
2962
|
networking.set_replicates:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ue-mcp",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Unreal Engine MCP server - 21 tools,
|
|
3
|
+
"version": "1.0.71",
|
|
4
|
+
"description": "Unreal Engine MCP server - 21 tools, 547+ actions for AI-driven editor control",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
#include "Engine/SimpleConstructionScript.h"
|
|
22
22
|
#include "Engine/SCS_Node.h"
|
|
23
23
|
#include "EdGraphSchema_K2.h"
|
|
24
|
+
#include "AbilitySystemComponent.h"
|
|
25
|
+
#include "AttributeSet.h"
|
|
26
|
+
#include "Engine/DataTable.h"
|
|
24
27
|
|
|
25
28
|
void FGasHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
26
29
|
{
|
|
@@ -33,6 +36,11 @@ void FGasHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
|
33
36
|
Registry.RegisterHandler(TEXT("add_attribute"), &AddAttribute);
|
|
34
37
|
Registry.RegisterHandler(TEXT("set_ability_tags"), &SetAbilityTags);
|
|
35
38
|
Registry.RegisterHandler(TEXT("set_effect_modifier"), &SetEffectModifier);
|
|
39
|
+
Registry.RegisterHandler(TEXT("set_asc_defaults"), &SetAscDefaults);
|
|
40
|
+
Registry.RegisterHandler(TEXT("apply_effect"), &ApplyEffect);
|
|
41
|
+
Registry.RegisterHandler(TEXT("set_attribute"), &SetAttribute);
|
|
42
|
+
Registry.RegisterHandler(TEXT("get_attribute"), &GetAttribute);
|
|
43
|
+
Registry.RegisterHandler(TEXT("init_asc"), &InitAsc);
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
TSharedPtr<FJsonValue> FGasHandlers::CreateGasBlueprint(
|
|
@@ -373,3 +381,109 @@ TSharedPtr<FJsonValue> FGasHandlers::SetEffectModifier(const TSharedPtr<FJsonObj
|
|
|
373
381
|
Result->SetStringField(TEXT("note"), TEXT("GameplayEffect modifier configuration set. Use execute_python for full GE modifier array manipulation."));
|
|
374
382
|
return MCPResult(Result);
|
|
375
383
|
}
|
|
384
|
+
|
|
385
|
+
TSharedPtr<FJsonValue> FGasHandlers::SetAscDefaults(const TSharedPtr<FJsonObject>& Params)
|
|
386
|
+
{
|
|
387
|
+
FString BPPath;
|
|
388
|
+
if (auto Err = RequireString(Params, TEXT("blueprintPath"), BPPath)) return Err;
|
|
389
|
+
|
|
390
|
+
FString AttrSetSpec;
|
|
391
|
+
if (auto Err = RequireStringAlt(Params, TEXT("attributeSet"), TEXT("attributeSetPath"), AttrSetSpec)) return Err;
|
|
392
|
+
|
|
393
|
+
UBlueprint* BP = Cast<UBlueprint>(UEditorAssetLibrary::LoadAsset(BPPath));
|
|
394
|
+
if (!BP) return MCPError(FString::Printf(TEXT("Blueprint not found: %s"), *BPPath));
|
|
395
|
+
|
|
396
|
+
UClass* ASCClass = FindObject<UClass>(nullptr, TEXT("/Script/GameplayAbilities.AbilitySystemComponent"));
|
|
397
|
+
if (!ASCClass) return MCPError(TEXT("AbilitySystemComponent not found. Enable GameplayAbilities plugin."));
|
|
398
|
+
|
|
399
|
+
// Resolve the AttributeSet class from a content path (BP generated class) or
|
|
400
|
+
// a native short name.
|
|
401
|
+
UClass* AttrSetClass = nullptr;
|
|
402
|
+
{
|
|
403
|
+
UClass* AttrBase = UAttributeSet::StaticClass();
|
|
404
|
+
auto Ok = [AttrBase](UClass* C) { return C && C->IsChildOf(AttrBase); };
|
|
405
|
+
if (AttrSetSpec.Contains(TEXT("/")))
|
|
406
|
+
{
|
|
407
|
+
if (UClass* C = LoadObject<UClass>(nullptr, *AttrSetSpec); Ok(C)) AttrSetClass = C;
|
|
408
|
+
if (!AttrSetClass)
|
|
409
|
+
{
|
|
410
|
+
FString AssetName;
|
|
411
|
+
AttrSetSpec.Split(TEXT("/"), nullptr, &AssetName, ESearchCase::CaseSensitive, ESearchDir::FromEnd);
|
|
412
|
+
if (UClass* C = LoadObject<UClass>(nullptr, *(AttrSetSpec + TEXT(".") + AssetName + TEXT("_C"))); Ok(C)) AttrSetClass = C;
|
|
413
|
+
}
|
|
414
|
+
if (!AttrSetClass)
|
|
415
|
+
{
|
|
416
|
+
if (UBlueprint* SetBP = Cast<UBlueprint>(UEditorAssetLibrary::LoadAsset(AttrSetSpec)))
|
|
417
|
+
{
|
|
418
|
+
if (Ok(SetBP->GeneratedClass)) AttrSetClass = SetBP->GeneratedClass;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
else if (UClass* C = FindClassByShortName(AttrSetSpec); Ok(C))
|
|
423
|
+
{
|
|
424
|
+
AttrSetClass = C;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (!AttrSetClass) return MCPError(FString::Printf(TEXT("AttributeSet class not found: %s"), *AttrSetSpec));
|
|
428
|
+
|
|
429
|
+
// Find the ASC component template on the blueprint's construction script.
|
|
430
|
+
const FString CompName = OptionalString(Params, TEXT("componentName"));
|
|
431
|
+
UAbilitySystemComponent* ASCTemplate = nullptr;
|
|
432
|
+
FString ResolvedComp;
|
|
433
|
+
if (BP->SimpleConstructionScript)
|
|
434
|
+
{
|
|
435
|
+
for (USCS_Node* N : BP->SimpleConstructionScript->GetAllNodes())
|
|
436
|
+
{
|
|
437
|
+
if (!N || !N->ComponentTemplate || !N->ComponentTemplate->IsA(ASCClass)) continue;
|
|
438
|
+
if (!CompName.IsEmpty() && N->GetVariableName() != FName(*CompName)) continue;
|
|
439
|
+
ASCTemplate = Cast<UAbilitySystemComponent>(N->ComponentTemplate);
|
|
440
|
+
ResolvedComp = N->GetVariableName().ToString();
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (!ASCTemplate)
|
|
445
|
+
{
|
|
446
|
+
return MCPError(TEXT("No AbilitySystemComponent on the blueprint - run add_ability_system_component first"));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Optional init DataTable (production path: starting values at ASC init).
|
|
450
|
+
UDataTable* InitTable = nullptr;
|
|
451
|
+
const FString TablePath = OptionalString(Params, TEXT("initDataTable"));
|
|
452
|
+
if (!TablePath.IsEmpty())
|
|
453
|
+
{
|
|
454
|
+
InitTable = LoadObject<UDataTable>(nullptr, *TablePath);
|
|
455
|
+
if (!InitTable) return MCPError(FString::Printf(TEXT("initDataTable not found: %s"), *TablePath));
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Idempotency: already wired for this attribute set?
|
|
459
|
+
for (const FAttributeDefaults& D : ASCTemplate->DefaultStartingData)
|
|
460
|
+
{
|
|
461
|
+
if (D.Attributes == AttrSetClass)
|
|
462
|
+
{
|
|
463
|
+
auto Existed = MCPSuccess();
|
|
464
|
+
MCPSetExisted(Existed);
|
|
465
|
+
Existed->SetStringField(TEXT("blueprintPath"), BPPath);
|
|
466
|
+
Existed->SetStringField(TEXT("component"), ResolvedComp);
|
|
467
|
+
Existed->SetStringField(TEXT("attributeSet"), AttrSetClass->GetName());
|
|
468
|
+
return MCPResult(Existed);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
FAttributeDefaults Def;
|
|
473
|
+
Def.Attributes = AttrSetClass;
|
|
474
|
+
Def.DefaultStartingTable = InitTable;
|
|
475
|
+
ASCTemplate->DefaultStartingData.Add(Def);
|
|
476
|
+
|
|
477
|
+
FKismetEditorUtilities::CompileBlueprint(BP);
|
|
478
|
+
SaveAssetPackage(BP);
|
|
479
|
+
|
|
480
|
+
auto Result = MCPSuccess();
|
|
481
|
+
MCPSetCreated(Result);
|
|
482
|
+
Result->SetStringField(TEXT("blueprintPath"), BPPath);
|
|
483
|
+
Result->SetStringField(TEXT("component"), ResolvedComp);
|
|
484
|
+
Result->SetStringField(TEXT("attributeSet"), AttrSetClass->GetName());
|
|
485
|
+
if (InitTable) Result->SetStringField(TEXT("initDataTable"), InitTable->GetPathName());
|
|
486
|
+
Result->SetStringField(TEXT("note"),
|
|
487
|
+
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."));
|
|
488
|
+
return MCPResult(Result);
|
|
489
|
+
}
|
|
@@ -32,4 +32,17 @@ private:
|
|
|
32
32
|
static TSharedPtr<FJsonValue> AddAttribute(const TSharedPtr<FJsonObject>& Params);
|
|
33
33
|
static TSharedPtr<FJsonValue> SetAbilityTags(const TSharedPtr<FJsonObject>& Params);
|
|
34
34
|
static TSharedPtr<FJsonValue> SetEffectModifier(const TSharedPtr<FJsonObject>& Params);
|
|
35
|
+
|
|
36
|
+
// Wire an AttributeSet (with optional init DataTable) onto a Blueprint's ASC
|
|
37
|
+
// component template via DefaultStartingData. Authoring; in GasHandlers.cpp.
|
|
38
|
+
static TSharedPtr<FJsonValue> SetAscDefaults(const TSharedPtr<FJsonObject>& Params);
|
|
39
|
+
|
|
40
|
+
// Runtime GAS control (operates on a live actor's AbilitySystemComponent,
|
|
41
|
+
// PIE by default). Implemented in GasHandlers_Runtime.cpp.
|
|
42
|
+
static TSharedPtr<FJsonValue> ApplyEffect(const TSharedPtr<FJsonObject>& Params);
|
|
43
|
+
static TSharedPtr<FJsonValue> SetAttribute(const TSharedPtr<FJsonObject>& Params);
|
|
44
|
+
static TSharedPtr<FJsonValue> GetAttribute(const TSharedPtr<FJsonObject>& Params);
|
|
45
|
+
// InitAbilityActorInfo + optionally GetOrCreateAttributeSubobject on a live
|
|
46
|
+
// actor, so a bridge-authored GAS actor has live attributes to test against.
|
|
47
|
+
static TSharedPtr<FJsonValue> InitAsc(const TSharedPtr<FJsonObject>& Params);
|
|
35
48
|
};
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
// Runtime GAS control: apply a GameplayEffect, and get/set attributes on a
|
|
2
|
+
// live actor's AbilitySystemComponent. These are the agnostic "affect a stat"
|
|
3
|
+
// test stimuli - they drive the game's OWN effects and attributes rather than
|
|
4
|
+
// assuming a damage pipeline, so they work for any GAS game. Non-GAS games set
|
|
5
|
+
// reflection-exposed stats via level.set_actor_property or call their own
|
|
6
|
+
// functions via editor.invoke_function instead.
|
|
7
|
+
|
|
8
|
+
#include "GasHandlers.h"
|
|
9
|
+
#include "HandlerUtils.h"
|
|
10
|
+
#include "Dom/JsonObject.h"
|
|
11
|
+
#include "Dom/JsonValue.h"
|
|
12
|
+
#include "GameFramework/Actor.h"
|
|
13
|
+
#include "AbilitySystemComponent.h"
|
|
14
|
+
#include "AbilitySystemBlueprintLibrary.h"
|
|
15
|
+
#include "AttributeSet.h"
|
|
16
|
+
#include "GameplayEffect.h"
|
|
17
|
+
#include "GameplayEffectTypes.h"
|
|
18
|
+
#include "GameplayTagContainer.h"
|
|
19
|
+
|
|
20
|
+
namespace
|
|
21
|
+
{
|
|
22
|
+
// Resolve the world for this call. Defaults to "auto" (prefer PIE), since
|
|
23
|
+
// runtime GAS control is almost always exercised during Play-In-Editor.
|
|
24
|
+
UWorld* ResolveRuntimeWorld(const TSharedPtr<FJsonObject>& Params)
|
|
25
|
+
{
|
|
26
|
+
return ResolveWorldScope(OptionalString(Params, TEXT("world"), TEXT("auto")));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Find the actor (by label or name) in the resolved world and return its
|
|
30
|
+
// AbilitySystemComponent. On any failure writes a structured error to
|
|
31
|
+
// OutError and returns nullptr.
|
|
32
|
+
UAbilitySystemComponent* ResolveASC(
|
|
33
|
+
const TSharedPtr<FJsonObject>& Params,
|
|
34
|
+
AActor*& OutActor,
|
|
35
|
+
TSharedPtr<FJsonValue>& OutError)
|
|
36
|
+
{
|
|
37
|
+
FString ActorLabel;
|
|
38
|
+
if (auto Err = RequireString(Params, TEXT("actorLabel"), ActorLabel))
|
|
39
|
+
{
|
|
40
|
+
OutError = Err;
|
|
41
|
+
return nullptr;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
UWorld* World = ResolveRuntimeWorld(Params);
|
|
45
|
+
if (!World)
|
|
46
|
+
{
|
|
47
|
+
OutError = MCPError(TEXT("No world available. For PIE actors, start Play-In-Editor first."));
|
|
48
|
+
return nullptr;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
AActor* Actor = FindActorByLabelOrName(World, ActorLabel);
|
|
52
|
+
if (!Actor)
|
|
53
|
+
{
|
|
54
|
+
OutError = MCPError(FString::Printf(TEXT("Actor not found: %s"), *ActorLabel));
|
|
55
|
+
return nullptr;
|
|
56
|
+
}
|
|
57
|
+
OutActor = Actor;
|
|
58
|
+
|
|
59
|
+
UAbilitySystemComponent* ASC =
|
|
60
|
+
UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Actor);
|
|
61
|
+
if (!ASC)
|
|
62
|
+
{
|
|
63
|
+
OutError = MCPError(FString::Printf(
|
|
64
|
+
TEXT("Actor '%s' has no AbilitySystemComponent (not a GAS actor)"), *ActorLabel));
|
|
65
|
+
return nullptr;
|
|
66
|
+
}
|
|
67
|
+
return ASC;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Resolve a FGameplayAttribute by name against the ASC's spawned attribute
|
|
71
|
+
// sets. Accepts a bare property name ("Health") or qualified forms
|
|
72
|
+
// ("HealthSet.Health" / "HealthSet:Health"). Returns an invalid attribute
|
|
73
|
+
// on miss; writes the matched set name to OutSetName on hit.
|
|
74
|
+
FGameplayAttribute FindAttributeByName(
|
|
75
|
+
UAbilitySystemComponent* ASC,
|
|
76
|
+
const FString& Name,
|
|
77
|
+
FString& OutSetName)
|
|
78
|
+
{
|
|
79
|
+
for (const UAttributeSet* Set : ASC->GetSpawnedAttributes())
|
|
80
|
+
{
|
|
81
|
+
if (!Set) continue;
|
|
82
|
+
UClass* SetClass = Set->GetClass();
|
|
83
|
+
const FString SetName = SetClass->GetName();
|
|
84
|
+
for (TFieldIterator<FProperty> It(SetClass); It; ++It)
|
|
85
|
+
{
|
|
86
|
+
FStructProperty* SProp = CastField<FStructProperty>(*It);
|
|
87
|
+
if (!SProp || SProp->Struct != FGameplayAttributeData::StaticStruct()) continue;
|
|
88
|
+
const FString PropName = SProp->GetName();
|
|
89
|
+
if (PropName == Name
|
|
90
|
+
|| (SetName + TEXT(".") + PropName) == Name
|
|
91
|
+
|| (SetName + TEXT(":") + PropName) == Name)
|
|
92
|
+
{
|
|
93
|
+
OutSetName = SetName;
|
|
94
|
+
return FGameplayAttribute(SProp);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return FGameplayAttribute();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Append one attribute's name/base/current to a JSON object.
|
|
102
|
+
void WriteAttributeRow(
|
|
103
|
+
TSharedPtr<FJsonObject> Obj,
|
|
104
|
+
UAbilitySystemComponent* ASC,
|
|
105
|
+
const FGameplayAttribute& Attr)
|
|
106
|
+
{
|
|
107
|
+
Obj->SetStringField(TEXT("attribute"), Attr.GetName());
|
|
108
|
+
Obj->SetNumberField(TEXT("baseValue"), ASC->GetNumericAttributeBase(Attr));
|
|
109
|
+
Obj->SetNumberField(TEXT("currentValue"), ASC->GetNumericAttribute(Attr));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Resolve a UClass deriving from Base from a content path or short class name.
|
|
113
|
+
// Handles native classes, Blueprint generated classes (path + "_C"), and a
|
|
114
|
+
// Blueprint-asset fallback. Returns nullptr unless the result is a Base subclass.
|
|
115
|
+
UClass* ResolveClassDeriving(const FString& Spec, UClass* Base)
|
|
116
|
+
{
|
|
117
|
+
auto Ok = [Base](UClass* C) { return C && Base && C->IsChildOf(Base); };
|
|
118
|
+
|
|
119
|
+
if (Spec.Contains(TEXT("/")))
|
|
120
|
+
{
|
|
121
|
+
if (UClass* C = LoadObject<UClass>(nullptr, *Spec); Ok(C)) return C;
|
|
122
|
+
FString AssetName;
|
|
123
|
+
Spec.Split(TEXT("/"), nullptr, &AssetName, ESearchCase::CaseSensitive, ESearchDir::FromEnd);
|
|
124
|
+
const FString ClassPath = Spec + TEXT(".") + AssetName + TEXT("_C");
|
|
125
|
+
if (UClass* C = LoadObject<UClass>(nullptr, *ClassPath); Ok(C)) return C;
|
|
126
|
+
if (UBlueprint* BP = LoadAssetByPath<UBlueprint>(Spec))
|
|
127
|
+
{
|
|
128
|
+
if (Ok(BP->GeneratedClass)) return BP->GeneratedClass;
|
|
129
|
+
}
|
|
130
|
+
return nullptr;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
UClass* C = FindClassByShortName(Spec);
|
|
134
|
+
return Ok(C) ? C : nullptr;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
TSharedPtr<FJsonValue> FGasHandlers::ApplyEffect(const TSharedPtr<FJsonObject>& Params)
|
|
139
|
+
{
|
|
140
|
+
MCP_CHECK_GAME_THREAD();
|
|
141
|
+
|
|
142
|
+
FString EffectSpec;
|
|
143
|
+
if (auto Err = RequireStringAlt(Params, TEXT("effectClass"), TEXT("effectPath"), EffectSpec)) return Err;
|
|
144
|
+
|
|
145
|
+
AActor* Actor = nullptr;
|
|
146
|
+
TSharedPtr<FJsonValue> Err;
|
|
147
|
+
UAbilitySystemComponent* ASC = ResolveASC(Params, Actor, Err);
|
|
148
|
+
if (!ASC) return Err;
|
|
149
|
+
|
|
150
|
+
UClass* EffectClass = ResolveClassDeriving(EffectSpec, UGameplayEffect::StaticClass());
|
|
151
|
+
if (!EffectClass)
|
|
152
|
+
{
|
|
153
|
+
return MCPError(FString::Printf(
|
|
154
|
+
TEXT("GameplayEffect class not found: %s (pass a content path or class name)"), *EffectSpec));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const float Level = static_cast<float>(OptionalNumber(Params, TEXT("level"), 1.0));
|
|
158
|
+
|
|
159
|
+
FGameplayEffectContextHandle Context = ASC->MakeEffectContext();
|
|
160
|
+
Context.AddInstigator(Actor, Actor);
|
|
161
|
+
FGameplayEffectSpecHandle SpecHandle = ASC->MakeOutgoingSpec(EffectClass, Level, Context);
|
|
162
|
+
if (!SpecHandle.IsValid() || !SpecHandle.Data.IsValid())
|
|
163
|
+
{
|
|
164
|
+
return MCPError(TEXT("Failed to build a GameplayEffectSpec for the effect"));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// SetByCaller magnitudes: { "<tag-or-name>": <number> }. Prefer a gameplay
|
|
168
|
+
// tag when the key resolves to one; otherwise use the FName overload.
|
|
169
|
+
const TSharedPtr<FJsonObject>* SetByCaller = nullptr;
|
|
170
|
+
TArray<FString> AppliedKeys;
|
|
171
|
+
if (Params->TryGetObjectField(TEXT("setByCaller"), SetByCaller) && SetByCaller && (*SetByCaller).IsValid())
|
|
172
|
+
{
|
|
173
|
+
for (const auto& KV : (*SetByCaller)->Values)
|
|
174
|
+
{
|
|
175
|
+
double Mag = 0.0;
|
|
176
|
+
if (!KV.Value.IsValid() || !KV.Value->TryGetNumber(Mag)) continue;
|
|
177
|
+
const FGameplayTag Tag = FGameplayTag::RequestGameplayTag(FName(*KV.Key), /*ErrorIfNotFound*/ false);
|
|
178
|
+
if (Tag.IsValid())
|
|
179
|
+
{
|
|
180
|
+
SpecHandle.Data->SetSetByCallerMagnitude(Tag, static_cast<float>(Mag));
|
|
181
|
+
}
|
|
182
|
+
else
|
|
183
|
+
{
|
|
184
|
+
SpecHandle.Data->SetSetByCallerMagnitude(FName(*KV.Key), static_cast<float>(Mag));
|
|
185
|
+
}
|
|
186
|
+
AppliedKeys.Add(KV.Key);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const FActiveGameplayEffectHandle Active = ASC->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data);
|
|
191
|
+
|
|
192
|
+
auto Result = MCPSuccess();
|
|
193
|
+
Result->SetStringField(TEXT("actorLabel"), Actor->GetActorLabel());
|
|
194
|
+
Result->SetStringField(TEXT("effect"), EffectClass->GetPathName());
|
|
195
|
+
Result->SetNumberField(TEXT("level"), Level);
|
|
196
|
+
Result->SetBoolField(TEXT("applied"), Active.WasSuccessfullyApplied());
|
|
197
|
+
// Duration/Infinite effects produce a live handle; instant effects don't.
|
|
198
|
+
Result->SetBoolField(TEXT("durationActive"), Active.IsValid());
|
|
199
|
+
if (AppliedKeys.Num() > 0)
|
|
200
|
+
{
|
|
201
|
+
TArray<TSharedPtr<FJsonValue>> Keys;
|
|
202
|
+
for (const FString& K : AppliedKeys) Keys.Add(MakeShared<FJsonValueString>(K));
|
|
203
|
+
Result->SetArrayField(TEXT("setByCaller"), Keys);
|
|
204
|
+
}
|
|
205
|
+
return MCPResult(Result);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
TSharedPtr<FJsonValue> FGasHandlers::SetAttribute(const TSharedPtr<FJsonObject>& Params)
|
|
209
|
+
{
|
|
210
|
+
MCP_CHECK_GAME_THREAD();
|
|
211
|
+
|
|
212
|
+
FString AttrName;
|
|
213
|
+
if (auto Err = RequireString(Params, TEXT("attribute"), AttrName)) return Err;
|
|
214
|
+
|
|
215
|
+
double NewValue = 0.0;
|
|
216
|
+
if (!Params->TryGetNumberField(TEXT("value"), NewValue))
|
|
217
|
+
{
|
|
218
|
+
return MCPError(TEXT("Missing required parameter 'value'"));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
AActor* Actor = nullptr;
|
|
222
|
+
TSharedPtr<FJsonValue> Err;
|
|
223
|
+
UAbilitySystemComponent* ASC = ResolveASC(Params, Actor, Err);
|
|
224
|
+
if (!ASC) return Err;
|
|
225
|
+
|
|
226
|
+
FString SetName;
|
|
227
|
+
const FGameplayAttribute Attr = FindAttributeByName(ASC, AttrName, SetName);
|
|
228
|
+
if (!Attr.IsValid())
|
|
229
|
+
{
|
|
230
|
+
return MCPError(FString::Printf(
|
|
231
|
+
TEXT("Attribute '%s' not found on '%s'. Use get_attribute with no 'attribute' to list available ones."),
|
|
232
|
+
*AttrName, *Actor->GetActorLabel()));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const float OldBase = ASC->GetNumericAttributeBase(Attr);
|
|
236
|
+
// SetNumericAttributeBase recalculates CurrentValue through the aggregator,
|
|
237
|
+
// so dependent modifiers stay consistent (unlike a raw property write).
|
|
238
|
+
ASC->SetNumericAttributeBase(Attr, static_cast<float>(NewValue));
|
|
239
|
+
|
|
240
|
+
auto Result = MCPSuccess();
|
|
241
|
+
MCPSetUpdated(Result);
|
|
242
|
+
Result->SetStringField(TEXT("actorLabel"), Actor->GetActorLabel());
|
|
243
|
+
Result->SetStringField(TEXT("attributeSet"), SetName);
|
|
244
|
+
Result->SetStringField(TEXT("attribute"), Attr.GetName());
|
|
245
|
+
Result->SetNumberField(TEXT("previousBaseValue"), OldBase);
|
|
246
|
+
Result->SetNumberField(TEXT("baseValue"), ASC->GetNumericAttributeBase(Attr));
|
|
247
|
+
Result->SetNumberField(TEXT("currentValue"), ASC->GetNumericAttribute(Attr));
|
|
248
|
+
return MCPResult(Result);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
TSharedPtr<FJsonValue> FGasHandlers::GetAttribute(const TSharedPtr<FJsonObject>& Params)
|
|
252
|
+
{
|
|
253
|
+
MCP_CHECK_GAME_THREAD();
|
|
254
|
+
|
|
255
|
+
AActor* Actor = nullptr;
|
|
256
|
+
TSharedPtr<FJsonValue> Err;
|
|
257
|
+
UAbilitySystemComponent* ASC = ResolveASC(Params, Actor, Err);
|
|
258
|
+
if (!ASC) return Err;
|
|
259
|
+
|
|
260
|
+
auto Result = MCPSuccess();
|
|
261
|
+
Result->SetStringField(TEXT("actorLabel"), Actor->GetActorLabel());
|
|
262
|
+
|
|
263
|
+
const FString AttrName = OptionalString(Params, TEXT("attribute"));
|
|
264
|
+
if (!AttrName.IsEmpty())
|
|
265
|
+
{
|
|
266
|
+
FString SetName;
|
|
267
|
+
const FGameplayAttribute Attr = FindAttributeByName(ASC, AttrName, SetName);
|
|
268
|
+
if (!Attr.IsValid())
|
|
269
|
+
{
|
|
270
|
+
return MCPError(FString::Printf(
|
|
271
|
+
TEXT("Attribute '%s' not found on '%s'"), *AttrName, *Actor->GetActorLabel()));
|
|
272
|
+
}
|
|
273
|
+
Result->SetStringField(TEXT("attributeSet"), SetName);
|
|
274
|
+
WriteAttributeRow(Result, ASC, Attr);
|
|
275
|
+
return MCPResult(Result);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// No attribute named: enumerate every attribute across all spawned sets.
|
|
279
|
+
TArray<TSharedPtr<FJsonValue>> Rows;
|
|
280
|
+
for (const UAttributeSet* Set : ASC->GetSpawnedAttributes())
|
|
281
|
+
{
|
|
282
|
+
if (!Set) continue;
|
|
283
|
+
UClass* SetClass = Set->GetClass();
|
|
284
|
+
const FString SetName = SetClass->GetName();
|
|
285
|
+
for (TFieldIterator<FProperty> It(SetClass); It; ++It)
|
|
286
|
+
{
|
|
287
|
+
FStructProperty* SProp = CastField<FStructProperty>(*It);
|
|
288
|
+
if (!SProp || SProp->Struct != FGameplayAttributeData::StaticStruct()) continue;
|
|
289
|
+
const FGameplayAttribute Attr(SProp);
|
|
290
|
+
TSharedPtr<FJsonObject> Row = MakeShared<FJsonObject>();
|
|
291
|
+
Row->SetStringField(TEXT("attributeSet"), SetName);
|
|
292
|
+
WriteAttributeRow(Row, ASC, Attr);
|
|
293
|
+
Rows.Add(MakeShared<FJsonValueObject>(Row));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
Result->SetArrayField(TEXT("attributes"), Rows);
|
|
297
|
+
Result->SetNumberField(TEXT("count"), Rows.Num());
|
|
298
|
+
return MCPResult(Result);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
TSharedPtr<FJsonValue> FGasHandlers::InitAsc(const TSharedPtr<FJsonObject>& Params)
|
|
302
|
+
{
|
|
303
|
+
MCP_CHECK_GAME_THREAD();
|
|
304
|
+
|
|
305
|
+
AActor* Actor = nullptr;
|
|
306
|
+
TSharedPtr<FJsonValue> Err;
|
|
307
|
+
UAbilitySystemComponent* ASC = ResolveASC(Params, Actor, Err);
|
|
308
|
+
if (!ASC) return Err;
|
|
309
|
+
|
|
310
|
+
// Establish owner/avatar so abilities activate and effect contexts target
|
|
311
|
+
// correctly. Safe to call again; a game's own pawn may also init the ASC.
|
|
312
|
+
ASC->InitAbilityActorInfo(Actor, Actor);
|
|
313
|
+
|
|
314
|
+
// Optionally guarantee an attribute set exists on the ASC. This is what lets
|
|
315
|
+
// a bridge-authored test actor have live attributes without shipping an init
|
|
316
|
+
// DataTable: spawn the set (with its default values) and register it if it
|
|
317
|
+
// isn't already present. GetOrCreateAttributeSubobject is protected, so use
|
|
318
|
+
// the public GetAttributeSet + AddSpawnedAttribute pair.
|
|
319
|
+
FString CreatedSet;
|
|
320
|
+
const FString AttrSetSpec = OptionalString(Params, TEXT("attributeSet"));
|
|
321
|
+
if (!AttrSetSpec.IsEmpty())
|
|
322
|
+
{
|
|
323
|
+
UClass* AttrSetClass = ResolveClassDeriving(AttrSetSpec, UAttributeSet::StaticClass());
|
|
324
|
+
if (!AttrSetClass)
|
|
325
|
+
{
|
|
326
|
+
return MCPError(FString::Printf(
|
|
327
|
+
TEXT("AttributeSet class not found: %s (pass a content path or class name)"), *AttrSetSpec));
|
|
328
|
+
}
|
|
329
|
+
const UAttributeSet* Existing = ASC->GetAttributeSet(AttrSetClass);
|
|
330
|
+
if (!Existing)
|
|
331
|
+
{
|
|
332
|
+
UAttributeSet* NewSet = NewObject<UAttributeSet>(Actor, AttrSetClass);
|
|
333
|
+
ASC->AddSpawnedAttribute(NewSet);
|
|
334
|
+
CreatedSet = NewSet->GetClass()->GetName();
|
|
335
|
+
}
|
|
336
|
+
else
|
|
337
|
+
{
|
|
338
|
+
CreatedSet = Existing->GetClass()->GetName();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Count attributes now live across all spawned sets.
|
|
343
|
+
int32 AttrCount = 0;
|
|
344
|
+
for (const UAttributeSet* Set : ASC->GetSpawnedAttributes())
|
|
345
|
+
{
|
|
346
|
+
if (!Set) continue;
|
|
347
|
+
for (TFieldIterator<FProperty> It(Set->GetClass()); It; ++It)
|
|
348
|
+
{
|
|
349
|
+
FStructProperty* SProp = CastField<FStructProperty>(*It);
|
|
350
|
+
if (SProp && SProp->Struct == FGameplayAttributeData::StaticStruct()) ++AttrCount;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
auto Result = MCPSuccess();
|
|
355
|
+
Result->SetStringField(TEXT("actorLabel"), Actor->GetActorLabel());
|
|
356
|
+
Result->SetBoolField(TEXT("initialized"), true);
|
|
357
|
+
if (!CreatedSet.IsEmpty()) Result->SetStringField(TEXT("attributeSet"), CreatedSet);
|
|
358
|
+
Result->SetNumberField(TEXT("attributeCount"), AttrCount);
|
|
359
|
+
return MCPResult(Result);
|
|
360
|
+
}
|