548 lines
18 KiB
C++
548 lines
18 KiB
C++
#ifndef IPROPERTY_H
|
|
#define IPROPERTY_H
|
|
|
|
#include <Common/Common.h>
|
|
#include <Common/CFourCC.h>
|
|
#include <Common/Math/CVector3f.h>
|
|
#include <Common/Math/MathUtil.h>
|
|
|
|
#include <memory>
|
|
|
|
/** Forward declares */
|
|
class CGameTemplate;
|
|
class CScriptTemplate;
|
|
class CStructProperty;
|
|
|
|
/** Typedefs */
|
|
typedef TString TIDString;
|
|
|
|
/** Property flags */
|
|
enum class EPropertyFlag : uint32
|
|
{
|
|
/** Property has been fully initialized and has had PostLoad called */
|
|
IsInitialized = 0x1,
|
|
/** Property is an archetype (a template for other properties to copy from) */
|
|
IsArchetype = 0x2,
|
|
/** Property is an array archetype (a template for elements of an array property) */
|
|
IsArrayArchetype = 0x4,
|
|
/** This property and all its children are a single unit and do not have individual property IDs, sizes, etc. */
|
|
IsAtomic = 0x8,
|
|
/** This is a property of a C++ class, not a script object */
|
|
IsIntrinsic = 0x10,
|
|
/** Property has been modified, and needs to be resaved. Only valid on archetypes */
|
|
IsDirty = 0x20,
|
|
/** We have cached whether the property name is correct */
|
|
HasCachedNameCheck = 0x40000000,
|
|
/** The name of the property is a match for the property ID hash */
|
|
HasCorrectPropertyName = 0x80000000,
|
|
|
|
/** Flags that are left intact when copying from an archetype */
|
|
ArchetypeCopyFlags = EPropertyFlag::IsAtomic,
|
|
/** Flags that are inheritable from parent */
|
|
InheritableFlags = EPropertyFlag::IsArchetype | EPropertyFlag::IsArrayArchetype | EPropertyFlag::IsAtomic,
|
|
};
|
|
DECLARE_FLAGS_ENUMCLASS(EPropertyFlag, FPropertyFlags)
|
|
|
|
/** Property type */
|
|
enum class EPropertyType
|
|
{
|
|
Bool = FOURCC('BOOL'),
|
|
Byte = FOURCC('BYTE'),
|
|
Short = FOURCC('SHRT'),
|
|
Int = FOURCC('INT '),
|
|
Float = FOURCC('REAL'),
|
|
Choice = FOURCC('CHOI'),
|
|
Enum = FOURCC('ENUM'),
|
|
Flags = FOURCC('FLAG'),
|
|
String = FOURCC('STRG'),
|
|
Vector = FOURCC('VECT'),
|
|
Color = FOURCC('COLR'),
|
|
Asset = FOURCC('ASST'),
|
|
Sound = FOURCC('SOND'),
|
|
Animation = FOURCC('ANIM'),
|
|
AnimationSet = FOURCC('ANMS'),
|
|
Sequence = FOURCC('SQNC'),
|
|
Spline = FOURCC('SPLN'),
|
|
Guid = FOURCC('GUID'),
|
|
Pointer = FOURCC('PNTR'),
|
|
Struct = FOURCC('STRC'),
|
|
Array = FOURCC('ARRY'),
|
|
Invalid = FOURCC('INVD')
|
|
};
|
|
|
|
inline const char* PropEnumToHashableTypeName(EPropertyType Type)
|
|
{
|
|
switch (Type)
|
|
{
|
|
// these names are required to generate accurate property ID hashes
|
|
case EPropertyType::Bool: return "bool";
|
|
case EPropertyType::Int: return "int";
|
|
case EPropertyType::Float: return "float";
|
|
case EPropertyType::Choice: return "choice";
|
|
case EPropertyType::Enum: return "enum";
|
|
case EPropertyType::Flags: return "Flags";
|
|
case EPropertyType::String: return "string";
|
|
case EPropertyType::Vector: return "Vector";
|
|
case EPropertyType::Color: return "Color";
|
|
case EPropertyType::Asset: return "asset";
|
|
case EPropertyType::Sound: return "sound";
|
|
case EPropertyType::Spline: return "spline";
|
|
case EPropertyType::Guid: return "guid";
|
|
// unknown hashable types - used in hashes but these names are inaccurate
|
|
case EPropertyType::Animation: return "animation";
|
|
case EPropertyType::Sequence: return "sequence";
|
|
// non hashable types - not used in ID hashes but still displayed on the UI
|
|
case EPropertyType::Byte: return "byte";
|
|
case EPropertyType::Short: return "short";
|
|
case EPropertyType::Array: return "array";
|
|
// fallback
|
|
default: return "";
|
|
}
|
|
}
|
|
|
|
/** Enum that describes when/how properties should be cooked out */
|
|
enum class ECookPreference
|
|
{
|
|
Default,
|
|
Always,
|
|
Never
|
|
};
|
|
|
|
/** New property class */
|
|
class IProperty
|
|
{
|
|
protected:
|
|
/** Flags */
|
|
FPropertyFlags mFlags;
|
|
|
|
/** Parent property */
|
|
IProperty* mpParent;
|
|
|
|
/** Pointer parent; if non-null, this parent needs to be dereferenced to access the correct
|
|
* memory region that our property data is stored in */
|
|
IProperty* mpPointerParent;
|
|
|
|
/** Archetype property; source property that we copied metadata from */
|
|
IProperty* mpArchetype;
|
|
|
|
/** Sub-instances of archetype properties. For non-archetypes, will be empty.
|
|
* @todo this really oughta be a linked list */
|
|
std::vector<IProperty*> mSubInstances;
|
|
|
|
/** Child properties; these appear underneath this property on the UI */
|
|
std::vector<IProperty*> mChildren;
|
|
|
|
/** Game this property belongs to */
|
|
EGame mGame;
|
|
|
|
/** Script template that this property belongs to. Null for struct/enum/flag archetypes. */
|
|
CScriptTemplate* mpScriptTemplate;
|
|
|
|
/** Offset of this property within the property block */
|
|
uint32 mOffset;
|
|
|
|
/** Property ID. This ID is used to uniquely identify this property within this struct. */
|
|
uint32 mID;
|
|
|
|
/** Property metadata */
|
|
TString mName;
|
|
TString mDescription;
|
|
TString mSuffix;
|
|
ECookPreference mCookPreference;
|
|
|
|
/** Min/max allowed version number. These numbers correspond to the game's internal build number.
|
|
* This is not used yet but in the future it can be used to configure certain properties to only
|
|
* show up when certain versions of the game are being edited. The default values allow the
|
|
* property to show up in all versions. */
|
|
float mMinVersion;
|
|
float mMaxVersion;
|
|
|
|
/** Private constructor - use static methods to instantiate */
|
|
IProperty(EGame Game);
|
|
void _ClearChildren();
|
|
|
|
public:
|
|
virtual ~IProperty();
|
|
|
|
/** Interface */
|
|
virtual EPropertyType Type() const = 0;
|
|
virtual uint32 DataSize() const = 0;
|
|
virtual uint32 DataAlignment() const = 0;
|
|
virtual void Construct(void* pData) const = 0;
|
|
virtual void Destruct(void* pData) const = 0;
|
|
virtual bool MatchesDefault(void* pData) const = 0;
|
|
virtual void RevertToDefault(void* pData) const = 0;
|
|
virtual void SerializeValue(void* pData, IArchive& Arc) const = 0;
|
|
|
|
virtual void PostInitialize() {}
|
|
virtual void PropertyValueChanged(void* pPropertyData) {}
|
|
virtual void CopyDefaultValueTo(IProperty* pOtherProperty) {}
|
|
virtual void SetDefaultFromData(void* pData) {}
|
|
virtual bool IsNumericalType() const { return false; }
|
|
virtual bool IsPointerType() const { return false; }
|
|
virtual TString ValueAsString(void* pData) const { return ""; }
|
|
virtual const char* HashableTypeName() const;
|
|
virtual void* GetChildDataPointer(void* pPropertyData) const;
|
|
virtual void Serialize(IArchive& rArc);
|
|
virtual void InitFromArchetype(IProperty* pOther);
|
|
virtual bool ShouldSerialize() const;
|
|
|
|
/** Utility methods */
|
|
void Initialize(IProperty* pInParent, CScriptTemplate* pInTemplate, uint32 InOffset);
|
|
void* RawValuePtr(void* pData) const;
|
|
IProperty* ChildByID(uint32 ID) const;
|
|
IProperty* ChildByIDString(const TIDString& rkIdString);
|
|
void GatherAllSubInstances(std::list<IProperty*>& OutList, bool Recursive);
|
|
TString GetTemplateFileName();
|
|
bool ShouldCook(void* pPropertyData) const;
|
|
void SetName(const TString& rkNewName);
|
|
void SetDescription(const TString& rkNewDescription);
|
|
void SetSuffix(const TString& rkNewSuffix);
|
|
void MarkDirty();
|
|
void ClearDirtyFlag();
|
|
bool ConvertType(EPropertyType NewType, IProperty* pNewArchetype = nullptr);
|
|
bool UsesNameMap();
|
|
bool HasAccurateName();
|
|
|
|
/** Accessors */
|
|
EGame Game() const;
|
|
inline ECookPreference CookPreference() const;
|
|
inline uint32 NumChildren() const;
|
|
inline IProperty* ChildByIndex(uint32 ChildIndex) const;
|
|
inline IProperty* Parent() const;
|
|
inline IProperty* RootParent();
|
|
inline IProperty* Archetype() const;
|
|
inline IProperty* RootArchetype();
|
|
inline CScriptTemplate* ScriptTemplate() const;
|
|
inline TString Name() const;
|
|
inline TString Description() const;
|
|
inline TString Suffix() const;
|
|
inline TIDString IDString(bool FullyQualified) const;
|
|
inline uint32 Offset() const;
|
|
inline uint32 ID() const;
|
|
|
|
inline bool IsInitialized() const { return mFlags.HasFlag(EPropertyFlag::IsInitialized); }
|
|
inline bool IsArchetype() const { return mFlags.HasFlag(EPropertyFlag::IsArchetype); }
|
|
inline bool IsArrayArchetype() const { return mFlags.HasFlag(EPropertyFlag::IsArrayArchetype); }
|
|
inline bool IsAtomic() const { return mFlags.HasFlag(EPropertyFlag::IsAtomic); }
|
|
inline bool IsIntrinsic() const { return mFlags.HasFlag(EPropertyFlag::IsIntrinsic); }
|
|
inline bool IsDirty() const { return mFlags.HasFlag(EPropertyFlag::IsDirty); }
|
|
inline bool IsRootParent() const { return mpParent == nullptr; }
|
|
inline bool IsRootArchetype() const { return mpArchetype == nullptr; }
|
|
|
|
/** Create */
|
|
static IProperty* Create(EPropertyType Type,
|
|
EGame Game);
|
|
|
|
static IProperty* CreateCopy(IProperty* pArchetype);
|
|
|
|
static IProperty* CreateIntrinsic(EPropertyType Type,
|
|
EGame Game,
|
|
uint32 Offset,
|
|
const TString& rkName);
|
|
|
|
static IProperty* CreateIntrinsic(EPropertyType Type,
|
|
IProperty* pParent,
|
|
uint32 Offset,
|
|
const TString& rkName);
|
|
|
|
static IProperty* ArchiveConstructor(EPropertyType Type,
|
|
const IArchive& Arc);
|
|
};
|
|
|
|
inline ECookPreference IProperty::CookPreference() const
|
|
{
|
|
return mCookPreference;
|
|
}
|
|
|
|
inline uint32 IProperty::NumChildren() const
|
|
{
|
|
return mChildren.size();
|
|
}
|
|
|
|
inline IProperty* IProperty::ChildByIndex(uint32 ChildIndex) const
|
|
{
|
|
ASSERT(ChildIndex >= 0 && ChildIndex < mChildren.size());
|
|
return mChildren[ChildIndex];
|
|
}
|
|
|
|
inline IProperty* IProperty::Parent() const
|
|
{
|
|
return mpParent;
|
|
}
|
|
|
|
inline IProperty* IProperty::RootParent()
|
|
{
|
|
IProperty* pParent = Parent();
|
|
IProperty* pOut = this;
|
|
|
|
while (pParent)
|
|
{
|
|
pOut = pParent;
|
|
pParent = pParent->Parent();
|
|
}
|
|
|
|
return pOut;
|
|
}
|
|
|
|
inline IProperty* IProperty::Archetype() const
|
|
{
|
|
return mpArchetype;
|
|
}
|
|
|
|
inline IProperty* IProperty::RootArchetype()
|
|
{
|
|
IProperty* pArchetype = Archetype();
|
|
IProperty* pOut = this;
|
|
|
|
while (pArchetype)
|
|
{
|
|
pOut = pArchetype;
|
|
pArchetype = pArchetype->Archetype();
|
|
}
|
|
|
|
return pOut;
|
|
}
|
|
|
|
inline CScriptTemplate* IProperty::ScriptTemplate() const
|
|
{
|
|
return mpScriptTemplate;
|
|
}
|
|
|
|
inline TString IProperty::Name() const
|
|
{
|
|
return mName;
|
|
}
|
|
|
|
inline TString IProperty::Description() const
|
|
{
|
|
return mDescription;
|
|
}
|
|
|
|
inline TString IProperty::Suffix() const
|
|
{
|
|
return mSuffix;
|
|
}
|
|
|
|
inline TString IProperty::IDString(bool FullyQualified) const
|
|
{
|
|
if (FullyQualified && mpParent != nullptr && mpParent->Parent() != nullptr)
|
|
return mpParent->IDString(FullyQualified) + ":" + TString::HexString(mID);
|
|
else
|
|
return TString::HexString(mID);
|
|
}
|
|
|
|
inline uint32 IProperty::Offset() const
|
|
{
|
|
return mOffset;
|
|
}
|
|
|
|
inline uint32 IProperty::ID() const
|
|
{
|
|
return mID;
|
|
}
|
|
|
|
template<typename PropType, EPropertyType PropEnum>
|
|
class TTypedProperty : public IProperty
|
|
{
|
|
friend class IProperty;
|
|
friend class CTemplateLoader;
|
|
public:
|
|
typedef PropType ValueType;
|
|
|
|
protected:
|
|
PropType mDefaultValue;
|
|
|
|
TTypedProperty(EGame Game)
|
|
: IProperty(Game)
|
|
{
|
|
memset(&mDefaultValue, 0, sizeof(PropType));
|
|
}
|
|
|
|
public:
|
|
virtual EPropertyType Type() const { return PropEnum; }
|
|
virtual uint32 DataSize() const { return sizeof(PropType); }
|
|
virtual uint32 DataAlignment() const { return alignof(PropType); }
|
|
virtual void Construct(void* pData) const { new(ValuePtr(pData)) PropType(mDefaultValue); }
|
|
virtual void Destruct(void* pData) const { ValueRef(pData).~PropType(); }
|
|
virtual bool MatchesDefault(void* pData) const { return ValueRef(pData) == mDefaultValue; }
|
|
virtual void RevertToDefault(void* pData) const { ValueRef(pData) = mDefaultValue; }
|
|
virtual void SetDefaultFromData(void* pData) { mDefaultValue = ValueRef(pData); MarkDirty(); }
|
|
|
|
virtual bool CanHaveDefault() const { return true; }
|
|
|
|
virtual void InitFromArchetype(IProperty* pOther)
|
|
{
|
|
IProperty::InitFromArchetype(pOther);
|
|
mDefaultValue = static_cast<TTypedProperty*>(pOther)->mDefaultValue;
|
|
}
|
|
|
|
virtual void CopyDefaultValueTo(IProperty* pOtherProperty)
|
|
{
|
|
// WARNING: We don't do any type checking here because this function is used for type conversion,
|
|
// which necessitates that the property class is allowed to be different. The underlying type is
|
|
// assumed to be the same. It is the caller's responsibility to ensure this function is not called
|
|
// with incompatible property types.
|
|
TTypedProperty* pTypedOther = static_cast<TTypedProperty*>(pOtherProperty);
|
|
pTypedOther->mDefaultValue = mDefaultValue;
|
|
}
|
|
|
|
inline PropType* ValuePtr(void* pData) const
|
|
{
|
|
return (PropType*) RawValuePtr(pData);
|
|
}
|
|
|
|
inline PropType& ValueRef(void* pData) const
|
|
{
|
|
return *ValuePtr(pData);
|
|
}
|
|
|
|
inline PropType Value(void* pData) const
|
|
{
|
|
return *ValuePtr(pData);
|
|
}
|
|
|
|
inline const PropType& DefaultValue() const
|
|
{
|
|
return mDefaultValue;
|
|
}
|
|
|
|
inline void SetDefaultValue(const PropType& kInDefaultValue)
|
|
{
|
|
mDefaultValue = kInDefaultValue;
|
|
}
|
|
|
|
inline static EPropertyType StaticType() { return PropEnum; }
|
|
};
|
|
|
|
template<typename PropType, EPropertyType PropEnum>
|
|
class TSerializeableTypedProperty : public TTypedProperty<PropType, PropEnum>
|
|
{
|
|
protected:
|
|
TSerializeableTypedProperty(EGame Game)
|
|
: TTypedProperty(Game)
|
|
{}
|
|
|
|
public:
|
|
virtual void Serialize(IArchive& rArc)
|
|
{
|
|
TTypedProperty::Serialize(rArc);
|
|
TSerializeableTypedProperty* pArchetype = static_cast<TSerializeableTypedProperty*>(mpArchetype);
|
|
|
|
// Determine if default value should be serialized as optional.
|
|
// All MP1 properties should be optional. For MP2 and on, we set optional
|
|
// on property types that don't have default values in the game executable.
|
|
bool MakeOptional = false;
|
|
|
|
if (Game() <= EGame::Prime || pArchetype != nullptr)
|
|
{
|
|
MakeOptional = true;
|
|
}
|
|
else
|
|
{
|
|
switch (Type())
|
|
{
|
|
case EPropertyType::String:
|
|
case EPropertyType::Asset:
|
|
case EPropertyType::Animation:
|
|
case EPropertyType::AnimationSet:
|
|
case EPropertyType::Sequence:
|
|
case EPropertyType::Spline:
|
|
case EPropertyType::Guid:
|
|
MakeOptional = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Branch here to avoid constructing a default value if we don't need to.
|
|
if (MakeOptional)
|
|
rArc << SerialParameter("DefaultValue", mDefaultValue, SH_Optional, pArchetype ? pArchetype->mDefaultValue : GetSerializationDefaultValue());
|
|
else
|
|
rArc << SerialParameter("DefaultValue", mDefaultValue);
|
|
}
|
|
|
|
virtual bool ShouldSerialize() const
|
|
{
|
|
TTypedProperty* pArchetype = static_cast<TTypedProperty*>(mpArchetype);
|
|
|
|
return TTypedProperty::ShouldSerialize() ||
|
|
!(mDefaultValue == pArchetype->DefaultValue());
|
|
}
|
|
|
|
/** Return default value for serialization - can be customized per type */
|
|
virtual PropType GetSerializationDefaultValue()
|
|
{
|
|
return PropType();
|
|
}
|
|
};
|
|
|
|
template<typename PropType, EPropertyType PropEnum>
|
|
class TNumericalProperty : public TSerializeableTypedProperty<PropType, PropEnum>
|
|
{
|
|
friend class IProperty;
|
|
friend class CTemplateLoader;
|
|
|
|
protected:
|
|
PropType mMinValue;
|
|
PropType mMaxValue;
|
|
|
|
TNumericalProperty(EGame Game)
|
|
: TSerializeableTypedProperty(Game)
|
|
, mMinValue( -1 )
|
|
, mMaxValue( -1 )
|
|
{}
|
|
|
|
public:
|
|
virtual void Serialize(IArchive& rArc)
|
|
{
|
|
TSerializeableTypedProperty::Serialize(rArc);
|
|
TNumericalProperty* pArchetype = static_cast<TNumericalProperty*>(mpArchetype);
|
|
|
|
rArc << SerialParameter("Min", mMinValue, SH_Optional, pArchetype ? pArchetype->mMinValue : (PropType) -1)
|
|
<< SerialParameter("Max", mMaxValue, SH_Optional, pArchetype ? pArchetype->mMaxValue : (PropType) -1);
|
|
}
|
|
|
|
virtual bool ShouldSerialize() const
|
|
{
|
|
TNumericalProperty* pArchetype = static_cast<TNumericalProperty*>(mpArchetype);
|
|
return TSerializeableTypedProperty::ShouldSerialize() ||
|
|
mMinValue != pArchetype->mMinValue ||
|
|
mMaxValue != pArchetype->mMaxValue;
|
|
}
|
|
|
|
virtual void InitFromArchetype(IProperty* pOther)
|
|
{
|
|
TSerializeableTypedProperty::InitFromArchetype(pOther);
|
|
TNumericalProperty* pCastOther = static_cast<TNumericalProperty*>(pOther);
|
|
mMinValue = pCastOther->mMinValue;
|
|
mMaxValue = pCastOther->mMaxValue;
|
|
}
|
|
|
|
virtual void PropertyValueChanged(void* pPropertyData)
|
|
{
|
|
TSerializeableTypedProperty::PropertyValueChanged(pPropertyData);
|
|
|
|
if (mMinValue >= 0 && mMaxValue >= 0)
|
|
{
|
|
PropType& rValue = ValueRef(pPropertyData);
|
|
rValue = Math::Clamp(mMinValue, mMaxValue, rValue);
|
|
}
|
|
}
|
|
};
|
|
|
|
/** Property casting with dynamic type checking */
|
|
template<class PropertyClass>
|
|
inline PropertyClass* TPropCast(IProperty* pProperty)
|
|
{
|
|
if (pProperty && pProperty->Type() == PropertyClass::StaticType())
|
|
{
|
|
return static_cast<PropertyClass*>(pProperty);
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
#endif // IPROPERTY_H
|