417 lines
13 KiB
C++
417 lines
13 KiB
C++
// Copyright Steve Streeting 2022
|
|
// Released under the MIT license https://opensource.org/license/MIT/
|
|
#pragma once
|
|
|
|
#include "SUDSCommon.h"
|
|
#include "SUDSValue.generated.h"
|
|
|
|
|
|
UENUM(BlueprintType)
|
|
enum class ESUDSValueType : uint8
|
|
{
|
|
Text = 0,
|
|
Int = 1,
|
|
Float = 2,
|
|
Boolean = 3,
|
|
Gender = 4,
|
|
Name = 5,
|
|
/// Access the value of another variable
|
|
Variable = 10,
|
|
|
|
Empty = 99
|
|
};
|
|
/// Struct which can hold any of the value types that SUDS needs to use, in a Blueprint friendly manner
|
|
/// For getting / setting these values from blueprints, see blueprint library functions SetSUDSValue<Type>() / GetSUDSValue<Type>()
|
|
/// For convenience these are wrapped in USUDSDialogue but in e.g. event callbacks they're not
|
|
USTRUCT(BlueprintType)
|
|
struct SUDS_API FSUDSValue
|
|
{
|
|
GENERATED_BODY()
|
|
protected:
|
|
ESUDSValueType Type;
|
|
union
|
|
{
|
|
int32 IntValue;
|
|
float FloatValue;
|
|
};
|
|
TOptional<FText> TextValue;
|
|
// Used for variables and name values
|
|
TOptional<FName> Name;
|
|
public:
|
|
|
|
FSUDSValue() : Type(ESUDSValueType::Empty), IntValue(0), TextValue(FText::GetEmpty()) {}
|
|
|
|
FSUDSValue(const int32 Value)
|
|
: Type(ESUDSValueType::Int) { IntValue = Value; }
|
|
|
|
FSUDSValue(const float Value)
|
|
: Type(ESUDSValueType::Float) { FloatValue = Value; }
|
|
|
|
FSUDSValue(const FText& Value)
|
|
: Type(ESUDSValueType::Text),
|
|
IntValue(0),
|
|
TextValue(Value)
|
|
{
|
|
}
|
|
|
|
FSUDSValue(FText&& Value)
|
|
: Type(ESUDSValueType::Text), IntValue(0), TextValue(MoveTemp(Value))
|
|
{
|
|
}
|
|
|
|
FSUDSValue(ETextGender Value)
|
|
: Type(ESUDSValueType::Gender), IntValue(0)
|
|
{
|
|
IntValue = static_cast<int32>(Value);
|
|
}
|
|
|
|
FSUDSValue(bool Value)
|
|
: Type(ESUDSValueType::Boolean), IntValue(0)
|
|
{
|
|
IntValue = Value ? 1 : 0;
|
|
}
|
|
|
|
FSUDSValue(const FName& ReferencedName, bool bIsVariable)
|
|
: Type(bIsVariable ? ESUDSValueType::Variable : ESUDSValueType::Name),
|
|
IntValue(0),
|
|
Name(ReferencedName)
|
|
{
|
|
}
|
|
|
|
// Construct a default value of a given type
|
|
explicit FSUDSValue(ESUDSValueType ValType)
|
|
: Type(ValType), IntValue(0)
|
|
{
|
|
}
|
|
|
|
/// Whether this value is empty, i.e. hasn't been set to anything
|
|
FORCEINLINE bool IsEmpty() const
|
|
{
|
|
return Type == ESUDSValueType::Empty;
|
|
}
|
|
|
|
FORCEINLINE ESUDSValueType GetType() const
|
|
{
|
|
return Type;
|
|
}
|
|
|
|
FORCEINLINE int32 GetIntValue() const
|
|
{
|
|
// We don't warn for unset variables / uninitialised values, use the defaults
|
|
if (!IsEmpty() && Type != ESUDSValueType::Int && Type != ESUDSValueType::Variable)
|
|
UE_LOG(LogSUDS, Warning, TEXT("Getting value as int but was type %s"), *StaticEnum<ESUDSValueType>()->GetValueAsString(Type))
|
|
|
|
return IntValue;
|
|
}
|
|
|
|
FORCEINLINE float GetFloatValue() const
|
|
{
|
|
// We don't warn for unset variables, use the defaults
|
|
if (!IsEmpty() && !IsNumeric() && Type != ESUDSValueType::Variable)
|
|
UE_LOG(LogSUDS, Warning, TEXT("Getting value as float but was type %s"), *StaticEnum<ESUDSValueType>()->GetValueAsString(Type))
|
|
|
|
if (Type == ESUDSValueType::Int)
|
|
{
|
|
// Allow int widening to float
|
|
return GetIntValue();
|
|
}
|
|
return FloatValue;
|
|
}
|
|
|
|
FORCEINLINE const FText& GetTextValue() const
|
|
{
|
|
// We don't warn for unset variables, use the defaults
|
|
if (!IsEmpty() && Type != ESUDSValueType::Text && Type != ESUDSValueType::Variable)
|
|
UE_LOG(LogSUDS, Warning, TEXT("Getting value as text but was type %s"), *StaticEnum<ESUDSValueType>()->GetValueAsString(Type))
|
|
|
|
if (TextValue.IsSet())
|
|
return TextValue.GetValue();
|
|
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
FORCEINLINE ETextGender GetGenderValue() const
|
|
{
|
|
// We don't warn for unset variables, use the defaults
|
|
if (!IsEmpty() && Type != ESUDSValueType::Gender && Type != ESUDSValueType::Variable)
|
|
UE_LOG(LogSUDS, Warning, TEXT("Getting value as float but was type %s"), *StaticEnum<ESUDSValueType>()->GetValueAsString(Type))
|
|
|
|
return static_cast<ETextGender>(IntValue);
|
|
}
|
|
|
|
FORCEINLINE bool GetBooleanValue() const
|
|
{
|
|
// We don't warn for unset variables, use the defaults
|
|
if (!IsEmpty() && Type != ESUDSValueType::Boolean && Type != ESUDSValueType::Variable)
|
|
UE_LOG(LogSUDS, Warning, TEXT("Getting value as boolean but was type %s"), *StaticEnum<ESUDSValueType>()->GetValueAsString(Type))
|
|
|
|
return IntValue != 0;
|
|
}
|
|
|
|
FORCEINLINE FName GetNameValue() const
|
|
{
|
|
// We don't warn for unset variables, use the defaults
|
|
if (!IsEmpty() && Type != ESUDSValueType::Name && Type != ESUDSValueType::Variable)
|
|
UE_LOG(LogSUDS, Warning, TEXT("Getting value as Name but was type %s"), *StaticEnum<ESUDSValueType>()->GetValueAsString(Type))
|
|
|
|
if (Name.IsSet())
|
|
return Name.GetValue();
|
|
|
|
return NAME_None;
|
|
}
|
|
|
|
FORCEINLINE FName GetVariableNameValue() const
|
|
{
|
|
if (!IsEmpty() && Type != ESUDSValueType::Variable)
|
|
UE_LOG(LogSUDS, Warning, TEXT("Getting value as variable name but was type %s"), *StaticEnum<ESUDSValueType>()->GetValueAsString(Type))
|
|
|
|
if (Name.IsSet())
|
|
return Name.GetValue();
|
|
|
|
return NAME_None;
|
|
}
|
|
|
|
FORCEINLINE bool IsVariable() const
|
|
{
|
|
return Type == ESUDSValueType::Variable;
|
|
}
|
|
|
|
bool IsNumeric() const
|
|
{
|
|
return Type == ESUDSValueType::Float || Type == ESUDSValueType::Int;
|
|
}
|
|
|
|
FFormatArgumentValue ToFormatArg() const
|
|
{
|
|
switch (Type)
|
|
{
|
|
default:
|
|
case ESUDSValueType::Text:
|
|
return FFormatArgumentValue(TextValue.GetValue());
|
|
case ESUDSValueType::Int:
|
|
return FFormatArgumentValue(GetIntValue());
|
|
case ESUDSValueType::Boolean:
|
|
return FFormatArgumentValue(GetBooleanValue());
|
|
case ESUDSValueType::Gender:
|
|
return FFormatArgumentValue(GetGenderValue());
|
|
case ESUDSValueType::Float:
|
|
return FFormatArgumentValue(GetFloatValue());
|
|
case ESUDSValueType::Name:
|
|
// return something useful from FName even though technically shouldn't use in visible text
|
|
return FFormatArgumentValue(FText::FromName(GetNameValue()));
|
|
case ESUDSValueType::Empty:
|
|
case ESUDSValueType::Variable:
|
|
return FFormatArgumentValue();
|
|
}
|
|
}
|
|
|
|
void SetValue(const FSUDSValue& RValue)
|
|
{
|
|
*this = RValue;
|
|
}
|
|
|
|
/// Not operation, technically only valid on booleans, but not enforced so as to allow unset vars & conversions
|
|
FSUDSValue operator!() const
|
|
{
|
|
return FSUDSValue(!GetBooleanValue());
|
|
}
|
|
|
|
FSUDSValue operator*(const FSUDSValue& Rhs) const
|
|
{
|
|
// We don't force types to be numeric here so that we can safely use unset variables
|
|
// If both int, keep int
|
|
if (Type == ESUDSValueType::Int && Rhs.Type == Type)
|
|
{
|
|
return FSUDSValue(GetIntValue() * Rhs.GetIntValue());
|
|
}
|
|
// Otherwise we'll let the type widening handle mixed types for us (ternary will always resolve to float)
|
|
return FSUDSValue(
|
|
(Type == ESUDSValueType::Float ? GetFloatValue() : GetIntValue())
|
|
*
|
|
(Rhs.GetType() == ESUDSValueType::Float ? Rhs.GetFloatValue() : Rhs.GetIntValue()));
|
|
}
|
|
FSUDSValue operator/(const FSUDSValue& Rhs) const
|
|
{
|
|
// We don't force types to be numeric here so that we can safely use unset variables
|
|
// If both int, keep int
|
|
if (Type == ESUDSValueType::Int && Rhs.Type == Type)
|
|
{
|
|
return FSUDSValue(GetIntValue() / Rhs.GetIntValue());
|
|
}
|
|
// Otherwise we'll let the type widening handle mixed types for us (ternary will always resolve to float)
|
|
return FSUDSValue(
|
|
(Type == ESUDSValueType::Float ? GetFloatValue() : GetIntValue())
|
|
/
|
|
(Rhs.GetType() == ESUDSValueType::Float ? Rhs.GetFloatValue() : Rhs.GetIntValue()));
|
|
}
|
|
FSUDSValue operator%(const FSUDSValue& Rhs) const
|
|
{
|
|
// We don't force types to be numeric here so that we can safely use unset variables
|
|
// If both int, keep int
|
|
if (Type == ESUDSValueType::Int && Rhs.Type == Type)
|
|
{
|
|
return FSUDSValue(GetIntValue() % Rhs.GetIntValue());
|
|
}
|
|
// Otherwise we'll let the type widening handle mixed types for us (ternary will always resolve to float)
|
|
float lval = (Type == ESUDSValueType::Float ? GetFloatValue() : GetIntValue());
|
|
float rval = (Rhs.GetType() == ESUDSValueType::Float ? Rhs.GetFloatValue() : Rhs.GetIntValue());
|
|
// Prevent NaN and errors reported when executing FMath::Fmod with invalid values.
|
|
return FSUDSValue(rval != 0 ? FMath::Fmod(lval, rval) : 0.0f);
|
|
}
|
|
FSUDSValue operator+(const FSUDSValue& Rhs) const
|
|
{
|
|
// We don't force types to be numeric here so that we can safely use unset variables
|
|
// If both int, keep int
|
|
if (Type == ESUDSValueType::Int && Rhs.Type == Type)
|
|
{
|
|
return FSUDSValue(GetIntValue() + Rhs.GetIntValue());
|
|
}
|
|
// Otherwise we'll let the type widening handle mixed types for us (ternary will always resolve to float)
|
|
return FSUDSValue(
|
|
(Type == ESUDSValueType::Float ? GetFloatValue() : GetIntValue())
|
|
+
|
|
(Rhs.GetType() == ESUDSValueType::Float ? Rhs.GetFloatValue() : Rhs.GetIntValue()));
|
|
}
|
|
FSUDSValue operator-(const FSUDSValue& Rhs) const
|
|
{
|
|
// We don't force types to be numeric here so that we can safely use unset variables
|
|
// If both int, keep int
|
|
if (Type == ESUDSValueType::Int && Rhs.Type == Type)
|
|
{
|
|
return FSUDSValue(GetIntValue() - Rhs.GetIntValue());
|
|
}
|
|
// Otherwise we'll let the type widening handle mixed types for us (ternary will always resolve to float)
|
|
return FSUDSValue(
|
|
(Type == ESUDSValueType::Float ? GetFloatValue() : GetIntValue())
|
|
-
|
|
(Rhs.GetType() == ESUDSValueType::Float ? Rhs.GetFloatValue() : Rhs.GetIntValue()));
|
|
}
|
|
FSUDSValue operator<(const FSUDSValue& Rhs) const
|
|
{
|
|
// Don't check types here. We'll fall back on int comparisons which is important for cases where
|
|
// a variable hasn't been set
|
|
// result is boolean so no need to protect types
|
|
return FSUDSValue(
|
|
(Type == ESUDSValueType::Float ? GetFloatValue() : GetIntValue())
|
|
<
|
|
(Rhs.GetType() == ESUDSValueType::Float ? Rhs.GetFloatValue() : Rhs.GetIntValue()));
|
|
|
|
}
|
|
FSUDSValue operator==(const FSUDSValue& Rhs) const
|
|
{
|
|
if (IsNumeric() || Rhs.IsNumeric())
|
|
{
|
|
if (GetType() == ESUDSValueType::Float || Rhs.GetType() == ESUDSValueType::Float)
|
|
{
|
|
// For floats, use tolerance
|
|
return FSUDSValue(FMath::IsNearlyEqual(
|
|
GetType() == ESUDSValueType::Int ? (float)GetIntValue() : GetFloatValue(),
|
|
Rhs.GetType() == ESUDSValueType::Int ? (float)Rhs.GetIntValue() : Rhs.GetFloatValue()));
|
|
}
|
|
else
|
|
{
|
|
return FSUDSValue(GetIntValue() == Rhs.GetIntValue());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no auto conversion here
|
|
// however, tolerate unresolved variables, they will return initial values
|
|
if (Type != Rhs.Type && Type != ESUDSValueType::Variable && Rhs.Type != ESUDSValueType::Variable)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Compare using type from whichever one isn't a variable (get values on unset variables will return defaults)
|
|
ESUDSValueType UseType = IsVariable() ? Rhs.GetType() : GetType();
|
|
switch (UseType)
|
|
{
|
|
case ESUDSValueType::Text:
|
|
return FSUDSValue(GetTextValue().EqualTo(Rhs.GetTextValue()));
|
|
case ESUDSValueType::Boolean:
|
|
return FSUDSValue(GetBooleanValue() == Rhs.GetBooleanValue());
|
|
case ESUDSValueType::Gender:
|
|
return FSUDSValue(GetGenderValue() == Rhs.GetGenderValue());
|
|
case ESUDSValueType::Variable:
|
|
return FSUDSValue(GetVariableNameValue() == Rhs.GetVariableNameValue());
|
|
case ESUDSValueType::Name:
|
|
return FSUDSValue(GetNameValue() == Rhs.GetNameValue());
|
|
// deal with int/float again here, this mops up cases where one side is an unset variable
|
|
case ESUDSValueType::Int:
|
|
return FSUDSValue(GetIntValue() == Rhs.GetIntValue());
|
|
case ESUDSValueType::Float:
|
|
return FSUDSValue(GetFloatValue() == Rhs.GetFloatValue());
|
|
default:
|
|
break;
|
|
};
|
|
}
|
|
return FSUDSValue(false);
|
|
}
|
|
FSUDSValue operator<=(const FSUDSValue& Rhs) const
|
|
{
|
|
if ((*this < Rhs).GetBooleanValue())
|
|
return FSUDSValue(true);
|
|
return (*this == Rhs);
|
|
}
|
|
|
|
FSUDSValue operator>(const FSUDSValue& Rhs) const
|
|
{
|
|
return Rhs < *this;
|
|
}
|
|
|
|
FSUDSValue operator>=(const FSUDSValue& Rhs) const
|
|
{
|
|
return Rhs <= *this;
|
|
}
|
|
|
|
FSUDSValue operator!=(const FSUDSValue& Rhs) const
|
|
{
|
|
return !(*this == Rhs);
|
|
}
|
|
|
|
FSUDSValue operator&&(const FSUDSValue& Rhs) const
|
|
{
|
|
// We always let unset variables degrade to false
|
|
check(Type == ESUDSValueType::Boolean || Type == ESUDSValueType::Variable);
|
|
check(Rhs.Type == ESUDSValueType::Boolean || Rhs.Type == ESUDSValueType::Variable);
|
|
return FSUDSValue(GetBooleanValue() && Rhs.GetBooleanValue());
|
|
}
|
|
|
|
FSUDSValue operator||(const FSUDSValue& Rhs) const
|
|
{
|
|
// We always let unset variables degrade to false
|
|
check(Type == ESUDSValueType::Boolean || Type == ESUDSValueType::Variable);
|
|
check(Rhs.Type == ESUDSValueType::Boolean || Rhs.Type == ESUDSValueType::Variable);
|
|
return FSUDSValue(GetBooleanValue() || Rhs.GetBooleanValue());
|
|
}
|
|
|
|
SUDS_API friend FArchive& operator<<(FArchive& Ar, FSUDSValue& Value);
|
|
SUDS_API friend void operator<<(FStructuredArchive::FSlot Slot, FSUDSValue& Value);
|
|
bool Serialize(FArchive& Ar)
|
|
{
|
|
Ar << *this;
|
|
return true;
|
|
}
|
|
bool Serialize(FStructuredArchive::FSlot Slot)
|
|
{
|
|
Slot << *this;
|
|
return true;
|
|
}
|
|
|
|
FString ToString() const;
|
|
|
|
bool ExportTextItem(FString& ValueStr, FSUDSValue const& DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope) const;
|
|
};
|
|
template<>
|
|
struct TStructOpsTypeTraits<FSUDSValue> : public TStructOpsTypeTraitsBase2<FSUDSValue>
|
|
{
|
|
enum
|
|
{
|
|
WithSerializer = true,
|
|
WithExportTextItem = true
|
|
};
|
|
};
|
|
|
|
|
|
|