You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
The SubobjectDataSubsystem is how users can manipulate a given object within Blueprints or Python scripting. Given a Uobject instance in a level or a UBlueprint asset, you can use GatherSubobjectData to get get an array of handles that you can use to manipulate that subobject. This is what the new SubobjectEditor (Previously the SCS editor) will be using instead of having all its logic within slate code. #rb marc.audy #jira UE-64131 #preflight 6082ce4f8de3a60001cf6af8 #preflight 6082d84a92d7e700019f53e0 [CL 16104548 by ben hoffman in ue5-main branch]
1041 lines
28 KiB
C++
1041 lines
28 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "SubobjectData.h"
|
|
#include "SubobjectDataSubsystem.h" // For iterating child if we need to
|
|
#include "Engine/Blueprint.h" // Casting to UBlueprint
|
|
#include "Components/ChildActorComponent.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "Kismet2/ComponentEditorUtils.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "ComponentAssetBroker.h" // FComponentAssetBrokerage
|
|
#include "Engine/World.h" // Finding the instance of a component on an actor
|
|
#include "Engine/InheritableComponentHandler.h"
|
|
#include "Engine/SCS_Node.h" // #TODO_BH We need to remove this when the actual subobject refactor happens
|
|
|
|
#define LOCTEXT_NAMESPACE "SubobjectData"
|
|
|
|
FSubobjectData::FSubobjectData()
|
|
: WeakObjectPtr(nullptr)
|
|
, ParentObjectHandle(FSubobjectDataHandle::InvalidHandle)
|
|
, SCSNodePtr(nullptr)
|
|
{
|
|
// By Default the handle will be invalid, it will only
|
|
// be generated if we are given an object.
|
|
}
|
|
|
|
FSubobjectData::FSubobjectData(UObject* ContextObject, const FSubobjectDataHandle& InParentHandle)
|
|
: WeakObjectPtr(ContextObject)
|
|
, ParentObjectHandle(InParentHandle)
|
|
, SCSNodePtr(nullptr)
|
|
{
|
|
AttemptToSetSCSNode();
|
|
}
|
|
|
|
FSubobjectData::~FSubobjectData()
|
|
{
|
|
ChildrenHandles.Empty();
|
|
WeakObjectPtr = nullptr;
|
|
}
|
|
|
|
bool FSubobjectData::CanEdit() const
|
|
{
|
|
// Actors are editable by default
|
|
if(const AActor* ActorContext = GetObject<AActor>())
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FSubobjectData::CanDelete() const
|
|
{
|
|
// Components can be deleted if they are not inherited or the scene root
|
|
if (const UActorComponent* ComponentTemplate = GetComponentTemplate())
|
|
{
|
|
return !IsInheritedComponent() && !IsDefaultSceneRoot() && !IsChildActor();
|
|
}
|
|
|
|
// Otherwise, it can't be deleted
|
|
return false;
|
|
}
|
|
|
|
bool FSubobjectData::CanDuplicate() const
|
|
{
|
|
return CanCopy();
|
|
}
|
|
|
|
bool FSubobjectData::CanCopy() const
|
|
{
|
|
return FComponentEditorUtils::CanCopyComponent(GetComponentTemplate());
|
|
}
|
|
|
|
bool FSubobjectData::CanReparent() const
|
|
{
|
|
if(IsComponent())
|
|
{
|
|
if(GetSCSNode() != nullptr)
|
|
{
|
|
return !IsInstancedInheritedComponent();
|
|
}
|
|
|
|
return !IsInheritedComponent() && !IsDefaultSceneRoot() && IsSceneComponent();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FSubobjectData::CanRename() const
|
|
{
|
|
if(!IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// You can rename instance actors in the level editor, but you cannot rename
|
|
// the default actors in blueprints.
|
|
if(IsActor())
|
|
{
|
|
return IsInstancedActor() && !IsChildActor();
|
|
}
|
|
|
|
// You can rename within the BP editor, but not if if this subobject
|
|
// is on an instance in the level
|
|
if(GetSCSNode() != nullptr)
|
|
{
|
|
return !IsInstancedInheritedComponent();
|
|
}
|
|
|
|
return !IsInheritedComponent() && !IsDefaultSceneRoot() && !IsChildActor();
|
|
}
|
|
|
|
const UObject* FSubobjectData::GetObjectForBlueprint(UBlueprint* Blueprint) const
|
|
{
|
|
const bool bCanEdit = CanEdit();
|
|
// We have to deal with the ICH (Inherited Component Handler) for components
|
|
if(IsComponent() && bCanEdit && !IsNativeComponent() && IsInheritedSCSNode())
|
|
{
|
|
UActorComponent* OverriddenComponent = nullptr;
|
|
FComponentKey Key(GetSCSNode());
|
|
|
|
const bool bBlueprintCanOverrideComponentFromKey = Key.IsValid()
|
|
&& Blueprint
|
|
&& Blueprint->ParentClass
|
|
&& Blueprint->ParentClass->IsChildOf(Key.GetComponentOwner());
|
|
|
|
if (bBlueprintCanOverrideComponentFromKey)
|
|
{
|
|
const bool bCreateIfNecessary = true;
|
|
UInheritableComponentHandler* InheritableComponentHandler = Blueprint->GetInheritableComponentHandler(bCreateIfNecessary);
|
|
if (InheritableComponentHandler)
|
|
{
|
|
OverriddenComponent = InheritableComponentHandler->GetOverridenComponentTemplate(Key);
|
|
if (!OverriddenComponent && bCreateIfNecessary)
|
|
{
|
|
OverriddenComponent = InheritableComponentHandler->CreateOverridenComponentTemplate(Key);
|
|
}
|
|
}
|
|
}
|
|
return OverriddenComponent;
|
|
}
|
|
|
|
// If this not a component, then we can simply return the current object as long as it's editable
|
|
return bCanEdit ? WeakObjectPtr.Get() : nullptr;
|
|
}
|
|
|
|
bool FSubobjectData::IsInstancedComponent() const
|
|
{
|
|
// Check the flags on the component (RF_ClassDefaultObject | RF_ArchetypeObject)
|
|
const UActorComponent* Component = GetComponentTemplate();
|
|
return Component && !Component->IsTemplate();
|
|
}
|
|
|
|
UActorComponent* FSubobjectData::FindMutableComponentInstanceInActor(const AActor* InActor) const
|
|
{
|
|
const USCS_Node* SCS_Node = GetSCSNode();
|
|
const UActorComponent* ComponentTemplate = GetComponentTemplate();
|
|
|
|
UActorComponent* ComponentInstance = nullptr;
|
|
if (InActor)
|
|
{
|
|
if (SCS_Node)
|
|
{
|
|
FName VariableName = SCS_Node->GetVariableName();
|
|
if (VariableName != NAME_None)
|
|
{
|
|
UWorld* World = InActor->GetWorld();
|
|
FObjectPropertyBase* Property = FindFProperty<FObjectPropertyBase>(InActor->GetClass(), VariableName);
|
|
if (Property != nullptr)
|
|
{
|
|
// Return the component instance that's stored in the property with the given variable name
|
|
ComponentInstance = Cast<UActorComponent>(Property->GetObjectPropertyValue_InContainer(InActor));
|
|
}
|
|
else if (World != nullptr && World->WorldType == EWorldType::EditorPreview)
|
|
{
|
|
// If this is the preview actor, return the cached component instance that's being used for the preview actor prior to recompiling the Blueprint
|
|
ComponentInstance = SCS_Node->EditorComponentInstance.Get();
|
|
}
|
|
}
|
|
}
|
|
else if (ComponentTemplate)
|
|
{
|
|
TInlineComponentArray<UActorComponent*> Components;
|
|
InActor->GetComponents(Components);
|
|
ComponentInstance = FComponentEditorUtils::FindMatchingComponent(ComponentTemplate, Components);
|
|
}
|
|
}
|
|
|
|
return ComponentInstance;
|
|
}
|
|
|
|
UBlueprint* FSubobjectData::GetBlueprint() const
|
|
{
|
|
// If this object is a BP, we can just return that
|
|
if (UBlueprint* BP = Cast<UBlueprint>(WeakObjectPtr.Get()))
|
|
{
|
|
return BP;
|
|
}
|
|
// If it is an actor, then we can get the BP from the UClass
|
|
else if (const AActor* DefaultActor = GetObject<AActor>())
|
|
{
|
|
return UBlueprint::GetBlueprintFromClass(DefaultActor->GetClass());
|
|
}
|
|
// For components, we need to get the blueprint from their owning actor or the SCS
|
|
else if(IsComponent())
|
|
{
|
|
if (const USCS_Node* SCS_Node = GetSCSNode())
|
|
{
|
|
if (const USimpleConstructionScript* SCS = SCS_Node->GetSCS())
|
|
{
|
|
return SCS->GetBlueprint();
|
|
}
|
|
}
|
|
else if(const UActorComponent* ActorComponent = GetComponentTemplate())
|
|
{
|
|
if (const AActor* Actor = ActorComponent->GetOwner())
|
|
{
|
|
return UBlueprint::GetBlueprintFromClass(Actor->GetClass());
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FString FSubobjectData::GetDisplayString(bool bShowNativeComponentNames /* = true */) const
|
|
{
|
|
FName VariableName = GetVariableName();
|
|
|
|
const UActorComponent* ComponentTemplate = GetComponentTemplate();
|
|
|
|
UBlueprint* Blueprint = GetBlueprint();
|
|
UClass* VariableOwner = (Blueprint != nullptr) ? Blueprint->SkeletonGeneratedClass : nullptr;
|
|
FProperty* VariableProperty = FindFProperty<FProperty>(VariableOwner, VariableName);
|
|
|
|
bool const bHasValidVarName = (VariableName != NAME_None);
|
|
bool const bIsArrayVariable = bHasValidVarName && (VariableOwner != nullptr) &&
|
|
VariableProperty && VariableProperty->IsA<FArrayProperty>();
|
|
|
|
// Only display SCS node variable names in the tree if they have not been autogenerated
|
|
if (ComponentTemplate && bHasValidVarName && !bIsArrayVariable)
|
|
{
|
|
const bool bIsNative = IsNativeComponent();
|
|
const bool bIsInherited = IsInheritedComponent();
|
|
const bool bIsInstance = IsInstancedComponent();
|
|
const bool bIsBlueprintInstanceInherited = bIsInherited && bIsInstance;
|
|
|
|
// Inherited/Native components will have "Name (Inherited)" as their display
|
|
if ((bIsNative || bIsInherited) && bShowNativeComponentNames)
|
|
{
|
|
FStringFormatNamedArguments Args;
|
|
Args.Add(TEXT("VarName"), VariableProperty && VariableProperty->IsNative() ? VariableProperty->GetDisplayNameText().ToString() : VariableName.ToString());
|
|
FString CompName = TEXT(" (") + ComponentTemplate->GetName() + TEXT(")");
|
|
Args.Add(TEXT("CompName"), bIsNative || IsInstancedInheritedComponent() ? CompName : TEXT(""));
|
|
return FString::Format(TEXT("{VarName}{CompName}"), Args);
|
|
}
|
|
else
|
|
{
|
|
return VariableName.ToString();
|
|
}
|
|
}
|
|
else if (ComponentTemplate != nullptr)
|
|
{
|
|
return ComponentTemplate->GetFName().ToString();
|
|
}
|
|
else if (const AActor* DefaultActor = GetObject<AActor>())
|
|
{
|
|
FString Name;
|
|
if (Blueprint != nullptr && !IsInstancedActor())
|
|
{
|
|
Blueprint->GetName(Name);
|
|
}
|
|
else
|
|
{
|
|
Name = DefaultActor->GetActorLabel();
|
|
}
|
|
|
|
FStringFormatNamedArguments Args;
|
|
Args.Add(TEXT("ActorName"), Name);
|
|
return FString::Format(TEXT("{ActorName}"), Args);
|
|
}
|
|
// If the context is a simple UObject, then we can get it's name
|
|
else if (const UObject* Context = GetObject())
|
|
{
|
|
FString Name;
|
|
Context->GetName(Name);
|
|
|
|
return Name;
|
|
}
|
|
// Anything else will be unknown!
|
|
else
|
|
{
|
|
FString UnnamedString = LOCTEXT("UnnamedToolTip", "Unnamed").ToString();
|
|
FString NativeString = IsNativeComponent() ? LOCTEXT("NativeToolTip", "Native ").ToString() : TEXT("");
|
|
|
|
if (ComponentTemplate != nullptr)
|
|
{
|
|
return FString::Printf(TEXT("[%s %s%s]"), *UnnamedString, *NativeString, *ComponentTemplate->GetClass()->GetName());
|
|
}
|
|
else
|
|
{
|
|
return FString::Printf(TEXT("[%s %s]"), *UnnamedString, *NativeString);
|
|
}
|
|
}
|
|
}
|
|
|
|
FText FSubobjectData::GetDisplayNameContextModifiers(bool bShowNativeComponentNames) const
|
|
{
|
|
if(IsActor())
|
|
{
|
|
if(IsChildActor())
|
|
{
|
|
return LOCTEXT("ActorContext_ChildActor", "(Child Actor)");
|
|
}
|
|
else
|
|
{
|
|
if (const AActor* DefaultActor = GetObject<AActor>())
|
|
{
|
|
if (const UBlueprint* Blueprint = UBlueprint::GetBlueprintFromClass(DefaultActor->GetClass()))
|
|
{
|
|
return LOCTEXT("ActorContext_self", "(Self)");
|
|
}
|
|
else
|
|
{
|
|
return LOCTEXT("ActorContext_Instance", "(Instance)");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(const UActorComponent* Template = GetComponentTemplate())
|
|
{
|
|
FName VariableName = GetVariableName();
|
|
UBlueprint* Blueprint = GetBlueprint();
|
|
UClass* VariableOwner = (Blueprint != nullptr) ? Blueprint->SkeletonGeneratedClass : nullptr;
|
|
FProperty* VariableProperty = FindFProperty<FProperty>(VariableOwner, VariableName);
|
|
|
|
bool const bHasValidVarName = (VariableName != NAME_None);
|
|
bool const bIsArrayVariable = bHasValidVarName && (VariableOwner != nullptr) && VariableProperty && VariableProperty->IsA<FArrayProperty>();
|
|
const bool bIsBlueprintInstanceInherited = GetSCSNode() != nullptr && IsInstancedInheritedComponent();
|
|
|
|
if(bIsBlueprintInstanceInherited)
|
|
{
|
|
FStringFormatNamedArguments Args;
|
|
Args.Add(TEXT("InheritedText"), TEXT("(Inherited)"));
|
|
return FText::FromString(FString::Format(TEXT("{InheritedText}"), Args));
|
|
}
|
|
|
|
// Only display SCS node variable names in the tree if they have not been autogenerated
|
|
if (bHasValidVarName && !bIsArrayVariable)
|
|
{
|
|
const bool bIsNative = IsNativeComponent();
|
|
const bool bIsInherited = IsInheritedComponent();
|
|
const bool bIsInstance = IsInstancedComponent();
|
|
|
|
if ((bIsNative || bIsInherited) && bShowNativeComponentNames)
|
|
{
|
|
FStringFormatNamedArguments Args;
|
|
Args.Add(TEXT("InheritedText"), bIsInherited || bIsBlueprintInstanceInherited ? TEXT("(Inherited)") : TEXT(""));
|
|
return FText::FromString(FString::Format(TEXT("{InheritedText}"), Args));
|
|
}
|
|
}
|
|
}
|
|
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
FText FSubobjectData::GetDisplayName() const
|
|
{
|
|
if (const AActor* DefaultActor = GetObject<AActor>())
|
|
{
|
|
FString Name;
|
|
UBlueprint* Blueprint = UBlueprint::GetBlueprintFromClass(DefaultActor->GetClass());
|
|
if (Blueprint != nullptr && !IsInstancedActor())
|
|
{
|
|
Blueprint->GetName(Name);
|
|
}
|
|
else
|
|
{
|
|
Name = DefaultActor->GetActorLabel();
|
|
}
|
|
return FText::FromString(Name);
|
|
}
|
|
else if (const UObject* Context = GetObject())
|
|
{
|
|
FString Name;
|
|
Context->GetName(Name);
|
|
|
|
return FText::FromString(Name);
|
|
}
|
|
|
|
return LOCTEXT("GetDisplayNameNotOverridden", "Unknown Subobject");
|
|
}
|
|
|
|
FName FSubobjectData::GetVariableName() const
|
|
{
|
|
FName VariableName = NAME_None;
|
|
|
|
const USCS_Node* SCS_Node = GetSCSNode();
|
|
const UActorComponent* ComponentTemplate = GetComponentTemplate();
|
|
|
|
if (SCS_Node != nullptr)
|
|
{
|
|
// Use the same variable name as is obtained by the compiler
|
|
VariableName = SCS_Node->GetVariableName();
|
|
}
|
|
else if (ComponentTemplate != nullptr)
|
|
{
|
|
// Try to find the component anchor variable name (first looks for an exact match then scans for any matching variable that points to the archetype in the CDO)
|
|
VariableName = FComponentEditorUtils::FindVariableNameGivenComponentInstance(ComponentTemplate);
|
|
}
|
|
|
|
return VariableName;
|
|
}
|
|
|
|
FText FSubobjectData::GetSocketName() const
|
|
{
|
|
if (USCS_Node* SCSNode = GetSCSNode())
|
|
{
|
|
return FText::FromName(SCSNode->AttachToName);
|
|
}
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
FName FSubobjectData::GetSocketFName() const
|
|
{
|
|
if (USCS_Node* SCSNode = GetSCSNode())
|
|
{
|
|
return SCSNode->AttachToName;
|
|
}
|
|
return NAME_None;
|
|
}
|
|
|
|
bool FSubobjectData::HasValidSocket() const
|
|
{
|
|
return GetSCSNode() != nullptr;
|
|
}
|
|
|
|
void FSubobjectData::SetSocketName(FName InNewName)
|
|
{
|
|
if(USCS_Node* SCS = GetSCSNode())
|
|
{
|
|
SCS->AttachToName = InNewName;
|
|
}
|
|
}
|
|
|
|
void FSubobjectData::SetupAttachment(FName SocketName, const FSubobjectDataHandle& AttachParentHandle)
|
|
{
|
|
USceneComponent* SceneComponentTemplate = Cast<USceneComponent>(GetMutableComponentTemplate());
|
|
|
|
FSubobjectData* AttachParentData = AttachParentHandle.GetData();
|
|
USceneComponent* AttachParent = AttachParentData ?
|
|
Cast<USceneComponent>(AttachParentData->GetMutableComponentTemplate()) :
|
|
SceneComponentTemplate->GetAttachParent();
|
|
|
|
SceneComponentTemplate->SetupAttachment(AttachParent, NAME_None);
|
|
if(USCS_Node* SCS_Node = GetSCSNode())
|
|
{
|
|
SCS_Node->Modify();
|
|
SCS_Node->AttachToName = NAME_None;
|
|
}
|
|
}
|
|
|
|
FSubobjectDataHandle FSubobjectData::GetRootSubobject() const
|
|
{
|
|
FSubobjectDataHandle Current = ParentObjectHandle;
|
|
while(Current.IsValid() && Current.GetSharedDataPtr()->HasParent())
|
|
{
|
|
Current = Current.GetSharedDataPtr()->GetParentHandle();
|
|
}
|
|
|
|
return Current;
|
|
}
|
|
|
|
bool FSubobjectData::HasChild(const FSubobjectDataHandle& InChildHandle) const
|
|
{
|
|
for(const FSubobjectDataHandle& MyChildHandle : ChildrenHandles)
|
|
{
|
|
if(MyChildHandle == InChildHandle)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FSubobjectDataHandle FSubobjectData::FindChild(const FSubobjectDataHandle& InChildHandle) const
|
|
{
|
|
for(const FSubobjectDataHandle& MyChildHandle : ChildrenHandles)
|
|
{
|
|
if(MyChildHandle == InChildHandle)
|
|
{
|
|
return MyChildHandle;
|
|
}
|
|
}
|
|
|
|
return FSubobjectDataHandle::InvalidHandle;
|
|
}
|
|
|
|
FSubobjectDataHandle FSubobjectData::FindChildByObject(UObject* ContextObject) const
|
|
{
|
|
if (!ContextObject)
|
|
{
|
|
return FSubobjectDataHandle::InvalidHandle;
|
|
}
|
|
|
|
if (USubobjectDataSubsystem* System = USubobjectDataSubsystem::Get())
|
|
{
|
|
for (const FSubobjectDataHandle& CurrentChild : ChildrenHandles)
|
|
{
|
|
if (FSubobjectData* ChildData = CurrentChild.GetData())
|
|
{
|
|
if (ChildData->GetObject() == ContextObject)
|
|
{
|
|
return CurrentChild;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return FSubobjectDataHandle::InvalidHandle;
|
|
}
|
|
|
|
bool FSubobjectData::AddChildHandleOnly(const FSubobjectDataHandle& InHandle)
|
|
{
|
|
// If we already have this child, then don't both with adding it
|
|
if (HasChild(InHandle) || !InHandle.IsValid() || InHandle == Handle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ChildrenHandles.Add(InHandle);
|
|
if(FSubobjectData* NewChildData = InHandle.GetData())
|
|
{
|
|
NewChildData->SetParentHandle(Handle);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FSubobjectData::RemoveChildHandleOnly(const FSubobjectDataHandle& InHandle)
|
|
{
|
|
if (HasChild(InHandle))
|
|
{
|
|
ChildrenHandles.Remove(InHandle);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FText FSubobjectData::GetAssetName() const
|
|
{
|
|
UActorComponent* Template = GetMutableComponentTemplate();
|
|
|
|
FText AssetName = LOCTEXT("None", "None");
|
|
if (Template)
|
|
{
|
|
UObject* Asset = FComponentAssetBrokerage::GetAssetFromComponent(Template);
|
|
if (Asset != nullptr)
|
|
{
|
|
AssetName = FText::FromString(Asset->GetName());
|
|
}
|
|
}
|
|
|
|
return AssetName;
|
|
}
|
|
|
|
FText FSubobjectData::GetAssetPath() const
|
|
{
|
|
FText AssetName = LOCTEXT("None", "None");
|
|
|
|
UActorComponent* Template = GetMutableComponentTemplate();
|
|
|
|
if (Template)
|
|
{
|
|
UObject* Asset = FComponentAssetBrokerage::GetAssetFromComponent(Template);
|
|
if (Asset != nullptr)
|
|
{
|
|
AssetName = FText::FromString(Asset->GetPathName());
|
|
}
|
|
}
|
|
|
|
return AssetName;
|
|
}
|
|
|
|
bool FSubobjectData::IsAssetVisible() const
|
|
{
|
|
UActorComponent* Template = GetMutableComponentTemplate();
|
|
|
|
if (Template && FComponentAssetBrokerage::SupportsAssets(Template))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FText FSubobjectData::GetMobilityToolTipText() const
|
|
{
|
|
FText MobilityToolTip;
|
|
|
|
if (const USceneComponent* SceneComponentTemplate = Cast<USceneComponent>(GetComponentTemplate()))
|
|
{
|
|
if (SceneComponentTemplate->Mobility == EComponentMobility::Movable)
|
|
{
|
|
MobilityToolTip = LOCTEXT("MovableMobilityTooltip", "Movable");
|
|
}
|
|
else if (SceneComponentTemplate->Mobility == EComponentMobility::Stationary)
|
|
{
|
|
MobilityToolTip = LOCTEXT("StationaryMobilityTooltip", "Stationary");
|
|
}
|
|
else if (SceneComponentTemplate->Mobility == EComponentMobility::Static)
|
|
{
|
|
MobilityToolTip = LOCTEXT("StaticMobilityTooltip", "Static");
|
|
}
|
|
else
|
|
{
|
|
// make sure we're the mobility type we're expecting (we've handled Movable & Stationary)
|
|
ensureMsgf(false, TEXT("Unhandled mobility type [%d], is this a new type that we don't handle here?"), SceneComponentTemplate->Mobility.GetValue());
|
|
MobilityToolTip = LOCTEXT("UnknownMobilityTooltip", "Component with unknown mobility");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MobilityToolTip = LOCTEXT("NoMobilityTooltip", "Non-scene component");
|
|
}
|
|
|
|
return MobilityToolTip;
|
|
}
|
|
|
|
FText FSubobjectData::GetComponentEditorOnlyTooltipText() const
|
|
{
|
|
FText ComponentType = LOCTEXT("ComponentEditorOnlyFalse", "False");
|
|
|
|
if (IsComponent())
|
|
{
|
|
if (const UActorComponent* Template = GetComponentTemplate())
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprint();
|
|
FObjectProperty* Prop = Blueprint ? FindFProperty<FObjectProperty>(Blueprint->SkeletonGeneratedClass, GetVariableName()) : nullptr;
|
|
|
|
if(Template->bIsEditorOnly || (Prop && Prop->HasAnyPropertyFlags(CPF_EditorOnly)))
|
|
{
|
|
ComponentType = LOCTEXT("ComponentEditorOnlyTrue", "True");
|
|
}
|
|
}
|
|
}
|
|
|
|
return ComponentType;
|
|
}
|
|
|
|
FText FSubobjectData::GetIntroducedInToolTipText() const
|
|
{
|
|
FText IntroducedInTooltip = LOCTEXT("IntroducedInThisBPTooltip", "this class");
|
|
|
|
if (IsInheritedComponent())
|
|
{
|
|
if (const UActorComponent* ComponentTemplate = GetComponentTemplate())
|
|
{
|
|
UClass* BestClass = nullptr;
|
|
AActor* OwningActor = ComponentTemplate->GetOwner();
|
|
|
|
if (IsNativeComponent() && (OwningActor != nullptr))
|
|
{
|
|
for (UClass* TestClass = OwningActor->GetClass(); TestClass != AActor::StaticClass(); TestClass = TestClass->GetSuperClass())
|
|
{
|
|
if (FindComponentInstanceInActor(Cast<AActor>(TestClass->GetDefaultObject())))
|
|
{
|
|
BestClass = TestClass;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (!IsNativeComponent())
|
|
{
|
|
USCS_Node* SCSNode = GetSCSNode();
|
|
|
|
if ((SCSNode == nullptr) && (OwningActor != nullptr))
|
|
{
|
|
SCSNode = FindSCSNodeForInstance(ComponentTemplate, OwningActor->GetClass());
|
|
}
|
|
|
|
if (SCSNode != nullptr)
|
|
{
|
|
if (UBlueprint* OwningBP = SCSNode->GetSCS()->GetBlueprint())
|
|
{
|
|
BestClass = OwningBP->GeneratedClass;
|
|
}
|
|
}
|
|
else if (OwningActor != nullptr)
|
|
{
|
|
if (UBlueprint* OwningBP = UBlueprint::GetBlueprintFromClass(OwningActor->GetClass()))
|
|
{
|
|
BestClass = OwningBP->GeneratedClass;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (BestClass == nullptr)
|
|
{
|
|
if (ComponentTemplate->IsCreatedByConstructionScript())
|
|
{
|
|
IntroducedInTooltip = LOCTEXT("IntroducedInUnknownError", "Unknown Blueprint Class (via an Add Component call)");
|
|
}
|
|
else
|
|
{
|
|
IntroducedInTooltip = LOCTEXT("IntroducedInNativeError", "Unknown native source (via C++ code)");
|
|
}
|
|
}
|
|
else if (IsInstancedComponent() && ComponentTemplate->CreationMethod == EComponentCreationMethod::Native && !ComponentTemplate->HasAnyFlags(RF_DefaultSubObject))
|
|
{
|
|
IntroducedInTooltip = FText::Format(LOCTEXT("IntroducedInCPPErrorFmt", "{0} (via C++ code)"), FBlueprintEditorUtils::GetFriendlyClassDisplayName(BestClass));
|
|
}
|
|
else if (IsInstancedComponent() && ComponentTemplate->CreationMethod == EComponentCreationMethod::UserConstructionScript)
|
|
{
|
|
IntroducedInTooltip = FText::Format(LOCTEXT("IntroducedInUCSErrorFmt", "{0} (via an Add Component call)"), FBlueprintEditorUtils::GetFriendlyClassDisplayName(BestClass));
|
|
}
|
|
else
|
|
{
|
|
IntroducedInTooltip = FBlueprintEditorUtils::GetFriendlyClassDisplayName(BestClass);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IntroducedInTooltip = LOCTEXT("IntroducedInNoTemplateError", "[no component template found]");
|
|
}
|
|
}
|
|
else if (IsInstancedComponent())
|
|
{
|
|
IntroducedInTooltip = LOCTEXT("IntroducedInThisActorInstanceTooltip", "this actor instance");
|
|
}
|
|
|
|
return IntroducedInTooltip;
|
|
}
|
|
|
|
FText FSubobjectData::GetActorDisplayText() const
|
|
{
|
|
if (const AActor* DefaultActor = GetObject<AActor>())
|
|
{
|
|
FString Name;
|
|
UBlueprint* Blueprint = UBlueprint::GetBlueprintFromClass(DefaultActor->GetClass());
|
|
if (Blueprint != nullptr && !IsInstancedActor())
|
|
{
|
|
Blueprint->GetName(Name);
|
|
}
|
|
else
|
|
{
|
|
Name = DefaultActor->GetActorLabel();
|
|
}
|
|
return FText::FromString(Name);
|
|
}
|
|
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
FLinearColor FSubobjectData::GetColorTintForIcon() const
|
|
{
|
|
// A blue-ish tint
|
|
static const FLinearColor InheritedBlueprintComponentColor(0.08f, 0.35f, 0.6f);
|
|
|
|
static const FLinearColor InstancedInheritedBlueprintComponentColor(0.08f, 0.35f, 0.6f);
|
|
// A green-ish tint
|
|
static const FLinearColor InheritedNativeComponentColor(0.7f, 0.9f, 0.7f);
|
|
|
|
static const FLinearColor IntroducedHereColor(FLinearColor::White);
|
|
|
|
if (IsInheritedComponent())
|
|
{
|
|
// Native C++ components will be tinted green
|
|
if (IsNativeComponent())
|
|
{
|
|
return InheritedNativeComponentColor;
|
|
}
|
|
else if (IsInstancedComponent())
|
|
{
|
|
return InstancedInheritedBlueprintComponentColor;
|
|
}
|
|
else
|
|
{
|
|
return InheritedBlueprintComponentColor;
|
|
}
|
|
}
|
|
// If we have an SCS but are not Inherited, then this is just a regular blueprint and we should be blue
|
|
else if(GetSCSNode() != nullptr)
|
|
{
|
|
// If it's inherited BP color then it should be blue (i.e. this is a BP component that came from a BP generated class)
|
|
if(IsInheritedSCSNode() || IsInstancedComponent())
|
|
{
|
|
return InheritedBlueprintComponentColor;
|
|
}
|
|
// Otherwise this is just a regular SCS node inside of the BP editor
|
|
else
|
|
{
|
|
return IntroducedHereColor;
|
|
}
|
|
}
|
|
|
|
// By default, this node will appear white to represent being introduced here
|
|
return IntroducedHereColor;
|
|
}
|
|
|
|
bool FSubobjectData::IsInstancedActor() const
|
|
{
|
|
if (const AActor* Actor = GetObject<AActor>())
|
|
{
|
|
return !Actor->IsTemplate();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FSubobjectData::IsNativeComponent() const
|
|
{
|
|
if (const UActorComponent* Template = GetComponentTemplate())
|
|
{
|
|
return Template->CreationMethod == EComponentCreationMethod::Native && GetSCSNode() == nullptr;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FSubobjectData::IsInheritedComponent() const
|
|
{
|
|
// If this component is not created via an instance, then it's an inherited component
|
|
|
|
// This covers a component that is added via blueprints
|
|
if(GetSCSNode() != nullptr)
|
|
{
|
|
return IsInheritedSCSNode();
|
|
}
|
|
else if (const UActorComponent* ComponentTemplate = GetComponentTemplate())
|
|
{
|
|
return IsNativeComponent() || ComponentTemplate->CreationMethod != EComponentCreationMethod::Instance;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FSubobjectData::IsSceneComponent() const
|
|
{
|
|
return Cast<USceneComponent>(GetComponentTemplate()) != nullptr;
|
|
}
|
|
|
|
bool FSubobjectData::IsRootComponent() const
|
|
{
|
|
const UActorComponent* ComponentTemplate = GetComponentTemplate();
|
|
|
|
if(!ComponentTemplate)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const AActor* CDO = ComponentTemplate ? ComponentTemplate->GetOwner() : nullptr;
|
|
|
|
if(CDO && (IsInstancedComponent() || IsInheritedComponent()))
|
|
{
|
|
return CDO->GetRootComponent() == ComponentTemplate;
|
|
}
|
|
|
|
bool bIsRoot = true;
|
|
if(USCS_Node* SCS_Node = GetSCSNode())
|
|
{
|
|
if(USimpleConstructionScript* SCS = SCS_Node->GetSCS())
|
|
{
|
|
// Evaluate to TRUE if we have an SCS node reference, it is contained in the SCS root set and does not have an external parent
|
|
bIsRoot = SCS->GetRootNodes().Contains(SCS_Node) && SCS_Node->ParentComponentOrVariableName == NAME_None;
|
|
}
|
|
}
|
|
else if(ComponentTemplate && CDO)
|
|
{
|
|
// Evaluate to TRUE if we have a valid component reference that matches the native root component
|
|
bIsRoot = (ComponentTemplate == CDO->GetRootComponent());
|
|
}
|
|
|
|
return bIsRoot;
|
|
}
|
|
|
|
bool FSubobjectData::IsDefaultSceneRoot() const
|
|
{
|
|
// If this is a scene component and is instanced, then it will have a specific name
|
|
const USceneComponent* SceneComponent = Cast<USceneComponent>(GetComponentTemplate());
|
|
if (SceneComponent && !SceneComponent->IsTemplate())
|
|
{
|
|
return SceneComponent->GetFName() == USceneComponent::GetDefaultSceneRootVariableName();
|
|
}
|
|
// If this isn't a scene component, then we can check an SCS node for a flag.
|
|
else if (const USCS_Node* SCS_Node = GetSCSNode())
|
|
{
|
|
USimpleConstructionScript* SCS = SCS_Node->GetSCS();
|
|
if (SCS != nullptr)
|
|
{
|
|
return SCS_Node == SCS->GetDefaultSceneRootNode();
|
|
}
|
|
}
|
|
|
|
// Nothing else can be a DefaultSceneRoot
|
|
return false;
|
|
}
|
|
|
|
bool FSubobjectData::IsComponent() const
|
|
{
|
|
// Check if we are pointing to a component
|
|
return GetComponentTemplate() != nullptr;
|
|
}
|
|
|
|
bool FSubobjectData::IsChildActor() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool FSubobjectData::IsRootActor() const
|
|
{
|
|
// This is the root actor if it points to an AActor and has no parent
|
|
if(const AActor* Actor = GetObject<AActor>())
|
|
{
|
|
return !ParentObjectHandle.IsValid();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FSubobjectData::IsActor() const
|
|
{
|
|
return GetObject<AActor>() != nullptr;
|
|
}
|
|
|
|
bool FSubobjectData::IsInstancedInheritedComponent() const
|
|
{
|
|
if(!IsComponent())
|
|
{
|
|
return false;
|
|
}
|
|
FSubobjectDataHandle CurrentHandle = ParentObjectHandle;
|
|
USubobjectDataSubsystem* System = USubobjectDataSubsystem::Get();
|
|
FSubobjectData* CurrentData = CurrentHandle.GetData();
|
|
|
|
while(CurrentHandle.IsValid() && CurrentData && !CurrentData->IsActor())
|
|
{
|
|
CurrentHandle = CurrentData->GetParentHandle();
|
|
CurrentData = CurrentHandle.GetData();
|
|
}
|
|
|
|
return CurrentData && CurrentData->IsInstancedActor();
|
|
}
|
|
|
|
bool FSubobjectData::IsAttachedTo(const FSubobjectDataHandle& InHandle) const
|
|
{
|
|
if (USubobjectDataSubsystem* System = USubobjectDataSubsystem::Get())
|
|
{
|
|
FSubobjectDataHandle TestParentHandle = ParentObjectHandle;
|
|
|
|
while (TestParentHandle.IsValid())
|
|
{
|
|
if (TestParentHandle == InHandle)
|
|
{
|
|
return true;
|
|
}
|
|
const FSubobjectData* TestParentData = TestParentHandle.GetData();
|
|
TestParentHandle = TestParentData ? TestParentData->GetParentHandle() : FSubobjectDataHandle::InvalidHandle;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
AActor* FSubobjectData::GetMutableActorContext()
|
|
{
|
|
if(AActor* Actor = GetMutableObject<AActor>())
|
|
{
|
|
return Actor;
|
|
}
|
|
else if(UActorComponent* Component = GetMutableComponentTemplate())
|
|
{
|
|
return Component->GetOwner();
|
|
}
|
|
else if(UBlueprint* BP = GetBlueprint())
|
|
{
|
|
return BP->GeneratedClass ? BP->GeneratedClass->GetDefaultObject<AActor>() : nullptr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
USCS_Node* FSubobjectData::FindSCSNodeForInstance(const UActorComponent* InstanceComponent, UClass* ClassToSearch)
|
|
{
|
|
if ((ClassToSearch != nullptr) && InstanceComponent->IsCreatedByConstructionScript())
|
|
{
|
|
for (UClass* TestClass = ClassToSearch; TestClass->ClassGeneratedBy != nullptr; TestClass = TestClass->GetSuperClass())
|
|
{
|
|
if (UBlueprint* TestBP = Cast<UBlueprint>(TestClass->ClassGeneratedBy))
|
|
{
|
|
if (TestBP->SimpleConstructionScript != nullptr)
|
|
{
|
|
if (USCS_Node* Result = TestBP->SimpleConstructionScript->FindSCSNode(InstanceComponent->GetFName()))
|
|
{
|
|
return Result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool FSubobjectData::AttemptToSetSCSNode()
|
|
{
|
|
if (USCS_Node* PossibleSCS = Cast<USCS_Node>(WeakObjectPtr.Get()))
|
|
{
|
|
WeakObjectPtr = PossibleSCS->ComponentTemplate;
|
|
SCSNodePtr = PossibleSCS;
|
|
return true;
|
|
}
|
|
// If this is an instanced component, then we can find it's SCS node
|
|
else if (IsInstancedComponent())
|
|
{
|
|
const UActorComponent* Template = GetComponentTemplate();
|
|
if (Template->GetOwner())
|
|
{
|
|
SCSNodePtr = FindSCSNodeForInstance(Template, Template->GetOwner()->GetClass());
|
|
if (SCSNodePtr.IsValid())
|
|
{
|
|
WeakObjectPtr = SCSNodePtr->ComponentTemplate;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
USCS_Node* FSubobjectData::GetSCSNode(bool bEvenIfPendingKill) const
|
|
{
|
|
// @todo Deprecate everything related to SCS nodes that could possibly be public facing.
|
|
return SCSNodePtr.IsValid() ? SCSNodePtr.Get() : Cast<USCS_Node>(WeakObjectPtr.Get(bEvenIfPendingKill));
|
|
}
|
|
|
|
bool FSubobjectData::IsInheritedSCSNode() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|