// Copyright Epic Games, Inc. All Rights Reserved. #include "BlueprintNodeHelpers.h" #include "UObject/UnrealType.h" #include "UObject/PropertyPortFlags.h" #include "TimerManager.h" #include "GameFramework/Actor.h" #include "GameFramework/Pawn.h" #include "Engine/World.h" #include "GameFramework/Controller.h" #include "BehaviorTree/BehaviorTreeTypes.h" #include "BehaviorTree/BTNode.h" #include "BehaviorTree/ValueOrBBKey.h" #include "GameplayTagContainer.h" #include "UObject/TextProperty.h" namespace BlueprintNodeHelpers { uint16 GetPropertiesMemorySize(const TArray& PropertyData) { int32 TotalMem = 0; for (int32 PropertyIndex = 0; PropertyIndex < PropertyData.Num(); PropertyIndex++) { TotalMem += PropertyData[PropertyIndex]->GetSize(); } if (TotalMem > MAX_uint16) { TotalMem = 0; } return IntCastChecked(TotalMem); } #define GET_STRUCT_NAME_CHECKED(StructName) \ ((void)sizeof(StructName), TEXT(#StructName)) bool CanUsePropertyType(const FProperty* TestProperty) { if (TestProperty->IsA(FNumericProperty::StaticClass()) || TestProperty->IsA(FBoolProperty::StaticClass()) || TestProperty->IsA(FNameProperty::StaticClass()) || TestProperty->IsA(FStrProperty::StaticClass()) || TestProperty->IsA(FTextProperty::StaticClass()) ) { return true; } const FStructProperty* StructProp = CastField(TestProperty); if (StructProp) { FString CPPType = StructProp->GetCPPType(NULL, CPPF_None); if (CPPType.Contains(GET_STRUCT_NAME_CHECKED(FVector)) || CPPType.Contains(GET_STRUCT_NAME_CHECKED(FRotator))) { return true; } } return false; } void CollectPropertyData(const UObject* Ob, const UClass* StopAtClass, TArray& PropertyData) { UE_LOG(LogBehaviorTree, Verbose, TEXT("Looking for runtime properties of class: %s"), *GetNameSafe(Ob->GetClass())); PropertyData.Reset(); for (FProperty* TestProperty = Ob->GetClass()->PropertyLink; TestProperty; TestProperty = TestProperty->PropertyLinkNext) { // stop when reaching base class if (TestProperty->GetOwner() == StopAtClass) { break; } // skip properties without any setup data if (TestProperty->HasAnyPropertyFlags(CPF_Transient) || TestProperty->HasAnyPropertyFlags(CPF_DisableEditOnInstance) == false) { continue; } // serialize only simple types if (CanUsePropertyType(TestProperty)) { UE_LOG(LogBehaviorTree, Verbose, TEXT("> name: '%s'"), *GetNameSafe(TestProperty)); PropertyData.Add(TestProperty); } } } FString DescribeProperty(const FProperty* Prop, const uint8* PropertyAddr) { FString ExportedStringValue; const FStructProperty* StructProp = CastField(Prop); const FFloatProperty* FloatProp = CastField(Prop); if (StructProp && StructProp->GetCPPType(NULL, CPPF_None).Contains(GET_STRUCT_NAME_CHECKED(FBlackboardKeySelector))) { // special case for blackboard key selectors const FBlackboardKeySelector* PropertyValue = (const FBlackboardKeySelector*)PropertyAddr; ExportedStringValue = PropertyValue->SelectedKeyName.ToString(); } else if (StructProp && StructProp->Struct && StructProp->Struct->IsChildOf(TBaseStructure::Get())) { ExportedStringValue = ((const FGameplayTag*)PropertyAddr)->ToString(); #if WITH_EDITOR const FString CategoryLimit = StructProp->GetMetaData(TEXT("Categories")); if (!CategoryLimit.IsEmpty() && ExportedStringValue.StartsWith(CategoryLimit)) { ExportedStringValue.MidInline(CategoryLimit.Len(), MAX_int32, EAllowShrinking::No); } #endif } #if WITH_EDITOR else if (StructProp && StructProp->Struct->IsChildOf(FValueOrBlackboardKeyBase::StaticStruct())) { ExportedStringValue = reinterpret_cast(PropertyAddr)->ToString(); } #endif // WITH_EDITOR else if (FloatProp) { // special case for floats to remove unnecessary zeros const float FloatValue = FloatProp->GetPropertyValue(PropertyAddr); ExportedStringValue = FString::SanitizeFloat(FloatValue); } else { Prop->ExportTextItem_Direct(ExportedStringValue, PropertyAddr, nullptr, nullptr, PPF_PropertyWindow | PPF_SimpleObjectText, nullptr); } const bool bIsBool = Prop->IsA(FBoolProperty::StaticClass()); return FString::Printf(TEXT("%s: %s"), *FName::NameToDisplayString(Prop->GetName(), bIsBool), *ExportedStringValue); } void CollectBlackboardSelectors(const UObject* Ob, const UClass* StopAtClass, TArray& KeyNames) { for (FProperty* TestProperty = Ob->GetClass()->PropertyLink; TestProperty; TestProperty = TestProperty->PropertyLinkNext) { // stop when reaching base class if (TestProperty->GetOwner() == StopAtClass) { break; } // skip properties without any setup data if (TestProperty->HasAnyPropertyFlags(CPF_Transient) || TestProperty->HasAnyPropertyFlags(CPF_DisableEditOnInstance)) { continue; } const FStructProperty* StructProp = CastField(TestProperty); if (StructProp && StructProp->GetCPPType(NULL, CPPF_None).Contains(GET_STRUCT_NAME_CHECKED(FBlackboardKeySelector))) { const FBlackboardKeySelector* PropData = TestProperty->ContainerPtrToValuePtr(Ob); KeyNames.AddUnique(PropData->SelectedKeyName); } } } void ResolveBlackboardSelectors(UObject& Ob, const UClass& StopAtClass, const UBlackboardData& BlackboardAsset) { for (FProperty* TestProperty = Ob.GetClass()->PropertyLink; TestProperty; TestProperty = TestProperty->PropertyLinkNext) { // stop when reaching base class if (TestProperty->GetOwner() == &StopAtClass) { break; } FStructProperty* StructProp = CastField(TestProperty); if (StructProp && StructProp->GetCPPType(NULL, CPPF_None).Contains(GET_STRUCT_NAME_CHECKED(FBlackboardKeySelector))) { FBlackboardKeySelector* PropData = const_cast(TestProperty->ContainerPtrToValuePtr(&Ob)); PropData->ResolveSelectedKey(BlackboardAsset); } } } bool HasAnyBlackboardSelectors(const UObject* Ob, const UClass* StopAtClass) { bool bResult = false; for (FProperty* TestProperty = Ob->GetClass()->PropertyLink; TestProperty; TestProperty = TestProperty->PropertyLinkNext) { // stop when reaching base class if (TestProperty->GetOwner() == StopAtClass) { break; } // skip properties without any setup data if (TestProperty->HasAnyPropertyFlags(CPF_Transient) || TestProperty->HasAnyPropertyFlags(CPF_DisableEditOnInstance)) { continue; } const FStructProperty* StructProp = CastField(TestProperty); if (StructProp && StructProp->GetCPPType(NULL, CPPF_None).Contains(GET_STRUCT_NAME_CHECKED(FBlackboardKeySelector))) { bResult = true; break; } } return bResult; } #undef GET_STRUCT_NAME_CHECKED FString CollectPropertyDescription(const UObject* Ob, const UClass* StopAtClass, const TArray& InPropertiesToSkip) { return CollectPropertyDescription(Ob, StopAtClass, [&InPropertiesToSkip](FProperty* InTestProperty){ return InPropertiesToSkip.Contains(InTestProperty); }); } FString CollectPropertyDescription(const UObject* Ob, const UClass* StopAtClass, TFunctionRef ShouldSkipProperty) { FString RetString; for (FProperty* TestProperty = Ob->GetClass()->PropertyLink; TestProperty; TestProperty = TestProperty->PropertyLinkNext) { // stop when reaching base class if (TestProperty->GetOwner() == StopAtClass) { break; } // skip properties without any setup data if (TestProperty->HasAnyPropertyFlags(CPF_Transient) || TestProperty->HasAnyPropertyFlags(CPF_DisableEditOnInstance) || ShouldSkipProperty(TestProperty)) { continue; } if (TestProperty->IsA(FSoftClassProperty::StaticClass()) || TestProperty->IsA(FClassProperty::StaticClass()) || TestProperty->IsA(FStructProperty::StaticClass()) || CanUsePropertyType(TestProperty)) { if (RetString.Len()) { RetString.AppendChar(TEXT('\n')); } const uint8* PropData = TestProperty->ContainerPtrToValuePtr(Ob); RetString += DescribeProperty(TestProperty, PropData); } } return RetString; } void DescribeRuntimeValues(const UObject* Ob, const TArray& PropertyData, TArray& RuntimeValues) { for (int32 PropertyIndex = 0; PropertyIndex < PropertyData.Num(); PropertyIndex++) { FProperty* TestProperty = PropertyData[PropertyIndex]; const uint8* PropAddr = TestProperty->ContainerPtrToValuePtr(Ob); RuntimeValues.Add(DescribeProperty(TestProperty, PropAddr)); } } bool FindNodeOwner(AActor* OwningActor, UBTNode* Node, UBehaviorTreeComponent*& OwningComp, int32& OwningInstanceIdx) { bool bFound = false; APawn* OwningPawn = Cast(OwningActor); if (OwningPawn && OwningPawn->GetController()) { bFound = FindNodeOwner(OwningPawn->GetController(), Node, OwningComp, OwningInstanceIdx); } if (OwningActor && !bFound) { for (UActorComponent* Component : OwningActor->GetComponents()) { if (UBehaviorTreeComponent* BTComp = Cast(Component)) { const int32 InstanceIdx = BTComp->FindInstanceContainingNode(Node); if (InstanceIdx != INDEX_NONE) { OwningComp = BTComp; OwningInstanceIdx = InstanceIdx; bFound = true; break; } } } } return bFound; } void AbortLatentActions(UActorComponent& OwnerOb, const UObject& Ob) { if (!OwnerOb.HasAnyFlags(RF_BeginDestroyed) && OwnerOb.GetOwner()) { UWorld* MyWorld = OwnerOb.GetOwner()->GetWorld(); if (MyWorld) { MyWorld->GetLatentActionManager().RemoveActionsForObject(MakeWeakObjectPtr(const_cast(&Ob))); MyWorld->GetTimerManager().ClearAllTimersForObject(&Ob); } } } }