More fixes. Property/serialization changes are finally basically finished now. Serialized property templates now load & display correctly in the editor

This commit is contained in:
Aruki 2018-09-22 03:26:15 -06:00
parent 33e915a638
commit 2118bbd0cd
32 changed files with 405 additions and 272 deletions

View File

@ -97,8 +97,13 @@ template<typename T> struct TIsContainer< std::set<T> > : std::true_type {};
template<typename T, typename V> struct TIsContainer< std::map<T,V> > : std::true_type {};
template<typename T, typename V> struct TIsContainer< std::unordered_map<T,V> > : std::true_type {};
/** Class that determines if the type is a smart pointer */
template<typename> struct TIsSmartPointer : std::false_type {};
template<typename T> struct TIsSmartPointer< std::shared_ptr<T> > : std::true_type {};
template<typename T> struct TIsSmartPointer< std::unique_ptr<T> > : std::true_type {};
/** Helper macro that tells us whether the parameter supports default property values */
#define SUPPORTS_DEFAULT_VALUES (!std::is_pointer_v<ValType> && std::is_copy_assignable_v<ValType> && THasEqualTo<ValType>::value && !TIsContainer<ValType>::value)
#define SUPPORTS_DEFAULT_VALUES (!std::is_pointer_v<ValType> && std::is_copy_assignable_v<ValType> && THasEqualTo<ValType>::value && !TIsContainer<ValType>::value && !TIsSmartPointer<ValType>::value)
/** TSerialParameter - name/value pair for generic serial parameters */
template<typename ValType>
@ -241,14 +246,26 @@ struct SerialType
None);
};
/** Helper for determining the type used by a given abstract object class (i.e. the type returned by the Type() function) */
#define ABSTRACT_TYPE decltype( std::declval<ValType>().Type() )
/** For abstract types, determine what kind of ArchiveConstructor the type has */
template<typename ValType, class ArchiveType>
struct ArchiveConstructorType
{
typedef ABSTRACT_TYPE ObjType;
/** Figure out the type being used to represent the object type.
* If there isn't a type function, then it doesn't matter; just substitute int.
*/
template<typename T>
static constexpr auto HasTypeMethod(int) -> decltype( std::declval<T>().Type() )
{
return std::declval<T>().Type();
}
template<typename T>
static constexpr int HasTypeMethod(...)
{
return 0;
}
using ObjType = decltype(HasTypeMethod<ValType>(0));
enum { Basic, Advanced, None };
@ -340,7 +357,7 @@ public:
mParmStack.reserve(16);
}
virtual ~IArchive() {}
virtual ~IArchive() { ASSERT(mParmStack.empty()); }
// Serialize archive version. Always call after opening a file.
void SerializeVersion()

View File

@ -266,7 +266,6 @@ CGameProject* CGameProject::LoadProject(const TString& rkProjPath, IProgressNoti
return nullptr;
}
CTemplateLoader::LoadGameTemplates(pProj->mGame);
pProj->mProjFileLock.Lock(ProjPath);
pProj->mpGameInfo->LoadGameInfo(pProj->mGame);
pProj->mpAudioManager->LoadAssets();

View File

@ -137,7 +137,7 @@ CStructPropertyNew* CLight::GetProperties() const
if (!pProperties)
{
pProperties = (CStructPropertyNew*) IPropertyNew::CreateIntrinsic(EPropertyTypeNew::Struct,
nullptr,
ePrime,
0,
"Light");

View File

@ -189,14 +189,14 @@ IPropertyNew* CTemplateLoader::LoadProperty(XMLElement* pElem, CScriptTemplate*
// create property as a copy of the archetype
if (pArchetype != nullptr)
{
pProp = IPropertyNew::CreateCopy(pArchetype, pParent);
pProp = IPropertyNew::CreateCopy(pArchetype);
}
}
// no archetype, so do normal create
if (!pProp)
{
pProp = IPropertyNew::Create(Type, pParent, mGame, pScript, false);
pProp = IPropertyNew::Create(Type, mGame);
}
// we need to have a valid property by this point
@ -340,7 +340,7 @@ IPropertyNew* CTemplateLoader::LoadProperty(XMLElement* pElem, CScriptTemplate*
}
else
{
pArray->mpItemArchetype = IPropertyNew::Create(EPropertyTypeNew::Struct, pArray, mGame, pScript, false);
pArray->mpItemArchetype = IPropertyNew::Create(EPropertyTypeNew::Struct, mGame);
pStruct = TPropCast<CStructPropertyNew>(pArray->mpItemArchetype);
pStruct->mFlags = EPropertyFlag::IsAtomic | EPropertyFlag::IsArrayArchetype;
}
@ -358,17 +358,16 @@ IPropertyNew* CTemplateLoader::LoadProperty(XMLElement* pElem, CScriptTemplate*
{
LoadProperties(pProperties, pScript, pStruct, rkTemplateName);
}
if (Type == EPropertyTypeNew::Array)
{
pStruct->PostInitialize();
}
}
if (IsNewProperty)
{
CMasterTemplate::AddProperty(pProp, mMasterDir + rkTemplateName);
pProp->PostInitialize();
if (pParent)
pParent->mChildren.push_back(pProp);
}
return pProp;
}
@ -393,11 +392,7 @@ CStructPropertyNew* CTemplateLoader::LoadStructArchetype(const TString& rkTempla
if (!Doc.Error())
{
pArchetype = TPropCast<CStructPropertyNew>(
IPropertyNew::Create(EPropertyTypeNew::Struct,
nullptr,
mGame,
nullptr,
false)
IPropertyNew::Create(EPropertyTypeNew::Struct, mGame)
);
ASSERT(pArchetype != nullptr);
@ -438,7 +433,7 @@ CStructPropertyNew* CTemplateLoader::LoadStructArchetype(const TString& rkTempla
ASSERT(pSubPropsElem);
LoadProperties(pSubPropsElem, nullptr, pArchetype, rkTemplateFileName);
pArchetype->PostInitialize();
pArchetype->Initialize(nullptr, nullptr, 0);
mpMaster->mPropertyTemplates.emplace(
std::make_pair(
@ -474,11 +469,7 @@ CEnumProperty* CTemplateLoader::LoadEnumArchetype(const TString& rkTemplateFileN
{
// use static_cast so this code works for both enum and choice
pArchetype = static_cast<CEnumProperty*>(
IPropertyNew::Create(bIsChoice ? EPropertyTypeNew::Choice : EPropertyTypeNew::Enum,
nullptr,
mGame,
nullptr,
false)
IPropertyNew::Create(bIsChoice ? EPropertyTypeNew::Choice : EPropertyTypeNew::Enum, mGame)
);
ASSERT(pArchetype != nullptr);
@ -493,7 +484,7 @@ CEnumProperty* CTemplateLoader::LoadEnumArchetype(const TString& rkTemplateFileN
ASSERT(pEnumers);
LoadEnumerators(pEnumers, pArchetype, rkTemplateFileName);
pArchetype->PostInitialize();
pArchetype->Initialize(nullptr, nullptr, 0);
mpMaster->mPropertyTemplates.emplace(
std::make_pair(
@ -528,11 +519,7 @@ CFlagsProperty* CTemplateLoader::LoadFlagsArchetype(const TString& rkTemplateFil
if (!Doc.Error())
{
pArchetype = TPropCast<CFlagsProperty>(
IPropertyNew::Create(EPropertyTypeNew::Flags,
nullptr,
mGame,
nullptr,
false)
IPropertyNew::Create(EPropertyTypeNew::Flags, mGame)
);
ASSERT(pArchetype != nullptr);
@ -547,7 +534,7 @@ CFlagsProperty* CTemplateLoader::LoadFlagsArchetype(const TString& rkTemplateFil
ASSERT(pFlags);
LoadBitFlags(pFlags, pArchetype, rkTemplateFileName);
pArchetype->PostInitialize();
pArchetype->Initialize(nullptr, nullptr, 0);
mpMaster->mPropertyTemplates.emplace(
std::make_pair(
@ -645,7 +632,7 @@ CScriptTemplate* CTemplateLoader::LoadScriptTemplate(XMLDocument *pDoc, const TS
pScript->mObjectID = ObjectID;
pScript->mSourceFile = rkTemplateName;
IPropertyNew* pBaseStruct = IPropertyNew::Create(EPropertyTypeNew::Struct, nullptr, mGame, pScript);
IPropertyNew* pBaseStruct = IPropertyNew::Create(EPropertyTypeNew::Struct, mGame);
pScript->mpProperties = std::make_unique<CStructPropertyNew>( *TPropCast<CStructPropertyNew>(pBaseStruct) );
XMLElement *pRoot = pDoc->FirstChildElement("ScriptTemplate");
@ -1139,7 +1126,7 @@ TString CTemplateLoader::ErrorName(XMLError Error)
}
// ************ PUBLIC ************
#define USE_NEW_TEMPLATES 0
#define USE_NEW_TEMPLATES 1
void CTemplateLoader::LoadGameList()
{
@ -1193,7 +1180,7 @@ void CTemplateLoader::LoadGameList()
for (auto Iter = MasterList.begin(); Iter != MasterList.end(); Iter++)
{
CMasterTemplate* pMaster = *Iter;
const TString kMasterPath = kTemplatesDir + pMaster->GetDirectory() + "Game.xml";
const TString kMasterPath = pMaster->GetGameDirectory(true) + "Game.xml";
CXMLReader Reader(kMasterPath);
ASSERT(Reader.IsValid());
@ -1354,7 +1341,7 @@ void CTemplateLoader::SaveGameList()
SGameInfo Info;
Info.Game = pMaster->Game();
Info.Name = pMaster->GameName();
Info.MasterPath = pMaster->GetDirectory() + "Game.xml";
Info.MasterPath = pMaster->GetGameDirectory() + "Game.xml";
Writer << SerialParameter("Game", Info);
}
Writer.ParamEnd();
@ -1373,7 +1360,7 @@ void CTemplateLoader::SaveGameList()
for (auto Iter = MasterList.begin(); Iter != MasterList.end(); Iter++)
{
CMasterTemplate* pMasterTemplate = *Iter;
TString MasterFilePath = kTemplatesDir + pMasterTemplate->GetDirectory() + "Game.xml";
TString MasterFilePath = pMasterTemplate->GetGameDirectory(true) + "Game.xml";
FileUtil::MakeDirectory( MasterFilePath.GetFileDirectory() );
CXMLWriter Writer(MasterFilePath, "Game", 0, pMasterTemplate->Game());

View File

@ -17,17 +17,52 @@ void CMasterTemplate::Serialize(IArchive& Arc)
void CMasterTemplate::LoadSubTemplates()
{
//todo
for (auto Iter = mScriptTemplates.begin(); Iter != mScriptTemplates.end(); Iter++)
Internal_LoadScriptTemplate( Iter->second );
for (auto Iter = mPropertyTemplates.begin(); Iter != mPropertyTemplates.end(); Iter++)
Internal_LoadPropertyTemplate( Iter->second );
}
void CMasterTemplate::Internal_LoadScriptTemplate(SScriptTemplatePath& Path)
{
ASSERT(Path.pTemplate == nullptr); // make sure it hasn't been loaded yet
const TString kGameDir = GetGameDirectory(true);
const TString kTemplateFilePath = kGameDir + Path.Path;
CXMLReader Reader(kTemplateFilePath);
ASSERT(Reader.IsValid());
Path.pTemplate = std::make_shared<CScriptTemplate>(this, Path.ID.ID, Path.Path);
Path.pTemplate->Serialize(Reader);
Path.pTemplate->PostLoad();
}
void CMasterTemplate::Internal_LoadPropertyTemplate(SPropertyTemplatePath& Path)
{
if (Path.pTemplate != nullptr) // don't load twice
return;
const TString kGameDir = GetGameDirectory(true);
const TString kTemplateFilePath = kGameDir + Path.Path;
CXMLReader Reader(kTemplateFilePath);
ASSERT(Reader.IsValid());
Reader << SerialParameter("PropertyArchetype", Path.pTemplate);
ASSERT(Path.pTemplate != nullptr);
Path.pTemplate->SetPropertyFlags( EPropertyFlag::IsArchetype );
Path.pTemplate->Initialize(nullptr, nullptr, 0);
}
void CMasterTemplate::SaveSubTemplates()
{
TString GameDir = "../templates_new/" + GetDirectory();
const TString kGameDir = GetGameDirectory(true);
for (auto Iter = mScriptTemplates.begin(); Iter != mScriptTemplates.end(); Iter++)
{
SScriptTemplatePath& Path = Iter->second;
TString OutPath = GameDir + Path.Path;
TString OutPath = kGameDir + Path.Path;
FileUtil::MakeDirectory( OutPath.GetFileDirectory() );
CXMLWriter Writer(OutPath, "ScriptObject", 0, Game());
@ -37,11 +72,11 @@ void CMasterTemplate::SaveSubTemplates()
for (auto Iter = mPropertyTemplates.begin(); Iter != mPropertyTemplates.end(); Iter++)
{
SPropertyTemplatePath& Path = Iter->second;
TString OutPath = GameDir + Path.Path;
TString OutPath = kGameDir + Path.Path;
FileUtil::MakeDirectory( OutPath.GetFileDirectory() );
CXMLWriter Writer(OutPath, "PropertyArchetype", 0, Game());
Path.pTemplate->Serialize(Writer);
CXMLWriter Writer(OutPath, "PropertyTemplate", 0, Game());
Writer << SerialParameter("PropertyArchetype", Path.pTemplate);
}
}
@ -115,10 +150,34 @@ SMessage CMasterTemplate::MessageByIndex(u32 Index)
return SMessage(Iter->first, Iter->second);
}
IPropertyNew* CMasterTemplate::FindPropertyArchetype(const TString& kTypeName) const
IPropertyNew* CMasterTemplate::FindPropertyArchetype(const TString& kTypeName)
{
auto Iter = mPropertyTemplates.find(kTypeName);
return (Iter != mPropertyTemplates.end()) ? Iter->second.pTemplate.get() : nullptr;
// Should require Iter to be valid in the future. For now, this is possible for some of the transition template loader code.
// ASSERT(Iter != mPropertyTemplates.end()); // Requested archetype property does not exist; missing or malformed template
if (Iter == mPropertyTemplates.end())
{
return nullptr;
}
// If the template isn't loaded yet, then load it.
// This has to be done here to allow recursion while loading other property archetypes, because some properties may
// request archetypes of other properties that haven't been loaded yet during their load.
SPropertyTemplatePath& Path = Iter->second;
if (!Path.pTemplate)
{
Internal_LoadPropertyTemplate(Path);
ASSERT(Path.pTemplate != nullptr); // Load failed; missing or malformed template
}
return Path.pTemplate.get();
}
TString CMasterTemplate::GetGameDirectory(bool Absolute) const
{
TString Out = mSourceFile.GetFileDirectory();
return Absolute ? "../templates_new/" + Out : Out;
}
// ************ STATIC ************

View File

@ -124,6 +124,9 @@ class CMasterTemplate
static std::map<u32, TString> smPropertyNames;
static u32 smGameListVersion;
void Internal_LoadScriptTemplate(SScriptTemplatePath& Path);
void Internal_LoadPropertyTemplate(SPropertyTemplatePath& Path);
public:
CMasterTemplate();
void Serialize(IArchive& Arc);
@ -139,7 +142,8 @@ public:
SMessage MessageByID(u32 MessageID);
SMessage MessageByID(const CFourCC& MessageID);
SMessage MessageByIndex(u32 Index);
IPropertyNew* FindPropertyArchetype(const TString& kTypeName) const;
IPropertyNew* FindPropertyArchetype(const TString& kTypeName);
TString GetGameDirectory(bool Absolute = false) const;
// Inline Accessors
inline EGame Game() const { return mGame; }
@ -148,7 +152,6 @@ public:
inline u32 NumStates() const { return mStates.size(); }
inline u32 NumMessages() const { return mMessages.size(); }
inline bool IsLoadedSuccessfully() { return mFullyLoaded; }
inline TString GetDirectory() const { return mSourceFile.GetFileDirectory(); }
// Static
static CMasterTemplate* MasterForGame(EGame Game);

View File

@ -79,6 +79,8 @@ void CScriptTemplate::Serialize(IArchive& Arc)
void CScriptTemplate::PostLoad()
{
mpProperties->Initialize(nullptr, this, 0);
if (!mNameIDString.IsEmpty()) mpNameProperty = TPropCast<CStringProperty>( mpProperties->ChildByIDString(mNameIDString) );
if (!mPositionIDString.IsEmpty()) mpPositionProperty = TPropCast<CVectorProperty>( mpProperties->ChildByIDString(mPositionIDString) );
if (!mRotationIDString.IsEmpty()) mpRotationProperty = TPropCast<CVectorProperty>( mpProperties->ChildByIDString(mRotationIDString) );

View File

@ -130,6 +130,8 @@ private:
bool mVisible;
public:
// Default constructor. Don't use. This is only here so the serializer doesn't complain
CScriptTemplate() { ASSERT(false); }
// Old constructor
CScriptTemplate(CMasterTemplate *pMaster);
// New constructor

View File

@ -9,10 +9,12 @@
#include "Core/Resource/Script/CScriptTemplate.h"
/** IPropertyNew */
IPropertyNew::IPropertyNew()
IPropertyNew::IPropertyNew(EGame Game)
: mpParent( nullptr )
, mpPointerParent( nullptr )
, mpArchetype( nullptr )
, mGame( Game )
, mpScriptTemplate( nullptr )
, mOffset( -1 )
, mID( -1 )
, mCookPreference( ECookPreferenceNew::Default )
@ -20,41 +22,6 @@ IPropertyNew::IPropertyNew()
, mMaxVersion( FLT_MAX )
{}
void IPropertyNew::_CalcOffset()
{
// For standard properties, append to the end of the parent.
bool IsRootArrayArchetype = (IsArrayArchetype() && TPropCast<CArrayProperty>(mpParent) != nullptr);
if (mpParent && !IsRootArrayArchetype)
{
// When we have a parent, our data is usually located inside the parent's property data. So we want to
// position ourself at the end of the parent's existing children so we don't overlap any other properties.
IPropertyNew* pLastChild = (mpParent->mChildren.empty() ? nullptr : mpParent->mChildren.back());
if (pLastChild)
{
mOffset = pLastChild->mOffset + pLastChild->DataSize();
}
else if (mpParent != mpPointerParent)
{
mOffset = mpParent->mOffset;
}
else
{
mOffset = 0;
}
mOffset = ALIGN(mOffset, DataAlignment());
}
// Array archetypes are accessed differently because they have no way of knowing
// which array index is meant to be accessed. So the offset is 0 and the caller
// is responsible for passing in a pointer to the correct array item.
else
{
mOffset = 0;
}
}
void IPropertyNew::_ClearChildren()
{
for (int ChildIdx = 0; ChildIdx < mChildren.size(); ChildIdx++)
@ -109,7 +76,6 @@ void IPropertyNew::Serialize(IArchive& rArc)
IPropertyNew* pArchetype = pMaster->FindPropertyArchetype(ArchetypeName);
// The archetype must exist, or else the template file is malformed.
//@TODO: I think this will actually always fail right now, because property archetype loading has not been implemented yet
ASSERT(pArchetype != nullptr);
InitFromArchetype(pArchetype);
@ -118,7 +84,12 @@ void IPropertyNew::Serialize(IArchive& rArc)
// In MP1, the game data does not use property IDs, so we serialize the name directly.
// In MP2 and on, property names are looked up based on the property ID via the property name map.
if (rArc.Game() <= ePrime && !IsArchetype())
// Exceptions: Properties that are not in the name map still need to serialize their names.
// This includes root-level properties, and properties of atomic structs.
//
// We can't currently tell if this property is atomic, as the flag hasn't been serialized and the parent
// hasn't been set, but atomic sub-properties don't use hash IDs, so we can do a pseudo-check against the ID.
if (rArc.Game() <= ePrime || IsRootParent() || mID <= 0xFF)
{
rArc << SerialParameter("Name", mName, mpArchetype ? SH_Optional : 0, mpArchetype ? mpArchetype->mName : "");
}
@ -137,7 +108,6 @@ void IPropertyNew::InitFromArchetype(IPropertyNew* pOther)
//@todo maybe somehow use Serialize for this instead?
mpArchetype = pOther;
mFlags = pOther->mFlags & EPropertyFlag::ArchetypeCopyFlags;
mID = pOther->mID;
mName = pOther->mName;
mDescription = pOther->mDescription;
mSuffix = pOther->mSuffix;
@ -145,12 +115,10 @@ void IPropertyNew::InitFromArchetype(IPropertyNew* pOther)
mMinVersion = pOther->mMinVersion;
mMaxVersion = pOther->mMaxVersion;
// Copy children
_ClearChildren();
for (u32 ChildIdx = 0; ChildIdx < pOther->mChildren.size(); ChildIdx++)
// Copy ID only if our existing ID is not valid.
if (mID == 0xFFFFFFFF)
{
CreateCopy( pOther->mChildren[ChildIdx], this );
mID = pOther->mID;
}
}
@ -183,6 +151,62 @@ TString IPropertyNew::GetTemplateFileName()
}
}
void IPropertyNew::Initialize(IPropertyNew* pInParent, CScriptTemplate* pInTemplate, u32 InOffset)
{
// Make sure we only get initialized once.
ASSERT( (mFlags & EPropertyFlag::IsInitialized) == 0 );
mFlags |= EPropertyFlag::IsInitialized;
mpParent = pInParent;
mOffset = InOffset;
mpScriptTemplate = pInTemplate;
// Look up property name if needed.
if (Game() >= eEchoesDemo && !IsRootParent() && !IsIntrinsic() && !mpParent->IsAtomic())
{
mName = CMasterTemplate::PropertyName(mID);
}
// Set any fields dependent on the parent...
if (mpParent)
{
mFlags |= mpParent->mFlags & EPropertyFlag::InheritableFlags;
if (mpParent->IsPointerType())
{
mpPointerParent = mpParent;
}
else
{
mpPointerParent = mpParent->mpPointerParent;
}
}
// Allow subclasses to handle any initialization tasks
PostInitialize();
// Now, route initialization to any child properties...
u32 ChildOffset = mOffset;
for (int ChildIdx = 0; ChildIdx < mChildren.size(); ChildIdx++)
{
IPropertyNew* pChild = mChildren[ChildIdx];
// update offset and round up to the child's alignment
if (ChildIdx > 0)
{
ChildOffset += mChildren[ChildIdx-1]->DataSize();
}
ChildOffset = ALIGN(ChildOffset, pChild->DataAlignment());
// Don't call Initialize on intrinsic children as they have already been initialized.
if (!pChild->IsIntrinsic())
{
pChild->Initialize(this, pInTemplate, ChildOffset);
}
}
}
void* IPropertyNew::RawValuePtr(void* pData) const
{
// For array archetypes, the caller needs to provide the pointer to the correct array item
@ -266,8 +290,27 @@ void IPropertyNew::SetSuffix(const TString& rkNewSuffix)
mSuffix = rkNewSuffix;
}
void IPropertyNew::SetPropertyFlags(FPropertyFlags FlagsToSet)
{
mFlags |= FlagsToSet;
}
bool IPropertyNew::HasAccurateName()
{
// Exceptions for the three hardcoded 4CC property IDs
if (mID == FOURCC('XFRM') || mID == FOURCC('INAM') || mID == FOURCC('ACTV'))
return true;
// Children of atomic properties defer to parents. Intrinsic properties also defer to parents.
if ( (mpParent && mpParent->IsAtomic()) || IsIntrinsic() )
{
if (mpParent)
return mpParent->HasAccurateName();
else
return true;
}
// For everything else, hash the property name and check if it is a match for the property ID
if (!mFlags.HasFlag(EPropertyFlag::HasCachedNameCheck))
{
CCRC32 Hash;
@ -293,114 +336,79 @@ EGame IPropertyNew::Game() const
}
IPropertyNew* IPropertyNew::Create(EPropertyTypeNew Type,
IPropertyNew* pParent,
EGame Game,
CScriptTemplate* pScript,
bool CallPostInit /*= true*/)
EGame Game)
{
IPropertyNew* pOut = nullptr;
switch (Type)
{
case EPropertyTypeNew::Bool: pOut = new CBoolProperty; break;
case EPropertyTypeNew::Byte: pOut = new CByteProperty; break;
case EPropertyTypeNew::Short: pOut = new CShortProperty; break;
case EPropertyTypeNew::Int: pOut = new CIntProperty; break;
case EPropertyTypeNew::Float: pOut = new CFloatProperty; break;
case EPropertyTypeNew::Choice: pOut = new CChoiceProperty; break;
case EPropertyTypeNew::Enum: pOut = new CEnumProperty; break;
case EPropertyTypeNew::Flags: pOut = new CFlagsProperty; break;
case EPropertyTypeNew::String: pOut = new CStringProperty; break;
case EPropertyTypeNew::Vector: pOut = new CVectorProperty; break;
case EPropertyTypeNew::Color: pOut = new CColorProperty; break;
case EPropertyTypeNew::Asset: pOut = new CAssetProperty; break;
case EPropertyTypeNew::Sound: pOut = new CSoundProperty; break;
case EPropertyTypeNew::Animation: pOut = new CAnimationProperty; break;
case EPropertyTypeNew::AnimationSet: pOut = new CAnimationSetProperty; break;
case EPropertyTypeNew::Sequence: pOut = new CSequenceProperty; break;
case EPropertyTypeNew::Spline: pOut = new CSplineProperty; break;
case EPropertyTypeNew::Guid: pOut = new CGuidProperty; break;
case EPropertyTypeNew::Pointer: pOut = new CPointerProperty; break;
case EPropertyTypeNew::Struct: pOut = new CStructPropertyNew; break;
case EPropertyTypeNew::Array: pOut = new CArrayProperty; break;
}
if (!pOut)
{
// this shouldn't be possible! unhandled type! someone fucked up!
ASSERT(false);
return nullptr;
}
// Set parent and offset
pOut->mpParent = pParent;
if (pParent)
{
pOut->mFlags = pParent->mFlags & EPropertyFlag::InheritableFlags;
if (pParent->IsPointerType())
{
pOut->mpPointerParent = pParent;
}
else
{
pOut->mpPointerParent = pParent->mpPointerParent;
}
}
// Set other metadata
pOut->mGame = Game;
pOut->mpScriptTemplate = pScript;
pOut->_CalcOffset();
// Add to the parent's array. This needs to be done -after- we calculate offset, as adding a child to
// the parent property will change the offset that gets calculated.
if (pParent)
{
pParent->mChildren.push_back(pOut);
}
if (CallPostInit)
{
pOut->PostInitialize();
case EPropertyTypeNew::Bool: pOut = new CBoolProperty(Game); break;
case EPropertyTypeNew::Byte: pOut = new CByteProperty(Game); break;
case EPropertyTypeNew::Short: pOut = new CShortProperty(Game); break;
case EPropertyTypeNew::Int: pOut = new CIntProperty(Game); break;
case EPropertyTypeNew::Float: pOut = new CFloatProperty(Game); break;
case EPropertyTypeNew::Choice: pOut = new CChoiceProperty(Game); break;
case EPropertyTypeNew::Enum: pOut = new CEnumProperty(Game); break;
case EPropertyTypeNew::Flags: pOut = new CFlagsProperty(Game); break;
case EPropertyTypeNew::String: pOut = new CStringProperty(Game); break;
case EPropertyTypeNew::Vector: pOut = new CVectorProperty(Game); break;
case EPropertyTypeNew::Color: pOut = new CColorProperty(Game); break;
case EPropertyTypeNew::Asset: pOut = new CAssetProperty(Game); break;
case EPropertyTypeNew::Sound: pOut = new CSoundProperty(Game); break;
case EPropertyTypeNew::Animation: pOut = new CAnimationProperty(Game); break;
case EPropertyTypeNew::AnimationSet: pOut = new CAnimationSetProperty(Game); break;
case EPropertyTypeNew::Sequence: pOut = new CSequenceProperty(Game); break;
case EPropertyTypeNew::Spline: pOut = new CSplineProperty(Game); break;
case EPropertyTypeNew::Guid: pOut = new CGuidProperty(Game); break;
case EPropertyTypeNew::Pointer: pOut = new CPointerProperty(Game); break;
case EPropertyTypeNew::Struct: pOut = new CStructPropertyNew(Game); break;
case EPropertyTypeNew::Array: pOut = new CArrayProperty(Game); break;
}
// If this assertion fails, then there is an unhandled type!
ASSERT(pOut != nullptr);
return pOut;
}
IPropertyNew* IPropertyNew::CreateCopy(IPropertyNew* pArchetype,
IPropertyNew* pParent)
IPropertyNew* IPropertyNew::CreateCopy(IPropertyNew* pArchetype)
{
// Note this is mainly going to be used to create copies from struct/enum/flag archetype properties.
// Properties that have archetypes will never be the root property of a script template, and there
// is no case where we will be creating archetypes outside this context. As such, pParent should
// always be valid.
ASSERT(pParent != nullptr);
IPropertyNew* pOut = Create(pArchetype->Type(), pParent, pParent->mGame, pParent->mpScriptTemplate, false);
IPropertyNew* pOut = Create(pArchetype->Type(), pArchetype->mGame);
pOut->InitFromArchetype(pArchetype);
pArchetype->mSubInstances.push_back(pOut);
return pOut;
}
IPropertyNew* IPropertyNew::CreateIntrinsic(EPropertyTypeNew Type,
EGame Game,
u32 Offset,
const TString& rkName)
{
IPropertyNew* pOut = Create(Type, Game);
pOut->mFlags |= EPropertyFlag::IsIntrinsic;
pOut->SetName(rkName);
pOut->Initialize(nullptr, nullptr, Offset);
return pOut;
}
IPropertyNew* IPropertyNew::CreateIntrinsic(EPropertyTypeNew Type,
IPropertyNew* pParent,
u32 Offset,
const TString& rkName)
{
IPropertyNew* pOut = Create(Type, pParent, pParent ? pParent->mGame : eUnknownGame, nullptr, false);
pOut->mOffset = Offset;
// pParent should always be valid.
// If you are creating a root property, call the other overload takes an EGame instead of a parent.
ASSERT(pParent != nullptr);
IPropertyNew* pOut = Create(Type, pParent->mGame);
pOut->mFlags |= EPropertyFlag::IsIntrinsic;
pOut->SetName(rkName);
pOut->PostInitialize();
pOut->Initialize(pParent, nullptr, Offset);
pParent->mChildren.push_back(pOut);
return pOut;
}
IPropertyNew* IPropertyNew::ArchiveConstructor(EPropertyTypeNew Type,
const IArchive& Arc)
{
IPropertyNew* pParent = Arc.FindParentObject<IPropertyNew>();
CScriptTemplate* pTemplate = (pParent ? pParent->ScriptTemplate() : Arc.FindParentObject<CScriptTemplate>());
EGame Game = Arc.Game();
return Create(Type, pParent, Game, pTemplate);
return Create(Type, Arc.Game());
}

View File

@ -19,12 +19,16 @@ typedef TString TIDString;
/** Property flags */
enum class EPropertyFlag : u32
{
/** 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 = 0x1,
IsArchetype = 0x2,
/** Property is an array archetype (a template for elements of an array property) */
IsArrayArchetype = 0x2,
IsArrayArchetype = 0x4,
/** This property and all its children are a single unit and do not have individual property IDs, sizes, etc. */
IsAtomic = 0x4,
IsAtomic = 0x8,
/** This is a property of a C++ class, not a script object */
IsIntrinsic = 0x10,
/** We have cached whether the property name is correct */
HasCachedNameCheck = 0x40000000,
/** The name of the property is a match for the property ID hash */
@ -122,8 +126,8 @@ protected:
/** Archetype property; source property that we copied metadata from */
IPropertyNew* mpArchetype;
/** Sub-instances of archetype properties. For non-archetypes, will be empty. @todo better
* method of storing this? maybe a linked list? */
/** Sub-instances of archetype properties. For non-archetypes, will be empty.
* @todo this really oughta be a linked list */
std::vector<IPropertyNew*> mSubInstances;
/** Child properties; these appear underneath this property on the UI */
@ -155,13 +159,9 @@ protected:
float mMaxVersion;
/** Private constructor - use static methods to instantiate */
IPropertyNew();
void _CalcOffset();
IPropertyNew(EGame Game);
void _ClearChildren();
/** Called after property is created and fully initialized */
virtual void PostInitialize() {}
public:
virtual ~IPropertyNew();
@ -175,11 +175,11 @@ public:
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 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);
@ -188,6 +188,7 @@ public:
virtual TString GetTemplateFileName();
/** Utility methods */
void Initialize(IPropertyNew* pInParent, CScriptTemplate* pInTemplate, u32 InOffset);
void* RawValuePtr(void* pData) const;
IPropertyNew* ChildByID(u32 ID) const;
IPropertyNew* ChildByIDString(const TIDString& rkIdString);
@ -195,6 +196,7 @@ public:
void SetName(const TString& rkNewName);
void SetDescription(const TString& rkNewDescription);
void SetSuffix(const TString& rkNewSuffix);
void SetPropertyFlags(FPropertyFlags FlagsToSet);
bool HasAccurateName();
/** Accessors */
@ -216,17 +218,19 @@ public:
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 IsRootParent() const { return mpParent == nullptr; }
/** Create */
static IPropertyNew* Create(EPropertyTypeNew Type,
IPropertyNew* pParent,
EGame Game,
CScriptTemplate* pScript,
bool CallPostInit = true);
EGame Game);
static IPropertyNew* CreateCopy(IPropertyNew* pArchetype,
IPropertyNew* pParent);
static IPropertyNew* CreateCopy(IPropertyNew* pArchetype);
static IPropertyNew* CreateIntrinsic(EPropertyTypeNew Type,
EGame Game,
u32 Offset,
const TString& rkName);
static IPropertyNew* CreateIntrinsic(EPropertyTypeNew Type,
IPropertyNew* pParent,
@ -326,8 +330,8 @@ public:
protected:
PropType mDefaultValue;
TTypedPropertyNew()
: IPropertyNew()
TTypedPropertyNew(EGame Game)
: IPropertyNew(Game)
{
memset(&mDefaultValue, 0, sizeof(PropType));
}
@ -369,6 +373,11 @@ public:
return mDefaultValue;
}
inline void SetDefaultValue(const PropType& kInDefaultValue)
{
mDefaultValue = kInDefaultValue;
}
inline static EPropertyTypeNew StaticType() { return PropEnum; }
};
@ -376,8 +385,8 @@ template<typename PropType, EPropertyTypeNew PropEnum>
class TSerializeableTypedProperty : public TTypedPropertyNew<PropType, PropEnum>
{
protected:
TSerializeableTypedProperty()
: TTypedPropertyNew()
TSerializeableTypedProperty(EGame Game)
: TTypedPropertyNew(Game)
{}
public:
@ -443,8 +452,8 @@ protected:
PropType mMinValue;
PropType mMaxValue;
TNumericalPropertyNew()
: TSerializeableTypedProperty()
TNumericalPropertyNew(EGame Game)
: TSerializeableTypedProperty(Game)
, mMinValue( -1 )
, mMaxValue( -1 )
{}

View File

@ -8,8 +8,8 @@ class CAnimationProperty : public TSerializeableTypedProperty< u32, EPropertyTyp
friend class IPropertyNew;
protected:
CAnimationProperty()
: TSerializeableTypedProperty()
CAnimationProperty(EGame Game)
: TSerializeableTypedProperty(Game)
{}
public:

View File

@ -8,16 +8,13 @@ class CAnimationSetProperty : public TSerializeableTypedProperty< CAnimationPara
friend class IPropertyNew;
protected:
CAnimationSetProperty()
: TSerializeableTypedProperty()
{}
public:
virtual void PostInitialize()
CAnimationSetProperty(EGame Game)
: TSerializeableTypedProperty(Game)
{
mDefaultValue.SetGame(Game());
mDefaultValue.SetGame(Game);
}
public:
virtual void SerializeValue(void* pData, IArchive& Arc) const
{
Value(pData).Serialize(Arc);

View File

@ -45,8 +45,8 @@ class CArrayProperty : public TTypedPropertyNew<u32, EPropertyTypeNew::Array>
}
protected:
CArrayProperty()
: TTypedPropertyNew()
CArrayProperty(EGame Game)
: TTypedPropertyNew(Game)
, mpItemArchetype(nullptr)
{}
@ -109,6 +109,11 @@ public:
{
TTypedPropertyNew::Serialize(rArc);
rArc << SerialParameter("ItemArchetype", mpItemArchetype);
if (rArc.IsReader())
{
mpItemArchetype->SetPropertyFlags( EPropertyFlag::IsArrayArchetype );
}
}
virtual void SerializeValue(void* pData, IArchive& Arc) const
@ -134,7 +139,13 @@ public:
{
TTypedPropertyNew::InitFromArchetype(pOther);
CArrayProperty* pOtherArray = static_cast<CArrayProperty*>(pOther);
mpItemArchetype = IPropertyNew::CreateCopy(pOtherArray->mpItemArchetype, this);
mpItemArchetype = IPropertyNew::CreateCopy(pOtherArray->mpItemArchetype);
}
virtual void PostInitialize()
{
TTypedPropertyNew::PostInitialize();
mpItemArchetype->Initialize(this, mpScriptTemplate, 0);
}
u32 ArrayCount(void* pPropertyData) const

View File

@ -7,18 +7,18 @@
class CAssetProperty : public TSerializeableTypedProperty<CAssetID, EPropertyTypeNew::Asset>
{
friend class CTemplateLoader;
friend class IPropertyNew;
CResTypeFilter mTypeFilter;
public:
virtual void PostInitialize()
protected:
CAssetProperty::CAssetProperty(EGame Game)
: TSerializeableTypedProperty(Game)
{
// Init default value to an invalid ID depending on the game
if (!mDefaultValue.IsValid())
{
mDefaultValue = CAssetID::InvalidID( mGame );
}
mDefaultValue = CAssetID::InvalidID( mGame );
}
public:
virtual void Serialize(IArchive& rArc)
{
TSerializeableTypedProperty::Serialize(rArc);

View File

@ -8,8 +8,8 @@ class CBoolProperty : public TSerializeableTypedProperty< bool, EPropertyTypeNew
friend class IPropertyNew;
protected:
CBoolProperty()
: TSerializeableTypedProperty()
CBoolProperty(EGame Game)
: TSerializeableTypedProperty(Game)
{}
public:

View File

@ -8,8 +8,8 @@ class CByteProperty : public TNumericalPropertyNew< s8, EPropertyTypeNew::Byte >
friend class IPropertyNew;
protected:
CByteProperty()
: TNumericalPropertyNew()
CByteProperty(EGame Game)
: TNumericalPropertyNew(Game)
{}
public:

View File

@ -2,27 +2,25 @@
#define CCOLORPROPERTY_H
#include "../IPropertyNew.h"
#include "CFloatProperty.h"
class CColorProperty : public TSerializeableTypedProperty< CColor, EPropertyTypeNew::Color >
{
friend class IPropertyNew;
protected:
CColorProperty()
: TSerializeableTypedProperty()
CColorProperty(EGame Game)
: TSerializeableTypedProperty(Game)
{}
public:
virtual void PostInitialize()
{
IPropertyNew* pR = Create(EPropertyTypeNew::Float, this, mGame, mpScriptTemplate);
IPropertyNew* pG = Create(EPropertyTypeNew::Float, this, mGame, mpScriptTemplate);
IPropertyNew* pB = Create(EPropertyTypeNew::Float, this, mGame, mpScriptTemplate);
IPropertyNew* pA = Create(EPropertyTypeNew::Float, this, mGame, mpScriptTemplate);
pR->SetName("R");
pG->SetName("G");
pB->SetName("B");
pA->SetName("A");
CreateIntrinsic(EPropertyTypeNew::Float, this, mOffset + 0, "R");
CreateIntrinsic(EPropertyTypeNew::Float, this, mOffset + 4, "G");
CreateIntrinsic(EPropertyTypeNew::Float, this, mOffset + 8, "B");
CreateIntrinsic(EPropertyTypeNew::Float, this, mOffset + 12, "A");
TPropCast<CFloatProperty>( mChildren.back() )->SetDefaultValue(1.0f);
}
virtual void SerializeValue(void* pData, IArchive& Arc) const

View File

@ -14,6 +14,8 @@ template<EPropertyTypeNew TypeEnum>
class TEnumPropertyBase : public TSerializeableTypedProperty<s32, TypeEnum>
{
friend class CTemplateLoader;
friend class IPropertyNew;
struct SEnumValue
{
TString Name;
@ -43,6 +45,12 @@ class TEnumPropertyBase : public TSerializeableTypedProperty<s32, TypeEnum>
/** XML template file that this enum originated from; for archetypes */
TString mSourceFile;
protected:
/** Constructor */
TEnumPropertyBase(EGame Game)
: TSerializeableTypedProperty(Game)
{}
public:
virtual const char* GetHashableTypeName() const
{

View File

@ -38,8 +38,8 @@ class CFlagsProperty : public TSerializeableTypedProperty<u32, EPropertyTypeNew:
/** XML template file that this enum originated from; for archetypes */
TString mSourceFile;
CFlagsProperty()
: TSerializeableTypedProperty()
CFlagsProperty(EGame Game)
: TSerializeableTypedProperty(Game)
, mAllFlags(0)
{}

View File

@ -8,8 +8,8 @@ class CFloatProperty : public TNumericalPropertyNew< float, EPropertyTypeNew::Fl
friend class IPropertyNew;
protected:
CFloatProperty()
: TNumericalPropertyNew()
CFloatProperty(EGame Game)
: TNumericalPropertyNew(Game)
{}
public:

View File

@ -8,8 +8,8 @@ class CGuidProperty : public TTypedPropertyNew< std::vector<char>, EPropertyType
friend class IPropertyNew;
protected:
CGuidProperty()
: TTypedPropertyNew()
CGuidProperty(EGame Game)
: TTypedPropertyNew(Game)
{}
public:

View File

@ -8,8 +8,8 @@ class CIntProperty : public TNumericalPropertyNew< s32, EPropertyTypeNew::Int >
friend class IPropertyNew;
protected:
CIntProperty()
: TNumericalPropertyNew()
CIntProperty(EGame Game)
: TNumericalPropertyNew(Game)
{}
public:

View File

@ -6,6 +6,12 @@
class CPointerProperty : public TTypedPropertyNew<void*, EPropertyTypeNew::Pointer>
{
friend class CTemplateLoader;
friend class IPropertyNew;
CPointerProperty(EGame Game)
: TTypedPropertyNew(Game)
{}
public:
virtual bool IsPointerType() const
{

View File

@ -8,8 +8,8 @@ class CSequenceProperty : public TTypedPropertyNew< s32, EPropertyTypeNew::Seque
friend class IPropertyNew;
protected:
CSequenceProperty()
: TTypedPropertyNew()
CSequenceProperty(EGame Game)
: TTypedPropertyNew(Game)
{}
virtual void SerializeValue(void* pData, IArchive& rArc) const

View File

@ -8,8 +8,8 @@ class CShortProperty : public TNumericalPropertyNew< s16, EPropertyTypeNew::Shor
friend class IPropertyNew;
protected:
CShortProperty()
: TNumericalPropertyNew()
CShortProperty(EGame Game)
: TNumericalPropertyNew(Game)
{}
public:

View File

@ -8,8 +8,8 @@ class CSoundProperty : public TSerializeableTypedProperty< s32, EPropertyTypeNew
friend class IPropertyNew;
protected:
CSoundProperty()
: TSerializeableTypedProperty()
CSoundProperty(EGame Game)
: TSerializeableTypedProperty(Game)
{}
public:

View File

@ -8,8 +8,8 @@ class CSplineProperty : public TTypedPropertyNew< std::vector<char>, EPropertyTy
friend class IPropertyNew;
protected:
CSplineProperty()
: TTypedPropertyNew()
CSplineProperty(EGame Game)
: TTypedPropertyNew(Game)
{}
public:

View File

@ -8,8 +8,8 @@ class CStringProperty : public TSerializeableTypedProperty< TString, EPropertyTy
friend class IPropertyNew;
protected:
CStringProperty()
: TSerializeableTypedProperty()
CStringProperty(EGame Game)
: TSerializeableTypedProperty(Game)
{}
public:

View File

@ -21,12 +21,8 @@ u32 CStructPropertyNew::DataSize() const
u32 CStructPropertyNew::DataAlignment() const
{
// TODO. Should be aligned with the first child, but this function is called before children are loaded.
// So for now just use 8 to ensure correct alignment for all child types, but this is wasteful...
// It's also problematic for casting property data to a struct
return 8;
//return (mChildren.empty() ? 1 : mChildren[0]->DataAlignment());
// Structs are aligned to the first child property.
return (mChildren.empty() ? 1 : mChildren[0]->DataAlignment());
}
void CStructPropertyNew::Construct(void* pData) const
@ -67,16 +63,25 @@ void CStructPropertyNew::RevertToDefault(void* pData) const
const char* CStructPropertyNew::HashableTypeName() const
{
if (IsArchetype() || !mpArchetype)
return *mName;
else
return mpArchetype->HashableTypeName();
return mpArchetype ? mpArchetype->HashableTypeName() : *mName;
}
void CStructPropertyNew::Serialize(IArchive& rArc)
{
IPropertyNew::Serialize(rArc);
// Serialize atomic flag
bool Atomic = IsAtomic();
rArc << SerialParameter("Atomic", Atomic, SH_Optional, false);
if (rArc.IsReader())
{
if (Atomic)
mFlags.SetFlag(EPropertyFlag::IsAtomic);
else
mFlags.ClearFlag(EPropertyFlag::IsAtomic);
}
// Serialize archetype
if (mpArchetype)
{
@ -96,13 +101,13 @@ void CStructPropertyNew::Serialize(IArchive& rArc)
if (rArc.ParamBegin("Element", SH_IgnoreName))
{
// Serialize type and ID, then look up the matching property and serialize it.
// We don't really need the type, but it's a good sanity check, and it's also helpful
// We don't really need the type, but it's a good sanity check, and it's also good practice
// to guarantee that parameters are read in order, as some serializers are order-dependent.
EPropertyTypeNew ChildType;
u32 ChildID;
rArc << SerialParameter("Type", ChildType, SH_Attribute)
<< SerialParameter("ID", ChildID, SH_Attribute);
<< SerialParameter("ID", ChildID, SH_Attribute | SH_HexDisplay );
IPropertyNew* pChild = ChildByID(ChildID);
ASSERT(pChild != nullptr && pChild->Type() == ChildType);
@ -154,6 +159,21 @@ void CStructPropertyNew::SerializeValue(void* pData, IArchive& Arc) const
}
}
void CStructPropertyNew::InitFromArchetype(IPropertyNew* pOther)
{
IPropertyNew::InitFromArchetype(pOther);
// Copy children
_ClearChildren();
mChildren.reserve( pOther->NumChildren() );
for (u32 ChildIdx = 0; ChildIdx < pOther->NumChildren(); ChildIdx++)
{
IPropertyNew* pChild = CreateCopy( pOther->ChildByIndex(ChildIdx) );
mChildren.push_back( pChild );
}
}
bool CStructPropertyNew::ShouldSerialize() const
{
if (IPropertyNew::ShouldSerialize())

View File

@ -6,6 +6,7 @@
class CStructPropertyNew : public IPropertyNew
{
friend class CTemplateLoader;
friend class IPropertyNew;
public:
// Must be a valid type for TPropertyRef
@ -15,6 +16,10 @@ protected:
/** For archetypes, the filename of the template XML file. */
TString mTemplateFileName;
CStructPropertyNew(EGame Game)
: IPropertyNew(Game)
{}
public:
virtual EPropertyTypeNew Type() const;
virtual u32 DataSize() const;
@ -26,6 +31,7 @@ public:
virtual const char* HashableTypeName() const;
virtual void Serialize(IArchive& rArc);
virtual void SerializeValue(void* pData, IArchive& Arc) const;
virtual void InitFromArchetype(IPropertyNew* pOther);
virtual bool ShouldSerialize() const;
virtual TString GetTemplateFileName();

View File

@ -8,19 +8,16 @@ class CVectorProperty : public TSerializeableTypedProperty< CVector3f, EProperty
friend class IPropertyNew;
protected:
CVectorProperty()
: TSerializeableTypedProperty()
CVectorProperty(EGame Game)
: TSerializeableTypedProperty(Game)
{}
public:
virtual void PostInitialize()
virtual void PostInitialize() override
{
IPropertyNew* pX = Create(EPropertyTypeNew::Float, this, mGame, mpScriptTemplate);
IPropertyNew* pY = Create(EPropertyTypeNew::Float, this, mGame, mpScriptTemplate);
IPropertyNew* pZ = Create(EPropertyTypeNew::Float, this, mGame, mpScriptTemplate);
pX->SetName("X");
pY->SetName("Y");
pZ->SetName("Z");
CreateIntrinsic(EPropertyTypeNew::Float, this, mOffset + 0, "X");
CreateIntrinsic(EPropertyTypeNew::Float, this, mOffset + 4, "Y");
CreateIntrinsic(EPropertyTypeNew::Float, this, mOffset + 8, "Z");
}
virtual void SerializeValue(void* pData, IArchive& Arc) const

View File

@ -235,7 +235,11 @@ void CPropertyView::CreateContextMenu(const QPoint& rkPos)
mpMenuProperty = pProp;
QMenu Menu;
Menu.addAction(mpEditTemplateAction);
if (!pProp->IsIntrinsic())
{
Menu.addAction(mpEditTemplateAction);
}
if (mpEditor->CurrentGame() >= eEchoesDemo)
{