// Copyright Epic Games, Inc. All Rights Reserved. #include "K2Node_SceneStateEventBase.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintNodeSpawner.h" #include "K2Node_CallFunction.h" #include "K2Node_MakeStruct.h" #include "Kismet/BlueprintInstancedStructLibrary.h" #include "Kismet2/BlueprintEditorUtils.h" #include "KismetCompiler.h" #include "SceneStateEventSchema.h" #include "StructUtils/StructView.h" #include "StructUtils/UserDefinedStruct.h" #define LOCTEXT_NAMESPACE "K2Node_SceneStateEventBase" const FLazyName UK2Node_SceneStateEventBase::PN_EventStream = TEXT("EventStream"); const FLazyName UK2Node_SceneStateEventBase::PN_WorldContextObject = TEXT("WorldContextObject"); FText UK2Node_SceneStateEventBase::GetSchemaDisplayNameText() const { if (const USceneStateEventSchemaObject* EventSchema = EventSchemaHandle.GetEventSchema()) { return FText::FromName(EventSchema->Name); } return LOCTEXT("InvalidSchemaText", "Invalid"); } FString UK2Node_SceneStateEventBase::GetSchemaHandleStringValue() const { FProperty* EventSchemaHandleProperty = FindFProperty(GetClass(), GET_MEMBER_NAME_CHECKED(UK2Node_SceneStateEventBase, EventSchemaHandle)); check(EventSchemaHandleProperty); FString StringValue; EventSchemaHandleProperty->ExportText_Direct(StringValue, &EventSchemaHandle, &EventSchemaHandle, nullptr, PPF_None); return StringValue; } void UK2Node_SceneStateEventBase::OnEventSchemaChanged() { if (!bHasEventData) { return; } // Remove all pins related to event data TArray OldPins = Pins; TArray OldClassPins; for (UEdGraphPin* OldPin : OldPins) { if (IsEventDataPin(OldPin)) { Pins.Remove(OldPin); OldClassPins.Add(OldPin); } } CreateEventDataPins(Pins); RestoreSplitPins(OldPins); // Rewire the old pins to the new pins so connections are maintained if possible RewireOldPinsToNewPins(OldClassPins, Pins, nullptr); // Refresh the UI for the graph so the pin changes show up GetGraph()->NotifyGraphChanged(); FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprint()); } bool UK2Node_SceneStateEventBase::IsEventDataPin(const UEdGraphPin* InPin) const { if (!InPin) { return false; } if (InPin->ParentPin) { return IsEventDataPin(InPin->ParentPin); } return InPin->PinName != UEdGraphSchema_K2::PN_Execute && InPin->PinName != UEdGraphSchema_K2::PN_Then && InPin->PinName != UEdGraphSchema_K2::PN_ReturnValue && InPin->PinName != UK2Node_SceneStateEventBase::PN_EventStream && InPin->PinName != UK2Node_SceneStateEventBase::PN_WorldContextObject; } void UK2Node_SceneStateEventBase::CreateEventDataPins(TConstArrayView InPinsToSearch) { if (!bHasEventData) { return; } const UEdGraphSchema_K2* GraphSchema = GetDefault(); const FStructView EventSchemaDefaultData = EventSchemaHandle.GetDefaultDataView(); if (!EventSchemaDefaultData.IsValid()) { return; } for (FProperty* Property : TFieldRange(EventSchemaDefaultData.GetScriptStruct(), EFieldIteratorFlags::IncludeSuper)) { // No need to add if Pin already is there if (FindPin(Property->GetFName(), Pins)) { continue; } if (UEdGraphPin* Pin = CreatePin(EventDataPinDirection, NAME_None, Property->GetFName())) { Pin->PinFriendlyName = Property->GetDisplayNameText(); GraphSchema->ConvertPropertyToPinType(Property, /*out*/Pin->PinType); if (GraphSchema->PinDefaultValueIsEditable(*Pin)) { FString DefaultValueStr; if (FBlueprintEditorUtils::PropertyValueToString(Property, EventSchemaDefaultData.GetMemory(), DefaultValueStr, this)) { GraphSchema->SetPinAutogeneratedDefaultValue(Pin, DefaultValueStr); } } // Copy tooltip from the property. GraphSchema->ConstructBasicPinTooltip(*Pin, Property->GetToolTipText(), Pin->PinToolTip); } } } void UK2Node_SceneStateEventBase::MoveEventDataPins(FKismetCompilerContext& InCompilerContext, UEdGraphNode* InTargetIntermediateNode) { if (!ensure(InTargetIntermediateNode) || !bHasEventData) { return; } for (UEdGraphPin* Pin : Pins) { if (IsEventDataPin(Pin)) { UEdGraphPin* const TargetPin = InTargetIntermediateNode->FindPin(Pin->GetFName(), EventDataPinDirection); if (!TargetPin || !InCompilerContext.MovePinLinksToIntermediate(*Pin, *TargetPin).CanSafeConnect()) { InCompilerContext.MessageLog.Error(*LOCTEXT("EventDataPinConnectError", "ICE: Error connecting Event Data Pin @@. @@").ToString() , Pin, this); } } } } bool UK2Node_SceneStateEventBase::ConnectPinsToIntermediate(FKismetCompilerContext& InCompilerContext, UK2Node* InTargetIntermediateNode, FName InSourcePin, FName InTargetPin) { check(InTargetIntermediateNode); UEdGraphPin* const SourcePin = FindPin(InSourcePin, Pins); UEdGraphPin* const TargetPin = InTargetIntermediateNode->FindPin(InTargetPin); return SourcePin && TargetPin && ensure(SourcePin->Direction == TargetPin->Direction) && InCompilerContext.MovePinLinksToIntermediate(*SourcePin, *TargetPin).CanSafeConnect(); } bool UK2Node_SceneStateEventBase::ChainNode(FNodeExpansionContext& Context, const UK2Node* InNode) { check(InNode); const UEdGraphSchema_K2* Schema = Context.CompilerContext.GetSchema(); check(Schema); // if a chaining node exists, connect the current node to it if (Context.ChainingNode) { UEdGraphPin* const ExecPin = InNode->GetExecPin(); UEdGraphPin* const ThenPin = Context.ChainingNode->GetThenPin(); if (!ExecPin || !ThenPin || !Schema->TryCreateConnection(ThenPin, ExecPin)) { Context.CompilerContext.MessageLog.Error(*LOCTEXT("SchemaConnectionError", "ICE: Error connecting '{0}' to '{1}' node. @@").ToString() , *Context.ChainingNode->GetName() , *InNode->GetName() , this); return false; } } // if a chaining node does not exist, move this node's exec pin to the given node's exec pin else { UEdGraphPin* const SourceExecPin = GetExecPin(); UEdGraphPin* const TargetExecPin = InNode->GetExecPin(); if (!SourceExecPin || !TargetExecPin || !Context.CompilerContext.MovePinLinksToIntermediate(*SourceExecPin, *TargetExecPin).CanSafeConnect()) { Context.CompilerContext.MessageLog.Error( *LOCTEXT("MoveExecPinLinksError", "ICE: Error moving exec pins to '{0}' node. @@").ToString() , *InNode->GetName() , this); return false; } } Context.ChainingNode = InNode; return true; } bool UK2Node_SceneStateEventBase::FinishChain(const FNodeExpansionContext& Context) { check(Context.ChainingNode); UEdGraphPin* const SourceThenPin = GetThenPin(); UEdGraphPin* const TargetThenPin = Context.ChainingNode->GetThenPin(); if (!SourceThenPin || !TargetThenPin || !Context.CompilerContext.MovePinLinksToIntermediate(*SourceThenPin, *TargetThenPin).CanSafeConnect()) { Context.CompilerContext.MessageLog.Error(*LOCTEXT("MoveThenPinLinksError", "ICE: Error moving then pin to '{0}' node. @@").ToString() , *Context.ChainingNode->GetName() , this); return false; } return true; } void UK2Node_SceneStateEventBase::SpawnEventDataNodes(FNodeExpansionContext& Context) { UUserDefinedStruct* EventStruct = EventSchemaHandle.GetEventStruct(); if (!EventStruct) { // OK for Event struct to be null. Could be an event with no parameters return; } const UEdGraphSchema_K2* Schema = Context.CompilerContext.GetSchema(); check(Schema); // Create 'Make Instanced Struct' node UK2Node_CallFunction* MakeInstancedStructNode = Context.CompilerContext.SpawnIntermediateNode(this, Context.SourceGraph); check(MakeInstancedStructNode); MakeInstancedStructNode->FunctionReference.SetExternalMember(GET_FUNCTION_NAME_CHECKED(UBlueprintInstancedStructLibrary, MakeInstancedStruct), UBlueprintInstancedStructLibrary::StaticClass()); MakeInstancedStructNode->AllocateDefaultPins(); // Connect the Make Instanced Struct Node ChainNode(Context, MakeInstancedStructNode); // Connect Event Data Pins to a Make Struct Node UK2Node_MakeStruct* const MakeStruct = Context.CompilerContext.SpawnIntermediateNode(this, Context.SourceGraph); check(MakeStruct); MakeStruct->StructType = EventStruct; MakeStruct->PostPlacedNewNode(); MakeStruct->AllocateDefaultPins(); // Move Event Data Input Pins to Make Struct Input MoveEventDataPins(Context.CompilerContext, MakeStruct); // Connect the output struct pin of 'MakeStruct' to the 'MakeInstancedStruct' struct input { UEdGraphPin* InputStructPin = MakeInstancedStructNode->FindPin(TEXT("Value")); UEdGraphPin* OutputStructPin = MakeStruct->FindPin(EventStruct->GetFName(), EGPD_Output); if (!InputStructPin || !OutputStructPin || !Schema->TryCreateConnection(OutputStructPin, InputStructPin)) { Context.CompilerContext.MessageLog.Error( *LOCTEXT("MakeStructConnectError", "ICE: Error connecting Make Struct result to Make Instanced Struct. @@").ToString() , this); return; } MakeInstancedStructNode->ReconstructNode(); } // Connect the Output of Instanced Struct to the Input Event Data Pin { UEdGraphPin* const InputEventDataPin = Context.EventDataPin; UEdGraphPin* const OutputInstancedStructPin = MakeInstancedStructNode->FindPin(UEdGraphSchema_K2::PN_ReturnValue); if (!InputEventDataPin || !OutputInstancedStructPin || !Schema->TryCreateConnection(OutputInstancedStructPin, InputEventDataPin)) { Context.CompilerContext.MessageLog.Error( *LOCTEXT("EventDataConnectError", "ICE: Error connecting Make Instance Struct result to Event Data. @@").ToString() , this); } } } UEdGraphPin* UK2Node_SceneStateEventBase::FindPin(FName InPinName, TConstArrayView InPinsToSearch) const { for (UEdGraphPin* Pin : InPinsToSearch) { if (Pin && Pin->PinName == InPinName) { return Pin; } } return nullptr; } void UK2Node_SceneStateEventBase::PostEditChangeProperty(FPropertyChangedEvent& InPropertyChangedEvent) { Super::PostEditChangeProperty(InPropertyChangedEvent); if (InPropertyChangedEvent.GetMemberPropertyName() == GET_MEMBER_NAME_CHECKED(UK2Node_SceneStateEventBase, EventSchemaHandle)) { OnEventSchemaChanged(); } } bool UK2Node_SceneStateEventBase::HasExternalDependencies(TArray* OutOptionalOutput) const { if (UUserDefinedStruct* EventStruct = EventSchemaHandle.GetEventStruct()) { if (OutOptionalOutput) { OutOptionalOutput->AddUnique(EventStruct); } return true; } return false; } bool UK2Node_SceneStateEventBase::IsCompatibleWithGraph(const UEdGraph* InTargetGraph) const { const UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(InTargetGraph); return Super::IsCompatibleWithGraph(InTargetGraph) && (!Blueprint || FBlueprintEditorUtils::FindUserConstructionScript(Blueprint) != InTargetGraph); } void UK2Node_SceneStateEventBase::PostPlacedNewNode() { Super::PostPlacedNewNode(); CreateEventDataPins(Pins); } bool UK2Node_SceneStateEventBase::ShouldShowNodeProperties() const { return true; } bool UK2Node_SceneStateEventBase::IsNodeSafeToIgnore() const { return true; } void UK2Node_SceneStateEventBase::ReallocatePinsDuringReconstruction(TArray& InOldPins) { AllocateDefaultPins(); CreateEventDataPins(InOldPins); RestoreSplitPins(InOldPins); } void UK2Node_SceneStateEventBase::GetNodeAttributes(TArray>& OutNodeAttributes) const { Super::GetNodeAttributes(OutNodeAttributes); const USceneStateEventSchemaObject* EventSchema = EventSchemaHandle.GetEventSchema(); const FString EventSchemaName = EventSchema ? EventSchema->Name.ToString() : TEXT("InvalidEventSchema"); OutNodeAttributes.Add(TKeyValuePair(TEXT("Type"), TEXT("AddSceneStateEvent"))); OutNodeAttributes.Add(TKeyValuePair(TEXT("Class"), GetNameSafe(GetClass()))); OutNodeAttributes.Add(TKeyValuePair(TEXT("Name"), GetName())); OutNodeAttributes.Add(TKeyValuePair(TEXT("EventSchema"), EventSchemaName)); } void UK2Node_SceneStateEventBase::GetMenuActions(FBlueprintActionDatabaseRegistrar& InActionRegistrar) const { UClass* ActionKey = GetClass(); if (InActionRegistrar.IsOpenForRegistration(ActionKey)) { UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(ActionKey); check(NodeSpawner); InActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); } } FText UK2Node_SceneStateEventBase::GetMenuCategory() const { return LOCTEXT("MenuCategory", "Scene State Event"); } #undef LOCTEXT_NAMESPACE