// Copyright Epic Games, Inc. All Rights Reserved. #include "Conditions/StateTreeCommonConditions.h" #include "StateTreeExecutionContext.h" #include "UObject/EnumProperty.h" #include "StateTreeNodeDescriptionHelpers.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(StateTreeCommonConditions) #define LOCTEXT_NAMESPACE "StateTree" namespace UE::StateTree::Conditions { template bool CompareNumbers(const T Left, const T Right, const EGenericAICheck Operator) { switch (Operator) { case EGenericAICheck::Equal: return Left == Right; break; case EGenericAICheck::NotEqual: return Left != Right; break; case EGenericAICheck::Less: return Left < Right; break; case EGenericAICheck::LessOrEqual: return Left <= Right; break; case EGenericAICheck::Greater: return Left > Right; break; case EGenericAICheck::GreaterOrEqual: return Left >= Right; break; default: ensureMsgf(false, TEXT("Unhandled operator %d"), Operator); return false; break; } } } // UE::StateTree::Conditions //----------------------------------------------------------------------// // FStateTreeCompareIntCondition //----------------------------------------------------------------------// bool FStateTreeCompareIntCondition::TestCondition(FStateTreeExecutionContext& Context) const { const FInstanceDataType& InstanceData = Context.GetInstanceData(*this); const bool bResult = UE::StateTree::Conditions::CompareNumbers(InstanceData.Left, InstanceData.Right, Operator); SET_NODE_CUSTOM_TRACE_TEXT(Context, Override, TEXT("%s%s %s %s") , *UE::StateTree::DescHelpers::GetInvertText(bInvert, EStateTreeNodeFormatting::Text).ToString() , *LexToString(InstanceData.Left) , *UE::StateTree::DescHelpers::GetOperatorText(Operator, EStateTreeNodeFormatting::Text).ToString() , *LexToString(InstanceData.Right)); return bResult ^ bInvert; } #if WITH_EDITOR FText FStateTreeCompareIntCondition::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting) const { const FInstanceDataType* InstanceData = InstanceDataView.GetPtr(); check(InstanceData); FText LeftValue = BindingLookup.GetBindingSourceDisplayName(FPropertyBindingPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Left)), Formatting); if (LeftValue.IsEmpty()) { LeftValue = FText::AsNumber(InstanceData->Left); } FText RightValue = BindingLookup.GetBindingSourceDisplayName(FPropertyBindingPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Right)), Formatting); if (RightValue.IsEmpty()) { RightValue = FText::AsNumber(InstanceData->Right); } const FText InvertText = UE::StateTree::DescHelpers::GetInvertText(bInvert, Formatting); const FText OperatorText = UE::StateTree::DescHelpers::GetOperatorText(Operator, Formatting); return FText::FormatNamed(LOCTEXT("CompareInt", "{EmptyOrNot}{Left} {Op} {Right}"), TEXT("EmptyOrNot"), InvertText, TEXT("Left"),LeftValue, TEXT("Op"), OperatorText, TEXT("Right"),RightValue); } #endif //----------------------------------------------------------------------// // FStateTreeCompareFloatCondition //----------------------------------------------------------------------// bool FStateTreeCompareFloatCondition::TestCondition(FStateTreeExecutionContext& Context) const { const FInstanceDataType& InstanceData = Context.GetInstanceData(*this); const bool bResult = UE::StateTree::Conditions::CompareNumbers(InstanceData.Left, InstanceData.Right, Operator); SET_NODE_CUSTOM_TRACE_TEXT(Context, Override, TEXT("%s%s %s %s") , *UE::StateTree::DescHelpers::GetInvertText(bInvert, EStateTreeNodeFormatting::Text).ToString() , *LexToString(InstanceData.Left) , *UE::StateTree::DescHelpers::GetOperatorText(Operator, EStateTreeNodeFormatting::Text).ToString() , *LexToString(InstanceData.Right)); return bResult ^ bInvert; } #if WITH_EDITOR FText FStateTreeCompareFloatCondition::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting) const { const FInstanceDataType* InstanceData = InstanceDataView.GetPtr(); check(InstanceData); FNumberFormattingOptions Options; Options.MinimumFractionalDigits = 1; Options.MaximumFractionalDigits = 3; FText LeftValue = BindingLookup.GetBindingSourceDisplayName(FPropertyBindingPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Left)), Formatting); if (LeftValue.IsEmpty()) { LeftValue = FText::AsNumber(InstanceData->Left, &Options); } FText RightValue = BindingLookup.GetBindingSourceDisplayName(FPropertyBindingPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Right)), Formatting); if (RightValue.IsEmpty()) { RightValue = FText::AsNumber(InstanceData->Right, &Options); } const FText InvertText = UE::StateTree::DescHelpers::GetInvertText(bInvert, Formatting); const FText OperatorText = UE::StateTree::DescHelpers::GetOperatorText(Operator, Formatting); return FText::FormatNamed(LOCTEXT("CompareFloat", "{EmptyOrNot}{Left} {Op} {Right}"), TEXT("EmptyOrNot"), InvertText, TEXT("Left"),LeftValue, TEXT("Op"), OperatorText, TEXT("Right"),RightValue); } #endif //----------------------------------------------------------------------// // FStateTreeCompareBoolCondition //----------------------------------------------------------------------// bool FStateTreeCompareBoolCondition::TestCondition(FStateTreeExecutionContext& Context) const { const FInstanceDataType& InstanceData = Context.GetInstanceData(*this); SET_NODE_CUSTOM_TRACE_TEXT(Context, Override, TEXT("%s%s is %s") , *UE::StateTree::DescHelpers::GetInvertText(bInvert, EStateTreeNodeFormatting::Text).ToString() , *UE::StateTree::DescHelpers::GetBoolText(InstanceData.bLeft, EStateTreeNodeFormatting::Text).ToString() , *UE::StateTree::DescHelpers::GetBoolText(InstanceData.bRight, EStateTreeNodeFormatting::Text).ToString()); return (InstanceData.bLeft == InstanceData.bRight) ^ bInvert; } #if WITH_EDITOR FText FStateTreeCompareBoolCondition::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting) const { const FInstanceDataType* InstanceData = InstanceDataView.GetPtr(); check(InstanceData); FText LeftValue = BindingLookup.GetBindingSourceDisplayName(FPropertyBindingPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, bLeft)), Formatting); if (LeftValue.IsEmpty()) { LeftValue = UE::StateTree::DescHelpers::GetBoolText(InstanceData->bLeft, Formatting); } FText RightValue = BindingLookup.GetBindingSourceDisplayName(FPropertyBindingPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, bRight)), Formatting); if (RightValue.IsEmpty()) { RightValue = UE::StateTree::DescHelpers::GetBoolText(InstanceData->bRight, Formatting); } const FText InvertText = UE::StateTree::DescHelpers::GetInvertText(bInvert, Formatting); const FText Format = (Formatting == EStateTreeNodeFormatting::RichText) ? LOCTEXT("CompareBoolRich", "{EmptyOrNot}{Left} is {Right}") : LOCTEXT("CompareBool", "{EmptyOrNot}{Left} is {Right}"); return FText::FormatNamed(Format, TEXT("EmptyOrNot"), InvertText, TEXT("Left"),LeftValue, TEXT("Right"),RightValue); } #endif //----------------------------------------------------------------------// // FStateTreeCompareEnumCondition //----------------------------------------------------------------------// bool FStateTreeCompareEnumCondition::TestCondition(FStateTreeExecutionContext& Context) const { const FInstanceDataType& InstanceData = Context.GetInstanceData(*this); SET_NODE_CUSTOM_TRACE_TEXT(Context, Override, TEXT("%s%s is %s") , *UE::StateTree::DescHelpers::GetInvertText(bInvert, EStateTreeNodeFormatting::Text).ToString() , *InstanceData.Left.Enum->GetNameStringByValue(InstanceData.Left.Value) , *InstanceData.Right.Enum->GetNameStringByValue(InstanceData.Right.Value)); return (InstanceData.Left == InstanceData.Right) ^ bInvert; } #if WITH_EDITOR FText FStateTreeCompareEnumCondition::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting) const { const FInstanceDataType* InstanceData = InstanceDataView.GetPtr(); check(InstanceData); FText LeftValue = BindingLookup.GetBindingSourceDisplayName(FPropertyBindingPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Left)), Formatting); if (LeftValue.IsEmpty()) { if (InstanceData->Left.Enum) { LeftValue = InstanceData->Left.Enum->GetDisplayNameTextByValue(InstanceData->Left.Value); } else { LeftValue = LOCTEXT("None", "None"); } } FText RightValue = BindingLookup.GetBindingSourceDisplayName(FPropertyBindingPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Right)), Formatting); if (RightValue.IsEmpty()) { if (InstanceData->Left.Enum) { RightValue = InstanceData->Right.Enum->GetDisplayNameTextByValue(InstanceData->Right.Value); } else { RightValue = LOCTEXT("None", "None"); } } const FText InvertText = UE::StateTree::DescHelpers::GetInvertText(bInvert, Formatting); const FText Format = (Formatting == EStateTreeNodeFormatting::RichText) ? LOCTEXT("CompareEnumRich", "{EmptyOrNot}{Left} is {Right}") : LOCTEXT("CompareEnum", "{EmptyOrNot}{Left} is {Right}"); return FText::FormatNamed(Format, TEXT("EmptyOrNot"), InvertText, TEXT("Left"),LeftValue, TEXT("Right"),RightValue); } void FStateTreeCompareEnumCondition::OnBindingChanged(const FGuid& ID, FStateTreeDataView InstanceData, const FPropertyBindingPath& SourcePath, const FPropertyBindingPath& TargetPath, const IStateTreeBindingLookup& BindingLookup) { if (!TargetPath.GetStructID().IsValid()) { return; } FInstanceDataType& Instance = InstanceData.GetMutable(); // Left has changed, update enums from the leaf property. if (!TargetPath.IsPathEmpty() && TargetPath.GetSegments().Last().GetName() == GET_MEMBER_NAME_CHECKED(FInstanceDataType, Left)) { if (const FProperty* LeafProperty = BindingLookup.GetPropertyPathLeafProperty(SourcePath)) { // Handle both old stype namespace enums and new class enum properties. UEnum* NewEnum = nullptr; if (const FByteProperty* ByteProperty = CastField(LeafProperty)) { NewEnum = ByteProperty->GetIntPropertyEnum(); } else if (const FEnumProperty* EnumProperty = CastField(LeafProperty)) { NewEnum = EnumProperty->GetEnum(); } if (Instance.Left.Enum != NewEnum) { Instance.Left.Initialize(NewEnum); } } else { Instance.Left.Initialize(nullptr); } if (Instance.Right.Enum != Instance.Left.Enum) { Instance.Right.Initialize(Instance.Left.Enum); } } } #endif //----------------------------------------------------------------------// // FStateTreeCompareDistanceCondition //----------------------------------------------------------------------// bool FStateTreeCompareDistanceCondition::TestCondition(FStateTreeExecutionContext& Context) const { const FInstanceDataType& InstanceData = Context.GetInstanceData(*this); const FVector::FReal Left = FVector::DistSquared(InstanceData.Source, InstanceData.Target); const FVector::FReal Right = FMath::Square(InstanceData.Distance); const bool bResult = UE::StateTree::Conditions::CompareNumbers(Left, Right, Operator); SET_NODE_CUSTOM_TRACE_TEXT(Context, Override, TEXT("%sDistance %s %s %s (from [%s] to [%s])") , *UE::StateTree::DescHelpers::GetInvertText(bInvert, EStateTreeNodeFormatting::Text).ToString() , *LexToString(FMath::Sqrt(Left)) , *UE::StateTree::DescHelpers::GetOperatorText(Operator, EStateTreeNodeFormatting::Text).ToString() , *LexToString(InstanceData.Distance) , *InstanceData.Source.ToString() , *InstanceData.Target.ToString()); return bResult ^ bInvert; } #if WITH_EDITOR FText FStateTreeCompareDistanceCondition::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting) const { const FInstanceDataType* InstanceData = InstanceDataView.GetPtr(); check(InstanceData); FNumberFormattingOptions Options; Options.MinimumFractionalDigits = 1; Options.MaximumFractionalDigits = 3; FText SourceValue = BindingLookup.GetBindingSourceDisplayName(FPropertyBindingPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Source)), Formatting); if (SourceValue.IsEmpty()) { SourceValue = InstanceData->Source.ToText(); } FText TargetValue = BindingLookup.GetBindingSourceDisplayName(FPropertyBindingPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Target)), Formatting); if (TargetValue.IsEmpty()) { TargetValue = InstanceData->Target.ToText(); } FText DistanceValue = BindingLookup.GetBindingSourceDisplayName(FPropertyBindingPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Distance)), Formatting); if (DistanceValue.IsEmpty()) { DistanceValue = FText::AsNumber(InstanceData->Distance, &Options); } const FText OperatorText = UE::StateTree::DescHelpers::GetOperatorText(Operator, Formatting); const FText InvertText = UE::StateTree::DescHelpers::GetInvertText(bInvert, Formatting); const FText Format = (Formatting == EStateTreeNodeFormatting::RichText) ? LOCTEXT("CompareDistanceRich", "{EmptyOrNot}Distance from {Source} to {Target} {Op} {Distance}") : LOCTEXT("CompareDistance", "{EmptyOrNot}Distance from {Source} to {Target} {Op} {Distance}"); return FText::FormatNamed(Format, TEXT("EmptyOrNot"), InvertText, TEXT("Source"), SourceValue, TEXT("Target"), TargetValue, TEXT("Op"), OperatorText, TEXT("Distance"), DistanceValue); } #endif //----------------------------------------------------------------------// // FStateTreeRandomCondition //----------------------------------------------------------------------// bool FStateTreeRandomCondition::TestCondition(FStateTreeExecutionContext& Context) const { const FInstanceDataType& InstanceData = Context.GetInstanceData(*this); const float RandomValue = FMath::FRandRange(0.0f, 1.0f); SET_NODE_CUSTOM_TRACE_TEXT(Context, Override, TEXT("Random value %f < %s") , RandomValue , *LexToString(InstanceData.Threshold)); return RandomValue < InstanceData.Threshold; } #if WITH_EDITOR FText FStateTreeRandomCondition::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting) const { const FInstanceDataType* InstanceData = InstanceDataView.GetPtr(); check(InstanceData); FNumberFormattingOptions Options; Options.MinimumFractionalDigits = 1; Options.MaximumFractionalDigits = 3; FText ThresholdValue = BindingLookup.GetBindingSourceDisplayName(FPropertyBindingPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Threshold)), Formatting); if (ThresholdValue.IsEmpty()) { ThresholdValue = FText::AsNumber(InstanceData->Threshold, &Options); } const FText Format = (Formatting == EStateTreeNodeFormatting::RichText) ? LOCTEXT("RandomRich", "Random [0..1] < {Threshold}") : LOCTEXT("Random", "Random [0..1] < {Threshold}"); return FText::FormatNamed(Format, TEXT("Threshold"), ThresholdValue); } #endif #undef LOCTEXT_NAMESPACE