// Copyright Epic Games, Inc. All Rights Reserved. #include "K2Node_AnimGetter.h" #include "AnimGraphNode_AssetPlayerBase.h" #include "AnimGraphNode_Base.h" #include "AnimGraphNode_StateMachine.h" #include "AnimStateNode.h" #include "AnimStateNodeBase.h" #include "AnimStateTransitionNode.h" #include "Animation/AnimBlueprint.h" #include "Animation/AnimationAsset.h" #include "AnimationCustomTransitionSchema.h" #include "AnimationGraphSchema.h" #include "AnimationStateMachineGraph.h" #include "AnimationTransitionGraph.h" #include "AnimationTransitionSchema.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintNodeBinder.h" #include "BlueprintNodeSpawner.h" #include "Delegates/Delegate.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphSchema.h" #include "HAL/Platform.h" #include "HAL/PlatformCrt.h" #include "Internationalization/Internationalization.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Misc/AssertionMacros.h" #include "Serialization/Archive.h" #include "Templates/Casts.h" #include "Templates/SubclassOf.h" #include "UObject/Class.h" #include "UObject/Object.h" #include "UObject/ReleaseObjectVersion.h" #include "UObject/Script.h" #include "UObject/UnrealType.h" class UBlueprint; #define LOCTEXT_NAMESPACE "AnimGetter" void UK2Node_AnimGetter::PostLoad() { Super::PostLoad(); if (!HasAnyFlags(RF_ClassDefaultObject)) { RegisterDelegates(); } } void UK2Node_AnimGetter::PostPlacedNewNode() { Super::PostPlacedNewNode(); RegisterDelegates(); } void UK2Node_AnimGetter::RegisterDelegates() { if (!HasAnyFlags(RF_Transient) && HasValidBlueprint()) { if (UAnimBlueprint* ABP = CastChecked(GetBlueprint())) { GraphRenameHandle = ABP->OnGraphRenamedEvent().AddLambda([this](UEdGraph*, FName, FName) { UpdateCachedTitle(); }); } } } void UK2Node_AnimGetter::UnregisterDelegates() { if (!HasAnyFlags(RF_Transient) && HasValidBlueprint()) { if (UAnimBlueprint* ABP = CastChecked(GetBlueprint())) { ABP->OnGraphRenamedEvent().Remove(GraphRenameHandle); } } } void UK2Node_AnimGetter::BeginDestroy() { Super::BeginDestroy(); UnregisterDelegates(); } void UK2Node_AnimGetter::Serialize(FArchive& Ar) { Super::Serialize(Ar); Ar.UsingCustomVersion(FReleaseObjectVersion::GUID); if (Ar.CustomVer(FReleaseObjectVersion::GUID) < FReleaseObjectVersion::FixBrokenStateMachineReferencesInTransitionGetters) { RestoreStateMachineNode(); } } void UK2Node_AnimGetter::PostPasteNode() { Super::PostPasteNode(); SourceAnimBlueprint = Cast(GetBlueprint()); RestoreStateMachineState(); RestoreStateMachineNode(); UpdateCachedTitle(); } void UK2Node_AnimGetter::AllocateDefaultPins() { Super::AllocateDefaultPins(); TArray PinsToHide; TArray PinNames; // TODO: Find a nicer way to maybe pull these down from the instance class and allow // projects to add new parameters from derived instances PinNames.Add(TEXT("CurrentTime")); PinNames.Add(TEXT("AssetPlayerIndex")); PinNames.Add(TEXT("MachineIndex")); PinNames.Add(TEXT("StateIndex")); PinNames.Add(TEXT("TransitionIndex")); for(FString& PinName : PinNames) { if(UEdGraphPin* FoundPin = FindPin(PinName)) { PinsToHide.Add(FoundPin); } } for(UEdGraphPin* Pin : PinsToHide) { Pin->bHidden = true; } } FText UK2Node_AnimGetter::GetNodeTitle(ENodeTitleType::Type TitleType) const { return CachedTitle; } bool UK2Node_AnimGetter::CanCreateUnderSpecifiedSchema(const UEdGraphSchema* Schema) const { return Cast(Schema) != NULL || Cast(Schema) != NULL; } void UK2Node_AnimGetter::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { // First cache the available functions for getters UClass* ActionKey = GetClass(); const UAnimBlueprint* AnimBlueprint = Cast(ActionRegistrar.GetActionKeyFilter()); if(AnimBlueprint && ActionRegistrar.IsOpenForRegistration(AnimBlueprint)) { UClass* BPClass = *AnimBlueprint->ParentClass; while(BPClass && !BPClass->HasAnyClassFlags(CLASS_Native)) { BPClass = BPClass->GetSuperClass(); } if(BPClass) { TArray AnimGetters; for(TFieldIterator FuncIter(BPClass) ; FuncIter ; ++FuncIter) { UFunction* Func = *FuncIter; if(Func->HasMetaData(TEXT("AnimGetter")) && Func->HasAnyFunctionFlags(FUNC_Native)) { AnimGetters.Add(Func); } } auto UiSpecOverride = [](const FBlueprintActionContext& /*Context*/, const IBlueprintNodeBinder::FBindingSet& Bindings, FBlueprintActionUiSpec* UiSpecOut, FText Title) { UiSpecOut->MenuName = Title; }; TArray AssetPlayerNodes; TArray MachineNodes; TArray StateNodes; TArray TransitionNodes; FBlueprintEditorUtils::GetAllNodesOfClass(AnimBlueprint, AssetPlayerNodes); FBlueprintEditorUtils::GetAllNodesOfClass(AnimBlueprint, MachineNodes); FBlueprintEditorUtils::GetAllNodesOfClass(AnimBlueprint, StateNodes); FBlueprintEditorUtils::GetAllNodesOfClass(AnimBlueprint, TransitionNodes); for(UFunction* Getter : AnimGetters) { FNodeSpawnData Params; Params.AnimInstanceClass = BPClass; Params.Getter = Getter; Params.SourceBlueprint = AnimBlueprint; Params.GetterContextString = Getter->GetMetaData(TEXT("GetterContext")); if(GetterRequiresParameter(Getter, TEXT("AssetPlayerIndex"))) { for(UAnimGraphNode_Base* AssetNode : AssetPlayerNodes) { // Should always succeed if(UAnimationAsset* NodeAsset = AssetNode->GetAnimationAsset()) { Params.SourceNode = AssetNode; UpdateCachedTitle(Params); UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), /*AssetNode->GetGraph()*/nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(const_cast(this), &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); Spawner->DynamicUiSignatureGetter = UBlueprintNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(UiSpecOverride, Params.CachedTitle); ActionRegistrar.AddBlueprintAction(AnimBlueprint, Spawner); } } } else if(GetterRequiresParameter(Getter, TEXT("MachineIndex"))) { if(GetterRequiresParameter(Getter, TEXT("StateIndex"))) { for(UAnimStateNode* StateNode : StateNodes) { // Get the state machine node from the outer chain UAnimationStateMachineGraph* Graph = Cast(StateNode->GetOuter()); if(Graph) { if(UAnimGraphNode_StateMachine* MachineNode = Cast(Graph->GetOuter())) { Params.SourceNode = MachineNode; } } Params.SourceStateNode = StateNode; UpdateCachedTitle(Params); UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), /*StateNode->GetGraph()*/nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(const_cast(this), &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); Spawner->DynamicUiSignatureGetter = UBlueprintNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(UiSpecOverride, Params.CachedTitle); ActionRegistrar.AddBlueprintAction(AnimBlueprint, Spawner); } } else if(GetterRequiresParameter(Getter, TEXT("TransitionIndex"))) { for(UAnimStateTransitionNode* TransitionNode : TransitionNodes) { UAnimationStateMachineGraph* Graph = Cast(TransitionNode->GetOuter()); if(Graph) { if(UAnimGraphNode_StateMachine* MachineNode = Cast(Graph->GetOuter())) { Params.SourceNode = MachineNode; } } Params.SourceStateNode = TransitionNode; UpdateCachedTitle(Params); UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), /*TransitionNode->GetGraph()*/nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(const_cast(this), &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); Spawner->DynamicUiSignatureGetter = UBlueprintNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(UiSpecOverride, Params.CachedTitle); ActionRegistrar.AddBlueprintAction(AnimBlueprint, Spawner); } } else { // Only requires the state machine for(UAnimGraphNode_StateMachine* MachineNode : MachineNodes) { Params.SourceNode = MachineNode; UpdateCachedTitle(Params); UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), /*MachineNode*/nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(const_cast(this), &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); Spawner->DynamicUiSignatureGetter = UBlueprintNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(UiSpecOverride, Params.CachedTitle); ActionRegistrar.AddBlueprintAction(AnimBlueprint, Spawner); } } } else { // Doesn't operate on a node, only need one entry UpdateCachedTitle(Params); UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UK2Node_AnimGetter::StaticClass(), nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateUObject(const_cast(this), &UK2Node_AnimGetter::PostSpawnNodeSetup, Params)); Spawner->DynamicUiSignatureGetter = UBlueprintNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(UiSpecOverride, Params.CachedTitle); ActionRegistrar.AddBlueprintAction(AnimBlueprint, Spawner); } } } } } void UK2Node_AnimGetter::ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const { const bool bAssetPlayerIndexRequired = GetterRequiresParameter(GetTargetFunction(), TEXT("AssetPlayerIndex")); const bool bMachineIndexRequired = GetterRequiresParameter(GetTargetFunction(), TEXT("MachineIndex")); const bool bTransitionIndexRequired = GetterRequiresParameter(GetTargetFunction(), TEXT("TransitionIndex")); const bool bStateIndexRequired = GetterRequiresParameter(GetTargetFunction(), TEXT("StateIndex")); const bool bSourceNodeRequired = bAssetPlayerIndexRequired || bMachineIndexRequired || bTransitionIndexRequired || bStateIndexRequired; if (bSourceNodeRequired && SourceNode == nullptr) { MessageLog.Error(TEXT("@@ contains invalid data. Please delete and recreate the node."), this); } } bool UK2Node_AnimGetter::IsActionFilteredOut(FBlueprintActionFilter const& Filter) { if(Filter.Context.Graphs.Num() > 0) { UEdGraph* CurrGraph = Filter.Context.Graphs[0]; if(CurrGraph && Filter.Context.Blueprints.Num() > 0) { UAnimBlueprint* AnimBlueprint = Cast(Filter.Context.Blueprints[0]); check(AnimBlueprint); if(SourceAnimBlueprint == AnimBlueprint) { // Get the native anim instance derived class UClass* NativeInstanceClass = AnimBlueprint->ParentClass; while(NativeInstanceClass && !NativeInstanceClass->HasAnyClassFlags(CLASS_Native)) { NativeInstanceClass = NativeInstanceClass->GetSuperClass(); } if(GetterClass != NativeInstanceClass) { // If the anim instance containing the getter is not the class we're currently using then bail return true; } const UEdGraphSchema* Schema = CurrGraph->GetSchema(); // Bail if we aren't meant for this graph at all if(!IsContextValidForSchema(Schema)) { return true; } if(Cast(Schema) || Cast(Schema)) { if(!SourceNode && !SourceStateNode) { // No dependancies, always allow return false; } // Inside a transition graph if(SourceNode) { if(UAnimStateTransitionNode* TransitionNode = Cast(CurrGraph->GetOuter())) { if(SourceStateNode) { if(UAnimStateTransitionNode* SourceTransitionNode = Cast(SourceStateNode)) { // if we have a transition node, make sure it's the same as the one we're in if(SourceTransitionNode == TransitionNode) { return false; } } else if(UAnimStateNode* PreviousStateNode = Cast(TransitionNode->GetPreviousState())) { // Only allow actions using states that are referencing the previous state if(SourceStateNode == PreviousStateNode) { return false; } } } else if(UAnimGraphNode_StateMachine* MachineNode = Cast(SourceNode)) { // Available everywhere return false; } else if(UAnimStateNode* PrevStateNode = Cast(TransitionNode->GetPreviousState())) { // Make sure the attached asset node is in the source graph if(SourceNode && SourceNode->GetGraph() == PrevStateNode->BoundGraph) { return false; } } } } } else if(Cast(Schema)) { // Inside normal anim graph if(SourceStateNode) { for(UBlueprint* Blueprint : Filter.Context.Blueprints) { TArray StateNodes; FBlueprintEditorUtils::GetAllNodesOfClass(Blueprint, StateNodes); if(StateNodes.Contains(SourceStateNode)) { return false; } } } } } } } return true; } void UK2Node_AnimGetter::RestoreStateMachineState() { if (SourceStateNode) { if (UAnimationTransitionGraph* TransitionGraph = Cast(GetOuter())) { if (UAnimStateTransitionNode* StateTransitionNode = Cast(TransitionGraph->GetOuter())) { SourceStateNode = StateTransitionNode->GetPreviousState(); } } } } void UK2Node_AnimGetter::RestoreStateMachineNode() { if (SourceStateNode) { UAnimationStateMachineGraph* Graph = Cast(SourceStateNode->GetOuter()); if (Graph) { if (UAnimGraphNode_StateMachine* MachineNode = Cast(Graph->GetOuter())) { SourceNode = MachineNode; } } } } bool UK2Node_AnimGetter::GetterRequiresParameter(const UFunction* Getter, FString ParamName) { bool bRequiresParameter = false; for(TFieldIterator PropIt(Getter); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { FProperty* Prop = *PropIt; if(Prop->GetName() == ParamName) { bRequiresParameter = true; break; } } return bRequiresParameter; } void UK2Node_AnimGetter::PostSpawnNodeSetup(UEdGraphNode* NewNode, bool bIsTemplateNode, FNodeSpawnData SpawnData) { UK2Node_AnimGetter* TypedNode = CastChecked(NewNode); // Apply parameters TypedNode->SourceNode = SpawnData.SourceNode; TypedNode->SourceStateNode = SpawnData.SourceStateNode; TypedNode->GetterClass = SpawnData.AnimInstanceClass; TypedNode->SourceAnimBlueprint = SpawnData.SourceBlueprint; TypedNode->SetFromFunction((UFunction*)SpawnData.Getter); TypedNode->CachedTitle = SpawnData.CachedTitle; SpawnData.GetterContextString.ParseIntoArray(TypedNode->Contexts, TEXT("|"), 1); } bool UK2Node_AnimGetter::IsContextValidForSchema(const UEdGraphSchema* Schema) const { if(Contexts.Num() == 0) { // Valid in all graphs return true; } const FString& Context = Contexts[0]; UClass* ClassToCheck = nullptr; if(Context == TEXT("CustomBlend")) { ClassToCheck = UAnimationCustomTransitionSchema::StaticClass(); } else if(Context == TEXT("Transition")) { ClassToCheck = UAnimationTransitionSchema::StaticClass(); } else if(Context == TEXT("AnimGraph")) { ClassToCheck = UAnimationGraphSchema::StaticClass(); } return Schema->GetClass() == ClassToCheck; } void UK2Node_AnimGetter::UpdateCachedTitle(FNodeSpawnData& SpawnData) { SpawnData.CachedTitle = GenerateTitle((UFunction*)SpawnData.Getter, SpawnData.SourceStateNode, SpawnData.SourceNode); } FText UK2Node_AnimGetter::GenerateTitle(UFunction* Getter, UAnimStateNodeBase* SourceStateNode, UAnimGraphNode_Base* SourceNode) { static const FText InvalidNodeTitle = LOCTEXT("NodeTitleInvalid", "{0} (Invalid node)"); if (GetterRequiresParameter(Getter, TEXT("AssetPlayerIndex"))) { if (!SourceNode) { return FText::Format(InvalidNodeTitle, Getter->GetDisplayNameText()); } // Should always succeed if (UAnimationAsset* NodeAsset = SourceNode->GetAnimationAsset()) { return FText::Format(LOCTEXT("NodeTitle", "{0} ({1})"), Getter->GetDisplayNameText(), FText::FromString(*NodeAsset->GetName())); } } else if (GetterRequiresParameter(Getter, TEXT("MachineIndex"))) { if (GetterRequiresParameter(Getter, TEXT("StateIndex")) || GetterRequiresParameter(Getter, TEXT("TransitionIndex"))) { return FText::Format(LOCTEXT("NodeTitle", "{0} ({1})"), Getter->GetDisplayNameText(), SourceStateNode->GetNodeTitle(ENodeTitleType::ListView)); } else { if (!SourceNode) { return FText::Format(InvalidNodeTitle, Getter->GetDisplayNameText()); } // Only requires the state machine return FText::Format(LOCTEXT("NodeTitle", "{0} ({1})"), Getter->GetDisplayNameText(), SourceNode->GetNodeTitle(ENodeTitleType::ListView)); } } // Doesn't operate on a node, only need one entry return Getter->GetDisplayNameText(); } void UK2Node_AnimGetter::UpdateCachedTitle() { CachedTitle = GenerateTitle(GetTargetFunction(), SourceStateNode, SourceNode); } FNodeSpawnData::FNodeSpawnData() : SourceNode(nullptr) , SourceStateNode(nullptr) , AnimInstanceClass(nullptr) , SourceBlueprint(nullptr) , Getter(nullptr) { } #undef LOCTEXT_NAMESPACE