Files
ProjectEleri/Plugins/SUDS/Source/SUDS/Public/SUDSValue.h

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
};
};