// Copyright Epic Games, Inc. All Rights Reserved. #include "K2Node_Variable.h" #include "BlueprintCompilationManager.h" #include "BlueprintEditorSettings.h" #include "Components/ActorComponent.h" #include "Components/PrimitiveComponent.h" #include "Containers/Set.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphSchema.h" #include "EdGraphSchema_K2.h" #include "Editor/UnrealEdEngine.h" #include "Engine/Blueprint.h" #include "Engine/BlueprintGeneratedClass.h" #include "Engine/SimpleConstructionScript.h" #include "GameFramework/Actor.h" #include "GameFramework/MovementComponent.h" #include "HAL/PlatformCrt.h" #include "Internationalization/Internationalization.h" #include "K2Node_FunctionEntry.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/CompilerResultsLog.h" #include "Kismet2/StructureEditorUtils.h" #include "KismetCompilerMisc.h" #include "Logging/MessageLog.h" #include "Misc/AssertionMacros.h" #include "Misc/Guid.h" #include "Preferences/UnrealEdOptions.h" #include "Serialization/Archive.h" #include "SourceCodeNavigation.h" #include "Styling/AppStyle.h" #include "Styling/SlateIconFinder.h" #include "Templates/Casts.h" #include "Templates/UnrealTemplate.h" #include "UObject/Class.h" #include "UObject/Object.h" #include "UObject/ObjectPtr.h" #include "UObject/ObjectVersion.h" #include "UObject/UnrealType.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" #include "UnrealEdGlobals.h" #include "Settings/BlueprintEditorProjectSettings.h" #include "ToolMenu.h" #define LOCTEXT_NAMESPACE "K2Node" UK2Node_Variable::UK2Node_Variable(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void UK2Node_Variable::Serialize(FArchive& Ar) { Super::Serialize(Ar); // Fix old content if(Ar.IsLoading()) { if(Ar.UEVer() < VER_UE4_VARK2NODE_USE_MEMBERREFSTRUCT) { // Copy info into new struct VariableReference.SetDirect(VariableName_DEPRECATED, FGuid(), VariableSourceClass_DEPRECATED, bSelfContext_DEPRECATED); } if(Ar.UEVer() < VER_UE4_K2NODE_VAR_REFERENCEGUIDS) { FGuid VarGuid; // Do not let this code run for local variables if (!VariableReference.IsLocalScope()) { const bool bSelf = VariableReference.IsSelfContext(); UClass* MemberParentClass = VariableReference.GetMemberParentClass(nullptr); if (UBlueprint::GetGuidFromClassByFieldName(bSelf? *GetBlueprint()->GeneratedClass : MemberParentClass, VariableReference.GetMemberName(), VarGuid)) { VariableReference.SetDirect(VariableReference.GetMemberName(), VarGuid, bSelf ? nullptr : MemberParentClass, bSelf); } } } } } void UK2Node_Variable::SetFromProperty(const FProperty* Property, bool bSelfContext, UClass* OwnerClass) { SelfContextInfo = bSelfContext ? ESelfContextInfo::Unspecified : ESelfContextInfo::NotSelfContext; VariableReference.SetFromField(Property, bSelfContext, OwnerClass); } bool UK2Node_Variable::CreatePinForVariable(EEdGraphPinDirection Direction, FName InPinName/* = NAME_None */) { const UEdGraphSchema_K2* K2Schema = GetDefault(); FProperty* VariableProperty = GetPropertyForVariable(); // favor the skeleton property if possible (in case the property type has // been changed, and not yet compiled). if (!VariableReference.IsSelfContext()) { UClass* VariableClass = VariableReference.GetMemberParentClass(GetBlueprint()->GeneratedClass); if (UBlueprintGeneratedClass* BpClassOwner = Cast(VariableClass)) { // this variable could currently only be a part of some skeleton // class (the blueprint has not be compiled with it yet), so let's // check the skeleton class as well, see if we can pull pin data // from there... UBlueprint* VariableBlueprint = CastChecked(BpClassOwner->ClassGeneratedBy.Get(), ECastCheckedType::NullAllowed); if (VariableBlueprint) { if (FProperty* SkelProperty = GetPropertyForVariableFromSkeleton()) { VariableProperty = SkelProperty; } } } } if (VariableProperty != nullptr) { const FName PinName = InPinName.IsNone() ? GetVarName() : InPinName; // Create the pin UEdGraphPin* VariablePin = CreatePin(Direction, NAME_None, PinName); if (VariableProperty->IsNative()) { VariablePin->PinFriendlyName = VariableProperty->GetDisplayNameText(); } K2Schema->ConvertPropertyToPinType(VariableProperty, /*out*/ VariablePin->PinType); K2Schema->SetPinAutogeneratedDefaultValueBasedOnType(VariablePin); } else { // Don't handle warning or error logging here, that needs to be done in ValidateNodeDuringCompilation return false; } return true; } void UK2Node_Variable::CreatePinForSelf() { const UEdGraphSchema_K2* K2Schema = GetDefault(); // Create the self pin if (!K2Schema->FindSelfPin(*this, EGPD_Input)) { // Do not create a self pin for locally scoped variables or sparse class data if (!VariableReference.IsLocalScope()) { bool bSelfTarget = VariableReference.IsSelfContext() && (ESelfContextInfo::NotSelfContext != SelfContextInfo); UClass* MemberParentClass = VariableReference.GetMemberParentClass(GetBlueprintClassFromNode()); UClass* TargetClass = MemberParentClass; FProperty* VariableProperty = GetPropertyForVariable(); if (VariableProperty) { UClass* PropertyClass = Cast(VariableProperty->GetOwner()); // Fix up target class if it's not correct, this fixes cases where variables have moved within the hierarchy if (PropertyClass && PropertyClass != TargetClass) { TargetClass = PropertyClass; } } // Self Target pins should always make the class be the owning class of the property, // so if the node is from a Macro Blueprint, it will hook up as self in any placed Blueprint if (bSelfTarget) { if (FProperty* Property = VariableReference.ResolveMember(GetBlueprintClassFromNode())) { UClass* OwnerClass = Property->GetOwnerClass(); if (OwnerClass) { TargetClass = OwnerClass->GetAuthoritativeClass(); } else if(TargetClass) { // check if it's a sparse member, sparse members are accessed via the authoritative // class - this matches the convention defined in BlueprintActionDatabaseImpl::AddClassDataObjectActions: UClass* AuthClass = TargetClass->GetAuthoritativeClass(); if (AuthClass->GetSparseClassDataStruct()->IsChildOf(Property->GetOwnerStruct()) ) { TargetClass = AuthClass; } } } else if (GetBlueprint()->SkeletonGeneratedClass) { TargetClass = GetBlueprint()->SkeletonGeneratedClass->GetAuthoritativeClass(); } } else if (TargetClass && TargetClass->ClassGeneratedBy) { TargetClass = TargetClass->GetAuthoritativeClass(); } UEdGraphPin* TargetPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, TargetClass, UEdGraphSchema_K2::PN_Self); TargetPin->PinFriendlyName = LOCTEXT("Target", "Target"); if (bSelfTarget) { TargetPin->bHidden = true; // don't show in 'self' context } } } else { //@TODO: Check that the self pin types match! } } bool UK2Node_Variable::RecreatePinForVariable(EEdGraphPinDirection Direction, TArray& OldPins, FName InPinName/* = NAME_None*/) { // probably the node was pasted to a blueprint without the variable // we don't want to beak any connection, so the pin will be recreated from old one, but compiler will throw error // find old variable pin const UEdGraphPin* OldVariablePin = nullptr; const FName PinName = InPinName.IsNone() ? GetVarName() : InPinName; for (const UEdGraphPin* Pin : OldPins) { if (Pin && PinName == Pin->PinName) { OldVariablePin = Pin; break; } } if (nullptr != OldVariablePin) { // create new pin from old one UEdGraphPin* VariablePin = CreatePin(Direction, NAME_None, PinName); VariablePin->PinType = OldVariablePin->PinType; const UEdGraphSchema_K2* K2Schema = GetDefault(); K2Schema->SetPinAutogeneratedDefaultValueBasedOnType(VariablePin); Message_Note(*FString::Printf(TEXT("Pin for variable '%s' recreated, but the variable is missing."), *PinName.ToString())); return true; } else { Message_Warn(*FString::Printf(TEXT("RecreatePinForVariable: '%s' pin not found"), *PinName.ToString())); return false; } } FLinearColor UK2Node_Variable::GetNodeTitleColor() const { FProperty* VariableProperty = GetPropertyForVariable(); if (VariableProperty) { const UEdGraphSchema_K2* K2Schema = GetDefault(); FEdGraphPinType VariablePinType; K2Schema->ConvertPropertyToPinType(VariableProperty, VariablePinType); return K2Schema->GetPinTypeColor(VariablePinType); } return FLinearColor::White; } FString UK2Node_Variable::GetFindReferenceSearchString_Impl(EGetFindReferenceSearchStringFlags InFlags) const { // Legacy behavior for variable nodes was to do an exact search if (EnumHasAnyFlags(InFlags, EGetFindReferenceSearchStringFlags::UseSearchSyntax) || EnumHasAnyFlags(InFlags, EGetFindReferenceSearchStringFlags::Legacy)) { if (VariableReference.IsLocalScope()) { // Generate local variable search query return VariableReference.GetReferenceSearchString(nullptr); } else if (FProperty* VariableProperty = VariableReference.ResolveMember(GetBlueprintClassFromNode())) { // Generate member variable search query return VariableReference.GetReferenceSearchString(VariableProperty->GetOwnerClass()); } } // Simple query: just search for variable name return FString::Printf(TEXT("\"%s\""), *VariableReference.GetMemberName().ToString()); } UK2Node::ERedirectType UK2Node_Variable::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const { const UEdGraphSchema_K2* K2Schema = GetDefault(); if( OldPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec ) { return Super::DoPinsMatchForReconstruction(NewPin, NewPinIndex, OldPin, OldPinIndex); } const bool bPinNamesMatch = (OldPin->PinName == NewPin->PinName); const bool bCanMatchSelfs = bPinNamesMatch || ((OldPin->PinName == UEdGraphSchema_K2::PN_Self) && (NewPin->PinName == UEdGraphSchema_K2::PN_Self)); const bool bTheSameDirection = (NewPin->Direction == OldPin->Direction); const bool bNewPinIsObject = (NewPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Object); const bool bNewPinIsInterface = (NewPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Interface); const bool bNewIsClass = (NewPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Class); const bool bNewPinIsObjectType = bNewPinIsObject || bNewPinIsInterface || bNewIsClass ; const FEdGraphPinType& InputType = (OldPin->Direction == EGPD_Output) ? OldPin->PinType : NewPin->PinType; const FEdGraphPinType& OutputType = (OldPin->Direction == EGPD_Output) ? NewPin->PinType : OldPin->PinType; const bool bHasIncompatibleObjectType = bNewPinIsObjectType && !K2Schema->ArePinTypesCompatible(OutputType, InputType) ; if (bCanMatchSelfs && bTheSameDirection) { // the order that the PinTypes are passed to ArePinTypesCompatible() // matters; object pin types are seen as compatible when the output- // pin's type is a subclass of the input-pin's type, so we want to keep // that in mind here (should the pins "MatchForReconstruction" if the // variable has been changed to a super class of the original? what // about a subclass? // // if these are output nodes, then it is perfectly acceptable that the // variable has been altered to be a sub-class ref (meaning we should // treat the NewPin as an output)... the opposite applies if the pins // are inputs if (NewPin->ParentPin) { // If the OldPin is not split, then these don't match if (OldPin->ParentPin == nullptr) { return ERedirectType_None; } // Go through and find the original variable pin. // If the number of steps out to the original variable pin is not the same then these don't match const UEdGraphPin* ParentmostNewPin = NewPin; const UEdGraphPin* ParentmostOldPin = OldPin; while (ParentmostNewPin->ParentPin) { if (ParentmostOldPin->ParentPin == nullptr) { return ERedirectType_None; } ParentmostNewPin = ParentmostNewPin->ParentPin; ParentmostOldPin = ParentmostOldPin->ParentPin; } if (ParentmostOldPin->ParentPin) { return ERedirectType_None; } // Compare whether the names, ignoring the original variable's name in the case of renames, match FName NewPinPropertyName = FName(*NewPin->PinName.ToString().RightChop(ParentmostNewPin->PinName.ToString().Len() + 1)); FName OldPinPropertyName = FName(*OldPin->PinName.ToString().RightChop(ParentmostOldPin->PinName.ToString().Len() + 1)); if (!DoesRenamedVariableMatch(OldPinPropertyName, NewPinPropertyName, Cast(NewPin->ParentPin->PinType.PinSubCategoryObject.Get()))) { return ERedirectType_None; } return ERedirectType_Name; } else if (bHasIncompatibleObjectType) { // Special Case: If we had a pin match, and the class isn't loaded // yet because of a cyclic dependency, temporarily // cast away the const, and fix up. if ( bPinNamesMatch && (bNewPinIsObject || bNewPinIsInterface) && (NewPin->PinType.PinSubCategoryObject == nullptr) ) { // @TODO: Fix this up to be less hacky UBlueprintGeneratedClass* TypeClass = Cast(OldPin->PinType.PinSubCategoryObject.Get()); if (TypeClass && TypeClass->ClassGeneratedBy && TypeClass->ClassGeneratedBy->HasAnyFlags(RF_BeingRegenerated)) { UEdGraphPin* NonConstNewPin = (UEdGraphPin*)NewPin; NonConstNewPin->PinType.PinSubCategoryObject = OldPin->PinType.PinSubCategoryObject.Get(); return ERedirectType_Name; } } // Special Case: if we have object pins that are "compatible" in the // reverse order (meaning one's type is a sub-class of // the other's), then they could still be acceptable // if all their connections are still valid (for // example: if the OldPin was an output only connected // to super-class pins) else if (bNewPinIsObject && K2Schema->ArePinTypesCompatible(InputType, OutputType)) { bool bLinksCompatible = (OldPin->LinkedTo.Num() > 0) && (OldPin->DefaultObject == nullptr); for (UEdGraphPin* OldLink : OldPin->LinkedTo) { const FEdGraphPinType& LinkInputType = (OldPin->Direction == EGPD_Input) ? NewPin->PinType : OldLink->PinType; const FEdGraphPinType& LinkOutputType = (OldPin->Direction == EGPD_Input) ? OldLink->PinType : NewPin->PinType; if (!K2Schema->ArePinTypesCompatible(LinkOutputType, LinkInputType)) { bLinksCompatible = false; break; } } if (bLinksCompatible) { return ERedirectType_Name; } } else { const UClass* PSCOClass = Cast(OldPin->PinType.PinSubCategoryObject.Get()); const bool bOldIsBlueprint = PSCOClass && PSCOClass->IsChildOf(UBlueprint::StaticClass()); // Special Case: If we're migrating from old blueprint references // to class references, allow pins to be reconnected if coerced if (bNewIsClass && bOldIsBlueprint) { UEdGraphPin* OldPinNonConst = (UEdGraphPin*)OldPin; OldPinNonConst->PinName = NewPin->PinName; return ERedirectType_Name; } } } else { // By default, we allow the redirect if direction and name match. // The type may not match, but we defer that check to ValidateLinkedPinTypes, // which attempts to address the incompatibility by inserting a conversion node. // This behavior is consistent with how UK2Node operates. return ERedirectType_Name; } } return ERedirectType_None; } UClass* UK2Node_Variable::GetVariableSourceClass() const { UClass* Result = VariableReference.GetMemberParentClass(GetBlueprintClassFromNode()); return Result; } FProperty* UK2Node_Variable::GetPropertyForVariable_Internal(UClass* OwningClass) const { const FName VarName = GetVarName(); FProperty* VariableProperty = VariableReference.ResolveMember(OwningClass); // if the variable has been deprecated, don't use it if (VariableProperty != nullptr) { UEdGraphPin* VariablePin = FindPin(VarName); // If the variable has been remapped update the pin if (VariablePin && VarName != GetVarName()) { VariablePin->PinName = GetVarName(); } } return VariableProperty; } FProperty* UK2Node_Variable::GetPropertyForVariable() const { if (!FBlueprintCompilationManager::IsGeneratedClassLayoutReady()) { // first look in the skeleton class: if (FProperty* SkeletonProperty = GetPropertyForVariableFromSkeleton()) { return SkeletonProperty; } } return GetPropertyForVariable_Internal(GetBlueprintClassFromNode()); } FString UK2Node_Variable::GetPinMetaData(FName InPinName, FName InKey) { FString MetaData; if (GetVarName() == InPinName) { if (FProperty* VariableProperty = GetPropertyForVariable()) { if (const FString* FoundMetaData = VariableProperty->FindMetaData(FBlueprintMetadata::MD_AllowAbstractClasses)) { MetaData = *FoundMetaData; } } } if (MetaData.IsEmpty()) { MetaData = Super::GetPinMetaData(InPinName, InKey); } return MetaData; } bool UK2Node_Variable::DoesRenamedVariableMatch(FName OldVariableName, FName NewVariableName, UStruct* StructType) { if (NewVariableName == OldVariableName) { return true; } FGuid OldPropertyGuid = FStructureEditorUtils::GetGuidFromPropertyName(OldVariableName); FGuid NewPropertyGuid = FStructureEditorUtils::GetGuidFromPropertyName(NewVariableName); // If guids match this was a user struct rename if (OldPropertyGuid.IsValid() && (OldPropertyGuid == NewPropertyGuid)) { return true; } // Also check native rename if we can find the struct if (StructType) { FName RedirectedPinName = FProperty::FindRedirectedPropertyName(StructType, OldVariableName); if (NewVariableName == RedirectedPinName) { return true; } } return false; } FProperty* UK2Node_Variable::GetPropertyForVariableFromSkeleton() const { if (UClass* SkeletonClass = FBlueprintEditorUtils::GetSkeletonClass(VariableReference.GetMemberParentClass(GetBlueprintClassFromNode()))) { return GetPropertyForVariable_Internal(SkeletonClass); } return nullptr; } UEdGraphPin* UK2Node_Variable::GetValuePin() const { UEdGraphPin* Pin = FindPin(GetVarName()); check(Pin == nullptr || Pin->Direction == EGPD_Output); return Pin; } void UK2Node_Variable::ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const { Super::ValidateNodeDuringCompilation(MessageLog); FProperty* VariableProperty = GetPropertyForVariable(); // Local variables do not exist until much later in the compilation than this function can provide if (VariableProperty == NULL && !VariableReference.IsLocalScope()) { if (!VariableReference.IsDeprecated()) { FString OwnerName; UBlueprint* Blueprint = GetBlueprint(); if (Blueprint != nullptr) { OwnerName = Blueprint->GetPathName(); if (UClass* VarOwnerClass = VariableReference.GetMemberParentClass(Blueprint->GeneratedClass)) { OwnerName = VarOwnerClass->GetPathName(); } } if (!FKismetCompilerUtilities::IsMissingMemberPotentiallyLoading(Blueprint, VariableReference.GetMemberParentClass())) { FString const VarName = VariableReference.GetMemberName().ToString(); FText const WarningFormat = LOCTEXT("VariableNotFoundFmt", "Could not find a variable named \"{0}\" in '{1}'.\nMake sure '{2}' has been compiled for @@"); MessageLog.Warning(*FText::Format(WarningFormat, FText::FromString(VarName), FText::FromString(OwnerName), FText::FromString(OwnerName)).ToString(), this); } } else { MessageLog.Warning(*FText::Format(LOCTEXT("VariableDeprecatedFmt", "Variable '{0}' for @@ was deprecated. Please update it."), FText::FromString(VariableReference.GetMemberName().ToString())).ToString(), this); } } if (VariableProperty && (VariableProperty->ArrayDim > 1)) { MessageLog.Warning(*LOCTEXT("StaticArray_Warning", "@@ - the native property is a static array, which is not supported by blueprints").ToString(), this); } } FSlateIcon UK2Node_Variable::GetIconAndTint(FLinearColor& ColorOut) const { const UStruct* VarScope = VariableReference.IsLocalScope() ? VariableReference.GetMemberScope(GetBlueprintClassFromNode()) : GetVariableSourceClass(); return GetVariableIconAndColor(VarScope, GetVarName(), ColorOut); } FSlateIcon UK2Node_Variable::GetVarIconFromPinType(const FEdGraphPinType& InPinType, FLinearColor& IconColorOut) { const UEdGraphSchema_K2* K2Schema = GetDefault(); IconColorOut = K2Schema->GetPinTypeColor(InPinType); if (InPinType.IsArray()) { return FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.AllClasses.ArrayVariableIcon"); } else if (InPinType.IsSet()) { return FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.AllClasses.SetVariableIcon"); } else if (InPinType.IsMap()) { // TODO: Need to properly deal with Key/Value stuff return FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.AllClasses.MapVariableKeyIcon"); } else if (InPinType.PinSubCategoryObject.IsValid()) { if(UClass* Class = Cast(InPinType.PinSubCategoryObject.Get())) { return FSlateIconFinder::FindIconForClass( Class ); } } return FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.AllClasses.VariableIcon"); } FText UK2Node_Variable::GetToolTipHeading() const { FText Heading = Super::GetToolTipHeading(); // attempt to reflect the node's GetCornerIcon() with some tooltip documentation FText IconTag; if ( FProperty const* VariableProperty = VariableReference.ResolveMember(GetBlueprintClassFromNode()) ) { const UActorComponent* Component = GetActorComponent(VariableProperty); const bool IsEditorOnly = VariableProperty->HasAnyPropertyFlags(CPF_EditorOnly) || (Component && Component->bIsEditorOnly); const bool IsReplicated = VariableProperty->HasAnyPropertyFlags(CPF_Net) || (Component && Component->GetIsReplicated()); const bool IsFunctionParameter = VariableProperty->HasAnyPropertyFlags(CPF_Parm); const bool IsLocalFunctionVariable = !VariableProperty->HasAnyPropertyFlags(CPF_Parm) && VariableReference.IsLocalScope(); const UBlueprintEditorSettings* EditorSettings = GetDefault(); if (IsEditorOnly && IsReplicated) { IconTag = LOCTEXT("ReplicatedEditorOnlyVar", "Editor-Only | Replicated"); } else if (IsReplicated) { IconTag = LOCTEXT("ReplicatedVar", "Replicated"); } else if (IsEditorOnly) { IconTag = LOCTEXT("EditorOnlyVar", "Editor-Only"); } else if (IsFunctionParameter && EditorSettings->bShowFunctionParameterIcon) { IconTag = LOCTEXT("FunctionParameterVar", "Function parameter"); } else if (IsLocalFunctionVariable && EditorSettings->bShowFunctionLocalVariableIcon) { IconTag = LOCTEXT("LocalFunctionVar", "Function local variable"); } } if (Heading.IsEmpty()) { return IconTag; } else if (!IconTag.IsEmpty()) { Heading = FText::Format(FText::FromString("{0}\n{1}"), IconTag, Heading); } return Heading; } void UK2Node_Variable::GetNodeAttributes( TArray>& OutNodeAttributes ) const { FProperty* VariableProperty = GetPropertyForVariable(); const FString VariableName = VariableProperty ? VariableProperty->GetName() : TEXT( "InvalidVariable" ); OutNodeAttributes.Add( TKeyValuePair( TEXT( "Type" ), TEXT( "Variable" ) )); OutNodeAttributes.Add( TKeyValuePair( TEXT( "Class" ), GetClass()->GetName() )); OutNodeAttributes.Add( TKeyValuePair( TEXT( "Name" ), VariableName )); } void UK2Node_Variable::HandleVariableRenamed(UBlueprint* InBlueprint, UClass* InVariableClass, UEdGraph* InGraph, const FName& InOldVarName, const FName& InNewVarName) { UClass* const NodeRefClass = VariableReference.GetMemberParentClass(InBlueprint->GeneratedClass); if (NodeRefClass && NodeRefClass->IsChildOf(InVariableClass) && InOldVarName == GetVarName()) { Modify(); if (VariableReference.IsLocalScope()) { VariableReference.SetLocalMember(InNewVarName, VariableReference.GetMemberScopeName(), VariableReference.GetMemberGuid()); } else if (VariableReference.IsSelfContext()) { VariableReference.SetSelfMember(InNewVarName); } else { VariableReference.SetExternalMember(InNewVarName, NodeRefClass); } RenameUserDefinedPin(InOldVarName, InNewVarName); } } void UK2Node_Variable::ReplaceReferences(UBlueprint* InBlueprint, UBlueprint* InReplacementBlueprint, const FMemberReference& InSource, const FMemberReference& InReplacement) { if (VariableReference.IsLocalScope() || VariableReference.IsSelfContext()) { VariableReference = InReplacement; } else { // Make a copy because ResolveMember is non-const FMemberReference Replacement = InReplacement; const FProperty* ResolvedProperty = Replacement.ResolveMember(InBlueprint); VariableReference.SetFromField(ResolvedProperty, InReplacementBlueprint->GeneratedClass); } } bool UK2Node_Variable::ReferencesVariable(const FName& InVarName, const UStruct* InScope) const { if (InVarName == GetVarName()) { if (InScope && VariableReference.GetMemberScopeName() != InScope->GetName()) { // Variables are not in the same scope return false; } return true; } return false; } FSlateIcon UK2Node_Variable::GetVariableIconAndColor(const UStruct* VarScope, FName VarName, FLinearColor& IconColorOut) { if(VarScope != NULL) { FProperty* Property = FindFProperty(VarScope, VarName); if(Property != NULL) { const UEdGraphSchema_K2* K2Schema = GetDefault(); FEdGraphPinType PinType; if(K2Schema->ConvertPropertyToPinType(Property, PinType)) // use schema to get the color { return GetVarIconFromPinType(PinType, IconColorOut); } } } return FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.AllClasses.VariableIcon"); } void UK2Node_Variable::CheckForErrors(const UEdGraphSchema_K2* Schema, FCompilerResultsLog& MessageLog) { if(!VariableReference.IsSelfContext() && VariableReference.GetMemberParentClass(GetBlueprintClassFromNode()) != NULL) { // Check to see if we're not a self context, if we have a valid context. It may have been purged because of a dead execution chain UEdGraphPin* ContextPin = Schema->FindSelfPin(*this, EGPD_Input); if((ContextPin != NULL) && (ContextPin->LinkedTo.Num() == 0) && (ContextPin->DefaultObject == NULL)) { MessageLog.Error(*LOCTEXT("VarNodeError_InvalidVarTarget", "Variable node @@ uses an invalid target. It may depend on a node that is not connected to the execution chain, and got purged.").ToString(), this); } } } void UK2Node_Variable::ReconstructNode() { // update the variable reference if the property was renamed UClass* const VarClass = GetVariableSourceClass(); if (VarClass) { bool bRemappedProperty = false; UClass* SearchClass = VarClass; while (SearchClass != nullptr) { FName NewPropertyName = FProperty::FindRedirectedPropertyName(SearchClass, VariableReference.GetMemberName()); if (NewPropertyName != NAME_None) { if (VariableReference.IsSelfContext()) { VariableReference.SetSelfMember(NewPropertyName); } else { VariableReference.SetExternalMember(NewPropertyName, VarClass); } // found, can break bRemappedProperty = true; break; } SearchClass = SearchClass->GetSuperClass(); } if (!bRemappedProperty) { static FName OldVariableName(TEXT("UpdatedComponent")); static FName NewVariableName(TEXT("UpdatedPrimitive")); bRemappedProperty = RemapRestrictedLinkReference(OldVariableName, NewVariableName, UMovementComponent::StaticClass(), UPrimitiveComponent::StaticClass(), true); } } const FGuid VarGuid = VariableReference.GetMemberGuid(); if (VarGuid.IsValid()) { const FName VarName = UBlueprint::GetFieldNameFromClassByGuid(VarClass, VarGuid); if (VarName != NAME_None && VarName != VariableReference.GetMemberName()) { if (VariableReference.IsSelfContext()) { VariableReference.SetSelfMember( VarName ); } else { VariableReference.SetExternalMember( VarName, VarClass ); } } } Super::ReconstructNode(); } bool UK2Node_Variable::RemapRestrictedLinkReference(FName OldVariableName, FName NewVariableName, const UClass* MatchInVariableClass, const UClass* RemapIfLinkedToClass, bool bLogWarning) { bool bRemapped = false; if (VariableReference.GetMemberName() == OldVariableName) { UClass* const VarClass = GetVariableSourceClass(); if (VarClass->IsChildOf(MatchInVariableClass)) { UEdGraphPin* VariablePin = GetValuePin(); if (VariablePin) { for (UEdGraphPin* OtherPin : VariablePin->LinkedTo) { if (OtherPin != nullptr && VariablePin->PinType.PinCategory == OtherPin->PinType.PinCategory) { // If any other pin we are linked to is a more restricted type, we need to do the remap. const UClass* OtherPinClass = Cast(OtherPin->PinType.PinSubCategoryObject.Get()); if (OtherPinClass && OtherPinClass->IsChildOf(RemapIfLinkedToClass)) { if (VariableReference.IsSelfContext()) { VariableReference.SetSelfMember(NewVariableName); } else { VariableReference.SetExternalMember(NewVariableName, VarClass); } bRemapped = true; break; } } } } } } if (bRemapped && bLogWarning && GetBlueprint()) { FMessageLog("BlueprintLog").Warning( FText::Format( LOCTEXT("RemapRestrictedLinkReference", "{0}: Variable '{1}' was automatically changed to '{2}'. Verify that logic works as intended. (This warning will disappear once the blueprint has been resaved)"), FText::FromString(GetBlueprint()->GetPathName()), FText::FromString(MatchInVariableClass->GetName() + TEXT(".") + OldVariableName.ToString()), FText::FromString(MatchInVariableClass->GetName() + TEXT(".") + NewVariableName.ToString()) )); } return bRemapped; } FName UK2Node_Variable::GetCornerIcon() const { if (const FProperty* VariableProperty = VariableReference.ResolveMember(GetBlueprintClassFromNode())) { const UActorComponent* Component = GetActorComponent(VariableProperty); const bool IsEditorOnly = VariableProperty->HasAnyPropertyFlags(CPF_EditorOnly) || (Component && Component->bIsEditorOnly); const bool IsReplicated = VariableProperty->HasAnyPropertyFlags(CPF_Net) || (Component && Component->GetIsReplicated()); const bool IsFunctionParameter = VariableProperty->HasAnyPropertyFlags(CPF_Parm); const bool IsLocalFunctionVariable = !VariableProperty->HasAnyPropertyFlags(CPF_Parm) && VariableReference.IsLocalScope(); const UBlueprintEditorSettings* EditorSettings = GetDefault(); if (IsReplicated) { return TEXT("Graph.Replication.Replicated"); } else if (IsEditorOnly) { return TEXT("Graph.Editor.EditorOnlyIcon"); } else if (IsFunctionParameter && EditorSettings->bShowFunctionParameterIcon) { return TEXT("Graph.Function.FunctionParameterIcon"); } else if (IsLocalFunctionVariable && EditorSettings->bShowFunctionLocalVariableIcon) { return TEXT("Graph.Function.FunctionLocalVariableIcon"); } } return Super::GetCornerIcon(); } bool UK2Node_Variable::HasExternalDependencies(TArray* OptionalOutput) const { UBlueprint* SourceBlueprint = GetBlueprint(); FProperty* VariableProperty = GetPropertyForVariable(); UClass* PropertySourceClass = VariableProperty ? VariableProperty->GetOwnerClass() : nullptr; bool bResult = (PropertySourceClass && (PropertySourceClass->ClassGeneratedBy != SourceBlueprint)); if (bResult && OptionalOutput) { OptionalOutput->AddUnique(PropertySourceClass); } // Also include underlying non-native variable types as external dependencies. Otherwise, contextual // type references serialized to bytecode can potentially be invalidated when the type is regenerated. if (const UEdGraphPin* VarPin = FindPin(GetVarName())) { if (UStruct* PinTypeStruct = Cast(VarPin->PinType.PinSubCategoryObject.Get())) { UClass* PinTypeClass = Cast(PinTypeStruct); if (!PinTypeStruct->IsNative() && (!PinTypeClass || PinTypeClass->ClassGeneratedBy != SourceBlueprint)) { bResult = true; if (OptionalOutput) { OptionalOutput->AddUnique(PinTypeStruct); } } } } const bool bSuperResult = Super::HasExternalDependencies(OptionalOutput); return bSuperResult || bResult; } FString UK2Node_Variable::GetDocumentationLink() const { if( FProperty* Property = GetPropertyForVariable() ) { // discover if the variable property is a non blueprint user variable UClass* SourceClass = Property->GetOwnerClass(); if( SourceClass && SourceClass->ClassGeneratedBy == NULL ) { UStruct* OwnerStruct = Property->GetOwnerStruct(); if( OwnerStruct ) { return FString::Printf( TEXT("Shared/Types/%s%s"), OwnerStruct->GetPrefixCPP(), *OwnerStruct->GetName() ); } } } return TEXT( "" ); } FString UK2Node_Variable::GetDocumentationExcerptName() const { return GetVarNameString(); } void UK2Node_Variable::AutowireNewNode(UEdGraphPin* FromPin) { const UEdGraphSchema_K2* K2Schema = CastChecked(GetSchema()); // Do some auto-connection if (FromPin != NULL) { bool bConnected = false; if(FromPin->Direction == EGPD_Output) { // If the source pin has a valid PinSubCategoryObject, we might be doing BP Comms, so check if it is a class if(FromPin->PinType.PinSubCategoryObject.IsValid() && FromPin->PinType.PinSubCategoryObject->IsA(UClass::StaticClass())) { FProperty* VariableProperty = VariableReference.ResolveMember(GetBlueprintClassFromNode()); if(VariableProperty) { UClass* PropertyOwner = VariableProperty->GetOwnerClass(); if (PropertyOwner != nullptr) { PropertyOwner = PropertyOwner->GetAuthoritativeClass(); } // BP Comms is highly likely at this point, if the source pin's type is a child of the variable's owner class, let's conform the "Target" pin if(FromPin->PinType.PinSubCategoryObject == PropertyOwner || dynamic_cast(FromPin->PinType.PinSubCategoryObject.Get())->IsChildOf(PropertyOwner)) { UEdGraphPin* TargetPin = FindPin(UEdGraphSchema_K2::PN_Self); if (TargetPin) { TargetPin->PinType.PinSubCategoryObject = PropertyOwner; if(K2Schema->TryCreateConnection(FromPin, TargetPin)) { bConnected = true; // Setup the VariableReference correctly since it may no longer be a self member VariableReference.SetFromField(GetPropertyForVariable(), false); TargetPin->bHidden = false; FromPin->GetOwningNode()->NodeConnectionListChanged(); this->NodeConnectionListChanged(); } } } } } } if(!bConnected) { Super::AutowireNewNode(FromPin); } } } FBPVariableDescription const* UK2Node_Variable::GetBlueprintVarDescription() const { FName const& VarName = VariableReference.GetMemberName(); UStruct const* VariableScope = VariableReference.GetMemberScope(GetBlueprintClassFromNode()); bool const bIsLocalVariable = (VariableScope != nullptr); if (bIsLocalVariable) { return FBlueprintEditorUtils::FindLocalVariable(GetBlueprint(), VariableScope, VarName); } else if (FProperty const* VarProperty = GetPropertyForVariable()) { UClass const* SourceClass = VarProperty->GetOwnerClass(); UBlueprint const* SourceBlueprint = (SourceClass != nullptr) ? Cast(SourceClass->ClassGeneratedBy) : nullptr; if (SourceBlueprint != nullptr) { int32 const VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(SourceBlueprint, VarName); return &SourceBlueprint->NewVariables[VarIndex]; } } return nullptr; } bool UK2Node_Variable::CanPasteHere(const UEdGraph* TargetGraph) const { // Do not allow pasting of variables in BPs that cannot handle them if ( FBlueprintEditorUtils::FindBlueprintForGraph(TargetGraph)->BlueprintType == BPTYPE_MacroLibrary && VariableReference.IsSelfContext() ) { // Self variables must be from a parent class to the macro BP if(FProperty* Property = VariableReference.ResolveMember(GetBlueprintClassFromNode())) { const UClass* CurrentClass = GetBlueprint()->SkeletonGeneratedClass->GetAuthoritativeClass(); const UClass* PropertyClass = Property->GetOwnerClass()->GetAuthoritativeClass(); const bool bIsChildOf = CurrentClass && CurrentClass->IsChildOf(PropertyClass); return bIsChildOf; } return false; } return true; } void UK2Node_Variable::PostPasteNode() { Super::PostPasteNode(); UBlueprint* Blueprint = GetBlueprint(); bool bInvalidateVariable = false; if (VariableReference.ResolveMember(Blueprint) == nullptr) { bInvalidateVariable = true; } else if (VariableReference.IsLocalScope()) { // Local scoped variables should always validate whether they are being placed in the same graph as their scope // ResolveMember will not return nullptr when the graph changes but the Blueprint remains the same. const UStruct* MemberScope = VariableReference.GetMemberScope(GetBlueprintClassFromNode()); UEdGraph* ScopeGraph = FBlueprintEditorUtils::FindScopeGraph(Blueprint, MemberScope); const bool bMemberScopeInvalid = (ScopeGraph && MemberScope && ScopeGraph->GetFName() != MemberScope->GetFName()); if(ScopeGraph != GetGraph() || bMemberScopeInvalid) { bInvalidateVariable = true; } } if (bInvalidateVariable) { // This invalidates the local scope VariableReference.InvalidateScope(); // If the current graph is a Function graph, look to see if there is a compatible local variable (same name) if (GetGraph()->GetSchema()->GetGraphType(GetGraph()) == GT_Function) { UEdGraph* FunctionGraph = FBlueprintEditorUtils::GetTopLevelGraph(GetGraph()); FBPVariableDescription* VariableDescription = FBlueprintEditorUtils::FindLocalVariable(Blueprint, FunctionGraph, VariableReference.GetMemberName()); bool bFoundParam = FunctionParameterExists(FunctionGraph, VariableReference.GetMemberName()); if(VariableDescription || bFoundParam) { VariableReference.SetLocalMember(VariableReference.GetMemberName(), FunctionGraph->GetName(), VariableReference.GetMemberGuid()); } } // If no variable was found, ResolveMember should automatically find a member variable with the same name in the current Blueprint and hook up to it as expected } } bool UK2Node_Variable::HasDeprecatedReference() const { bool bDeprecated = false; FProperty* VariableProperty = nullptr; // Declare up here so we can reuse if we would have resolved twice // Check if the referenced variable is deprecated. if (VariableReference.IsDeprecated()) { bDeprecated = true; } else { VariableProperty = VariableReference.ResolveMember(GetBlueprintClassFromNode()); if (VariableProperty) { // Backcompat: Allow variables tagged only with 'DeprecationMessage' meta to be seen as deprecated if inherited from a native parent class. const bool bHasDeprecationMessage = VariableProperty->HasMetaData(FBlueprintMetadata::MD_DeprecationMessage); if (bHasDeprecationMessage && VariableProperty->GetOwnerUObject()->IsNative()) { bDeprecated = true; } } } if (bDeprecated) { const UBlueprintEditorProjectSettings* BlueprintEditorProjectSettings = GetDefault(); if (!VariableProperty) { VariableProperty = VariableReference.ResolveMember(GetBlueprintClassFromNode()); } if (VariableProperty) { const FString PathName = VariableProperty->GetPathName(); if (BlueprintEditorProjectSettings->SuppressedDeprecationMessages.Contains(PathName)) { bDeprecated = false; } } } return bDeprecated; } FEdGraphNodeDeprecationResponse UK2Node_Variable::GetDeprecationResponse(EEdGraphNodeDeprecationType DeprecationType) const { FEdGraphNodeDeprecationResponse Response = Super::GetDeprecationResponse(DeprecationType); if (DeprecationType == EEdGraphNodeDeprecationType::NodeHasDeprecatedReference) { if (FProperty* VariableProperty = VariableReference.ResolveMember(GetBlueprintClassFromNode())) { // Check the deprecation type to override the severity FString MessageType = VariableProperty->GetMetaData(FBlueprintMetadata::MD_DeprecatedProperty); Response.MessageType = FBlueprintEditorUtils::GetDeprecatedMessageType(MessageType); FText MemberName = FText::FromName(VariableReference.GetMemberName()); FText DetailedMessage = FText::FromString(VariableProperty->GetMetaData(FBlueprintMetadata::MD_DeprecationMessage)); Response.MessageText = FBlueprintEditorUtils::GetDeprecatedMemberUsageNodeWarning(MemberName, DetailedMessage); } } return Response; } UObject* UK2Node_Variable::GetJumpTargetForDoubleClick() const { // Jump to the RepNotify function graph if one exists UBlueprint* OwnerBlueprint = GetBlueprint(); FName RepNotifyFunc = FBlueprintEditorUtils::GetBlueprintVariableRepNotifyFunc(OwnerBlueprint, GetVarName()); if (RepNotifyFunc != NAME_None) { for (UEdGraph* Graph : OwnerBlueprint->FunctionGraphs) { if (Graph->GetFName() == RepNotifyFunc) { return Graph; } } } return nullptr; } bool UK2Node_Variable::CanJumpToDefinition() const { const FProperty* VariableProperty = GetPropertyForVariable(); const bool bNativeVariable = (VariableProperty != nullptr) && (VariableProperty->IsNative()); const bool bCanJumpToNativeVariable = bNativeVariable && ensure(GUnrealEd) && GUnrealEd->GetUnrealEdOptions()->IsCPPAllowed(); return bCanJumpToNativeVariable || (GetJumpTargetForDoubleClick() != nullptr); } void UK2Node_Variable::JumpToDefinition() const { if (ensure(GUnrealEd) && GUnrealEd->GetUnrealEdOptions()->IsCPPAllowed()) { // For native variables, try going to the variable definition in C++ if available if (FProperty* VariableProperty = GetPropertyForVariable()) { if (VariableProperty->IsNative()) { FSourceCodeNavigation::NavigateToProperty(VariableProperty); return; } } } // Otherwise, fall back to the inherited behavior Super::JumpToDefinition(); } void UK2Node_Variable::GetNodeContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const { Super::GetNodeContextMenuActions(Menu, Context); if (HasDeprecatedReference()) { FText MenuEntryTitle = LOCTEXT("SuppressVariableDeprecationWarningTitle", "Suppress Deprecation Warning"); FText MenuEntryTooltip = LOCTEXT("SuppressVariableDeprecationWarningTooltip", "Adds this variable to the suppressed deprecation warnings list in the Bluperint Editor Project Settings for this project."); FToolMenuSection& Section = Menu->AddSection("K2NodeVariable", LOCTEXT("VariableHeader", "Variable")); Section.AddMenuEntry( "SuppressDeprecationWarning", MenuEntryTitle, MenuEntryTooltip, FSlateIcon(), FUIAction( FExecuteAction::CreateUObject(this, &UK2Node_Variable::SuppressDeprecationWarning), FCanExecuteAction::CreateUObject(this, &UK2Node_Variable::HasDeprecatedReference), FIsActionChecked() ) ); } } bool UK2Node_Variable::FunctionParameterExists(const UEdGraph* InFunctionGraph, const FName InParameterName) { TArray Entry; InFunctionGraph->GetNodesOfClass(Entry); if (ensureMsgf(Entry.Num() == 1, TEXT("Couldn't find a Function Entry node in graph %s"), *InFunctionGraph->GetName())) { check(Entry[0]); return Entry[0]->UserDefinedPinExists(InParameterName); } return false; } const UActorComponent* UK2Node_Variable::GetActorComponent(const FProperty* VariableProperty) const { if (!VariableProperty) { return nullptr; } UBlueprint* OwnerBlueprint = GetBlueprint(); if(OwnerBlueprint && OwnerBlueprint->SimpleConstructionScript) { if (const AActor* EditorActorInstance = OwnerBlueprint->SimpleConstructionScript->GetComponentEditorActorInstance()) { for (const UActorComponent* Component : EditorActorInstance->GetComponents()) { if (!Component || Component->GetFName() != VariableProperty->GetFName()) { continue; } return Component; } } } return nullptr; } void UK2Node_Variable::SuppressDeprecationWarning() const { if (const FProperty* Property = VariableReference.ResolveMember(GetBlueprintClassFromNode())) { FString PathName = Property->GetPathName(); UBlueprintEditorProjectSettings* BlueprintEditorProjectSettings = GetMutableDefault(); BlueprintEditorProjectSettings->SuppressedDeprecationMessages.Add(MoveTemp(PathName)); BlueprintEditorProjectSettings->SaveConfig(); BlueprintEditorProjectSettings->TryUpdateDefaultConfigFile("", false); } } #undef LOCTEXT_NAMESPACE