You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Removed container and struct conversions from the script VM. This introduced complexity that the VM doesn't need, nor did it scale for the various struct types that we want to implicitly convert in Blueprints. Instead, the script VM is *only* aware of float<->double conversion. Container and struct conversions have now been moved to BlueprintTypeConversions. Currently, only the existing FVector3f<->FVector3d conversion has been added, but the remaining LWC types will be added in a subsequent change. During Blueprint compilation of container and struct conversions, we now inject a function call into the bytecode that performs the conversion, which is better suited to the task instead of burdening the VM with the work. One drawback to this technique is that containers are slightly more inefficient when it comes to conversions. They won't know their type(s) ahead of time, which requires dynamically looking up a conversion function at runtime. We can possibly optimize this further, but the generaly recommendation is to avoid implicit conversions of container types when possible. Additionally, a couple of convenience functions were added to the KismetCastingUtils to help remove a fair amount of boilerplate code that was used for implicit casting in various node types. ScriptCastingUtils.h was also removed since the VM no longer needs to concern itself with complex conversions. #jira UE-151338 #preflight 629a507fb42820769428c133 #rb phillip.kavan [CL 20560449 by dave jones2 in ue5-main branch]
979 lines
28 KiB
C++
979 lines
28 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "K2Node_Select.h"
|
|
#include "Misc/CoreMisc.h"
|
|
#include "Kismet/KismetSystemLibrary.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "EdGraphSchema_K2.h"
|
|
#include "K2Node_CallFunction.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "Kismet/KismetMathLibrary.h"
|
|
#include "EdGraphUtilities.h"
|
|
#include "BPTerminal.h"
|
|
#include "BlueprintCompiledStatement.h"
|
|
#include "KismetCastingUtils.h"
|
|
#include "KismetCompilerMisc.h"
|
|
#include "KismetCompiler.h"
|
|
#include "BlueprintNodeSpawner.h"
|
|
#include "EditorCategoryUtils.h"
|
|
#include "BlueprintActionDatabaseRegistrar.h"
|
|
#include "Styling/AppStyle.h"
|
|
|
|
#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<UEdGraphNode*, FBPTerminal*> DefaultTermMap;
|
|
|
|
public:
|
|
FKCHandler_Select(FKismetCompilerContext& InCompilerContext)
|
|
: FNodeHandlingFunctor(InCompilerContext)
|
|
{
|
|
}
|
|
|
|
virtual void RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node) override
|
|
{
|
|
UK2Node_Select* SelectNode = Cast<UK2Node_Select>(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<UEdGraphPin*> 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<UK2Node_Select>(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<UEdGraphPin*> 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<UEdGraphSchema_K2>(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<UEdGraphPin*>& OldPins)
|
|
{
|
|
Super::ReallocatePinsDuringReconstruction(OldPins);
|
|
|
|
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
// 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<UEdGraphPin*> 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<UEdGraphPin*> 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<UEdGraphPin*> 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<bool(UEdGraphPin*)> 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<UEdGraphPin*>& 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<UEdGraphSchema_K2>();
|
|
|
|
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<UEdGraphSchema_K2>();
|
|
|
|
// 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);
|
|
Blueprint->BroadcastChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
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<UEnum>(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);
|
|
Blueprint->BroadcastChanged();
|
|
}
|
|
}
|
|
|
|
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<UEdGraphPin*> 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<UEdGraphSchema_K2>();
|
|
|
|
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<UEnum>(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<UEdGraphPin*> 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<FNodeHandlingFunctor*>(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;
|
|
UObject* 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
|