// Copyright 2017 ~ 2022 Critical Failure Studio Ltd. All rights reserved. #include "Graphs/PaperZDStateMachineSchema.h" #include "Graphs/PaperZDStateMachineGraph.h" #include "Graphs/Nodes/PaperZDStateGraphNode_Root.h" #include "Graphs/Nodes/PaperZDStateGraphNode_Transition.h" #include "Graphs/Nodes/PaperZDStateGraphNode_State.h" #include "Graphs/Nodes/PaperZDStateGraphNode_Conduit.h" #include "Graphs/Nodes/PaperZDStateGraphNode_Jump.h" #include "Graphs/PaperZDStateMachineConnectionDrawingPolicy.h" #include "Framework/Commands/GenericCommands.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "PaperZDAnimBP.h" #include "EdGraphNode_Comment.h" #include "ScopedTransaction.h" #include "GraphEditorActions.h" #include "ToolMenus.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #if ZD_VERSION_INLINED_CPP_SUPPORT #include UE_INLINE_GENERATED_CPP_BY_NAME(PaperZDStateMachineSchema) #endif #define LOCTEXT_NAMESPACE "PaperZDStateMachineSchema" ///////////////////////////////////////////////////// // FPaperZDStateMachineSchemaAction_NewNode UEdGraphNode* FPaperZDStateMachineSchemaAction_NewNode::PerformAction(UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode/* = true*/) { check(NodeClass); UBlueprint* AnimBP = FBlueprintEditorUtils::FindBlueprintForGraph(ParentGraph); const FScopedTransaction Transaction(LOCTEXT("PaperZDStateMachine_NewNode", "PaperZD AnimBP: New StateMachine Node")); ParentGraph->Modify(); AnimBP->Modify(); //We need to use FGraphNodeCreator which doesn't allow us to specify the node class by variable, only by template. UPaperZDStateGraphNode* NewNode = nullptr; if (NodeClass == UPaperZDStateGraphNode_State::StaticClass()) { FGraphNodeCreator NodeCreator(*ParentGraph); NewNode = NodeCreator.CreateNode(bSelectNewNode); NodeCreator.Finalize(); } else if (NodeClass == UPaperZDStateGraphNode_Transition::StaticClass()) { FGraphNodeCreator NodeCreator(*ParentGraph); NewNode = NodeCreator.CreateNode(bSelectNewNode); NodeCreator.Finalize(); } else if (NodeClass == UPaperZDStateGraphNode_Conduit::StaticClass()) { FGraphNodeCreator NodeCreator(*ParentGraph); NewNode = NodeCreator.CreateNode(bSelectNewNode); NodeCreator.Finalize(); } else if (NodeClass == UPaperZDStateGraphNode_Jump::StaticClass()) { FGraphNodeCreator NodeCreator(*ParentGraph); NewNode = NodeCreator.CreateNode(bSelectNewNode); NodeCreator.Finalize(); } //Should have a node created by now check(NewNode); //Setup the location NewNode->NodePosX = Location.X; NewNode->NodePosY = Location.Y; //Create the autowire NewNode->AutowireNewNode(FromPin); //Mark AnimBP as modified AnimBP->PostEditChange(); AnimBP->MarkPackageDirty(); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(AnimBP); return NewNode; } ///////////////////////////////////////////////////// // FPaperZDStateMachineSchemaAction_NewComment UEdGraphNode* FPaperZDStateMachineSchemaAction_NewComment::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode) { // Add menu item for creating comment boxes UEdGraphNode_Comment* CommentTemplate = NewObject(); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(ParentGraph); FVector2D SpawnLocation = Location; FSlateRect Bounds; if ((Blueprint != nullptr) && FKismetEditorUtilities::GetBoundsForSelectedNodes(Blueprint, Bounds, 50.0f)) { CommentTemplate->SetBounds(Bounds); SpawnLocation.X = CommentTemplate->NodePosX; SpawnLocation.Y = CommentTemplate->NodePosY; } UEdGraphNode* NewNode = FEdGraphSchemaAction_NewNode::SpawnNodeFromTemplate(ParentGraph, CommentTemplate, SpawnLocation, bSelectNewNode); // Update Analytics for these nodes FBlueprintEditorUtils::AnalyticsTrackNewNode(NewNode); // Mark Blueprint as structurally modified since // UK2Node_Comment::NodeCausesStructuralBlueprintChange used to return true FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); return NewNode; } ////////////////////////////////////////////////////////////////////////// //// PaperZD State Machine Schema ////////////////////////////////////////////////////////////////////////// UPaperZDStateMachineSchema::UPaperZDStateMachineSchema(const FObjectInitializer& ObjectInitializer) : Super() { } FConnectionDrawingPolicy* UPaperZDStateMachineSchema::CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraphObj) const { return new FPaperZDStateMachineConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, InZoomFactor, InClippingRect, InDrawElements, InGraphObj); } void UPaperZDStateMachineSchema::CreateDefaultNodesForGraph(UEdGraph& Graph) const { //Create default node FGraphNodeCreator NodeCreator(Graph); UPaperZDStateGraphNode_Root* RootNode = NodeCreator.CreateNode(); NodeCreator.Finalize(); SetNodeMetaData(RootNode, FNodeMetadata::DefaultGraphNode); // if (UPaperZDStateMachineGraph* StateMachineGraph = CastChecked(&Graph)) // { // StateMachineGraph->EntryNode = EntryNode; // } } void UPaperZDStateMachineSchema::GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const { //State Action TSharedPtr AddStateAction(new FPaperZDStateMachineSchemaAction_NewNode( LOCTEXT("PaperZDStateMachineNodeCategory", "Nodes"), LOCTEXT("PaperZDStateMachineNodeState","Animation State"), LOCTEXT("NewPaperZDStateMachineNodeTooltipState", "Adds an Animation Node node here"), 0)); AddStateAction->NodeClass = UPaperZDStateGraphNode_State::StaticClass(); ContextMenuBuilder.AddAction(AddStateAction); //Conduit Action TSharedPtr AddConduitAction(new FPaperZDStateMachineSchemaAction_NewNode( LOCTEXT("PaperZDStateMachineNodeCategory", "Nodes"), LOCTEXT("PaperZDStateMachineNodeConduit", "Conduit"), LOCTEXT("NewPaperZDStateMachineNodeTooltipConduit", "Adds a Conduit node here"), 0)); AddConduitAction->NodeClass = UPaperZDStateGraphNode_Conduit::StaticClass(); ContextMenuBuilder.AddAction(AddConduitAction); //The following actions cannot be added when dragging from a pin if (ContextMenuBuilder.FromPin == nullptr) { //Jump Action TSharedPtr AddJumpAction(new FPaperZDStateMachineSchemaAction_NewNode( LOCTEXT("PaperZDStateMachineNodeCategory", "Nodes"), LOCTEXT("PaperZDStateMachineNodeJump", "Jump"), LOCTEXT("NewPaperZDStateMachineNodeTooltipJump", "Adds a Jump node here"), 0)); AddJumpAction->NodeClass = UPaperZDStateGraphNode_Jump::StaticClass(); ContextMenuBuilder.AddAction(AddJumpAction); //Comment Action TSharedPtr AddCommentAction(new FPaperZDStateMachineSchemaAction_NewComment( LOCTEXT("PaperZDStateMachineNodeCategory", "Nodes"), LOCTEXT("PaperZDStateMachineNodeComment", "Comment"), LOCTEXT("NewPaperZDStateMachineNodeTooltipComment", "Adds a Comment Node node here"), 0)); ContextMenuBuilder.AddAction(AddCommentAction); } } void UPaperZDStateMachineSchema::GetContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const { if (Context && Context->Node) { UBlueprint* OwnerBlueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(Context->Graph); FToolMenuSection& Section = Menu->AddSection("ZDAnimationStateMachineNodeActions", LOCTEXT("NodeActionsMenuHeader", "Node Actions")); if (!Context->bIsDebugging) { // Node contextual actions Section.AddMenuEntry(FGenericCommands::Get().Delete); Section.AddMenuEntry(FGenericCommands::Get().Cut); Section.AddMenuEntry(FGenericCommands::Get().Copy); Section.AddMenuEntry(FGenericCommands::Get().Duplicate); Section.AddMenuEntry(FGraphEditorCommands::Get().ReconstructNodes); Section.AddMenuEntry(FGraphEditorCommands::Get().BreakNodeLinks); if (Context->Node->bCanRenameNode) { Section.AddMenuEntry(FGenericCommands::Get().Rename); } } } Super::GetContextMenuActions(Menu, Context); } const FPinConnectionResponse UPaperZDStateMachineSchema::CanCreateConnection(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const { if (!PinA || !PinB) return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("No entry pin!")); UPaperZDStateGraphNode *NodeA = Cast(PinA->GetOwningNode()); UPaperZDStateGraphNode *NodeB = Cast(PinB->GetOwningNode()); //Abort on same nodes if (NodeA == NodeB) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Cannot connect to the same node")); } //Create a transition for state nodes if (NodeA->IsA(UPaperZDStateGraphNode_State::StaticClass()) && NodeB->IsA(UPaperZDStateGraphNode_State::StaticClass())) { return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, TEXT("Create a Transition")); } //Don't allow transition-transition connections if (NodeA->IsA(UPaperZDStateGraphNode_Transition::StaticClass()) && NodeB->IsA(UPaperZDStateGraphNode_Transition::StaticClass())) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Cannot connect transition to transition")); } //Create Transition - State Connection if (NodeA->IsA(UPaperZDStateGraphNode_Transition::StaticClass()) && NodeB->IsA(UPaperZDStateGraphNode_State::StaticClass())) { return FPinConnectionResponse(CONNECT_RESPONSE_BREAK_OTHERS_A, TEXT("Connect Transition to State")); } //Create State - Conduit if (NodeA->IsA(UPaperZDStateGraphNode_State::StaticClass()) && NodeB->IsA(UPaperZDStateGraphNode_Conduit::StaticClass())) { return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, TEXT("Create a Transition")); } //Create Conduit - State if (NodeA->IsA(UPaperZDStateGraphNode_Conduit::StaticClass()) && NodeB->IsA(UPaperZDStateGraphNode_State::StaticClass())) { return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, TEXT("Create a Transition")); } //Create Conduit - Conduit if (NodeA->IsA(UPaperZDStateGraphNode_Conduit::StaticClass()) && NodeB->IsA(UPaperZDStateGraphNode_Conduit::StaticClass())) { return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, TEXT("Create a Transition")); } //Create State - Transition Connection if (NodeA->IsA(UPaperZDStateGraphNode_State::StaticClass()) && NodeB->IsA(UPaperZDStateGraphNode_Transition::StaticClass())) { return FPinConnectionResponse(CONNECT_RESPONSE_BREAK_OTHERS_B, TEXT("Connect State to Transition")); } //Create Root - State Connection if (NodeA->IsA(UPaperZDStateGraphNode_Root::StaticClass()) && NodeB->IsA(UPaperZDStateGraphNode_State::StaticClass())) { return FPinConnectionResponse(CONNECT_RESPONSE_BREAK_OTHERS_A, TEXT("Connect Root to State")); } //Create Jump - State if (NodeA->IsA(UPaperZDStateGraphNode_Jump::StaticClass()) && NodeB->IsA(UPaperZDStateGraphNode_State::StaticClass())) { return FPinConnectionResponse(CONNECT_RESPONSE_BREAK_OTHERS_A, TEXT("Connect Jump to State")); } return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Connection not Allowed")); } bool UPaperZDStateMachineSchema::TryCreateConnection(UEdGraphPin* PinA, UEdGraphPin* PinB) const { //Transform an OUTPUT - OUTPUT connection into an INPUT - OUTPUT Connection (SPaperZDAnimGraphNode_State has only output as a draggable node, input node is invisible) if (PinB->Direction == PinA->Direction) { if (UPaperZDStateGraphNode* Node = Cast(PinB->GetOwningNode())) { if (PinA->Direction == EGPD_Input) { PinB = Node->GetOutputPin(); } else { PinB = Node->GetInputPin(); } } } const bool bModified = UEdGraphSchema::TryCreateConnection(PinA, PinB); if (bModified) { UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(PinA->GetOwningNode()); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } return bModified; } bool UPaperZDStateMachineSchema::CreateAutomaticConversionNodeAndConnections(UEdGraphPin* PinA, UEdGraphPin* PinB) const { UPaperZDStateGraphNode* NodeA = Cast(PinA->GetOwningNode()); UPaperZDStateGraphNode* NodeB = Cast(PinB->GetOwningNode()); if ((NodeA->IsA(UPaperZDStateGraphNode_State::StaticClass()) || NodeA->IsA(UPaperZDStateGraphNode_Conduit::StaticClass())) && (NodeB->IsA(UPaperZDStateGraphNode_State::StaticClass()) || NodeB->IsA(UPaperZDStateGraphNode_Conduit::StaticClass()))) { UPaperZDStateGraphNode_Transition* TransitionNode = FPaperZDStateMachineSchemaAction_NewNode::SpawnGraphNode(NodeA->GetGraph(), FVector2D(0.0f, 0.0f), false); if (PinA->Direction == EEdGraphPinDirection::EGPD_Output) { TransitionNode->CreateConnections(NodeA, NodeB); } else { TransitionNode->CreateConnections(NodeB, NodeA); } return true; } return false; } void UPaperZDStateMachineSchema::GetGraphDisplayInformation(const UEdGraph& Graph, FGraphDisplayInfo& DisplayInfo) const { DisplayInfo.PlainName = FText::FromString(Graph.GetName()); DisplayInfo.DisplayName = DisplayInfo.PlainName; } TSharedPtr UPaperZDStateMachineSchema::GetCreateCommentAction() const { return TSharedPtr(new FPaperZDStateMachineSchemaAction_NewComment); } void UPaperZDStateMachineSchema::BreakNodeLinks(UEdGraphNode& TargetNode) const { const FScopedTransaction Transaction(NSLOCTEXT("PaperZD", "StateMachine_BreakNodeLinks", "Break Node Links")); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(&TargetNode); Super::BreakNodeLinks(TargetNode); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } void UPaperZDStateMachineSchema::BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotification) const { const FScopedTransaction Transaction(NSLOCTEXT("PaperZD", "StateMachine_BreakPinLinks", "Break Pin Links")); // cache this here, as BreakPinLinks can trigger a node reconstruction invalidating the TargetPin references UBlueprint* const Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(TargetPin.GetOwningNode()); Super::BreakPinLinks(TargetPin, bSendsNodeNotification); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } void UPaperZDStateMachineSchema::BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) const { const FScopedTransaction Transaction(NSLOCTEXT("PaperZD", "StateMachine_BreakSinglePinLink", "Break Pin Link")); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(TargetPin->GetOwningNode()); Super::BreakSinglePinLink(SourcePin, TargetPin); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } #undef LOCTEXT_NAMESPACE