// Copyright Epic Games, Inc. All Rights Reserved. #include "K2Node_Select.h" #include "BPTerminal.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintCompiledStatement.h" #include "BlueprintNodeSpawner.h" #include "Containers/EnumAsByte.h" #include "Containers/IndirectArray.h" #include "Containers/Map.h" #include "Containers/UnrealString.h" #include "CoreGlobals.h" #include "EdGraph/EdGraphSchema.h" #include "EdGraphSchema_K2.h" #include "EdGraphUtilities.h" #include "EditorCategoryUtils.h" #include "Engine/Blueprint.h" #include "HAL/PlatformMath.h" #include "Internationalization/Internationalization.h" #include "K2Node_CallFunction.h" #include "Kismet/KismetMathLibrary.h" #include "Kismet/KismetSystemLibrary.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/CompilerResultsLog.h" #include "KismetCastingUtils.h" #include "KismetCompiledFunctionContext.h" #include "KismetCompiler.h" #include "KismetCompilerMisc.h" #include "Misc/AssertionMacros.h" #include "Styling/AppStyle.h" #include "Templates/Casts.h" #include "Templates/Function.h" #include "Templates/UnrealTemplate.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/UnrealNames.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" struct FLinearColor; #define LOCTEXT_NAMESPACE "K2Node_Select" namespace { FName NAME_bPickOption0(TEXT("bPickOption0")); FName NAME_Index(TEXT("Index")); FName NAME_Option_0(TEXT("Option 0")); FName NAME_Option_1(TEXT("Option 1")); } ////////////////////////////////////////////////////////////////////////// // FKCHandler_Select class FKCHandler_Select : public FNodeHandlingFunctor { protected: TMap DefaultTermMap; public: FKCHandler_Select(FKismetCompilerContext& InCompilerContext) : FNodeHandlingFunctor(InCompilerContext) { } virtual void RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node) override { UK2Node_Select* SelectNode = Cast(Node); UEdGraphPin* ReturnPin = SelectNode ? SelectNode->GetReturnValuePin() : nullptr; if (!ReturnPin) { Context.MessageLog.Error(*LOCTEXT("Error_NoReturnPin", "No return pin in @@").ToString(), Node); return; } // return inline term if (Context.NetMap.Find(ReturnPin)) { Context.MessageLog.Error(*LOCTEXT("Error_ReturnTermAlreadyRegistered", "ICE: Return term is already registered @@").ToString(), Node); return; } { FBPTerminal* Term = new FBPTerminal(); Context.InlineGeneratedValues.Add(Term); Term->CopyFromPin(ReturnPin, Context.NetNameMap->MakeValidName(ReturnPin)); Context.NetMap.Add(ReturnPin, Term); } //Register Default term { TArray OptionPins; SelectNode->GetOptionPins(OptionPins); if (!OptionPins.Num()) { Context.MessageLog.Error(*LOCTEXT("Error_NoOptionPin", "No option pin in @@").ToString(), Node); return; } FString DefaultTermName = Context.NetNameMap->MakeValidName(Node, TEXT("Default")); FBPTerminal* DefaultTerm = Context.CreateLocalTerminalFromPinAutoChooseScope(OptionPins[0], DefaultTermName); DefaultTermMap.Add(Node, DefaultTerm); } FNodeHandlingFunctor::RegisterNets(Context, Node); } virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override { UK2Node_Select* SelectNode = CastChecked(Node); FBPTerminal* DefaultTerm = nullptr; FBPTerminal* ReturnTerm = nullptr; FBPTerminal* IndexTerm = nullptr; { UEdGraphPin* IndexPin = SelectNode->GetIndexPin(); UEdGraphPin* IndexPinNet = IndexPin ? FEdGraphUtilities::GetNetFromPin(IndexPin) : nullptr; FBPTerminal** IndexTermPtr = IndexPinNet ? Context.NetMap.Find(IndexPinNet) : nullptr; IndexTerm = IndexTermPtr ? *IndexTermPtr : nullptr; UEdGraphPin* ReturnPin = SelectNode->GetReturnValuePin(); UEdGraphPin* ReturnPinNet = ReturnPin ? FEdGraphUtilities::GetNetFromPin(ReturnPin) : nullptr; FBPTerminal** ReturnTermPtr = ReturnPinNet ? Context.NetMap.Find(ReturnPinNet) : nullptr; ReturnTerm = ReturnTermPtr ? *ReturnTermPtr : nullptr; FBPTerminal** DefaultTermPtr = DefaultTermMap.Find(SelectNode); DefaultTerm = DefaultTermPtr ? *DefaultTermPtr : nullptr; if (!ReturnTerm || !IndexTerm || !DefaultTerm) { Context.MessageLog.Error(*LOCTEXT("Error_InvalidSelect", "ICE: invalid select node @@").ToString(), Node); return; } } FBlueprintCompiledStatement* SelectStatement = new FBlueprintCompiledStatement(); SelectStatement->Type = EKismetCompiledStatementType::KCST_SwitchValue; Context.AllGeneratedStatements.Add(SelectStatement); ReturnTerm->InlineGeneratedParameter = SelectStatement; SelectStatement->RHS.Add(IndexTerm); TArray OptionPins; SelectNode->GetOptionPins(OptionPins); for (int32 OptionIdx = 0; OptionIdx < OptionPins.Num(); ++OptionIdx) { { FBPTerminal* LiteralTerm = Context.CreateLocalTerminal(ETerminalSpecification::TS_Literal); LiteralTerm->Type = IndexTerm->Type; LiteralTerm->bIsLiteral = true; const UEnum* NodeEnum = SelectNode->GetEnum(); LiteralTerm->Name = NodeEnum ? OptionPins[OptionIdx]->PinName.ToString() : FString::Printf(TEXT("%d"), OptionIdx); //-V595 if (!CompilerContext.GetSchema()->DefaultValueSimpleValidation(LiteralTerm->Type, *LiteralTerm->Name, LiteralTerm->Name, nullptr, FText())) { Context.MessageLog.Error(*FText::Format(LOCTEXT("Error_InvalidOptionValueFmt", "Invalid option value '{0}' in @@"), FText::FromString(LiteralTerm->Name)).ToString(), Node); return; } SelectStatement->RHS.Add(LiteralTerm); } { UEdGraphPin* NetPin = OptionPins[OptionIdx] ? FEdGraphUtilities::GetNetFromPin(OptionPins[OptionIdx]) : nullptr; FBPTerminal** ValueTermPtr = NetPin ? Context.NetMap.Find(NetPin) : nullptr; FBPTerminal* ValueTerm = ValueTermPtr ? *ValueTermPtr : nullptr; if (!ensure(ValueTerm)) { Context.MessageLog.Error(*LOCTEXT("Error_NoTermFound", "No term registered for pin @@").ToString(), NetPin); return; } { using namespace UE::KismetCompiler; FBPTerminal* ImplicitCastEntry = CastingUtils::InsertImplicitCastStatement(Context, OptionPins[OptionIdx], ValueTerm); if (ImplicitCastEntry) { ValueTerm = ImplicitCastEntry; } } SelectStatement->RHS.Add(ValueTerm); } } SelectStatement->RHS.Add(DefaultTerm); } }; UK2Node_Select::UK2Node_Select(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { NumOptionPins = 2; IndexPinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard; IndexPinType.PinSubCategory = UEdGraphSchema_K2::PSC_Index; IndexPinType.PinSubCategoryObject = nullptr; OrphanedPinSaveMode = ESaveOrphanPinMode::SaveNone; } void UK2Node_Select::AllocateDefaultPins() { // To refresh, just in case it changed SetEnum(Enum, true); // No need to reconstruct the node after force setting the enum, we are at the start of reconstruction already bReconstructNode = false; if (Enum) { NumOptionPins = EnumEntries.Num(); } // Create the option pins for (int32 Idx = 0; Idx < NumOptionPins; Idx++) { UEdGraphPin* NewPin = nullptr; if (Enum) { const FName PinName = EnumEntries[Idx]; UEdGraphPin* TempPin = FindPin(PinName); if (!TempPin) { NewPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, PinName); } } else { const FName PinName = *FString::Printf(TEXT("Option %d"), Idx); NewPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, PinName); } if (NewPin) { NewPin->bDisplayAsMutableRef = true; if (IndexPinType.PinCategory == UEdGraphSchema_K2::PC_Boolean) { const FCoreTexts& CoreTexts = FCoreTexts::Get(); NewPin->PinFriendlyName = (Idx == 0 ? CoreTexts.False : CoreTexts.True); } else if (Idx < EnumEntryFriendlyNames.Num()) { NewPin->PinFriendlyName = EnumEntryFriendlyNames[Idx]; } } } // Create the index wildcard pin CreatePin(EGPD_Input, IndexPinType.PinCategory, IndexPinType.PinSubCategory, IndexPinType.PinSubCategoryObject.Get(), TEXT("Index")); // Create the return value UEdGraphPin* ReturnPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Wildcard, UEdGraphSchema_K2::PN_ReturnValue); ReturnPin->bDisplayAsMutableRef = true; Super::AllocateDefaultPins(); } void UK2Node_Select::AutowireNewNode(UEdGraphPin* FromPin) { if (FromPin) { // Attempt to autowire to the index pin as users generally drag off of something intending to use // it as an index in a select statement rather than an arbitrary entry: const UEdGraphSchema_K2* K2Schema = CastChecked(GetSchema()); UEdGraphPin* IndexPin = GetIndexPin(); ECanCreateConnectionResponse ConnectResponse = K2Schema->CanCreateConnection(FromPin, IndexPin).Response; if (ConnectResponse == ECanCreateConnectionResponse::CONNECT_RESPONSE_MAKE) { if (K2Schema->TryCreateConnection(FromPin, IndexPin)) { FromPin->GetOwningNode()->NodeConnectionListChanged(); this->NodeConnectionListChanged(); return; } } } // No connection made, just use default autowire logic: Super::AutowireNewNode(FromPin); } FText UK2Node_Select::GetTooltipText() const { return LOCTEXT("SelectNodeTooltip", "Return the option at Index, (first option is indexed at 0)"); } FText UK2Node_Select::GetNodeTitle(ENodeTitleType::Type TitleType) const { return LOCTEXT("Select", "Select"); } UK2Node::ERedirectType UK2Node_Select::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const { if (bReconstructForPinTypeChange) { // If we're reconstructing for the purposes of changing the index pin type then we want to // keep our connections based on the index of the option pin if (NewPin != GetIndexPin() && NewPin != GetReturnValuePin()) { if (NewPinIndex == OldPinIndex) { return UK2Node::ERedirectType_Name; } } } // Check to see if the new pin name matches the old pin name. if (Enum && (NewPinIndex < NumOptionPins) && (NewPin->PinName != OldPin->PinName)) { // The names don't match, so check for an enum redirect from the old pin name. const int32 EnumIndex = Enum->GetIndexByName(OldPin->PinName); if(EnumIndex != INDEX_NONE) { // Found a redirect. Attempt to match it to the new pin name. // Can't use Enum->GetNameByIndex here because it doesn't do namespace mangling const FString NewPinName = Enum->GetNameStringByIndex(EnumIndex); if (NewPinName == NewPin->PinName.ToString()) { // The redirect is a match, so we can reconstruct this pin using the old pin's state. return UK2Node::ERedirectType_Name; } } } // Fall back to base class functionality for all other cases. return Super::DoPinsMatchForReconstruction(NewPin, NewPinIndex, OldPin, OldPinIndex); } void UK2Node_Select::ReallocatePinsDuringReconstruction(TArray& OldPins) { Super::ReallocatePinsDuringReconstruction(OldPins); const UEdGraphSchema_K2* Schema = GetDefault(); // See if this node was saved in the old version with a boolean as the condition UEdGraphPin* OldConditionPin = nullptr; UEdGraphPin* OldIndexPin = nullptr; UEdGraphPin* OldReturnPin = nullptr; for (UEdGraphPin* OldPin : OldPins) { if (OldPin->PinName == NAME_bPickOption0) { OldConditionPin = OldPin; } else if (OldPin->PinName == NAME_Index) { OldIndexPin = OldPin; } else if (OldPin->PinName == UEdGraphSchema_K2::PN_ReturnValue) { OldReturnPin = OldPin; } } UEdGraphPin* ReturnPin = GetReturnValuePin(); check(ReturnPin); if (OldReturnPin && (ReturnPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard)) { // Always copy type from node prior, if pins have changed those will error at compilation time ReturnPin->PinType = OldReturnPin->PinType; } UEdGraphPin* IndexPin = GetIndexPin(); check(IndexPin); // If we are fixing up an old bool node (swap the options and copy the condition links) if (OldConditionPin) { // Set the index pin type IndexPinType.PinCategory = UEdGraphSchema_K2::PC_Boolean; IndexPinType.PinSubCategory = NAME_None; IndexPinType.PinSubCategoryObject = nullptr; // Set the pin type and Copy the pin IndexPin->PinType = IndexPinType; Schema->CopyPinLinks(*OldConditionPin, *IndexPin); // If we copy links, we need to send a notification if (IndexPin->LinkedTo.Num() > 0) { PinConnectionListChanged(IndexPin); } UEdGraphPin* OptionPin0 = FindPin(NAME_Option_0); UEdGraphPin* OptionPin1 = FindPin(NAME_Option_1); for (UEdGraphPin* OldPin : OldPins) { if (OldPin->PinName == OptionPin0->PinName) { Schema->MovePinLinks(*OldPin, *OptionPin1); } else if (OldPin->PinName == OptionPin1->PinName) { Schema->MovePinLinks(*OldPin, *OptionPin0); } } } // If the index pin has links or a default value but is a wildcard, this is an old int pin so convert it if (OldIndexPin && IndexPinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard && (OldIndexPin->LinkedTo.Num() > 0 || !OldIndexPin->DefaultValue.IsEmpty())) { IndexPinType.PinCategory = UEdGraphSchema_K2::PC_Int; IndexPinType.PinSubCategory = NAME_None; IndexPinType.PinSubCategoryObject = nullptr; IndexPin->PinType = IndexPinType; } // Set up default values for index and option pins now that the information is available Schema->SetPinAutogeneratedDefaultValueBasedOnType(IndexPin); const bool bFillTypeFromReturn = ReturnPin->PinType.PinCategory != UEdGraphSchema_K2::PC_Wildcard; TArray OptionPins; GetOptionPins(OptionPins); for (UEdGraphPin* Pin : OptionPins) { const bool bTypeShouldBeFilled = Pin && (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard); if (bTypeShouldBeFilled && bFillTypeFromReturn) { Pin->PinType = ReturnPin->PinType; } Schema->SetPinAutogeneratedDefaultValueBasedOnType(Pin); } } void UK2Node_Select::PostReconstructNode() { // After ReconstructNode we must be sure that no additional reconstruction is required bReconstructNode = false; bReconstructForPinTypeChange = false; UEdGraphPin* ReturnPin = GetReturnValuePin(); const bool bFillTypeFromConnected = ReturnPin && (ReturnPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard); if (bFillTypeFromConnected) { FEdGraphPinType PinType = ReturnPin->PinType; if (ReturnPin->LinkedTo.Num() > 0) { PinType = ReturnPin->LinkedTo[0]->PinType; } else { TArray OptionPins; GetOptionPins(OptionPins); for (UEdGraphPin* Pin : OptionPins) { if (Pin && Pin->LinkedTo.Num() > 0) { PinType = Pin->LinkedTo[0]->PinType; break; } } } ReturnPin->PinType = PinType; OnPinTypeChanged(ReturnPin); } Super::PostReconstructNode(); } /** Determine if any pins are connected, if so make all the other pins the same type, if not, make sure pins are switched back to wildcards */ void UK2Node_Select::NotifyPinConnectionListChanged(UEdGraphPin* Pin) { Super::NotifyPinConnectionListChanged(Pin); // If this is the Enum pin we need to set the enum and reconstruct the node if (Pin == GetIndexPin()) { // If the index pin was just linked to another pin if (Pin->LinkedTo.Num() > 0 && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard) { UEdGraphPin* LinkPin = Pin->LinkedTo[0]; if (Pin->PinType != LinkPin->PinType) { Pin->PinType = LinkPin->PinType; OnPinTypeChanged(Pin); } } } else { // Grab references to all option pins and the return pin TArray OptionPins; GetOptionPins(OptionPins); UEdGraphPin* ReturnPin = FindPin(UEdGraphSchema_K2::PN_ReturnValue); // See if this pin is one of the wildcard pins const bool bIsWildcardPin = ((Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard) && ((Pin == ReturnPin) || (OptionPins.Find(Pin) != INDEX_NONE))); TFunction PinInUse = [&PinInUse](UEdGraphPin* PinToConsider) { bool bPinInUse = ((PinToConsider->LinkedTo.Num() > 0) || (PinToConsider->ParentPin != nullptr) || !PinToConsider->DoesDefaultValueMatchAutogenerated()); if (!bPinInUse) { for (UEdGraphPin* SubPin : PinToConsider->SubPins) { bPinInUse = PinInUse(SubPin); if (bPinInUse) { break; } } } return bPinInUse; }; bool bPinsInUse = PinInUse(ReturnPin); if (!bPinsInUse) { for (UEdGraphPin* OptionPin : OptionPins) { bPinsInUse = PinInUse(OptionPin); if (bPinsInUse) { break; } } } bool bPinTypeChanged = false; if (bPinsInUse) { // If the pin was one of the wildcards we have to handle it specially if (bIsWildcardPin) { // If the pin is linked, make sure the other wildcard pins match if (Pin->LinkedTo.Num() > 0) { UEdGraphPin* LinkPin = Pin->LinkedTo[0]; if (Pin->PinType != LinkPin->PinType) { Pin->PinType = LinkPin->PinType; bPinTypeChanged = true; } } } } else { bPinTypeChanged = true; Pin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard; Pin->PinType.PinSubCategory = NAME_None; Pin->PinType.PinSubCategoryObject = nullptr; } if (bPinTypeChanged) { OnPinTypeChanged(Pin); } } } UEdGraphPin* UK2Node_Select::GetReturnValuePin() const { UEdGraphPin* Pin = FindPin(UEdGraphSchema_K2::PN_ReturnValue); check(Pin); return Pin; } UEdGraphPin* UK2Node_Select::GetIndexPin() const { UEdGraphPin* Pin = GetIndexPinUnchecked(); check(Pin != NULL); return Pin; } UEdGraphPin* UK2Node_Select::GetIndexPinUnchecked() const { return FindPin(TEXT("Index")); } void UK2Node_Select::GetOptionPins(TArray& OptionPins) const { OptionPins.Reset(); // If the select node is currently dealing with an enum if (IndexPinType.PinCategory == UEdGraphSchema_K2::PC_Byte && IndexPinType.PinSubCategory.IsNone() && IndexPinType.PinSubCategoryObject != nullptr && IndexPinType.PinSubCategoryObject->IsA(UEnum::StaticClass())) { for (UEdGraphPin* Pin : Pins) { if (EnumEntries.Contains(Pin->PinName)) { OptionPins.Add(Pin); } } } else { const TCHAR* OptionStr = TEXT("Option"); for (UEdGraphPin* Pin : Pins) { if (Pin->PinName.ToString().StartsWith(OptionStr)) { OptionPins.Add(Pin); } } } } void UK2Node_Select::GetConditionalFunction(FName& FunctionName, UClass** FunctionClass) { const UEdGraphSchema_K2* K2Schema = GetDefault(); if (IndexPinType.PinCategory == UEdGraphSchema_K2::PC_Boolean) { FunctionName = GET_FUNCTION_NAME_CHECKED(UKismetMathLibrary, EqualEqual_BoolBool); } else if (IndexPinType.PinCategory == UEdGraphSchema_K2::PC_Byte) { FunctionName = GET_FUNCTION_NAME_CHECKED(UKismetMathLibrary, EqualEqual_ByteByte); } else if (IndexPinType.PinCategory == UEdGraphSchema_K2::PC_Int) { FunctionName = GET_FUNCTION_NAME_CHECKED(UKismetMathLibrary, EqualEqual_IntInt); } *FunctionClass = UKismetMathLibrary::StaticClass(); } void UK2Node_Select::GetPrintStringFunction(FName& FunctionName, UClass** FunctionClass) { FunctionName = GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, PrintWarning); *FunctionClass = UKismetSystemLibrary::StaticClass(); } void UK2Node_Select::AddInputPin() { Modify(); const UEdGraphSchema_K2* K2Schema = GetDefault(); // Increment the pin count NumOptionPins++; // We guarantee at least 2 options by default and since we just increased the count // to more than 2, we need to make sure we're now dealing with an index for selection // instead of the default boolean check if (IndexPinType.PinCategory == UEdGraphSchema_K2::PC_Boolean) { IndexPinType.PinCategory = UEdGraphSchema_K2::PC_Int; GetIndexPin()->BreakAllPinLinks(); } // We will let the AllocateDefaultPins call handle the actual addition via ReconstructNode ReconstructNode(); } void UK2Node_Select::RemoveOptionPinToNode() { // Increment the pin count NumOptionPins--; // We will let the AllocateDefaultPins call handle the actual subtraction via ReconstructNode ReconstructNode(); } void UK2Node_Select::SetEnum(UEnum* InEnum, bool bForceRegenerate) { UEnum* PrevEnum = Enum; Enum = InEnum; OrphanedPinSaveMode = (Enum ? ESaveOrphanPinMode::SaveAll : ESaveOrphanPinMode::SaveNone); if (bForceRegenerate || (PrevEnum != Enum)) { // regenerate enum name list EnumEntries.Reset(); EnumEntryFriendlyNames.Reset(); if (Enum) { for (int32 EnumIndex = 0; EnumIndex < Enum->NumEnums() - 1; ++EnumIndex) { bool const bShouldBeHidden = Enum->HasMetaData(TEXT("Hidden"), EnumIndex ) || Enum->HasMetaData(TEXT("Spacer"), EnumIndex ); if (!bShouldBeHidden) { FString EnumValueName = Enum->GetNameStringByIndex(EnumIndex); FText EnumFriendlyName = Enum->GetDisplayNameTextByIndex(EnumIndex); EnumEntries.Add(FName(*EnumValueName)); EnumEntryFriendlyNames.Add(EnumFriendlyName); } } } bReconstructNode = true; } } void UK2Node_Select::NodeConnectionListChanged() { Super::NodeConnectionListChanged(); if (bReconstructNode) { ReconstructNode(); UBlueprint* Blueprint = GetBlueprint(); if(!Blueprint->bBeingCompiled) { FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } } } bool UK2Node_Select::CanAddPin() const { if (IndexPinType.PinCategory == UEdGraphSchema_K2::PC_Byte && IndexPinType.PinSubCategoryObject.IsValid() && IndexPinType.PinSubCategoryObject.Get()->IsA(UEnum::StaticClass())) { return false; } else if (IndexPinType.PinCategory == UEdGraphSchema_K2::PC_Boolean) { return false; } return true; } bool UK2Node_Select::CanRemoveOptionPinToNode() const { if (IndexPinType.PinCategory == UEdGraphSchema_K2::PC_Byte && (NULL != Cast(IndexPinType.PinSubCategoryObject.Get()))) { return false; } else if (IndexPinType.PinCategory == UEdGraphSchema_K2::PC_Boolean) { return false; } return true; } void UK2Node_Select::ChangePinType(UEdGraphPin* Pin) { OnPinTypeChanged(Pin); if (bReconstructNode) { bReconstructForPinTypeChange = true; ReconstructNode(); } UBlueprint* Blueprint = GetBlueprint(); if (!Blueprint->bBeingCompiled) { FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); } } bool UK2Node_Select::CanChangePinType(UEdGraphPin* Pin) const { // If this is the index pin, only allow type switching if nothing is linked to the pin if (Pin == GetIndexPin()) { if (Pin->LinkedTo.Num() > 0) { return false; } } // Else it's one of the wildcard pins that share their type, so make sure none of them have a link else { if (GetReturnValuePin()->LinkedTo.Num() > 0) { return false; } else { TArray OptionPins; GetOptionPins(OptionPins); for (UEdGraphPin* OptionPin : OptionPins) { if (OptionPin && OptionPin->LinkedTo.Num() > 0) { return false; } } } } return true; } void UK2Node_Select::PinTypeChanged(UEdGraphPin* Pin) { bReconstructForPinTypeChange = true; OnPinTypeChanged(Pin); } void UK2Node_Select::OnPinTypeChanged(UEdGraphPin* Pin) { const UEdGraphSchema_K2* Schema = GetDefault(); if (Pin == GetIndexPin()) { if (IndexPinType != Pin->PinType) { IndexPinType = Pin->PinType; // Since it is an interactive action we want the pins to go away regardless of the new type for (UEdGraphPin* PinToDiscard : Pins) { PinToDiscard->SetSavePinIfOrphaned(false); } if (IndexPinType.PinSubCategoryObject.IsValid()) { SetEnum(Cast(IndexPinType.PinSubCategoryObject.Get())); } else if (Enum) { SetEnum(nullptr); } // Remove all but two options if we switched to a bool index if (IndexPinType.PinCategory == UEdGraphSchema_K2::PC_Boolean) { NumOptionPins = 2; } if (!Schema->IsPinDefaultValid(Pin, Pin->DefaultValue, Pin->DefaultObject, Pin->DefaultTextValue).IsEmpty()) { Schema->ResetPinToAutogeneratedDefaultValue(Pin); } bReconstructNode = true; } } else { // Set the return value UEdGraphPin* ReturnPin = GetReturnValuePin(); // Recombine the sub pins back into the ReturnPin if (ReturnPin->SubPins.Num() > 0) { Schema->RecombinePin(ReturnPin->SubPins[0]); } ReturnPin->PinType = Pin->PinType; // Recombine all option pins back into their root TArray OptionPins; GetOptionPins(OptionPins); for (UEdGraphPin* OptionPin : OptionPins) { // Recombine the sub pins back into the OptionPin if (OptionPin->ParentPin == nullptr && OptionPin->SubPins.Num() > 0) { Schema->RecombinePin(OptionPin->SubPins[0]); } } // Get the options again and set them GetOptionPins(OptionPins); for (UEdGraphPin* OptionPin : OptionPins) { if (OptionPin->PinType != Pin->PinType || OptionPin == Pin) { OptionPin->PinType = Pin->PinType; } if (!Schema->IsPinDefaultValid(OptionPin, OptionPin->DefaultValue, OptionPin->DefaultObject, OptionPin->DefaultTextValue).IsEmpty()) { Schema->ResetPinToAutogeneratedDefaultValue(OptionPin); } } bReconstructNode = true; } } void UK2Node_Select::PostPasteNode() { Super::PostPasteNode(); if (UEdGraphPin* IndexPin = GetIndexPinUnchecked()) { // This information will be cleared and we want to restore it FString OldDefaultValue = IndexPin->DefaultValue; // Corrects data in the index pin that is not valid after pasting OnPinTypeChanged(IndexPin); // Restore the default value of the index pin IndexPin->DefaultValue = MoveTemp(OldDefaultValue); } } FSlateIcon UK2Node_Select::GetIconAndTint(FLinearColor& OutColor) const { static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "GraphEditor.Select_16x"); return Icon; } bool UK2Node_Select::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const { if (OtherPin && (OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)) { OutReason = LOCTEXT("ExecConnectionDisallowed", "Cannot connect with Exec pin.").ToString(); return true; } return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason); } FNodeHandlingFunctor* UK2Node_Select::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const { return static_cast(new FKCHandler_Select(CompilerContext)); } void UK2Node_Select::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { // actions get registered under specific object-keys; the idea is that // actions might have to be updated (or deleted) if their object-key is // mutated (or removed)... here we use the node's class (so if the node // type disappears, then the action should go with it) UClass* ActionKey = GetClass(); // to keep from needlessly instantiating a UBlueprintNodeSpawner, first // check to make sure that the registrar is looking for actions of this type // (could be regenerating actions for a specific asset, and therefore the // registrar would only accept actions corresponding to that asset) if (ActionRegistrar.IsOpenForRegistration(ActionKey)) { UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); check(NodeSpawner != nullptr); ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); } } FText UK2Node_Select::GetMenuCategory() const { return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Utilities); } void UK2Node_Select::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); for (UEdGraphPin* Pin : Pins) { const bool bValidAutoRefPin = Pin && !Schema->IsMetaPin(*Pin) && (Pin->Direction == EGPD_Input) && (!Pin->LinkedTo.Num() || (GetIndexPin() == Pin)); if (!bValidAutoRefPin) { continue; } // copy defaults as default values can be reset when the pin is connected const FString DefaultValue = Pin->DefaultValue; TObjectPtr DefaultObject = Pin->DefaultObject; const FText DefaultTextValue = Pin->DefaultTextValue; bool bMatchesDefaults = Pin->DoesDefaultValueMatchAutogenerated(); UEdGraphPin* ValuePin = UK2Node_CallFunction::InnerHandleAutoCreateRef(this, Pin, CompilerContext, SourceGraph, true); if (ValuePin) { if (bMatchesDefaults) { // Use the latest code to set default value Schema->SetPinAutogeneratedDefaultValueBasedOnType(ValuePin); } else { ValuePin->DefaultValue = DefaultValue; ValuePin->DefaultObject = DefaultObject; ValuePin->DefaultTextValue = DefaultTextValue; } } } } void UK2Node_Select::ReloadEnum(class UEnum* InEnum) { SetEnum(InEnum, true); } #undef LOCTEXT_NAMESPACE