Files
UnrealEngineUWP/Engine/Source/Editor/BlueprintGraph/Private/K2Node_GetDataTableRow.cpp
dave jones2 64ae24ec12 Removed extraneous GetSelfPin implementations.
Since GetThenPin was added to UK2Node in 5.x, derived classes don't need to provide their own implementations. These were largely identical to the one in UK2Node. Additionally, overriding non-virtual functions can be error prone if there's an expectation of polymorphic behavior.

#rb dan.oconnor, jodon.karlik
#rnx
[FYI] George.Rolfe

[CL 35782464 by dave jones2 in ue5-main branch]
2024-08-23 16:46:50 -04:00

516 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "K2Node_GetDataTableRow.h"
#include "BlueprintActionDatabaseRegistrar.h"
#include "BlueprintNodeSpawner.h"
#include "Containers/EnumAsByte.h"
#include "Containers/Map.h"
#include "Containers/UnrealString.h"
#include "DataTableEditorUtils.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphPin.h"
#include "EdGraph/EdGraphSchema.h"
#include "EdGraphSchema_K2.h"
#include "EditorCategoryUtils.h"
#include "Engine/DataTable.h"
#include "Engine/MemberReference.h"
#include "HAL/PlatformMath.h"
#include "Internationalization/Internationalization.h"
#include "K2Node_CallFunction.h"
#include "K2Node_IfThenElse.h"
#include "Kismet/DataTableFunctionLibrary.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/CompilerResultsLog.h"
#include "KismetCompiler.h"
#include "Math/Color.h"
#include "Misc/AssertionMacros.h"
#include "Styling/AppStyle.h"
#include "Templates/Casts.h"
#include "UObject/Class.h"
#include "UObject/NameTypes.h"
#include "UObject/Object.h"
#include "UObject/ObjectPtr.h"
#include "UObject/UObjectBaseUtility.h"
#include "UObject/UnrealNames.h"
#include "UObject/WeakObjectPtr.h"
#include "UObject/WeakObjectPtrTemplates.h"
class UBlueprint;
#define LOCTEXT_NAMESPACE "K2Node_GetDataTableRow"
namespace GetDataTableRowHelper
{
const FName DataTablePinName = "DataTable";
const FName RowNotFoundPinName = "RowNotFound";
const FName RowNamePinName = "RowName";
}
UK2Node_GetDataTableRow::UK2Node_GetDataTableRow(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
NodeTooltip = LOCTEXT("NodeTooltip", "Attempts to retrieve a TableRow from a DataTable via it's RowName");
}
void UK2Node_GetDataTableRow::AllocateDefaultPins()
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
// Add execution pins
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
UEdGraphPin* RowFoundPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
RowFoundPin->PinFriendlyName = LOCTEXT("GetDataTableRow Row Found Exec pin", "Row Found");
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GetDataTableRowHelper::RowNotFoundPinName);
// Add DataTable pin
UEdGraphPin* DataTablePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UDataTable::StaticClass(), GetDataTableRowHelper::DataTablePinName);
SetPinToolTip(*DataTablePin, LOCTEXT("DataTablePinDescription", "The DataTable you want to retreive a row from"));
// Row Name pin
UEdGraphPin* RowNamePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Name, GetDataTableRowHelper::RowNamePinName);
SetPinToolTip(*RowNamePin, LOCTEXT("RowNamePinDescription", "The name of the row to retrieve from the DataTable"));
// Result pin
UEdGraphPin* ResultPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Wildcard, UEdGraphSchema_K2::PN_ReturnValue);
ResultPin->PinFriendlyName = LOCTEXT("GetDataTableRow Output Row", "Out Row");
SetPinToolTip(*ResultPin, LOCTEXT("ResultPinDescription", "The returned TableRow, if found"));
Super::AllocateDefaultPins();
}
void UK2Node_GetDataTableRow::SetPinToolTip(UEdGraphPin& MutatablePin, const FText& PinDescription) const
{
MutatablePin.PinToolTip = UEdGraphSchema_K2::TypeToText(MutatablePin.PinType).ToString();
UEdGraphSchema_K2 const* const K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
if (K2Schema != nullptr)
{
MutatablePin.PinToolTip += TEXT(" ");
MutatablePin.PinToolTip += K2Schema->GetPinDisplayName(&MutatablePin).ToString();
}
MutatablePin.PinToolTip += FString(TEXT("\n")) + PinDescription.ToString();
}
void UK2Node_GetDataTableRow::RefreshOutputPinType()
{
UScriptStruct* OutputType = GetDataTableRowStructType();
SetReturnTypeForStruct(OutputType);
}
void UK2Node_GetDataTableRow::RefreshRowNameOptions()
{
// When the DataTable pin gets a new value assigned, we need to update the Slate UI so that SGraphNodeCallParameterCollectionFunction will update the ParameterName drop down
UEdGraph* Graph = GetGraph();
Graph->NotifyNodeChanged(this);
}
void UK2Node_GetDataTableRow::SetReturnTypeForStruct(UScriptStruct* NewRowStruct)
{
UScriptStruct* OldRowStruct = GetReturnTypeForStruct();
if (NewRowStruct != OldRowStruct)
{
UEdGraphPin* ResultPin = GetResultPin();
if (ResultPin->SubPins.Num() > 0)
{
GetSchema()->RecombinePin(ResultPin);
}
// NOTE: purposefully not disconnecting the ResultPin (even though it changed type)... we want the user to see the old
// connections, and incompatible connections will produce an error (plus, some super-struct connections may still be valid)
ResultPin->PinType.PinSubCategoryObject = NewRowStruct;
ResultPin->PinType.PinCategory = (NewRowStruct == nullptr) ? UEdGraphSchema_K2::PC_Wildcard : UEdGraphSchema_K2::PC_Struct;
CachedNodeTitle.Clear();
}
}
UScriptStruct* UK2Node_GetDataTableRow::GetReturnTypeForStruct()
{
UScriptStruct* ReturnStructType = (UScriptStruct*)(GetResultPin()->PinType.PinSubCategoryObject.Get());
return ReturnStructType;
}
UScriptStruct* UK2Node_GetDataTableRow::GetDataTableRowStructType() const
{
UScriptStruct* RowStructType = nullptr;
UEdGraphPin* DataTablePin = GetDataTablePin();
if(DataTablePin && DataTablePin->DefaultObject != nullptr && DataTablePin->LinkedTo.Num() == 0)
{
if (const UDataTable* DataTable = Cast<const UDataTable>(DataTablePin->DefaultObject))
{
RowStructType = DataTable->RowStruct;
}
}
if (RowStructType == nullptr)
{
UEdGraphPin* ResultPin = GetResultPin();
if (ResultPin && ResultPin->LinkedTo.Num() > 0)
{
RowStructType = Cast<UScriptStruct>(ResultPin->LinkedTo[0]->PinType.PinSubCategoryObject.Get());
if (RowStructType == nullptr && ResultPin->PinType.PinCategory != UEdGraphSchema_K2::PC_Wildcard)
{
RowStructType = GetFallbackStruct();
}
for (int32 LinkIndex = 1; LinkIndex < ResultPin->LinkedTo.Num(); ++LinkIndex)
{
UEdGraphPin* Link = ResultPin->LinkedTo[LinkIndex];
UScriptStruct* LinkType = Cast<UScriptStruct>(Link->PinType.PinSubCategoryObject.Get());
if (RowStructType && RowStructType->IsChildOf(LinkType))
{
RowStructType = LinkType;
}
}
}
}
return RowStructType;
}
void UK2Node_GetDataTableRow::OnDataTableRowListChanged(const UDataTable* DataTable)
{
UEdGraphPin* DataTablePin = GetDataTablePin();
if (DataTable && DataTablePin && DataTable == DataTablePin->DefaultObject)
{
UEdGraphPin* RowNamePin = GetRowNamePin();
const bool TryRefresh = RowNamePin && !RowNamePin->LinkedTo.Num();
const FName CurrentName = RowNamePin ? FName(*RowNamePin->GetDefaultAsString()) : NAME_None;
if (TryRefresh && RowNamePin && !DataTable->GetRowNames().Contains(CurrentName))
{
if (UBlueprint* BP = GetBlueprint())
{
FBlueprintEditorUtils::MarkBlueprintAsModified(BP);
}
}
}
}
void UK2Node_GetDataTableRow::ReallocatePinsDuringReconstruction(TArray<UEdGraphPin*>& OldPins)
{
Super::ReallocatePinsDuringReconstruction(OldPins);
if (UEdGraphPin* DataTablePin = GetDataTablePin(&OldPins))
{
if (UDataTable* DataTable = Cast<UDataTable>(DataTablePin->DefaultObject))
{
// make sure to properly load the data-table object so that we can
// farm the "RowStruct" property from it (below, in GetDataTableRowStructType)
PreloadObject(DataTable);
}
}
}
void UK2Node_GetDataTableRow::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_GetDataTableRow::GetMenuCategory() const
{
return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Utilities);
}
bool UK2Node_GetDataTableRow::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const
{
if (MyPin == GetResultPin() && MyPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard)
{
bool bDisallowed = true;
if (OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct)
{
if (UScriptStruct* ConnectionType = Cast<UScriptStruct>(OtherPin->PinType.PinSubCategoryObject.Get()))
{
bDisallowed = !FDataTableEditorUtils::IsValidTableStruct(ConnectionType);
}
}
else if (OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard)
{
bDisallowed = false;
}
if (bDisallowed)
{
OutReason = TEXT("Must be a struct that can be used in a DataTable");
}
return bDisallowed;
}
return false;
}
void UK2Node_GetDataTableRow::PinDefaultValueChanged(UEdGraphPin* ChangedPin)
{
if (ChangedPin && ChangedPin->PinName == GetDataTableRowHelper::DataTablePinName)
{
RefreshOutputPinType();
UEdGraphPin* RowNamePin = GetRowNamePin();
UDataTable* DataTable = Cast<UDataTable>(ChangedPin->DefaultObject);
if (RowNamePin)
{
if (DataTable && (RowNamePin->DefaultValue.IsEmpty() || !DataTable->GetRowMap().Contains(*RowNamePin->DefaultValue)))
{
if (auto Iterator = DataTable->GetRowMap().CreateConstIterator())
{
RowNamePin->DefaultValue = Iterator.Key().ToString();
}
}
RefreshRowNameOptions();
}
}
}
FText UK2Node_GetDataTableRow::GetTooltipText() const
{
return NodeTooltip;
}
UEdGraphPin* UK2Node_GetDataTableRow::GetDataTablePin(const TArray<UEdGraphPin*>* InPinsToSearch /*= NULL*/) const
{
const TArray<UEdGraphPin*>* PinsToSearch = InPinsToSearch ? InPinsToSearch : &Pins;
UEdGraphPin* Pin = nullptr;
for (UEdGraphPin* TestPin : *PinsToSearch)
{
if (TestPin && TestPin->PinName == GetDataTableRowHelper::DataTablePinName)
{
Pin = TestPin;
break;
}
}
check(Pin == nullptr || Pin->Direction == EGPD_Input);
return Pin;
}
UEdGraphPin* UK2Node_GetDataTableRow::GetRowNamePin() const
{
UEdGraphPin* Pin = FindPinChecked(GetDataTableRowHelper::RowNamePinName);
check(Pin->Direction == EGPD_Input);
return Pin;
}
UEdGraphPin* UK2Node_GetDataTableRow::GetRowNotFoundPin() const
{
UEdGraphPin* Pin = FindPinChecked(GetDataTableRowHelper::RowNotFoundPinName);
check(Pin->Direction == EGPD_Output);
return Pin;
}
UEdGraphPin* UK2Node_GetDataTableRow::GetResultPin() const
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
UEdGraphPin* Pin = FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue);
check(Pin->Direction == EGPD_Output);
return Pin;
}
FText UK2Node_GetDataTableRow::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
if (TitleType == ENodeTitleType::MenuTitle)
{
return LOCTEXT("ListViewTitle", "Get Data Table Row");
}
else if (UEdGraphPin* DataTablePin = GetDataTablePin())
{
if (DataTablePin->LinkedTo.Num() > 0)
{
return NSLOCTEXT("K2Node", "DataTable_Title_Unknown", "Get Data Table Row");
}
else if (DataTablePin->DefaultObject == nullptr)
{
return NSLOCTEXT("K2Node", "DataTable_Title_None", "Get Data Table Row NONE");
}
else if (CachedNodeTitle.IsOutOfDate(this))
{
FFormatNamedArguments Args;
Args.Add(TEXT("DataTableName"), FText::FromString(DataTablePin->DefaultObject->GetName()));
FText LocFormat = NSLOCTEXT("K2Node", "DataTable", "Get Data Table Row {DataTableName}");
// FText::Format() is slow, so we cache this to save on performance
CachedNodeTitle.SetCachedText(FText::Format(LocFormat, Args), this);
}
}
else
{
return NSLOCTEXT("K2Node", "DataTable_Title_None", "Get Data Table Row NONE");
}
return CachedNodeTitle;
}
void UK2Node_GetDataTableRow::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
Super::ExpandNode(CompilerContext, SourceGraph);
UEdGraphPin* OriginalDataTableInPin = GetDataTablePin();
UDataTable* Table = (OriginalDataTableInPin != NULL) ? Cast<UDataTable>(OriginalDataTableInPin->DefaultObject) : NULL;
if((nullptr == OriginalDataTableInPin) || (0 == OriginalDataTableInPin->LinkedTo.Num() && nullptr == Table))
{
CompilerContext.MessageLog.Error(*LOCTEXT("GetDataTableRowNoDataTable_Error", "GetDataTableRow must have a DataTable specified.").ToString(), this);
// we break exec links so this is the only error we get
BreakAllNodeLinks();
return;
}
// FUNCTION NODE
const FName FunctionName = GET_FUNCTION_NAME_CHECKED(UDataTableFunctionLibrary, GetDataTableRowFromName);
UK2Node_CallFunction* GetDataTableRowFunction = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph);
GetDataTableRowFunction->FunctionReference.SetExternalMember(FunctionName, UDataTableFunctionLibrary::StaticClass());
GetDataTableRowFunction->AllocateDefaultPins();
CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *(GetDataTableRowFunction->GetExecPin()));
// Connect the input of our GetDataTableRow to the Input of our Function pin
UEdGraphPin* DataTableInPin = GetDataTableRowFunction->FindPinChecked(TEXT("Table"));
if(OriginalDataTableInPin->LinkedTo.Num() > 0)
{
// Copy the connection
CompilerContext.MovePinLinksToIntermediate(*OriginalDataTableInPin, *DataTableInPin);
}
else
{
// Copy literal
DataTableInPin->DefaultObject = OriginalDataTableInPin->DefaultObject;
}
UEdGraphPin* RowNameInPin = GetDataTableRowFunction->FindPinChecked(TEXT("RowName"));
CompilerContext.MovePinLinksToIntermediate(*GetRowNamePin(), *RowNameInPin);
// Get some pins to work with
UEdGraphPin* OriginalOutRowPin = FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue);
UEdGraphPin* FunctionOutRowPin = GetDataTableRowFunction->FindPinChecked(TEXT("OutRow"));
UEdGraphPin* FunctionReturnPin = GetDataTableRowFunction->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue);
UEdGraphPin* FunctionThenPin = GetDataTableRowFunction->GetThenPin();
// Set the type of the OutRow pin on this expanded mode to match original
FunctionOutRowPin->PinType = OriginalOutRowPin->PinType;
FunctionOutRowPin->PinType.PinSubCategoryObject = OriginalOutRowPin->PinType.PinSubCategoryObject;
//BRANCH NODE
UK2Node_IfThenElse* BranchNode = CompilerContext.SpawnIntermediateNode<UK2Node_IfThenElse>(this, SourceGraph);
BranchNode->AllocateDefaultPins();
// Hook up inputs to branch
FunctionThenPin->MakeLinkTo(BranchNode->GetExecPin());
FunctionReturnPin->MakeLinkTo(BranchNode->GetConditionPin());
// Hook up outputs
CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *(BranchNode->GetThenPin()));
CompilerContext.MovePinLinksToIntermediate(*GetRowNotFoundPin(), *(BranchNode->GetElsePin()));
CompilerContext.MovePinLinksToIntermediate(*OriginalOutRowPin, *FunctionOutRowPin);
BreakAllNodeLinks();
}
FSlateIcon UK2Node_GetDataTableRow::GetIconAndTint(FLinearColor& OutColor) const
{
OutColor = GetNodeTitleColor();
static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "Kismet.AllClasses.FunctionIcon");
return Icon;
}
void UK2Node_GetDataTableRow::PostReconstructNode()
{
Super::PostReconstructNode();
RefreshOutputPinType();
}
void UK2Node_GetDataTableRow::EarlyValidation(class FCompilerResultsLog& MessageLog) const
{
Super::EarlyValidation(MessageLog);
const UEdGraphPin* DataTablePin = GetDataTablePin();
const UEdGraphPin* RowNamePin = GetRowNamePin();
if (!DataTablePin || !RowNamePin)
{
MessageLog.Error(*LOCTEXT("MissingPins", "Missing pins in @@").ToString(), this);
return;
}
if (DataTablePin->LinkedTo.Num() == 0)
{
const UDataTable* DataTable = Cast<UDataTable>(DataTablePin->DefaultObject);
if (!DataTable)
{
MessageLog.Error(*LOCTEXT("NoDataTable", "No DataTable in @@").ToString(), this);
return;
}
if (!RowNamePin->LinkedTo.Num())
{
const FName CurrentName = FName(*RowNamePin->GetDefaultAsString());
if (!DataTable->GetRowNames().Contains(CurrentName))
{
const FString Msg = FText::Format(
LOCTEXT("WrongRowNameFmt", "'{0}' row name is not stored in '{1}'. @@"),
FText::FromString(CurrentName.ToString()),
FText::FromString(GetFullNameSafe(DataTable))
).ToString();
MessageLog.Error(*Msg, this);
return;
}
}
}
}
void UK2Node_GetDataTableRow::PreloadRequiredAssets()
{
if (UEdGraphPin* DataTablePin = GetDataTablePin())
{
if (UDataTable* DataTable = Cast<UDataTable>(DataTablePin->DefaultObject))
{
// make sure to properly load the data-table object so that we can
// farm the "RowStruct" property from it (below, in GetDataTableRowStructType)
PreloadObject(DataTable);
}
}
return Super::PreloadRequiredAssets();
}
void UK2Node_GetDataTableRow::NotifyPinConnectionListChanged(UEdGraphPin* Pin)
{
Super::NotifyPinConnectionListChanged(Pin);
if (Pin == GetResultPin())
{
UEdGraphPin* TablePin = GetDataTablePin();
// this connection would only change the output type if the table pin is undefined
const bool bIsTypeAuthority = (TablePin->LinkedTo.Num() > 0 || TablePin->DefaultObject == nullptr);
if (bIsTypeAuthority)
{
RefreshOutputPinType();
}
}
else if (Pin == GetDataTablePin())
{
const bool bConnectionAdded = Pin->LinkedTo.Num() > 0;
if (bConnectionAdded)
{
// if a connection was made, then we may need to rid ourselves of the row dropdown
RefreshRowNameOptions();
// if the output connection was previously, incompatible, it now becomes the authority on this node's output type
RefreshOutputPinType();
}
}
}
#undef LOCTEXT_NAMESPACE