// Copyright 2020 Phyronnaz #include "K2Node_AddDataItem.h" #include "VoxelGenerators/VoxelGeneratorInstanceWrapper.h" #include "VoxelPlaceableItems/VoxelPlaceableItemManager.h" #include "AssetRegistryModule.h" #include "KismetCompiler.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintNodeSpawner.h" #include "K2Node_CallFunction.h" #include "K2Node_MakeArray.h" #include "K2Node_MakeStruct.h" const FName UK2Node_AddDataItem::PC_Generator = "Generator"; const FName UK2Node_AddDataItem::PC_Bounds = "Bounds"; const FName UK2Node_AddDataItem::PC_Mask = "Mask"; UEdGraphPin* FindOutputStructPinChecked(UEdGraphNode* Node) { check(NULL != Node); UEdGraphPin* OutputPin = NULL; for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) { UEdGraphPin* Pin = Node->Pins[PinIndex]; if (Pin && (EGPD_Output == Pin->Direction)) { OutputPin = Pin; break; } } check(NULL != OutputPin); return OutputPin; } void UK2Node_AddDataItem::AllocateDefaultPins() { Super::AllocateDefaultPins(); // Execution pins CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then); if (!ensure(DataItemConfig)) { return; } if (DataItemConfig->HasAnyFlags(RF_NeedLoad)) { // Preload to get correct pins PreloadObject(const_cast(DataItemConfig.Get())); } CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UVoxelPlaceableItemManager::StaticClass(), UEdGraphSchema_K2::PSC_Self); CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UVoxelGeneratorInstanceWrapper::StaticClass(), PC_Generator); CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Struct, FVoxelIntBox::StaticStruct(), PC_Bounds); auto* MaskPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Int, UEdGraphSchema_K2::PSC_Bitmask, StaticEnum(), PC_Mask); MaskPin->DefaultValue = "1"; for (auto& Parameter : DataItemConfig->Parameters) { CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Real, Parameter); } } FText UK2Node_AddDataItem::GetNodeTitle(ENodeTitleType::Type TitleType) const { return GetTitle(DataItemConfig ? DataItemConfig->GetName() : FString()); } FText UK2Node_AddDataItem::GetTooltipText() const { return GetTooltip(DataItemConfig ? DataItemConfig->GetName() : FString()); } FText UK2Node_AddDataItem::GetMenuCategory() const { return VOXEL_LOCTEXT("Voxel"); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void UK2Node_AddDataItem::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); if (!DataItemConfig) { CompilerContext.MessageLog.Error(TEXT("DataItemConfig is not set!"), this); return; } TArray ParameterInputPins; { TArray InputPinsName; for (auto& Pin : Pins) { if (Pin->Direction == EGPD_Input && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Float) { ParameterInputPins.Add(Pin); InputPinsName.Add(Pin->GetFName()); } } if (DataItemConfig->Parameters != InputPinsName) { CompilerContext.MessageLog.Error(TEXT("Outdated node @@! Right click it and click Refresh Node"), this); return; } } UK2Node_MakeArray* MakeArrayNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); MakeArrayNode->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeArrayNode, this); UK2Node_MakeStruct* MakeStruct = CompilerContext.SpawnIntermediateNode(this, SourceGraph); MakeStruct->StructType = FVoxelDataItemConstructionInfo::StaticStruct(); MakeStruct->AllocateDefaultPins(); MakeStruct->bMadeAfterOverridePinRemoval = true; CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeStruct, this); UEdGraphPin* ArrayOut = MakeArrayNode->GetOutputPin(); // Connect the output of the "Make Array" pin to the function's "Parameters" pin ArrayOut->MakeLinkTo(MakeStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FVoxelDataItemConstructionInfo, Parameters))); // This will set the "Make Array" node's type, only works if one pin is connected. MakeArrayNode->PinConnectionListChanged(ArrayOut); for (int32 Index = 0; Index < ParameterInputPins.Num(); Index++) { // The "Make Array" node already has one pin available, so don't create one for ArgIdx == 0 if (Index > 0) { MakeArrayNode->AddInputPin(); } // Find the input pin on the "Make Array" node by index. const FString PinName = FString::Printf(TEXT("[%d]"), Index); UEdGraphPin* ArrayPin = MakeArrayNode->FindPinChecked(PinName); // Move our input pin to the array one CompilerContext.MovePinLinksToIntermediate(*ParameterInputPins[Index], *ArrayPin); } UFunction* BlueprintFunction = UVoxelPlaceableItemManager::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UVoxelPlaceableItemManager, AddDataItem)); UK2Node_CallFunction* CallFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); CallFunction->SetFromFunction(BlueprintFunction); CallFunction->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallFunction, this); FindOutputStructPinChecked(MakeStruct)->MakeLinkTo(CallFunction->FindPinChecked(TEXT("Info"))); CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(UEdGraphSchema_K2::PSC_Self), *CallFunction->FindPinChecked(UEdGraphSchema_K2::PSC_Self)); CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(PC_Generator), *MakeStruct->FindPinChecked(GET_FUNCTION_NAME_STRING_CHECKED(FVoxelDataItemConstructionInfo, Generator))); CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(PC_Bounds), *MakeStruct->FindPinChecked(GET_FUNCTION_NAME_STRING_CHECKED(FVoxelDataItemConstructionInfo, Bounds))); CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(PC_Mask), *MakeStruct->FindPinChecked(GET_FUNCTION_NAME_STRING_CHECKED(FVoxelDataItemConstructionInfo, Mask))); CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *CallFunction->GetExecPin()); CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(UEdGraphSchema_K2::PN_Then), *CallFunction->GetThenPin()); BreakAllNodeLinks(); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void UK2Node_AddDataItem::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { if (!ActionRegistrar.GetActionKeyFilter()) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); FARFilter Filter; Filter.ClassNames.Add(UVoxelGraphDataItemConfig::StaticClass()->GetFName()); Filter.bRecursiveClasses = true; TArray Assets; AssetRegistryModule.Get().GetAssets(Filter, Assets); for (auto& Asset : Assets) { UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); NodeSpawner->DefaultMenuSignature.MenuName = GetTitle(Asset.AssetName.ToString()); NodeSpawner->DefaultMenuSignature.Tooltip = GetTooltip(Asset.AssetName.ToString()); // Force load // This sucks, but else we get weird errors when assets are loaded as AddDataItem nodes dependencies // The item configs cannot have any dependencies, and are relatively small assets, so it should be fine Asset.GetAsset(); if (Asset.IsAssetLoaded()) { UVoxelGraphDataItemConfig* NewDataItemConfig = CastChecked(Asset.GetAsset()); NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateLambda( [NewDataItemConfig = MakeWeakObjectPtr(NewDataItemConfig)](UEdGraphNode* NewNode, bool /*bIsTemplateNode*/) { CastChecked(NewNode)->DataItemConfig = NewDataItemConfig.Get(); }); } else { NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateLambda( [Asset](UEdGraphNode* NewNode, bool /*bIsTemplateNode*/) { CastChecked(NewNode)->DataItemConfig = CastChecked(Asset.GetAsset()); }); } ActionRegistrar.AddBlueprintAction(Asset, NodeSpawner); } } else { auto* NewDataItemConfig = Cast(ActionRegistrar.GetActionKeyFilter()); if (NewDataItemConfig) { UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateLambda( [NewDataItemConfig = MakeWeakObjectPtr(NewDataItemConfig)](UEdGraphNode* NewNode, bool /*bIsTemplateNode*/) { CastChecked(NewNode)->DataItemConfig = NewDataItemConfig.Get(); }); NodeSpawner->DefaultMenuSignature.MenuName = GetTitle(NewDataItemConfig->GetName()); NodeSpawner->DefaultMenuSignature.Tooltip = GetTooltip(NewDataItemConfig->GetName()); ActionRegistrar.AddBlueprintAction(NewDataItemConfig, NodeSpawner); } } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// FText UK2Node_AddDataItem::GetTitle(const FString& AssetName) { return FText::Format(VOXEL_LOCTEXT("Add Data Item: {0}"), FText::FromString(FName::NameToDisplayString(AssetName, false))); } FText UK2Node_AddDataItem::GetTooltip(const FString& AssetName) { return FText::Format(VOXEL_LOCTEXT("Use this to add a new voxel data item with the parameters defined in {0} to your voxel world"), FText::FromString(FName::NameToDisplayString(AssetName, false))); }