Files
UnrealEngineUWP/Engine/Source/Editor/BlueprintGraph/Private/K2Node_Variable.cpp
Michael Schoell de28759ddd Improvements to Find-in-Blueprints for variable nodes.
Properties in nodes can be marked as Blueprint searchable. Content will be gathered by FiB system for use in unloaded Blueprints on resave.

"Find References" on variables in the MyBlueprint list or on variable nodes is now more accurate.

Removed FEdGraphSchemaAction_K2AddCallOnVariable and FEdGraphSchemaAction_K2AddDocumentation, both unused in the editor and are a part of the old Blueprint menu system.

FMemberReference::SetSelfMember and FMemberReference::SetExternalMember support accepting a FGuid for the reference.

Variable nodes will assign their variable's Guid to their VariableReference
Added UK2Node::GetFindReferenceSearchString to find the search query for a node to find references to it, currently returns the node title for all nodes but variables (where it tries to do an advanced search to limit to only actual references to the same variable).

Can mark UProperties with metadata that informs the Find-in-Blueprint system to gather the property for searching. Currently FVariableReference in K2Node_Variable is the only thing gathered and only nodes are yet supported by this feature. Will expand to all objects contained within UBlueprint in the future.

When using Find-in-Blueprints, can use category names as functions to filter into child tags and values.

All Find-in-Blueprint data is now stored in an Asset Registry tag titled "FiBData" instead of "FiB", this is to support the new version feature.

#jira UE-21164 - Find-in-Blueprints needs an easier way of marking properties as searchable.

[CL 2702574 by Michael Schoell in Main branch]
2015-09-23 11:05:29 -04:00

858 lines
29 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "BlueprintGraphPrivatePCH.h"
#include "CompilerResultsLog.h"
#include "ClassIconFinder.h"
#include "MessageLog.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.UE4Ver() < VER_UE4_VARK2NODE_USE_MEMBERREFSTRUCT)
{
// Copy info into new struct
VariableReference.SetDirect(VariableName_DEPRECATED, FGuid(), VariableSourceClass_DEPRECATED, bSelfContext_DEPRECATED);
}
if(Ar.UE4Ver() < 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<UProperty>(bSelf? GetBlueprint()->GeneratedClass : MemberParentClass, VariableReference.GetMemberName(), VarGuid))
{
VariableReference.SetDirect(VariableReference.GetMemberName(), VarGuid, bSelf ? nullptr : MemberParentClass, bSelf);
}
}
}
}
}
void UK2Node_Variable::SetFromProperty(const UProperty* Property, bool bSelfContext)
{
SelfContextInfo = bSelfContext ? ESelfContextInfo::Unspecified : ESelfContextInfo::NotSelfContext;
VariableReference.SetFromField<UProperty>(Property, bSelfContext);
}
bool UK2Node_Variable::CreatePinForVariable(EEdGraphPinDirection Direction, FString InPinName/* = FString()*/)
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
UProperty* 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<UBlueprintGeneratedClass>(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<UBlueprint>(BpClassOwner->ClassGeneratedBy, ECastCheckedType::NullAllowed);
if (VariableBlueprint)
{
if (UProperty* SkelProperty = FindField<UProperty>(VariableBlueprint->SkeletonGeneratedClass, VariableReference.GetMemberName()))
{
VariableProperty = SkelProperty;
}
}
}
}
if (VariableProperty != NULL)
{
const FString PinName = InPinName.IsEmpty()? GetVarNameString() : InPinName;
// Create the pin
UEdGraphPin* VariablePin = CreatePin(Direction, TEXT(""), TEXT(""), NULL, false, false, PinName);
K2Schema->ConvertPropertyToPinType(VariableProperty, /*out*/ VariablePin->PinType);
K2Schema->SetPinDefaultValueBasedOnType(VariablePin);
}
else
{
if (!VariableReference.IsLocalScope())
{
Message_Warn(*FString::Printf(TEXT("CreatePinForVariable: '%s' variable not found. Base class was probably changed."), *GetVarNameString()));
}
return false;
}
return true;
}
void UK2Node_Variable::CreatePinForSelf()
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
// Create the self pin
if (!K2Schema->FindSelfPin(*this, EGPD_Input))
{
// Do not create a self pin for locally scoped variables
if( !VariableReference.IsLocalScope() )
{
bool bSelfTarget = VariableReference.IsSelfContext() && (ESelfContextInfo::NotSelfContext != SelfContextInfo);
UClass* MemberParentClass = VariableReference.GetMemberParentClass(GetBlueprintClassFromNode());
UClass* TargetClass = MemberParentClass;
// 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(UProperty* Property = VariableReference.ResolveMember<UProperty>(GetBlueprintClassFromNode()))
{
TargetClass = Property->GetOwnerClass()->GetAuthoritativeClass();
}
else
{
TargetClass = GetBlueprint()->SkeletonGeneratedClass->GetAuthoritativeClass();
}
}
else if(MemberParentClass && MemberParentClass->ClassGeneratedBy)
{
TargetClass = MemberParentClass->GetAuthoritativeClass();
}
UEdGraphPin* TargetPin = CreatePin(EGPD_Input, K2Schema->PC_Object, TEXT(""), TargetClass, false, false, K2Schema->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<UEdGraphPin*>& OldPins, FString InPinName/* = FString()*/)
{
// 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 = NULL;
const FString PinName = InPinName.IsEmpty()? GetVarNameString() : InPinName;
for(auto Iter = OldPins.CreateConstIterator(); Iter; ++Iter)
{
if(const UEdGraphPin* Pin = *Iter)
{
if(PinName == Pin->PinName)
{
OldVariablePin = Pin;
break;
}
}
}
if(NULL != OldVariablePin)
{
// create new pin from old one
UEdGraphPin* VariablePin = CreatePin(Direction, TEXT(""), TEXT(""), NULL, false, false, PinName);
VariablePin->PinType = OldVariablePin->PinType;
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
K2Schema->SetPinDefaultValueBasedOnType(VariablePin);
Message_Note(*FString::Printf(TEXT("Pin for variable '%s' recreated, but the variable is missing."), *PinName));
return true;
}
else
{
Message_Warn(*FString::Printf(TEXT("RecreatePinForVariable: '%s' pin not found"), *PinName));
return false;
}
}
FLinearColor UK2Node_Variable::GetNodeTitleColor() const
{
UProperty* VariableProperty = GetPropertyForVariable();
if (VariableProperty)
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
FEdGraphPinType VariablePinType;
K2Schema->ConvertPropertyToPinType(VariableProperty, VariablePinType);
return K2Schema->GetPinTypeColor(VariablePinType);
}
return FLinearColor::White;
}
FString UK2Node_Variable::GetFindReferenceSearchString() const
{
FString ResultSearchString;
if (VariableReference.IsLocalScope())
{
return FString::Printf(TEXT("Nodes(VariableReference(MemberName=+%s && MemberScope=+%s))"), *VariableReference.GetMemberName().ToString(), *VariableReference.GetMemberScopeName());
}
else
{
FGuid Guid = VariableReference.GetMemberGuid();
if (Guid.IsValid())
{
ResultSearchString = FString::Printf(TEXT("Nodes(VariableReference(MemberName=+%s && MemberGuid(A=%i && B=%i && C=%i && D=%i)))"), *VariableReference.GetMemberName().ToString(), Guid.A, Guid.B, Guid.C, Guid.D);
}
else
{
ResultSearchString = FString::Printf(TEXT("Nodes(VariableReference(MemberName=+%s))"), *VariableReference.GetMemberName().ToString());
}
}
return ResultSearchString;
}
UK2Node::ERedirectType UK2Node_Variable::DoPinsMatchForReconstruction( const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex ) const
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
if( OldPin->PinType.PinCategory == K2Schema->PC_Exec )
{
return Super::DoPinsMatchForReconstruction(NewPin, NewPinIndex, OldPin, OldPinIndex);
}
const bool bPinNamesMatch = (OldPin->PinName == NewPin->PinName);
const bool bCanMatchSelfs = bPinNamesMatch || ((OldPin->PinName == K2Schema->PN_Self) == (NewPin->PinName == K2Schema->PN_Self));
const bool bTheSameDirection = (NewPin->Direction == OldPin->Direction);
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
const FEdGraphPinType& InputType = (OldPin->Direction == EGPD_Output) ? OldPin->PinType : NewPin->PinType;
const FEdGraphPinType& OutputType = (OldPin->Direction == EGPD_Output) ? NewPin->PinType : OldPin->PinType;
if (K2Schema->ArePinTypesCompatible(OutputType, InputType))
{
// If these are split pins, we need to do some name checking logic
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
FString NewPinPropertyName = NewPin->PinName.RightChop(ParentmostNewPin->PinName.Len() + 1);
FString OldPinPropertyName = OldPin->PinName.RightChop(ParentmostOldPin->PinName.Len() + 1);
if (NewPinPropertyName != OldPinPropertyName)
{
return ERedirectType_None;
}
}
return ERedirectType_Name;
}
else
{
const bool bNewPinIsObject = (NewPin->PinType.PinCategory == K2Schema->PC_Object);
// 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 || (NewPin->PinType.PinCategory == K2Schema->PC_Interface)) &&
(NewPin->PinType.PinSubCategoryObject == NULL) )
{
// @TODO: Fix this up to be less hacky
UBlueprintGeneratedClass* TypeClass = Cast<UBlueprintGeneratedClass>(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<UClass>(OldPin->PinType.PinSubCategoryObject.Get());
const bool bOldIsBlueprint = PSCOClass && PSCOClass->IsChildOf(UBlueprint::StaticClass());
const bool bNewIsClass = (NewPin->PinType.PinCategory == K2Schema->PC_Class);
// 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;
}
}
}
}
return ERedirectType_None;
}
UClass* UK2Node_Variable::GetVariableSourceClass() const
{
UClass* Result = VariableReference.GetMemberParentClass(GetBlueprintClassFromNode());
return Result;
}
UProperty* UK2Node_Variable::GetPropertyForVariable() const
{
const FName VarName = GetVarName();
UEdGraphPin* VariablePin = FindPin(GetVarNameString());
UProperty* VariableProperty = VariableReference.ResolveMember<UProperty>(GetBlueprintClassFromNode());
// if the variable has been deprecated, don't use it
if(VariableProperty != NULL)
{
if (VariableProperty->HasAllPropertyFlags(CPF_Deprecated))
{
VariableProperty = NULL;
}
// If the variable has been remapped update the pin
else if (VariablePin && VarName != GetVarName())
{
VariablePin->PinName = GetVarNameString();
}
}
return VariableProperty;
}
UEdGraphPin* UK2Node_Variable::GetValuePin() const
{
UEdGraphPin* Pin = FindPin(GetVarNameString());
check(Pin == NULL || Pin->Direction == EGPD_Output);
return Pin;
}
void UK2Node_Variable::ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const
{
Super::ValidateNodeDuringCompilation(MessageLog);
UProperty* 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->GetName();
if (UClass* VarOwnerClass = VariableReference.GetMemberParentClass(Blueprint->GeneratedClass))
{
OwnerName = VarOwnerClass->GetName();
}
}
FString const VarName = VariableReference.GetMemberName().ToString();
FText const WarningFormat = LOCTEXT("VariableNotFound", "Could not find a variable named \"%s\" in '%s'.\nMake sure '%s' has been compiled for @@");
MessageLog.Warning(*FString::Printf(*WarningFormat.ToString(), *VarName, *OwnerName, *OwnerName), this);
}
else
{
MessageLog.Warning(*FString::Printf(*LOCTEXT("VariableDeprecated", "Variable '%s' for @@ was deprecated. Please update it.").ToString(), *VariableReference.GetMemberName().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);
}
}
FName UK2Node_Variable::GetPaletteIcon(FLinearColor& ColorOut) const
{
FName ReturnIconName;
if(VariableReference.IsLocalScope())
{
ReturnIconName = GetVariableIconAndColor(VariableReference.GetMemberScope(GetBlueprintClassFromNode()), GetVarName(), ColorOut);
}
else
{
ReturnIconName = GetVariableIconAndColor(GetVariableSourceClass(), GetVarName(), ColorOut);
}
return ReturnIconName;
}
FName UK2Node_Variable::GetVarIconFromPinType(const FEdGraphPinType& InPinType, FLinearColor& IconColorOut)
{
FName IconBrush = TEXT("Kismet.AllClasses.VariableIcon");
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
IconColorOut = K2Schema->GetPinTypeColor(InPinType);
if(InPinType.bIsArray)
{
IconBrush = TEXT("Kismet.AllClasses.ArrayVariableIcon");
}
else if(InPinType.PinSubCategoryObject.IsValid())
{
if(UClass* Class = Cast<UClass>(InPinType.PinSubCategoryObject.Get()))
{
IconBrush = FClassIconFinder::FindIconNameForClass( Class );
}
}
return IconBrush;
}
FText UK2Node_Variable::GetToolTipHeading() const
{
FText Heading = Super::GetToolTipHeading();
// attempt to reflect the node's GetCornerIcon() with some tooltip documentation
FText IconTag;
if ( UProperty const* VariableProperty = VariableReference.ResolveMember<UProperty>(GetBlueprintClassFromNode()) )
{
if (VariableProperty->HasAllPropertyFlags(CPF_Net | CPF_EditorOnly))
{
IconTag = LOCTEXT("ReplicatedEditorOnlyVar", "Editor-Only | Replicated");
}
else if (VariableProperty->HasAnyPropertyFlags(CPF_Net))
{
IconTag = LOCTEXT("ReplicatedVar", "Replicated");
}
else if (VariableProperty->HasAnyPropertyFlags(CPF_EditorOnly))
{
IconTag = LOCTEXT("EditorOnlyVar", "Editor-Only");
}
}
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<TKeyValuePair<FString, FString>>& OutNodeAttributes ) const
{
UProperty* VariableProperty = GetPropertyForVariable();
const FString VariableName = VariableProperty ? VariableProperty->GetName() : TEXT( "InvalidVariable" );
OutNodeAttributes.Add( TKeyValuePair<FString, FString>( TEXT( "Type" ), TEXT( "Variable" ) ));
OutNodeAttributes.Add( TKeyValuePair<FString, FString>( TEXT( "Class" ), GetClass()->GetName() ));
OutNodeAttributes.Add( TKeyValuePair<FString, FString>( TEXT( "Name" ), VariableName ));
}
FName UK2Node_Variable::GetVariableIconAndColor(const UStruct* VarScope, FName VarName, FLinearColor& IconColorOut)
{
FName IconBrush = TEXT("Kismet.AllClasses.VariableIcon");
if(VarScope != NULL)
{
UProperty* Property = FindField<UProperty>(VarScope, VarName);
if(Property != NULL)
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
FEdGraphPinType PinType;
if(K2Schema->ConvertPropertyToPinType(Property, PinType)) // use schema to get the color
{
IconBrush = GetVarIconFromPinType(PinType, IconColorOut);
}
}
}
return IconBrush;
}
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 != NULL)
{
const TMap<FName, FName>* const ClassTaggedPropertyRedirects = UStruct::TaggedPropertyRedirects.Find( SearchClass->GetFName() );
if (ClassTaggedPropertyRedirects)
{
const FName* const NewPropertyName = ClassTaggedPropertyRedirects->Find( VariableReference.GetMemberName() );
if (NewPropertyName)
{
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<UProperty>(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<UClass>(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 UProperty* VariableProperty = VariableReference.ResolveMember<UProperty>(GetBlueprintClassFromNode()))
{
if (VariableProperty->HasAllPropertyFlags(CPF_Net))
{
return TEXT("Graph.Replication.Replicated");
}
else if (VariableProperty->HasAllPropertyFlags(CPF_EditorOnly))
{
return TEXT("Graph.Editor.EditorOnlyIcon");
}
}
return Super::GetCornerIcon();
}
bool UK2Node_Variable::HasExternalDependencies(TArray<class UStruct*>* OptionalOutput) const
{
UClass* SourceClass = GetVariableSourceClass();
UBlueprint* SourceBlueprint = GetBlueprint();
const bool bResult = (SourceClass && (SourceClass->ClassGeneratedBy != SourceBlueprint));
if (bResult && OptionalOutput)
{
OptionalOutput->AddUnique(SourceClass);
}
const bool bSuperResult = Super::HasExternalDependencies(OptionalOutput);
return bSuperResult || bResult;
}
FString UK2Node_Variable::GetDocumentationLink() const
{
if( UProperty* 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 GetVarName().ToString();
}
void UK2Node_Variable::AutowireNewNode(UEdGraphPin* FromPin)
{
const UEdGraphSchema_K2* K2Schema = CastChecked<UEdGraphSchema_K2>(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()))
{
UProperty* VariableProperty = VariableReference.ResolveMember<UProperty>(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<UClass*>(FromPin->PinType.PinSubCategoryObject.Get())->IsChildOf(PropertyOwner))
{
UEdGraphPin* TargetPin = FindPin(K2Schema->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<UProperty>(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 (UProperty const* VarProperty = GetPropertyForVariable())
{
UClass const* SourceClass = VarProperty->GetOwnerClass();
UBlueprint const* SourceBlueprint = (SourceClass != nullptr) ? Cast<UBlueprint>(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(UProperty* Property = VariableReference.ResolveMember<UProperty>(GetBlueprintClassFromNode()))
{
const UClass* CurrentClass = GetBlueprint()->SkeletonGeneratedClass->GetAuthoritativeClass();
const UClass* PropertyClass = Property->GetOwnerClass()->GetAuthoritativeClass();
const bool bIsChildOf = CurrentClass->IsChildOf(PropertyClass);
return bIsChildOf;
}
return false;
}
return true;
}
void UK2Node_Variable::PostPasteNode()
{
Super::PostPasteNode();
UBlueprint* Blueprint = GetBlueprint();
bool bInvalidateVariable = false;
if (VariableReference.ResolveMember<UProperty>(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.
UEdGraph* ScopeGraph = FBlueprintEditorUtils::FindScopeGraph(Blueprint, VariableReference.GetMemberScope(GetBlueprintClassFromNode()));
if(ScopeGraph != GetGraph())
{
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());
if(VariableDescription)
{
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
}
}
#undef LOCTEXT_NAMESPACE