// Copyright Epic Games, Inc. All Rights Reserved. #include "K2Node_MakeStruct.h" #include "Containers/Array.h" #include "Containers/EnumAsByte.h" #include "Containers/Map.h" #include "Containers/UnrealString.h" #include "Delegates/Delegate.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphSchema_K2.h" #include "EditorCategoryUtils.h" #include "Engine/Blueprint.h" #include "Internationalization/Internationalization.h" #include "Kismet/KismetMathLibrary.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/CompilerResultsLog.h" #include "KismetCompilerMisc.h" #include "MakeStructHandler.h" #include "Math/Rotator.h" #include "Math/UnrealMathSSE.h" #include "Math/Vector2D.h" #include "Misc/AssertionMacros.h" #include "PropertyCustomizationHelpers.h" #include "Serialization/Archive.h" #include "Styling/AppStyle.h" #include "UObject/Class.h" #include "UObject/ObjectPtr.h" #include "UObject/ObjectSaveContext.h" #include "UObject/StructOnScope.h" #include "UObject/TextProperty.h" #include "UObject/UnrealType.h" #include "UObject/WeakObjectPtrTemplates.h" class FKismetCompilerContext; #define LOCTEXT_NAMESPACE "K2Node_MakeStruct" ////////////////////////////////////////////////////////////////////////// // UK2Node_MakeStruct UK2Node_MakeStruct::FMakeStructPinManager::FMakeStructPinManager(const uint8* InSampleStructMemory, UBlueprint* InOwningBP) : FStructOperationOptionalPinManager() , SampleStructMemory(InSampleStructMemory) , OwningBP(InOwningBP) , bHasAdvancedPins(false) { } void UK2Node_MakeStruct::FMakeStructPinManager::GetRecordDefaults(FProperty* TestProperty, FOptionalPinFromProperty& Record) const { UK2Node_StructOperation::FStructOperationOptionalPinManager::GetRecordDefaults(TestProperty, Record); Record.bIsMarkedForAdvancedDisplay = TestProperty ? TestProperty->HasAnyPropertyFlags(CPF_AdvancedDisplay) : false; bHasAdvancedPins |= Record.bIsMarkedForAdvancedDisplay; } void UK2Node_MakeStruct::FMakeStructPinManager::CustomizePinData(UEdGraphPin* Pin, FName SourcePropertyName, int32 ArrayIndex, FProperty* Property) const { UK2Node_StructOperation::FStructOperationOptionalPinManager::CustomizePinData(Pin, SourcePropertyName, ArrayIndex, Property); if (Pin && Property) { const UEdGraphSchema_K2* Schema = GetDefault(); check(Schema); // Should pin default value be filled as FText? const bool bIsText = Property->IsA(); checkSlow(bIsText == ((UEdGraphSchema_K2::PC_Text == Pin->PinType.PinCategory) && !Pin->PinType.IsContainer())); const bool bIsObject = Property->IsA(); checkSlow(bIsObject == ((UEdGraphSchema_K2::PC_Object == Pin->PinType.PinCategory || UEdGraphSchema_K2::PC_Class == Pin->PinType.PinCategory || UEdGraphSchema_K2::PC_SoftObject == Pin->PinType.PinCategory || UEdGraphSchema_K2::PC_SoftClass == Pin->PinType.PinCategory) && !Pin->PinType.IsContainer())); if (Property->HasAnyPropertyFlags(CPF_AdvancedDisplay)) { Pin->bAdvancedView = true; bHasAdvancedPins = true; } const FString& MetadataDefaultValue = Property->GetMetaData(TEXT("MakeStructureDefaultValue")); if (!MetadataDefaultValue.IsEmpty()) { Schema->SetPinAutogeneratedDefaultValue(Pin, MetadataDefaultValue); return; } if (nullptr != SampleStructMemory) { FString NewDefaultValue; if (FBlueprintEditorUtils::PropertyValueToString(Property, SampleStructMemory, NewDefaultValue)) { if (Schema->IsPinDefaultValid(Pin, NewDefaultValue, nullptr, FText::GetEmpty()).IsEmpty()) { Schema->SetPinAutogeneratedDefaultValue(Pin, NewDefaultValue); return; } } } Schema->SetPinAutogeneratedDefaultValueBasedOnType(Pin); } } static bool CanBeExposed(const FProperty* Property, UBlueprint* BP) { if (Property) { const UEdGraphSchema_K2* Schema = GetDefault(); check(Schema); // Treat all inline edit condition properties as override flags; that is, don't allow // these to be exposed as part of the optional input pin set. Their value will be set // implicitly at runtime based on whether or not any bound members are exposed, rather // than explicitly via an exposed input pin. This emulates how the Property Editor // handles setting these values at edit time (they appear as an inline checkbox that // the user ticks on to set the flag and enable/override a bound property's value). static const FName MD_InlineEditConditionToggle(TEXT("InlineEditConditionToggle")); if (Property->HasMetaData(MD_InlineEditConditionToggle)) { return false; } const bool bIsEditorBP = IsEditorOnlyObject(BP); const bool bIsEditAnywhereProperty = Property->HasAllPropertyFlags(CPF_Edit) && !Property->HasAnyPropertyFlags(CPF_EditConst); if (!Property->HasAllPropertyFlags(CPF_BlueprintReadOnly) || (bIsEditorBP && bIsEditAnywhereProperty) ) { if (Property->HasAllPropertyFlags(CPF_BlueprintVisible) && !(Property->ArrayDim > 1)) { FEdGraphPinType DumbGraphPinType; if (Schema->ConvertPropertyToPinType(Property, /*out*/ DumbGraphPinType)) { return true; } } } } return false; } bool UK2Node_MakeStruct::FMakeStructPinManager::CanTreatPropertyAsOptional(FProperty* TestProperty) const { return CanBeExposed(TestProperty, OwningBP); } UK2Node_MakeStruct::UK2Node_MakeStruct(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , bMadeAfterOverridePinRemoval(false) { } void UK2Node_MakeStruct::AllocateDefaultPins() { if (StructType) { PreloadObject(StructType); CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Struct, StructType, StructType->GetFName()); bool bHasAdvancedPins = false; { FStructOnScope StructOnScope(StructType); FMakeStructPinManager OptionalPinManager(StructOnScope.GetStructMemory(), GetBlueprint()); OptionalPinManager.RebuildPropertyList(ShowPinForProperties, StructType); OptionalPinManager.CreateVisiblePins(ShowPinForProperties, StructType, EGPD_Input, this); bHasAdvancedPins = OptionalPinManager.HasAdvancedPins(); } // Set container pin types to have their default values ignored, which will in turn // enable auto generation for any that are not set by the user. for(UEdGraphPin* Pin : Pins) { Pin->bDefaultValueIsIgnored = Pin->bDefaultValueIsIgnored || Pin->PinType.IsContainer(); } // When struct has a lot of fields, mark their pins as advanced if(!bHasAdvancedPins && Pins.Num() > 5) { for(int32 PinIndex = 3; PinIndex < Pins.Num(); ++PinIndex) { if(UEdGraphPin * EdGraphPin = Pins[PinIndex]) { EdGraphPin->bAdvancedView = true; bHasAdvancedPins = true; } } } if (bHasAdvancedPins && (ENodeAdvancedPins::NoPins == AdvancedPinDisplay)) { AdvancedPinDisplay = ENodeAdvancedPins::Hidden; } } } void UK2Node_MakeStruct::PreloadRequiredAssets() { PreloadObject(StructType); Super::PreloadRequiredAssets(); } void UK2Node_MakeStruct::ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const { Super::ValidateNodeDuringCompilation(MessageLog); if(!StructType) { MessageLog.Error(*LOCTEXT("NoStruct_Error", "No Struct in @@").ToString(), this); } else { UBlueprint* BP = GetBlueprint(); for (TFieldIterator It(StructType); It; ++It) { const FProperty* Property = *It; if (CanBeExposed(Property, BP)) { if (Property->ArrayDim > 1) { const UEdGraphPin* Pin = FindPin(Property->GetFName()); MessageLog.Warning(*LOCTEXT("StaticArray_Warning", "@@ - the native property is a static array, which is not supported by blueprints").ToString(), Pin); } } } if (!bMadeAfterOverridePinRemoval) { MessageLog.Note(*NSLOCTEXT("K2Node", "OverridePinRemoval_SetFieldsInStruct", "Override pins have been removed from @@ in @@, it functions the same as it did but some functionality may be deprecated! This note will go away after you resave the asset!").ToString(), this, GetBlueprint()); } } } FText UK2Node_MakeStruct::GetNodeTitle(ENodeTitleType::Type TitleType) const { if (StructType == nullptr) { return LOCTEXT("MakeNullStructTitle", "Make "); } else if (CachedNodeTitle.IsOutOfDate(this)) { FFormatNamedArguments Args; Args.Add(TEXT("StructName"), StructType->GetDisplayNameText()); // FText::Format() is slow, so we cache this to save on performance CachedNodeTitle.SetCachedText(FText::Format(LOCTEXT("MakeNodeTitle", "Make {StructName}"), Args), this); } return CachedNodeTitle; } FText UK2Node_MakeStruct::GetTooltipText() const { if (StructType == nullptr) { return LOCTEXT("MakeNullStruct_Tooltip", "Adds a node that create an '' from its members"); } else if (CachedTooltip.IsOutOfDate(this)) { // FText::Format() is slow, so we cache this to save on performance CachedTooltip.SetCachedText(FText::Format( LOCTEXT("MakeStruct_Tooltip", "Adds a node that create a '{0}' from its members"), StructType->GetDisplayNameText() ), this); } return CachedTooltip; } FSlateIcon UK2Node_MakeStruct::GetIconAndTint(FLinearColor& OutColor) const { static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "GraphEditor.MakeStruct_16x"); return Icon; } FLinearColor UK2Node_MakeStruct::GetNodeTitleColor() const { if(const UEdGraphSchema_K2* K2Schema = GetDefault()) { FEdGraphPinType PinType; PinType.PinCategory = UEdGraphSchema_K2::PC_Struct; PinType.PinSubCategoryObject = StructType; return K2Schema->GetPinTypeColor(PinType); } return UK2Node::GetNodeTitleColor(); } bool UK2Node_MakeStruct::CanBeMade(const UScriptStruct* Struct, const bool bForInternalUse) { return (Struct && !Struct->HasMetaData(FBlueprintMetadata::MD_NativeMakeFunction) && UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Struct, bForInternalUse)); } bool UK2Node_MakeStruct::CanBeSplit(const UScriptStruct* Struct, UBlueprint* InBP) { if (CanBeMade(Struct)) { for (TFieldIterator It(Struct); It; ++It) { if (CanBeExposed(*It, InBP)) { return true; } } } return false; } FNodeHandlingFunctor* UK2Node_MakeStruct::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const { return new FKCHandler_MakeStruct(CompilerContext); } UK2Node::ERedirectType UK2Node_MakeStruct::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const { ERedirectType Result = UK2Node::DoPinsMatchForReconstruction(NewPin, NewPinIndex, OldPin, OldPinIndex); if ((ERedirectType_None == Result) && DoRenamedPinsMatch(NewPin, OldPin, false)) { Result = ERedirectType_Name; } return Result; } void UK2Node_MakeStruct::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { Super::SetupMenuActions(ActionRegistrar, FMakeStructSpawnerAllowedDelegate::CreateStatic(&UK2Node_MakeStruct::CanBeMade), EGPD_Input); } FText UK2Node_MakeStruct::GetMenuCategory() const { return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Struct); } void UK2Node_MakeStruct::PreSave(FObjectPreSaveContext SaveContext) { Super::PreSave(SaveContext); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNode(this); if (Blueprint && !Blueprint->bBeingCompiled) { bMadeAfterOverridePinRemoval = true; } } void UK2Node_MakeStruct::PostPlacedNewNode() { Super::PostPlacedNewNode(); // New nodes automatically have this set. bMadeAfterOverridePinRemoval = true; } void UK2Node_MakeStruct::Serialize(FArchive& Ar) { Super::Serialize(Ar); if (Ar.IsLoading() && !Ar.IsTransacting() && !HasAllFlags(RF_Transient)) { UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNode(this); if (Blueprint && !bMadeAfterOverridePinRemoval) { // Check if this node actually requires warning the user that functionality has changed. bMadeAfterOverridePinRemoval = true; if (StructType != nullptr) { FOptionalPinManager PinManager; // Have to check if this node is even in danger. for (FOptionalPinFromProperty& PropertyEntry : ShowPinForProperties) { FProperty* Property = StructType->FindPropertyByName(PropertyEntry.PropertyName); bool bNegate = false; if (FProperty* OverrideProperty = PropertyCustomizationHelpers::GetEditConditionProperty(Property, bNegate)) { bool bHadOverridePropertySeparation = false; for (FOptionalPinFromProperty& OverridePropertyEntry : ShowPinForProperties) { if (OverridePropertyEntry.PropertyName == OverrideProperty->GetFName()) { bHadOverridePropertySeparation = true; break; } } bMadeAfterOverridePinRemoval = false; UEdGraphPin* Pin = FindPin(Property->GetFName()); if (bHadOverridePropertySeparation) { UEdGraphPin* OverridePin = FindPin(OverrideProperty->GetFName()); if (OverridePin) { // Override pins are always booleans check(OverridePin->PinType.PinCategory == UEdGraphSchema_K2::PC_Boolean); // If the old override pin's default value was true, then the override should be marked as enabled PropertyEntry.bIsOverrideEnabled = OverridePin->DefaultValue.ToBool(); // It had an override pin, so conceptually the override pin is visible PropertyEntry.bIsOverridePinVisible = true; // Because there was an override pin visible for this property, this property will be forced to have a pin PropertyEntry.bShowPin = true; } else { // No override pin, ensure all override bools are false PropertyEntry.bIsOverrideEnabled = false; PropertyEntry.bIsOverridePinVisible = false; } } else { if (Pin) { PropertyEntry.bIsOverrideEnabled = true; PropertyEntry.bIsOverridePinVisible = true; } } // If the pin for this property, which sets the property's value, does not exist then the user was not trying to set the value PropertyEntry.bIsSetValuePinVisible = Pin != nullptr; } } } } } } void UK2Node_MakeStruct::ConvertDeprecatedNode(UEdGraph* Graph, bool bOnlySafeChanges) { const UEdGraphSchema_K2* Schema = GetDefault(); // User may have since deleted the struct type if (StructType == nullptr) { return; } // Check to see if the struct has a native make/break that we should try to convert to. if (StructType->HasMetaData(FBlueprintMetadata::MD_NativeMakeFunction)) { UFunction* MakeNodeFunction = nullptr; // If any pins need to change their names during the conversion, add them to the map. TMap OldPinToNewPinMap; if (StructType == TBaseStructure::Get()) { MakeNodeFunction = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetMathLibrary, MakeRotator)); OldPinToNewPinMap.Add(TEXT("Rotator"), UEdGraphSchema_K2::PN_ReturnValue); } else if (StructType == TBaseStructure::Get()) { MakeNodeFunction = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED_ThreeParams(UKismetMathLibrary, MakeVector, double, double, double)); OldPinToNewPinMap.Add(TEXT("Vector"), UEdGraphSchema_K2::PN_ReturnValue); } else if (StructType == TBaseStructure::Get()) { MakeNodeFunction = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED_TwoParams(UKismetMathLibrary, MakeVector2D, double, double)); OldPinToNewPinMap.Add(TEXT("Vector2D"), UEdGraphSchema_K2::PN_ReturnValue); } else { const FString& MetaData = StructType->GetMetaData(FBlueprintMetadata::MD_NativeMakeFunction); MakeNodeFunction = FindObject(nullptr, *MetaData, true); if (MakeNodeFunction) { OldPinToNewPinMap.Add(*StructType->GetName(), UEdGraphSchema_K2::PN_ReturnValue); } } if (MakeNodeFunction) { Schema->ConvertDeprecatedNodeToFunctionCall(this, MakeNodeFunction, OldPinToNewPinMap, Graph); } } } #undef LOCTEXT_NAMESPACE