// Copyright 2025 https://yuewu.dev/en All Rights Reserved. #include "GGA_K2Node_EffectContextPayload.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintNodeSpawner.h" #include "EdGraphSchema_K2.h" #include "K2Node_AssignmentStatement.h" #include "K2Node_CallFunction.h" #include "K2Node_TemporaryVariable.h" #include "KismetCompiler.h" #include "Kismet/BlueprintInstancedStructLibrary.h" #include "Utilities/GGA_GameplayEffectFunctionLibrary.h" #define LOCTEXT_NAMESPACE "K2Node" /////////////////////////////////////////////////////////////////////////// // Node Change events interface implementations /////////////////////////////////////////////////////////////////////////// void UGGA_K2Node_EffectContextPayload::ReallocatePinsDuringReconstruction(TArray& OldPins) { AllocateDefaultPins(); RestoreSplitPins(OldPins); } void UGGA_K2Node_EffectContextPayload::PostPlacedNewNode() { Super::PostPlacedNewNode(); RefreshOutputType(); } void UGGA_K2Node_EffectContextPayload::PinConnectionListChanged(UEdGraphPin* Pin) { Super::PinConnectionListChanged(Pin); if (Pin && Pin == GetPayloadTypePin()) { RefreshOutputType(); } } void UGGA_K2Node_EffectContextPayload::PinDefaultValueChanged(UEdGraphPin* Pin) { if (Pin == GetPayloadTypePin()) { if (Pin->LinkedTo.Num() == 0) { RefreshOutputType(); } } } void UGGA_K2Node_EffectContextPayload::PostReconstructNode() { Super::PostReconstructNode(); RefreshOutputType(); } void UGGA_K2Node_EffectContextPayload::RefreshOutputType() { UEdGraphPin* OutStructPin = GetOutStructPin(); UEdGraphPin* PayloadType = GetPayloadTypePin(); if (PayloadType->DefaultObject != OutStructPin->PinType.PinSubCategoryObject) { if (OutStructPin->SubPins.Num() > 0) { GetSchema()->RecombinePin(OutStructPin); } OutStructPin->PinType.PinSubCategoryObject = PayloadType->DefaultObject; OutStructPin->PinType.PinCategory = (PayloadType->DefaultObject == nullptr) ? UEdGraphSchema_K2::PC_Wildcard : UEdGraphSchema_K2::PC_Struct; } } //////////////////////////////////////////////////////////////////////////// //UK2_Node and EDGraph Interface Implementations //////////////////////////////////////////////////////////////////////////// FString UGGA_K2Node_EffectContextPayload::GetPinMetaData(FName InPinName, FName InKey) { FString MetaData = Super::GetPinMetaData(InPinName, InKey); if (MetaData.IsEmpty()) { //Filters out the abstract classes from the pin search if (InPinName == GetPayloadTypePin()->GetName() && InKey == FBlueprintMetadata::MD_AllowAbstractClasses) { MetaData = TEXT("false"); } } return MetaData; } FSlateIcon UGGA_K2Node_EffectContextPayload::GetIconAndTint(FLinearColor& OutColor) const { OutColor = FLinearColor(FColor::Black); return FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.AllClasses.FunctionIcon"); } FLinearColor UGGA_K2Node_EffectContextPayload::GetNodeTitleColor() const { return FLinearColor(FColor::Cyan); } FText UGGA_K2Node_EffectContextPayload::GetMenuCategory() const { return LOCTEXT("K2Node_GetProperty_Category", "GGA|GameplayEffect|Context"); } /////////////////////////////////////////////////////////////////// // PIN GETTERS /////////////////////////////////////////////////////////////////// UEdGraphPin* UGGA_K2Node_EffectContextPayload::GetEffectContextPin() const { UEdGraphPin* Pin = FindPinChecked(K2Node_ContextPayload_PinNames::EffectContextPinName); check(Pin->Direction == EGPD_Input); return Pin; } UEdGraphPin* UGGA_K2Node_EffectContextPayload::GetInstancedStructPin() const { UEdGraphPin* Pin = FindPinChecked(K2Node_ContextPayload_PinNames::InstancedStructPinName); check(Pin->Direction == EGPD_Input); return Pin; } UEdGraphPin* UGGA_K2Node_EffectContextPayload::GetKeyPin() const { UEdGraphPin* Pin = FindPinChecked(K2Node_ContextPayload_PinNames::KeyPinName); check(Pin->Direction == EGPD_Input); return Pin; } UEdGraphPin* UGGA_K2Node_EffectContextPayload::GetPayloadTypePin() const { UEdGraphPin* Pin = FindPinChecked(K2Node_ContextPayload_PinNames::PayloadTypePinName); check(Pin->Direction == EGPD_Input); return Pin; } UEdGraphPin* UGGA_K2Node_EffectContextPayload::GetOutStructPin() const { UEdGraphPin* Pin = FindPinChecked(K2Node_ContextPayload_PinNames::OutStructPinName); check(Pin->Direction == EGPD_Output); return Pin; } UEdGraphPin* UGGA_K2Node_EffectContextPayload::GetValidPin() const { UEdGraphPin* Pin = FindPinChecked(K2Node_ContextPayload_PinNames::ValidExecPinName); check(Pin->Direction == EGPD_Output); return Pin; } UEdGraphPin* UGGA_K2Node_EffectContextPayload::GetInvalidPin() const { UEdGraphPin* Pin = FindPinChecked(K2Node_ContextPayload_PinNames::InvalidExecPinName); check(Pin->Direction == EGPD_Output); return Pin; } void UGGA_K2Node_GetEffectContextPayload::AllocateDefaultPins() { CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Struct, FGameplayEffectContextHandle::StaticStruct(), K2Node_ContextPayload_PinNames::EffectContextPinName); CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UScriptStruct::StaticClass(), K2Node_ContextPayload_PinNames::PayloadTypePinName); CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, K2Node_ContextPayload_PinNames::ValidExecPinName); CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, K2Node_ContextPayload_PinNames::InvalidExecPinName); CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Wildcard, K2Node_ContextPayload_PinNames::OutStructPinName); Super::AllocateDefaultPins(); } void UGGA_K2Node_GetEffectContextPayload::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); UFunction* GetValidContextPayloadFunc = UGGA_GameplayEffectFunctionLibrary::StaticClass()->FindFunctionByName( GET_FUNCTION_NAME_CHECKED_ThreeParams(UGGA_GameplayEffectFunctionLibrary, GetValidContextPayload, FGameplayEffectContextHandle, const UScriptStruct*, bool&)); UFunction* GetInstStructValueFunc = UBlueprintInstancedStructLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UBlueprintInstancedStructLibrary, GetInstancedStructValue)); if (!GetValidContextPayloadFunc || !GetInstStructValueFunc) { CompilerContext.MessageLog.Error( *LOCTEXT("InvalidClass", "The GetValidContextPayload or GetInstancedStructValue functions have not been found. Check ExpandNode in GGA_K2Node_EffectContextPayload.cpp").ToString(), this); return; } const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); bool bIsErrorFree = true; const FEdGraphPinType& PinType = GetOutStructPin()->PinType; UK2Node_TemporaryVariable* TempVarOutput = CompilerContext.SpawnInternalVariable( this, PinType.PinCategory, PinType.PinSubCategory, PinType.PinSubCategoryObject.Get(), PinType.ContainerType, PinType.PinValueType); UK2Node_AssignmentStatement* AssignNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); UK2Node_CallFunction* const GetPayloadNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); UK2Node_CallFunction* const GetStructValueNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); GetPayloadNode->SetFromFunction(GetValidContextPayloadFunc); GetStructValueNode->SetFromFunction(GetInstStructValueFunc); GetPayloadNode->AllocateDefaultPins(); GetStructValueNode->AllocateDefaultPins(); AssignNode->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(GetPayloadNode, this); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(GetStructValueNode, this); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(AssignNode, this); //Connect input pins to the GetValidContextPayloadNode //---------------------------------------------------------------------------------------- // UEdGraphPin* EffectContextPin = Schema->FindSelfPin(*GetPayloadNode, EGPD_Input); UEdGraphPin* EffectContextPin = GetPayloadNode->FindPinChecked(TEXT("EffectContext")); UEdGraphPin* InterExecLeftPin = GetPayloadNode->GetThenPin(); UEdGraphPin* PayloadTypePin = GetPayloadNode->FindPinChecked(TEXT("PayloadType")); UEdGraphPin* InterStructLeftPin = GetPayloadNode->GetReturnValuePin(); bIsErrorFree &= CompilerContext.MovePinLinksToIntermediate(*GetEffectContextPin(), *EffectContextPin).CanSafeConnect(); bIsErrorFree &= CompilerContext.MovePinLinksToIntermediate(*GetPayloadTypePin(), *PayloadTypePin).CanSafeConnect(); //---------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------- //Connect input Target Pin and the output GetPayloadNode pins to the GetStructValueNode pin //---------------------------------------------------------------------------------------- UEdGraphPin* InterExecRightPin = GetStructValueNode->GetExecPin(); UEdGraphPin* ValidPin = GetStructValueNode->FindPinChecked(TEXT("Valid")); UEdGraphPin* InvalidPin = GetStructValueNode->FindPinChecked(TEXT("NotValid")); UEdGraphPin* OutStructPin = GetStructValueNode->FindPinChecked(TEXT("Value")); UEdGraphPin* InterStructRightPin = GetStructValueNode->FindPinChecked(TEXT("InstancedStruct")); bIsErrorFree &= CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *InterExecRightPin).CanSafeConnect(); bIsErrorFree &= Schema->TryCreateConnection(InterStructLeftPin, InterStructRightPin); //---------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------- //Connect the output of GetStructValueNode to the input of Assign node. The Invalid pin directly goes to Invalid pin // but the valid pin goes through the Assign node. The temporary variable is assigned a value, and is connected to the // node's output struct. The order that the connections are made is critical. First the variable needs to connect to the output, // then the variable should be connected to the Variable pin of Assign so the variable type is updated, and only then the Value pin // of Assign can be connected to OutStructPin of the GetStructValueNode. //---------------------------------------------------------------------------------------- bIsErrorFree &= CompilerContext.MovePinLinksToIntermediate(*GetOutStructPin(), *TempVarOutput->GetVariablePin()).CanSafeConnect(); bIsErrorFree &= Schema->TryCreateConnection(AssignNode->GetVariablePin(), TempVarOutput->GetVariablePin()); bIsErrorFree &= Schema->TryCreateConnection(AssignNode->GetValuePin(), OutStructPin); bIsErrorFree &= Schema->TryCreateConnection(ValidPin, AssignNode->GetExecPin()); bIsErrorFree &= CompilerContext.MovePinLinksToIntermediate(*GetInvalidPin(), *InvalidPin).CanSafeConnect(); bIsErrorFree &= CompilerContext.MovePinLinksToIntermediate(*GetValidPin(), *AssignNode->GetThenPin()).CanSafeConnect(); //---------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------- if (!bIsErrorFree) { CompilerContext.MessageLog.Error(*LOCTEXT("InternalConnectionError", "Get Context Payload: Internal connection error. @@").ToString(), this); } BreakAllNodeLinks(); } //////////////////////////////////////////////////////////////////////////// //UK2_Node and EDGraph Interface Implementations //////////////////////////////////////////////////////////////////////////// void UGGA_K2Node_GetEffectContextPayload::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { Super::GetMenuActions(ActionRegistrar); UClass* Action = GetClass(); if (ActionRegistrar.IsOpenForRegistration(Action)) { UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(GetClass()); ActionRegistrar.AddBlueprintAction(Action, Spawner); } } FText UGGA_K2Node_GetEffectContextPayload::GetNodeTitle(ENodeTitleType::Type TitleType) const { return LOCTEXT("K2Node_GetProperty_NodeTitle", "Get Context Payload"); } FText UGGA_K2Node_GetEffectContextPayload::GetTooltipText() const { return LOCTEXT("K2Node_GetProperty_TooltipText", "Outputs the context payload based on the selected payload type"); } FText UGGA_K2Node_GetEffectContextPayload::GetKeywords() const { return LOCTEXT("GetEffectContextPayload", "Get Gameplay Effect Context Payload"); } #undef LOCTEXT_NAMESPACE