孟宇 hace 5 meses
padre
commit
de199659b9

+ 369 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Private/Condition/EzAbilityCommonConditions.cpp

@@ -0,0 +1,369 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "Condition/EzAbilityCommonConditions.h"
+
+#include "EzAbilityContext.h"
+#include "EzAbilityNodeDescriptionHelpers.h"
+
+#define LOCTEXT_NAMESPACE "Ability"
+
+namespace UE::EzAbility::Conditions
+{
+
+	template<typename T>
+	bool CompareNumbers(const T Left, const T Right, const EGenericCheck Operator)
+	{
+		switch (Operator)
+		{
+		case EGenericCheck::Equal:
+			return Left == Right;
+			break;
+		case EGenericCheck::NotEqual:
+			return Left != Right;
+			break;
+		case EGenericCheck::Less:
+			return Left < Right;
+			break;
+		case EGenericCheck::LessOrEqual:
+			return Left <= Right;
+			break;
+		case EGenericCheck::Greater:
+			return Left > Right;
+			break;
+		case EGenericCheck::GreaterOrEqual:
+			return Left >= Right;
+			break;
+		default:
+			ensureMsgf(false, TEXT("Unhandled operator %d"), Operator);
+			return false;
+			break;
+		}
+	}
+
+} 
+
+//----------------------------------------------------------------------//
+//  FAbilityCompareIntCondition
+//----------------------------------------------------------------------//
+
+bool FAbilityCompareIntCondition::TestCondition(FEzAbilityContext& Context) const
+{
+	const FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
+
+	const bool bResult = UE::EzAbility::Conditions::CompareNumbers<int32>(InstanceData.Left, InstanceData.Right, Operator);
+	return bResult ^ bInvert;
+}
+
+#if WITH_EDITOR
+FText FAbilityCompareIntCondition::GetDescription(const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting) const
+{
+	const FInstanceDataType* InstanceData = InstanceDataView.GetPtr<FInstanceDataType>();
+	check(InstanceData);
+
+	FText LeftValue = BindingLookup.GetBindingSourceDisplayName(FEzAbilityPropertyPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Left)), Formatting);
+	if (LeftValue.IsEmpty())
+	{
+		LeftValue = FText::AsNumber(InstanceData->Left);
+	}
+
+	FText RightValue = BindingLookup.GetBindingSourceDisplayName(FEzAbilityPropertyPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Right)), Formatting);
+	if (RightValue.IsEmpty())
+	{
+		RightValue = FText::AsNumber(InstanceData->Right);
+	}
+
+	const FText InvertText = UE::EzAbility::DescHelpers::GetInvertText(bInvert, Formatting);
+	const FText OperatorText = UE::EzAbility::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
+
+//----------------------------------------------------------------------//
+//  FAbilityCompareFloatCondition
+//----------------------------------------------------------------------//
+
+bool FAbilityCompareFloatCondition::TestCondition(FEzAbilityContext& Context) const
+{
+	const FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
+
+	const bool bResult = UE::EzAbility::Conditions::CompareNumbers<double>(InstanceData.Left, InstanceData.Right, Operator);
+	return bResult ^ bInvert;
+}
+
+#if WITH_EDITOR
+FText FAbilityCompareFloatCondition::GetDescription(const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting) const
+{
+	const FInstanceDataType* InstanceData = InstanceDataView.GetPtr<FInstanceDataType>();
+	check(InstanceData);
+
+	FNumberFormattingOptions Options;
+	Options.MinimumFractionalDigits = 1;
+	Options.MaximumFractionalDigits = 3;
+
+	FText LeftValue = BindingLookup.GetBindingSourceDisplayName(FEzAbilityPropertyPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Left)), Formatting);
+	if (LeftValue.IsEmpty())
+	{
+		LeftValue = FText::AsNumber(InstanceData->Left, &Options);
+	}
+
+	FText RightValue = BindingLookup.GetBindingSourceDisplayName(FEzAbilityPropertyPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Right)), Formatting);
+	if (RightValue.IsEmpty())
+	{
+		RightValue = FText::AsNumber(InstanceData->Right, &Options);
+	}
+
+	const FText InvertText = UE::EzAbility::DescHelpers::GetInvertText(bInvert, Formatting);
+	const FText OperatorText = UE::EzAbility::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
+
+//----------------------------------------------------------------------//
+//  FAbilityCompareBoolCondition
+//----------------------------------------------------------------------//
+
+bool FAbilityCompareBoolCondition::TestCondition(FEzAbilityContext& Context) const
+{
+	const FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
+	
+	return (InstanceData.bLeft == InstanceData.bRight) ^ bInvert;
+}
+
+#if WITH_EDITOR
+FText FAbilityCompareBoolCondition::GetDescription(const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting) const
+{
+	const FInstanceDataType* InstanceData = InstanceDataView.GetPtr<FInstanceDataType>();
+	check(InstanceData);
+
+	FText LeftValue = BindingLookup.GetBindingSourceDisplayName(FEzAbilityPropertyPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, bLeft)), Formatting);
+	if (LeftValue.IsEmpty())
+	{
+		LeftValue = UE::EzAbility::DescHelpers::GetBoolText(InstanceData->bLeft, Formatting);
+	}
+
+	FText RightValue = BindingLookup.GetBindingSourceDisplayName(FEzAbilityPropertyPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, bRight)), Formatting);
+	if (RightValue.IsEmpty())
+	{
+		RightValue = UE::EzAbility::DescHelpers::GetBoolText(InstanceData->bRight, Formatting);
+	}
+
+	const FText InvertText = UE::EzAbility::DescHelpers::GetInvertText(bInvert, Formatting);
+
+	const FText Format = (Formatting == EEzAbilityNodeFormatting::RichText)
+		? LOCTEXT("CompareBoolRich", "{EmptyOrNot}{Left} <s>is</> {Right}")
+		: LOCTEXT("CompareBool", "{EmptyOrNot}{Left} is {Right}");
+
+	return FText::FormatNamed(Format,
+		TEXT("EmptyOrNot"), InvertText,
+		TEXT("Left"),LeftValue,
+		TEXT("Right"),RightValue);
+}
+#endif
+
+//----------------------------------------------------------------------//
+//  FAbilityCompareEnumCondition
+//----------------------------------------------------------------------//
+
+bool FAbilityCompareEnumCondition::TestCondition(FEzAbilityContext& Context) const
+{
+	const FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
+
+	return (InstanceData.Left == InstanceData.Right) ^ bInvert;
+}
+
+#if WITH_EDITOR
+FText FAbilityCompareEnumCondition::GetDescription(const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting) const
+{
+	const FInstanceDataType* InstanceData = InstanceDataView.GetPtr<FInstanceDataType>();
+	check(InstanceData);
+
+	FText LeftValue = BindingLookup.GetBindingSourceDisplayName(FEzAbilityPropertyPath(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(FEzAbilityPropertyPath(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::EzAbility::DescHelpers::GetInvertText(bInvert, Formatting);
+
+	const FText Format = (Formatting == EEzAbilityNodeFormatting::RichText)
+		? LOCTEXT("CompareEnumRich", "{EmptyOrNot}{Left} <s>is</> {Right}")
+		: LOCTEXT("CompareEnum", "{EmptyOrNot}{Left} is {Right}");
+
+	return FText::FormatNamed(Format,
+		TEXT("EmptyOrNot"), InvertText,
+		TEXT("Left"),LeftValue,
+		TEXT("Right"),RightValue);
+}
+
+void FAbilityCompareEnumCondition::OnBindingChanged(const FGuid& ID, FEzAbilityDataView InstanceData, const FEzAbilityPropertyPath& SourcePath, const FEzAbilityPropertyPath& TargetPath, const IEzAbilityBindingLookup& BindingLookup)
+{
+	if (!TargetPath.GetStructID().IsValid())
+	{
+		return;
+	}
+
+	FInstanceDataType& Instance = InstanceData.GetMutable<FInstanceDataType>();
+
+	// 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<FByteProperty>(LeafProperty))
+			{
+				NewEnum = ByteProperty->GetIntPropertyEnum();
+			}
+			else if (const FEnumProperty* EnumProperty = CastField<FEnumProperty>(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
+
+
+//----------------------------------------------------------------------//
+//  FAbilityCompareDistanceCondition
+//----------------------------------------------------------------------//
+
+bool FAbilityCompareDistanceCondition::TestCondition(FEzAbilityContext& 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::EzAbility::Conditions::CompareNumbers<FVector::FReal>(Left, Right, Operator);
+	return bResult ^ bInvert;
+}
+
+#if WITH_EDITOR
+FText FAbilityCompareDistanceCondition::GetDescription(const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting) const
+{
+	const FInstanceDataType* InstanceData = InstanceDataView.GetPtr<FInstanceDataType>();
+	check(InstanceData);
+
+	FNumberFormattingOptions Options;
+	Options.MinimumFractionalDigits = 1;
+	Options.MaximumFractionalDigits = 3;
+
+	FText SourceValue = BindingLookup.GetBindingSourceDisplayName(FEzAbilityPropertyPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Source)), Formatting);
+	if (SourceValue.IsEmpty())
+	{
+		SourceValue = InstanceData->Source.ToText();
+	}
+
+	FText TargetValue = BindingLookup.GetBindingSourceDisplayName(FEzAbilityPropertyPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Target)), Formatting);
+	if (TargetValue.IsEmpty())
+	{
+		TargetValue = InstanceData->Target.ToText();
+	}
+
+	FText DistanceValue = BindingLookup.GetBindingSourceDisplayName(FEzAbilityPropertyPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Distance)), Formatting);
+	if (DistanceValue.IsEmpty())
+	{
+		DistanceValue = FText::AsNumber(InstanceData->Distance, &Options);
+	}
+
+	const FText OperatorText = UE::EzAbility::DescHelpers::GetOperatorText(Operator, Formatting);
+	const FText InvertText = UE::EzAbility::DescHelpers::GetInvertText(bInvert, Formatting);
+
+	const FText Format = (Formatting == EEzAbilityNodeFormatting::RichText)
+		? LOCTEXT("CompareDistanceRich", "{EmptyOrNot}<s>Distance from</> {Source} <s>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
+
+//----------------------------------------------------------------------//
+//  FAbilityRandomCondition
+//----------------------------------------------------------------------//
+
+bool FAbilityRandomCondition::TestCondition(FEzAbilityContext& Context) const
+{
+	const FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
+
+	return FMath::FRandRange(0.0f, 1.0f) < InstanceData.Threshold;
+}
+
+#if WITH_EDITOR
+FText FAbilityRandomCondition::GetDescription(const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting) const
+{
+	const FInstanceDataType* InstanceData = InstanceDataView.GetPtr<FInstanceDataType>();
+	check(InstanceData);
+
+	FNumberFormattingOptions Options;
+	Options.MinimumFractionalDigits = 1;
+	Options.MaximumFractionalDigits = 3;
+
+	FText ThresholdValue = BindingLookup.GetBindingSourceDisplayName(FEzAbilityPropertyPath(ID, GET_MEMBER_NAME_CHECKED(FInstanceDataType, Threshold)), Formatting);
+	if (ThresholdValue.IsEmpty())
+	{
+		ThresholdValue = FText::AsNumber(InstanceData->Threshold, &Options);
+	}
+
+	const FText Format = (Formatting == EEzAbilityNodeFormatting::RichText)
+		? LOCTEXT("RandomRich", "<s>Random [0..1] &lt;</> {Threshold}")
+		: LOCTEXT("Random", "Random [0..1] &lt; {Threshold}");
+
+	return FText::FormatNamed(Format,
+		TEXT("Threshold"), ThresholdValue);
+}
+#endif
+
+#undef LOCTEXT_NAMESPACE

+ 224 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Private/EzAbilityNodeDescriptionHelpers.cpp

@@ -0,0 +1,224 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "EzAbilityNodeDescriptionHelpers.h"
+
+#include "Condition/EzAbilityCondition.h"
+
+#if WITH_EDITOR
+#define LOCTEXT_NAMESPACE "EzAbility"
+
+namespace UE::EzAbility::DescHelpers
+{
+
+FText GetOperatorText(const EGenericCheck Operator, EEzAbilityNodeFormatting Formatting)
+{
+	if (Formatting == EEzAbilityNodeFormatting::RichText)
+	{
+		switch (Operator)
+		{
+		case EGenericCheck::Equal:
+			return FText::FromString(TEXT("<s>==</>"));
+		case EGenericCheck::NotEqual:
+			return FText::FromString(TEXT("<s>!=</>"));
+		case EGenericCheck::Less:
+			return FText::FromString(TEXT("<s>&lt;</>"));
+		case EGenericCheck::LessOrEqual:
+			return FText::FromString(TEXT("<s>&lt;=</>"));
+		case EGenericCheck::Greater:
+			return FText::FromString(TEXT("<s>&gt;</>"));
+		case EGenericCheck::GreaterOrEqual:
+			return FText::FromString(TEXT("<s>&gt;=</>"));
+		default:
+			break;
+		}
+	}
+	else
+	{
+		switch (Operator)
+		{
+		case EGenericCheck::Equal:
+			return FText::FromString(TEXT("=="));
+		case EGenericCheck::NotEqual:
+			return FText::FromString(TEXT("!="));
+		case EGenericCheck::Less:
+			return FText::FromString(TEXT("&lt;"));
+		case EGenericCheck::LessOrEqual:
+			return FText::FromString(TEXT("&lt;="));
+		case EGenericCheck::Greater:
+			return FText::FromString(TEXT("&gt;"));
+		case EGenericCheck::GreaterOrEqual:
+			return FText::FromString(TEXT("&gt;="));
+		default:
+			break;
+		}
+	}
+
+	return FText::FromString(TEXT("??"));
+}
+
+FText GetInvertText(bool bInvert, EEzAbilityNodeFormatting Formatting)
+{
+	FText InvertText = FText::GetEmpty();
+	if (bInvert)
+	{
+		if (Formatting == EEzAbilityNodeFormatting::RichText)
+		{
+			InvertText = LOCTEXT("InvertRich", "<s>Not</>  ");
+		}
+		else
+		{
+			InvertText = LOCTEXT("Invert", "Not  ");
+		}
+	}
+	return InvertText;
+}
+
+FText GetBoolText(bool bValue, EEzAbilityNodeFormatting Formatting)
+{
+	return bValue ? LOCTEXT("True", "True") : LOCTEXT("False", "False");
+}
+
+FText GetIntervalText(const FFloatInterval& Interval, EEzAbilityNodeFormatting Formatting)
+{
+	return GetIntervalText(Interval.Min, Interval.Max, Formatting);
+}
+
+FText GetIntervalText(float Min, float Max, EEzAbilityNodeFormatting Formatting)
+{
+	FNumberFormattingOptions Options;
+	Options.MinimumFractionalDigits = 1;
+	Options.MaximumFractionalDigits = 2;
+
+	FText MinValueText = FText::AsNumber(Min, &Options);
+	FText MaxValueText = FText::AsNumber(Max, &Options);
+
+	return GetIntervalText(MinValueText, MaxValueText, Formatting);
+}
+
+FText GetIntervalText(const FText& MinValueText, const FText& MaxValueText, EEzAbilityNodeFormatting Formatting)
+{
+	FText IntervalText;
+	if (Formatting == EEzAbilityNodeFormatting::RichText)
+	{
+		IntervalText = FText::FormatNamed(LOCTEXT("IntervalRich", "[{Min}<s>,</> {Max}]"),
+			TEXT("Min"), MinValueText,
+			TEXT("Max"), MaxValueText);
+	}
+	else //EEzAbilityNodeFormatting::Text
+	{
+		IntervalText = FText::FormatNamed(LOCTEXT("Interval", "[{Min}, {Max}]"),
+			TEXT("Min"), MinValueText,
+			TEXT("Max"), MaxValueText);
+	}
+
+	return IntervalText;
+}
+
+FText GetGameplayTagContainerAsText(const FGameplayTagContainer& TagContainer, const int ApproxMaxLength)
+{
+	if (TagContainer.IsEmpty())
+	{
+		return LOCTEXT("Empty", "Empty");
+	}
+		
+	FString Combined;
+	for (const FGameplayTag& Tag : TagContainer)
+	{
+		FString TagString = Tag.ToString();
+
+		if (Combined.Len() > 0)
+		{
+			Combined += TEXT(", ");
+		}
+			
+		if ((Combined.Len() + TagString.Len()) > ApproxMaxLength)
+		{
+			// Overflow
+			if (Combined.Len() == 0)
+			{
+				Combined += TagString.Left(ApproxMaxLength);
+			}
+			Combined += TEXT("...");
+			break;
+		}
+
+		Combined += TagString;
+	}
+
+	return FText::FromString(Combined);
+}
+
+FText GetGameplayTagQueryAsText(const FGameplayTagQuery& TagQuery, const int ApproxMaxLength)
+{
+	// Limit the presented query description.
+	FText QueryValue = LOCTEXT("Empty", "Empty");
+	constexpr int32 MaxDescLen = 120; 
+	FString QueryDesc = TagQuery.GetDescription();
+	if (!QueryDesc.IsEmpty())
+	{
+		if (QueryDesc.Len() > MaxDescLen)
+		{
+			QueryDesc = QueryDesc.Left(MaxDescLen);
+			QueryDesc += TEXT("...");
+		}
+		QueryValue = FText::FromString(QueryDesc);
+	}
+	return QueryValue;
+}
+
+FText GetExactMatchText(bool bExactMatch, EEzAbilityNodeFormatting Formatting)
+{
+	if (Formatting == EEzAbilityNodeFormatting::RichText)
+	{
+		return bExactMatch ? LOCTEXT("ExactlyRich", "<s>exactly</> ") : FText::GetEmpty();
+	}
+	return bExactMatch ? LOCTEXT("Exactly", "exactly ") : FText::GetEmpty();
+}
+
+FText GetText(const FVector& Value, EEzAbilityNodeFormatting Formatting)
+{
+	return Value.ToCompactText();
+}
+
+FText GetText(float Value, EEzAbilityNodeFormatting Formatting)
+{
+	return FText::AsNumber(Value);
+}
+
+FText GetText(int32 Value, EEzAbilityNodeFormatting Formatting)
+{
+	return FText::AsNumber(Value);
+}
+
+FText GetText(const UObject* Value, EEzAbilityNodeFormatting Formatting)
+{
+	return FText::FromName(GetFNameSafe(Value));
+}
+
+FText GetMathOperationText(const FText& OperationText, const FText& LeftValue, const FText& RightValue, EEzAbilityNodeFormatting Formatting)
+{
+	const FText Format = (Formatting == EEzAbilityNodeFormatting::RichText)
+		? LOCTEXT("MathFuncRich", "({Left} <s>{Operation}</> {Right})")
+		: LOCTEXT("MathFunc", "({Left} {Operation} {Right})");
+
+	return FText::FormatNamed(Format,
+		TEXT("Left"), LeftValue,
+		TEXT("Operation"), OperationText,
+		TEXT("Right"), RightValue);
+}
+
+FText GetSingleParamFunctionText(const FText& FunctionText, const FText& ParamText, EEzAbilityNodeFormatting Formatting)
+{
+	const FText Format = (Formatting == EEzAbilityNodeFormatting::RichText)
+		? LOCTEXT("SingleParamFuncRich", "<s>{Function}</>({Input})")
+		: LOCTEXT("SingleParamFunc", "{Function}({Input})");
+
+	return FText::FormatNamed(Format,
+		TEXT("Function"), FunctionText,
+		TEXT("Input"), ParamText);
+}
+} // UE::EzAbility::Helpers
+
+#undef LOCTEXT_NAMESPACE
+#endif // WITH_EDITOR

+ 1 - 1
Ability/Plugins/EzAbility/Source/EzAbility/Private/EzAbilityPropertyBindings.cpp

@@ -1623,7 +1623,7 @@ bool FEzAbilityPropertyPath::ResolveIndirectionsWithValue(const FEzAbilityDataVi
 				return false;
 			}
 			ArrayIndex = FMath::Max(0, Segment->GetArrayIndex());
-			Offset = Property->GetOffset_ForInternal() + Property->ElementSize * ArrayIndex;
+			Offset = Property->GetOffset_ForInternal() + Property->GetElementSize() * ArrayIndex;
 		}
 
 		FEzAbilityPropertyPathIndirection& Indirection = OutIndirections.AddDefaulted_GetRef();

+ 35 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Private/EzAbilityTypes.cpp

@@ -5,6 +5,41 @@
 
 #include "EzAbilityIndexTypes.h"
 
+namespace UE::EzAbility::Colors
+{
+	FColor Darken(const FColor Col, const float Level)
+	{
+		const int32 Mul = (int32)FMath::Clamp(Level * 255.f, 0.f, 255.f);
+		const int32 R = (int32)Col.R * Mul / 255;
+		const int32 G = (int32)Col.G * Mul / 255;
+		const int32 B = (int32)Col.B * Mul / 255;
+		return FColor((uint8)R, (uint8)G, (uint8)B, Col.A);
+	}
+
+	const FColor Grey = FColor::FromHex(TEXT("#949494"));
+	const FColor Red = FColor::FromHex(TEXT("#DE6659"));
+	const FColor Orange = FColor::FromHex(TEXT("#E3983F"));
+	const FColor Yellow = FColor::FromHex(TEXT("#EFD964"));
+	const FColor Green = FColor::FromHex(TEXT("#8AB75E"));
+	const FColor Cyan = FColor::FromHex(TEXT("#56C3BD"));
+	const FColor Blue = FColor::FromHex(TEXT("#649ED3"));
+	const FColor Purple = FColor::FromHex(TEXT("#B397D6"));
+	const FColor Magenta = FColor::FromHex(TEXT("#CE85C7"));
+	const FColor Bronze = FColorList::Bronze;
+
+	constexpr float DarkenLevel = 0.6f;
+	const FColor DarkGrey = Darken(Grey, DarkenLevel);
+	const FColor DarkRed = Darken(Red, DarkenLevel);
+	const FColor DarkOrange = Darken(Orange, DarkenLevel);
+	const FColor DarkYellow = Darken(Yellow, DarkenLevel);
+	const FColor DarkGreen = Darken(Green, DarkenLevel);
+	const FColor DarkCyan = Darken(Cyan, DarkenLevel);
+	const FColor DarkBlue = Darken(Blue, DarkenLevel);
+	const FColor DarkPurple = Darken(Purple, DarkenLevel);
+	const FColor DarkMagenta = Darken(Magenta, DarkenLevel);
+	const FColor DarkBronze = Darken(Bronze, DarkenLevel);
+} 
+
 const FEzAbilityStateHandle FEzAbilityStateHandle::Invalid		= FEzAbilityStateHandle();
 const FEzAbilityStateHandle FEzAbilityStateHandle::Succeeded	= FEzAbilityStateHandle(SucceededIndex);
 const FEzAbilityStateHandle FEzAbilityStateHandle::Failed		= FEzAbilityStateHandle(FailedIndex);

+ 259 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/Condition/EzAbilityCommonConditions.h

@@ -0,0 +1,259 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "EzAbilityAnyEnum.h"
+#include "EzAbilityCondition.h"
+#include "UObject/Object.h"
+#include "EzAbilityCommonConditions.generated.h"
+
+struct FEzAbilityDataView;
+
+USTRUCT()
+struct EZABILITY_API FAbilityCompareIntConditionInstanceData
+{
+	GENERATED_BODY()
+	
+	UPROPERTY(EditAnywhere, Category = "Input")
+	int32 Left = 0;
+
+	UPROPERTY(EditAnywhere, Category = "Parameter")
+	int32 Right = 0;
+};
+ABILITY_POD_INSTANCEDATA(FAbilityCompareIntConditionInstanceData);
+
+/**
+ * Condition comparing two integers.
+ */
+USTRUCT(DisplayName = "Integer Compare")
+struct EZABILITY_API FAbilityCompareIntCondition : public FEzAbilityCondition
+{
+	GENERATED_BODY()
+
+	using FInstanceDataType = FAbilityCompareIntConditionInstanceData;
+
+	FAbilityCompareIntCondition() = default;
+	explicit FAbilityCompareIntCondition(const EGenericCheck InOperator, const EEzAbilityCompare InInverts = EEzAbilityCompare::Default)
+		: bInvert(InInverts == EEzAbilityCompare::Invert)
+		, Operator(InOperator)
+	{}
+
+	virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
+	virtual bool TestCondition(FEzAbilityContext& Context) const override;
+#if WITH_EDITOR
+	virtual FText GetDescription(const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting = EEzAbilityNodeFormatting::Text) const override;
+#endif
+	
+	UPROPERTY(EditAnywhere, Category = "Parameter")
+	bool bInvert = false;
+
+	UPROPERTY(EditAnywhere, Category = "Parameter", meta = (InvalidEnumValues = "IsTrue"))
+	EGenericCheck Operator = EGenericCheck::Equal;
+};
+
+
+USTRUCT()
+struct EZABILITY_API FAbilityCompareFloatConditionInstanceData
+{
+	GENERATED_BODY()
+
+	UPROPERTY(EditAnywhere, Category = "Input")
+	double Left = 0.0;
+
+	UPROPERTY(EditAnywhere, Category = "Parameter")
+	double Right = 0.0;
+};
+ABILITY_POD_INSTANCEDATA(FAbilityCompareFloatConditionInstanceData);
+
+/**
+ * Condition comparing two floats.
+ */
+USTRUCT(DisplayName = "Float Compare")
+struct EZABILITY_API FAbilityCompareFloatCondition : public FEzAbilityCondition
+{
+	GENERATED_BODY()
+
+	using FInstanceDataType = FAbilityCompareFloatConditionInstanceData;
+
+	FAbilityCompareFloatCondition() = default;
+	explicit FAbilityCompareFloatCondition(const EGenericCheck InOperator, const EEzAbilityCompare InInverts = EEzAbilityCompare::Default)
+		: bInvert(InInverts == EEzAbilityCompare::Invert)
+		, Operator(InOperator)
+	{}
+
+	virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
+	virtual bool TestCondition(FEzAbilityContext& Context) const override;
+#if WITH_EDITOR
+	virtual FText GetDescription(const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting = EEzAbilityNodeFormatting::Text) const override;
+#endif
+
+	UPROPERTY(EditAnywhere, Category = "Parameter")
+	bool bInvert = false;
+
+	UPROPERTY(EditAnywhere, Category = "Parameter", meta = (InvalidEnumValues = "IsTrue"))
+	EGenericCheck Operator = EGenericCheck::Equal;
+};
+
+
+USTRUCT()
+struct EZABILITY_API FAbilityCompareBoolConditionInstanceData
+{
+	GENERATED_BODY()
+
+	UPROPERTY(EditAnywhere, Category = "Input")
+	bool bLeft = false;
+
+	UPROPERTY(EditAnywhere, Category = "Parameter")
+	bool bRight = false;
+};
+ABILITY_POD_INSTANCEDATA(FAbilityCompareBoolConditionInstanceData);
+
+/**
+ * Condition comparing two booleans.
+ */
+USTRUCT(DisplayName = "Bool Compare")
+struct EZABILITY_API FAbilityCompareBoolCondition : public FEzAbilityCondition
+{
+	GENERATED_BODY()
+
+	using FInstanceDataType = FAbilityCompareBoolConditionInstanceData;
+
+	FAbilityCompareBoolCondition() = default;
+	explicit FAbilityCompareBoolCondition(const EEzAbilityCompare InInverts)
+		: bInvert(InInverts == EEzAbilityCompare::Invert)
+	{}
+
+	FAbilityCompareBoolCondition(const bool bInInverts)
+		: bInvert(bInInverts)
+	{}
+
+	virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
+	virtual bool TestCondition(FEzAbilityContext& Context) const override;
+#if WITH_EDITOR
+	virtual FText GetDescription(const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting = EEzAbilityNodeFormatting::Text) const override;
+#endif
+
+	UPROPERTY(EditAnywhere, Category = "Parameter")
+	bool bInvert = false;
+};
+
+
+USTRUCT()
+struct EZABILITY_API FAbilityCompareEnumConditionInstanceData
+{
+	GENERATED_BODY()
+
+	UPROPERTY(EditAnywhere, Category = "Input", meta=(AllowAnyBinding))
+	FEzAbilityAnyEnum Left;
+
+	UPROPERTY(EditAnywhere, Category = "Parameter")
+	FEzAbilityAnyEnum Right;
+};
+ABILITY_POD_INSTANCEDATA(FAbilityCompareEnumConditionInstanceData);
+
+/**
+ * Condition comparing two enums.
+ */
+USTRUCT(DisplayName = "Enum Compare")
+struct EZABILITY_API FAbilityCompareEnumCondition : public FEzAbilityCondition
+{
+	GENERATED_BODY()
+
+	using FInstanceDataType = FAbilityCompareEnumConditionInstanceData;
+
+	FAbilityCompareEnumCondition() = default;
+	explicit FAbilityCompareEnumCondition(const EEzAbilityCompare InInverts)
+		: bInvert(InInverts == EEzAbilityCompare::Invert)
+	{}
+
+	FAbilityCompareEnumCondition(const bool bInInverts)
+		: bInvert(bInInverts)
+	{}
+
+	virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
+	virtual bool TestCondition(FEzAbilityContext& Context) const override;
+#if WITH_EDITOR
+	virtual FText GetDescription(const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting = EEzAbilityNodeFormatting::Text) const override;
+	virtual void OnBindingChanged(const FGuid& ID, FEzAbilityDataView InstanceData, const FEzAbilityPropertyPath& SourcePath, const FEzAbilityPropertyPath& TargetPath, const IEzAbilityBindingLookup& BindingLookup) override;
+#endif
+
+	UPROPERTY(EditAnywhere, Category = "Parameter")
+	bool bInvert = false;
+};
+
+
+USTRUCT()
+struct EZABILITY_API FAbilityCompareDistanceConditionInstanceData
+{
+	GENERATED_BODY()
+
+	UPROPERTY(EditAnywhere, Category = "Input")
+	FVector Source = FVector(EForceInit::ForceInitToZero);
+
+	UPROPERTY(EditAnywhere, Category = "Parameter")
+	FVector Target = FVector(EForceInit::ForceInitToZero);
+
+	UPROPERTY(EditAnywhere, Category = "Parameter")
+	double Distance = 0.0;
+};
+ABILITY_POD_INSTANCEDATA(FAbilityCompareDistanceConditionInstanceData);
+
+/**
+ * Condition comparing distance between two vectors.
+ */
+USTRUCT(DisplayName = "Distance Compare")
+struct EZABILITY_API FAbilityCompareDistanceCondition : public FEzAbilityCondition
+{
+	GENERATED_BODY()
+
+	using FInstanceDataType = FAbilityCompareDistanceConditionInstanceData;
+
+	FAbilityCompareDistanceCondition() = default;
+	explicit FAbilityCompareDistanceCondition(const EGenericCheck InOperator, const EEzAbilityCompare InInverts = EEzAbilityCompare::Default)
+		: bInvert(InInverts == EEzAbilityCompare::Invert)
+		, Operator(InOperator)
+	{}
+	
+	virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
+	virtual bool TestCondition(FEzAbilityContext& Context) const override;
+#if WITH_EDITOR
+	virtual FText GetDescription(const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting = EEzAbilityNodeFormatting::Text) const override;
+#endif
+
+	UPROPERTY(EditAnywhere, Category = "Condition")
+	bool bInvert = false;
+
+	UPROPERTY(EditAnywhere, Category = "Condition", meta = (InvalidEnumValues = "IsTrue"))
+	EGenericCheck Operator = EGenericCheck::Equal;
+};
+
+
+USTRUCT()
+struct EZABILITY_API FAbilityRandomConditionInstanceData
+{
+	GENERATED_BODY()
+	UPROPERTY(EditAnywhere, Category = "Parameter", meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0"))
+	float Threshold = 0.5f;
+};
+ABILITY_POD_INSTANCEDATA(FAbilityRandomConditionInstanceData);
+
+/**
+ * Random condition
+ */
+USTRUCT(DisplayName = "Random")
+struct EZABILITY_API FAbilityRandomCondition : public FEzAbilityCondition
+{
+	GENERATED_BODY()
+
+	using FInstanceDataType = FAbilityRandomConditionInstanceData;
+
+	FAbilityRandomCondition() = default;
+	
+	virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
+	virtual bool TestCondition(FEzAbilityContext& Context) const override;
+#if WITH_EDITOR
+	virtual FText GetDescription(const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting = EEzAbilityNodeFormatting::Text) const override;
+#endif
+};
+

+ 35 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/Condition/EzAbilityCondition.h

@@ -8,6 +8,27 @@
 #include "EzAbilityCondition.generated.h"
 
 struct FEzAbilityContext;
+
+enum class EEzAbilityCompare : uint8
+{
+	Default,
+	Invert,
+};
+
+UENUM()
+enum class EGenericCheck : uint8
+{
+	Less,
+	LessOrEqual,
+	Equal,
+	NotEqual,
+	GreaterOrEqual,
+	Greater,
+	IsTrue,
+
+	MAX UMETA(Hidden)
+};
+
 /**
  * 
  */
@@ -28,3 +49,17 @@ struct EZABILITY_API FEzAbilityCondition : public FEzAbilityNodeBase
 	UPROPERTY()
 	EEzAbilityConditionEvaluationMode EvaluationMode = EEzAbilityConditionEvaluationMode::Evaluated;
 };
+
+
+/** Helper macro to define instance data as simple constructible. */
+#define ABILITY_POD_INSTANCEDATA(Type) \
+template <> struct TIsPODType<Type> { enum { Value = true }; }; \
+template<> \
+struct TStructOpsTypeTraits<Type> : public TStructOpsTypeTraitsBase2<Type> \
+{ \
+enum \
+{ \
+WithZeroConstructor = true, \
+WithNoDestructor = true, \
+}; \
+};

+ 35 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityContext.h

@@ -4,6 +4,7 @@
 
 #include "CoreMinimal.h"
 #include "EzAbilityInstanceData.h"
+#include "EzAbilityNodeBase.h"
 #include "UObject/Object.h"
 #include "EzAbilityTypes.h"
 #include "EzAbilityContext.generated.h"
@@ -90,6 +91,40 @@ public:
 	
 	TArray<FName>		GetActiveStateNames() const;
 	const UEzAbility*	GetAbility() const { return Ability; }
+
+	/** @returns pointer to the instance data of specified node. */
+	template <typename T>
+	T* GetInstanceDataPtr(const FEzAbilityNodeBase& Node) const
+	{
+		check(CurrentNodeDataHandle == Node.InstanceDataHandle);
+		return CurrentNodeInstanceData.template GetMutablePtr<T>();
+	}
+
+	/** @returns reference to the instance data of specified node. */
+	template <typename T>
+	T& GetInstanceData(const FEzAbilityNodeBase& Node) const
+	{
+		check(CurrentNodeDataHandle == Node.InstanceDataHandle);
+		return CurrentNodeInstanceData.template GetMutable<T>();
+	}
+
+	/** @returns reference to the instance data of specified node. Infers the instance data type from the node's FInstanceDataType. */
+	template <typename T>
+	typename T::FInstanceDataType& GetInstanceData(const T& Node) const
+	{
+		static_assert(TIsDerivedFrom<T, FEzAbilityNodeBase>::IsDerived, "Expecting Node to derive from FEzAbilityNodeBase.");
+		check(CurrentNodeDataHandle == Node.InstanceDataHandle);
+		return CurrentNodeInstanceData.template GetMutable<typename T::FInstanceDataType>();
+	}
+
+	/** @returns reference to instance data struct that can be passed to lambdas. See TEzAbilityInstanceDataStructRef for usage. */
+	template <typename T>
+	TEzAbilityInstanceDataStructRef<typename T::FInstanceDataType> GetInstanceDataStructRef(const T& Node) const
+	{
+		static_assert(TIsDerivedFrom<T, FEzAbilityNodeBase>::IsDerived, "Expecting Node to derive from FEzAbilityNodeBase.");
+		check(CurrentlyProcessedFrame);
+		return TStateTreeInstanceDataStructRef<typename T::FInstanceDataType>(InstanceData, *CurrentlyProcessedFrame, Node.InstanceDataHandle);
+	}
 	
 protected:
 

+ 98 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityInstanceData.h

@@ -194,4 +194,102 @@ struct TStructOpsTypeTraits<FEzAbilityInstanceData> : public TStructOpsTypeTrait
 		WithSerializer = true,
 		WithGetPreloadDependencies = true,
 	};
+};
+
+template <typename T>
+struct TEzAbilityInstanceDataStructRef
+{
+	TEzAbilityInstanceDataStructRef(FEzAbilityInstanceData& InInstanceData, const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityDataHandle InDataHandle)
+		: WeakStorage(InInstanceData.GetWeakMutableStorage())
+		, WeakAbility(CurrentFrame.Ability)
+		, RootState(CurrentFrame.RootState)
+		, DataHandle(InDataHandle)
+	{
+		checkf(InDataHandle.GetSource() == EEzAbilityDataSourceType::ActiveInstanceData
+			|| InDataHandle.GetSource() == EEzAbilityDataSourceType::GlobalInstanceData,
+			TEXT("TEzAbilityInstanceDataStructRef supports only struct instance data."));
+	}
+
+	bool IsValid() const { return WeakAbility.IsValid() && RootState.IsValid() && DataHandle.IsValid(); }
+
+	T* GetPtr() const
+	{
+		if (!WeakStorage.IsValid())
+		{
+			return nullptr;
+		}
+
+		FEzAbilityInstanceStorage& Storage = *WeakStorage.Pin();
+
+		const FEzAbilityExecutionState& Exec = Storage.GetExecutionState();
+		const UEzAbility* EzAbility = WeakAbility.Get();
+		
+		const FEzAbilityExecutionFrame* CurrentFrame = Exec.ActiveFrames.FindByPredicate([EzAbility, RootState = RootState](const FEzAbilityExecutionFrame& Frame)
+		{
+			return Frame.Ability == EzAbility && Frame.RootState == RootState;
+		});
+
+		FStructView Struct;
+		if (CurrentFrame)
+		{
+			if (IsHandleSourceValid(Storage, *CurrentFrame, DataHandle))
+			{
+				Struct = GetDataView(Storage, *CurrentFrame, DataHandle);
+			}
+			else
+			{
+				Struct = Storage.GetMutableTemporaryStruct(*CurrentFrame, DataHandle);
+			}
+		}
+
+		check(Struct.GetScriptStruct() == TBaseStructure<T>::Get());
+		return reinterpret_cast<T*>(Struct.GetMemory());
+	}
+
+	UE_DEPRECATED(5.4, "Please use GetPtr(), as the ref may be invalidated while in use.")
+	T& operator*()
+	{
+		T* Result = GetPtr();
+		check(Result);
+		return *Result;
+	}
+
+protected:
+
+	FStructView GetDataView(FEzAbilityInstanceStorage& Storage, const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityDataHandle Handle) const
+	{
+		switch (DataHandle.GetSource())
+		{
+		case EEzAbilityDataSourceType::GlobalInstanceData:
+			return Storage.GetMutableStruct(CurrentFrame.GlobalInstanceIndexBase.Get() + DataHandle.GetIndex());
+		case EEzAbilityDataSourceType::ActiveInstanceData:
+			return Storage.GetMutableStruct(CurrentFrame.ActiveInstanceIndexBase.Get() + DataHandle.GetIndex());
+		default:
+			checkf(false, TEXT("Unhandle case %s"), *UEnum::GetValueAsString(Handle.GetSource()));
+		}
+		return {};
+	}
+	
+	bool IsHandleSourceValid(FEzAbilityInstanceStorage& Storage, const FEzAbilityExecutionFrame& CurrentFrame, const FEzAbilityDataHandle Handle) const
+	{
+		switch (Handle.GetSource())
+		{
+		case EEzAbilityDataSourceType::GlobalInstanceData:
+			return CurrentFrame.GlobalInstanceIndexBase.IsValid()
+				&& Storage.IsValidIndex(CurrentFrame.GlobalInstanceIndexBase.Get() + Handle.GetIndex());
+
+		case EEzAbilityDataSourceType::ActiveInstanceData:
+			return CurrentFrame.ActiveInstanceIndexBase.IsValid()
+				&& CurrentFrame.ActiveStates.Contains(Handle.GetState())
+				&& Storage.IsValidIndex(CurrentFrame.ActiveInstanceIndexBase.Get() + Handle.GetIndex());
+		default:
+			checkf(false, TEXT("Unhandle case %s"), *UEnum::GetValueAsString(Handle.GetSource()));
+		}
+		return false;
+	}
+	
+	TWeakPtr<FEzAbilityInstanceStorage> WeakStorage = nullptr;
+	TWeakObjectPtr<const UEzAbility> WeakAbility = nullptr;
+	FEzAbilityStateHandle RootState = FEzAbilityStateHandle::Invalid;
+	FEzAbilityDataHandle DataHandle = FEzAbilityDataHandle::Invalid;
 };

+ 57 - 5
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityNodeBase.h

@@ -5,11 +5,33 @@
 #include "CoreMinimal.h"
 #include "EzAbilityIndexTypes.h"
 #include "EzAbilityLinker.h"
-#include "EzAbilityPropertyBindings.h"
 #include "EzAbilityTypes.h"
 #include "UObject/Object.h"
 #include "EzAbilityNodeBase.generated.h"
 
+struct IEzAbilityBindingLookup;
+struct FEzAbilityPropertyPath;
+
+/**
+ * Enum describing in what format a text is expected to be returned.
+ *
+ * - Normal text should be used for values
+ * - Bold text should generally be used for actions, like name a of a task "<b>Play Animation</> {AnimName}".
+ * - Subdued should be generally used for secondary/structural information, like "{Left} <s>equals</> {Right}".
+ */
+UENUM(BlueprintType)
+enum EEzAbilityNodeFormatting : uint8
+{
+	/**
+	 * The returned text can contain following right text formatting (no nesting)
+	 *	- <b>Bold</> (bolder font is used)
+	 *	- <s>Subdued</> (normal font with lighter color) */
+	RichText,
+	
+	/** The text should be unformatted */
+	Text,
+};
+
 /**
  * 
  */
@@ -39,10 +61,40 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS
 	virtual EDataValidationResult Compile(FEzAbilityDataView InstanceDataView, TArray<FText>& ValidationMessages) { return EDataValidationResult::NotValidated; }
 	
 #if WITH_EDITOR
-	PRAGMA_DISABLE_DEPRECATION_WARNINGS
-	UE_DEPRECATED(5.3, "Use version with FEzAbilityPropertyPath instead.")
-	virtual void OnBindingChanged(const FGuid& ID, FEzAbilityDataView InstanceData, const FEzAbilityEditorPropertyPath& SourcePath, const FEzAbilityEditorPropertyPath& TargetPath, const IEzAbilityBindingLookup& BindingLookup) final {}
-	PRAGMA_ENABLE_DEPRECATION_WARNINGS
+
+	/**
+	 * Returns description for the node, use in the UI.
+	 * The UI description is selected as follows: 
+	 * - Node Name, if not empty
+	 * - Description if not empty
+	 * - Display name of the node struct
+	 * @param ID ID of the item, can be used make property paths to this item.
+	 * @param InstanceDataView View to the instance data, can be struct or class.
+	 * @param BindingLookup Reference to binding lookup which can be used to reason about property paths.
+	 * @param Formatting Requested formatting (whether rich or plain text should be returned).
+	 */
+	virtual FText GetDescription(const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting = EEzAbilityNodeFormatting::Text) const
+	{
+		return FText::GetEmpty();
+	}
+
+	/**
+	 * @returns name of the icon in format:
+	 *		StyleSetName | StyleName [ | SmallStyleName | StatusOverlayStyleName]
+	 *		SmallStyleName and StatusOverlayStyleName are optional.
+	 *		Example: "EzAbilityEditorStyle|Node.Animation" 
+	 */
+	virtual FName GetIconName() const
+	{
+		return FName();
+	}
+
+	/** @return the color to be used with the icon. */
+	virtual FColor GetIconColor() const
+	{
+		return UE::EzAbility::Colors::DarkGrey;
+	}
+	
 	/**
 	 * Called when binding of any of the properties in the node changes.
 	 * @param ID ID of the item, can be used make property paths to this item.

+ 102 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityNodeDescriptionHelpers.h

@@ -0,0 +1,102 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "EzAbilityPropertyBindings.h"
+#include "Condition/EzAbilityCondition.h"
+#include "UObject/Object.h"
+
+
+enum EEzAbilityNodeFormatting : uint8;
+struct FGameplayTagContainer;
+struct FGameplayTagQuery;
+
+namespace UE::EzAbility::DescHelpers
+{
+#if WITH_EDITOR
+
+/** @return description for a EGenericCheck. */
+extern EZABILITY_API FText GetOperatorText(const EGenericCheck Operator, EEzAbilityNodeFormatting Formatting);
+
+/** @return description for condition inversion (returns "Not" plus a space). */
+extern EZABILITY_API FText GetInvertText(bool bInvert, EEzAbilityNodeFormatting Formatting);
+
+/** @return description of a boolean value. */
+extern EZABILITY_API FText GetBoolText(bool bValue, EEzAbilityNodeFormatting Formatting);
+
+/** @return description of a float interval. */
+extern EZABILITY_API FText GetIntervalText(const FFloatInterval& Interval, EEzAbilityNodeFormatting Formatting);
+
+/** @return description of a float interval. */
+extern EZABILITY_API FText GetIntervalText(float Min, float Max, EEzAbilityNodeFormatting Formatting);
+
+/** @return description of a float interval. */
+extern EZABILITY_API FText GetIntervalText(const FText& MinValueText, const FText& MaxValueText, EEzAbilityNodeFormatting Formatting);
+
+/** @return description for a Gameplay Tag Container. If the length of container description is longer than ApproxMaxLength, the it truncated and ... as added to the end. */
+extern EZABILITY_API FText GetGameplayTagContainerAsText(const FGameplayTagContainer& TagContainer, const int ApproxMaxLength = 60);
+
+/** @return description for a Gameplay Tag Query. If the query description is longer than ApproxMaxLength, the it truncated and ... as added to the end. */
+extern EZABILITY_API FText GetGameplayTagQueryAsText(const FGameplayTagQuery& TagQuery, const int ApproxMaxLength = 120);
+
+/** @return description for exact match, used for Gameplay Tag matching functions (returns "Exactly" plus space). */
+extern EZABILITY_API FText GetExactMatchText(bool bExactMatch, EEzAbilityNodeFormatting Formatting);
+
+/** @return description of a vector value. */
+extern EZABILITY_API FText GetText(const FVector& Value, EEzAbilityNodeFormatting Formatting);
+
+/** @return description of a float value. */
+extern EZABILITY_API FText GetText(float Value, EEzAbilityNodeFormatting Formatting);
+
+/** @return description of an int value. */
+extern EZABILITY_API FText GetText(int32 Value, EEzAbilityNodeFormatting Formatting);
+
+/** @return description of a UObject value. */
+extern EZABILITY_API FText GetText(const UObject* Value, EEzAbilityNodeFormatting Formatting);
+
+extern EZABILITY_API FText GetMathOperationText(const FText& OperationText, const FText& LeftText, const FText& RightText, EEzAbilityNodeFormatting Formatting);
+
+/** @return description in the form of (Left OperationText Right).
+*	Expect TInstanceDataType to have a member Left and Right whose types have an overloaded UE::EzAbility::DescHelpers::GetText function.
+*/
+template <typename TInstanceDataType>
+FText GetDescriptionForMathOperation(FText OperationText, const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting)
+{
+	const TInstanceDataType& InstanceData = InstanceDataView.Get<TInstanceDataType>();
+
+	FText LeftValue = BindingLookup.GetBindingSourceDisplayName(FEzAbilityPropertyPath(ID, GET_MEMBER_NAME_CHECKED(TInstanceDataType, Left)), Formatting);
+	if (LeftValue.IsEmpty())
+	{
+		LeftValue = UE::EzAbility::DescHelpers::GetText(InstanceData.Left, Formatting);
+	}
+
+	FText RightValue = BindingLookup.GetBindingSourceDisplayName(FEzAbilityPropertyPath(ID, GET_MEMBER_NAME_CHECKED(TInstanceDataType, Right)), Formatting);
+	if (RightValue.IsEmpty())
+	{
+		RightValue = UE::EzAbility::DescHelpers::GetText(InstanceData.Right, Formatting);
+	}
+
+	return GetMathOperationText(OperationText, LeftValue, RightValue, Formatting);
+}
+
+extern EZABILITY_API FText GetSingleParamFunctionText(const FText& FunctionText, const FText& ParamText, EEzAbilityNodeFormatting Formatting);
+
+/** @return description in the form of OperationText(Input).
+ *	Expect TInstanceDataType to have a member input whose type has an overloaded UE::EzAbility::DescHelpers::GetText function.
+ */
+template <typename TInstanceDataType>
+FText GetDescriptionForSingleParameterFunc(FText OperationText, const FGuid& ID, FEzAbilityDataView InstanceDataView, const IEzAbilityBindingLookup& BindingLookup, EEzAbilityNodeFormatting Formatting)
+{
+	const TInstanceDataType& InstanceData = InstanceDataView.Get<TInstanceDataType>();
+
+	FText InputValue = BindingLookup.GetBindingSourceDisplayName(FEzAbilityPropertyPath(ID, GET_MEMBER_NAME_CHECKED(TInstanceDataType, Input)), Formatting);
+	if (InputValue.IsEmpty())
+	{
+		InputValue = UE::EzAbility::DescHelpers::GetText(InstanceData.Input, Formatting);
+	}
+
+	return GetSingleParamFunctionText(OperationText, InputValue, Formatting);
+}
+#endif // WITH_EDITOR
+} // namespace UE::EzAbility::DescHelpers

+ 4 - 10
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityPropertyBindings.h

@@ -2,6 +2,7 @@
 
 #pragma once
 
+#include "EzAbilityNodeBase.h"
 #include "EzAbilityTypes.h"
 #include "EzAbilityPropertyRefHelpers.h"
 #include "EzAbilityPropertyBindings.generated.h"
@@ -1031,19 +1032,12 @@ struct EZABILITY_API IEzAbilityBindingLookup
 	/** @return Display name given property path. */
 	virtual FText GetPropertyPathDisplayName(const FEzAbilityPropertyPath& InPath) const PURE_VIRTUAL(IEzAbilityBindingLookup::GetPropertyPathDisplayName, return FText::GetEmpty(); );
 
+	/** @return Display name of binding source, or empty if binding does not exists. */
+	virtual FText GetBindingSourceDisplayName(const FEzAbilityPropertyPath& InTargetPath, EEzAbilityNodeFormatting Formatting = EEzAbilityNodeFormatting::Text) const PURE_VIRTUAL(IEzAbilityBindingLookup::GetPropertyPathDisplayName, return FText::GetEmpty(); );
+	
 	/** @return Leaf property based on property path. */
 	virtual const FProperty* GetPropertyPathLeafProperty(const FEzAbilityPropertyPath& InPath) const PURE_VIRTUAL(IEzAbilityBindingLookup::GetPropertyPathLeafProperty, return nullptr; );
-
-PRAGMA_DISABLE_DEPRECATION_WARNINGS
-	UE_DEPRECATED(5.3, "Use version with FEzAbilityPropertyPath instead.")
-	virtual const FEzAbilityEditorPropertyPath* GetPropertyBindingSource(const FEzAbilityEditorPropertyPath& InTargetPath) const final { return nullptr; }
 	
-	UE_DEPRECATED(5.3, "Use version with FEzAbilityPropertyPath instead.")
-	virtual FText GetPropertyPathDisplayName(const FEzAbilityEditorPropertyPath& InPath) const final { return FText::GetEmpty(); }
-	
-	UE_DEPRECATED(5.3, "Use version with FEzAbilityPropertyPath instead.")
-	virtual const FProperty* GetPropertyPathLeafProperty(const FEzAbilityEditorPropertyPath& InPath) const final { return nullptr; }
-PRAGMA_ENABLE_DEPRECATION_WARNINGS
 };
 
 

+ 27 - 0
Ability/Plugins/EzAbility/Source/EzAbility/Public/EzAbilityTypes.h

@@ -16,6 +16,33 @@ namespace UE::EzAbility
 	inline constexpr int32 MaxConditionIndent = 4;
 
 	inline const FName SchemaTag(TEXT("Schema"));
+	
+	inline const FName SchemaCanBeOverridenTag(TEXT("SchemaCanBeOverriden"));
+
+	namespace Colors
+	{
+		// Common and consistent colors to be used with State Tree nodes.
+		extern const EZABILITY_API FColor Grey;
+		extern const EZABILITY_API FColor DarkGrey;
+		extern const EZABILITY_API FColor Red;
+		extern const EZABILITY_API FColor DarkRed;
+		extern const EZABILITY_API FColor Orange;
+		extern const EZABILITY_API FColor DarkOrange;
+		extern const EZABILITY_API FColor Yellow;
+		extern const EZABILITY_API FColor DarkYellow;
+		extern const EZABILITY_API FColor Green;
+		extern const EZABILITY_API FColor DarkGreen;
+		extern const EZABILITY_API FColor Cyan;
+		extern const EZABILITY_API FColor DarkCyan;
+		extern const EZABILITY_API FColor Blue;
+		extern const EZABILITY_API FColor DarkBlue;
+		extern const EZABILITY_API FColor Purple;
+		extern const EZABILITY_API FColor DarkPurple;
+		extern const EZABILITY_API FColor Magenta;
+		extern const EZABILITY_API FColor DarkMagenta;
+		extern const EZABILITY_API FColor Bronze;
+		extern const EZABILITY_API FColor DarkBronze;
+	} // Colors
 }
 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 UENUM()

+ 7 - 6
Ability/Plugins/EzAbility/Source/EzAbilityTest/Private/EzAbilityTest.cpp

@@ -11,6 +11,7 @@
 #include "EzAbilityEditorData.h"
 #include "EzAblityTestTypes.h"
 #include "GameplayTagsManager.h"
+#include "Condition/EzAbilityCommonConditions.h"
 
 #include UE_INLINE_GENERATED_CPP_BY_NAME(EzAbilityTest)
 
@@ -88,10 +89,10 @@ struct FEzAbilityTest_MakeAndBakeEzAbility : FAITestBase
 		auto& TaskB1 = StateA.AddTask<FEzAbilityTestTask_B>();
 		EditorData.AddPropertyBinding(EvalA, TEXT("IntA"), TaskB1, TEXT("IntB"));
 		
-		// auto& IntCond = StateA.AddEnterCondition<FEzAbilityCompareIntCondition>(EGenericAICheck::Less);
-		// IntCond.GetInstanceData().Right = 2;
+		auto& IntCond = StateA.AddEnterCondition<FAbilityCompareIntCondition>(EGenericCheck::Less);
+		IntCond.GetInstanceData().Right = 2;
 		
-		//EditorData.AddPropertyBinding(EvalA, TEXT("IntA"), IntCond, TEXT("Left"));
+		EditorData.AddPropertyBinding(EvalA, TEXT("IntA"), IntCond, TEXT("Left"));
 		
 		StateA.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::GotoState, &StateB);
 		
@@ -100,9 +101,9 @@ struct FEzAbilityTest_MakeAndBakeEzAbility : FAITestBase
 		EditorData.AddPropertyBinding(EvalA, TEXT("bBoolA"), TaskB2, TEXT("bBoolB"));
 		
 		FEzAbilityTransition& Trans = StateB.AddTransition({}, EEzAbilityTransitionType::GotoState, &Root);
-		// auto& TransFloatCond = Trans.AddCondition<FEzAbilityCompareFloatCondition>(EGenericAICheck::Less);
-		// TransFloatCond.GetInstanceData().Right = 13.0f;
-		//EditorData.AddPropertyBinding(EvalA, TEXT("FloatA"), TransFloatCond, TEXT("Left"));
+		auto& TransFloatCond = Trans.AddCondition<FAbilityCompareFloatCondition>(EGenericCheck::Less);
+		TransFloatCond.GetInstanceData().Right = 13.0f;
+		EditorData.AddPropertyBinding(EvalA, TEXT("FloatA"), TransFloatCond, TEXT("Left"));
 		
 		StateB.AddTransition(EEzAbilityTransitionTrigger::OnStateCompleted, EEzAbilityTransitionType::Succeeded);