Load ESM4 actors

revert-6246b479
Petr Mikheev 1 year ago
parent 488657d9b4
commit 9b511fdf7a

@ -68,5 +68,7 @@ namespace MWClass
ESM4Tree::registerSelf(); ESM4Tree::registerSelf();
ESM4Named<ESM4::Weapon>::registerSelf(); ESM4Named<ESM4::Weapon>::registerSelf();
ESM4Light::registerSelf(); ESM4Light::registerSelf();
ESM4Actor<ESM4::Npc>::registerSelf();
ESM4Actor<ESM4::Creature>::registerSelf();
} }
} }

@ -124,6 +124,39 @@ namespace MWClass
return ptr.get<Record>()->mBase->mFullName; return ptr.get<Record>()->mBase->mFullName;
} }
}; };
template <typename Record>
class ESM4Actor : public MWWorld::RegisteredClass<ESM4Actor<Record>, ESM4Base<Record>>
{
public:
friend MWWorld::RegisteredClass<ESM4Actor, ESM4Base<Record>>;
ESM4Actor()
: MWWorld::RegisteredClass<ESM4Actor, ESM4Base<Record>>(Record::sRecordId)
{
}
void insertObjectPhysics(
const MWWorld::Ptr&, const std::string&, const osg::Quat&, MWPhysics::PhysicsSystem&) const override
{
}
bool hasToolTip(const MWWorld::ConstPtr& ptr) const override { return true; }
MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override
{
return ESM4Impl::getToolTipInfo(ptr.get<Record>()->mBase->mEditorId, count);
}
std::string getModel(const MWWorld::ConstPtr& ptr) const override
{
// TODO: Not clear where to get something renderable:
// ESM4::Npc::mModel is usually an empty string
// ESM4::Race::mModelMale is only a skeleton
// For now show error marker as a dummy model.
return "meshes/marker_error.nif";
}
};
} }
#endif // GAME_MWCLASS_ESM4BASE_H #endif // GAME_MWCLASS_ESM4BASE_H

@ -25,10 +25,16 @@ namespace MWWorld
{ {
} }
CellRef::CellRef(const ESM4::ActorCharacter& ref)
: mCellRef(ESM::ReferenceVariant(ref))
{
}
const ESM::RefNum& CellRef::getRefNum() const const ESM::RefNum& CellRef::getRefNum() const
{ {
return std::visit(ESM::VisitOverload{ return std::visit(ESM::VisitOverload{
[&](const ESM4::Reference& ref) -> const ESM::RefNum& { return ref.mId; }, [&](const ESM4::Reference& ref) -> const ESM::RefNum& { return ref.mId; },
[&](const ESM4::ActorCharacter& ref) -> const ESM::RefNum& { return ref.mId; },
[&](const ESM::CellRef& ref) -> const ESM::RefNum& { return ref.mRefNum; }, [&](const ESM::CellRef& ref) -> const ESM::RefNum& { return ref.mRefNum; },
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -38,6 +44,7 @@ namespace MWWorld
{ {
ESM::RefNum& refNum = std::visit(ESM::VisitOverload{ ESM::RefNum& refNum = std::visit(ESM::VisitOverload{
[&](ESM4::Reference& ref) -> ESM::RefNum& { return ref.mId; }, [&](ESM4::Reference& ref) -> ESM::RefNum& { return ref.mId; },
[&](ESM4::ActorCharacter& ref) -> ESM::RefNum& { return ref.mId; },
[&](ESM::CellRef& ref) -> ESM::RefNum& { return ref.mRefNum; }, [&](ESM::CellRef& ref) -> ESM::RefNum& { return ref.mRefNum; },
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -63,6 +70,7 @@ namespace MWWorld
{ {
std::visit(ESM::VisitOverload{ std::visit(ESM::VisitOverload{
[&](ESM4::Reference& ref) { ref.mId = refNum; }, [&](ESM4::Reference& ref) { ref.mId = refNum; },
[&](ESM4::ActorCharacter& ref) { ref.mId = refNum; },
[&](ESM::CellRef& ref) { ref.mRefNum = refNum; }, [&](ESM::CellRef& ref) { ref.mRefNum = refNum; },
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -73,9 +81,11 @@ namespace MWWorld
ESM::Position CellRef::getDoorDest() const ESM::Position CellRef::getDoorDest() const
{ {
return std::visit(ESM::VisitOverload{ return std::visit(
ESM::VisitOverload{
[&](const ESM4::Reference& ref) { return ref.mDoor.destPos; }, [&](const ESM4::Reference& ref) { return ref.mDoor.destPos; },
[&](const ESM::CellRef& ref) -> ESM::Position { return ref.mDoorDest; }, [&](const ESM::CellRef& ref) -> ESM::Position { return ref.mDoorDest; },
[&](const ESM4::ActorCharacter&) -> ESM::Position { throw std::logic_error("Not applicable"); },
}, },
mCellRef.mVariant); mCellRef.mVariant);
} }
@ -102,8 +112,10 @@ namespace MWWorld
return refDest->mParent; return refDest->mParent;
return ESM::RefId(); return ESM::RefId();
}; };
auto actorDestCell
= [&](const ESM4::ActorCharacter&) -> ESM::RefId { throw std::logic_error("Not applicable"); };
return std::visit(ESM::VisitOverload{ esm3Visit, esm4Visit }, mCellRef.mVariant); return std::visit(ESM::VisitOverload{ esm3Visit, esm4Visit, actorDestCell }, mCellRef.mVariant);
} }
void CellRef::setScale(float scale) void CellRef::setScale(float scale)
@ -126,6 +138,7 @@ namespace MWWorld
return std::visit(ESM::VisitOverload{ return std::visit(ESM::VisitOverload{
[&](const ESM4::Reference& /*ref*/) { return 0.f; }, [&](const ESM4::Reference& /*ref*/) { return 0.f; },
[&](const ESM::CellRef& ref) { return ref.mEnchantmentCharge; }, [&](const ESM::CellRef& ref) { return ref.mEnchantmentCharge; },
[&](const ESM4::ActorCharacter&) -> float { throw std::logic_error("Not applicable"); },
}, },
mCellRef.mVariant); mCellRef.mVariant);
} }
@ -155,6 +168,7 @@ namespace MWWorld
std::visit(ESM::VisitOverload{ std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {}, [&](ESM4::Reference& /*ref*/) {},
[&](ESM4::ActorCharacter&) {},
[&](ESM::CellRef& ref) { ref.mEnchantmentCharge = charge; }, [&](ESM::CellRef& ref) { ref.mEnchantmentCharge = charge; },
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -165,6 +179,7 @@ namespace MWWorld
{ {
std::visit(ESM::VisitOverload{ std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {}, [&](ESM4::Reference& /*ref*/) {},
[&](ESM4::ActorCharacter&) {},
[&](ESM::CellRef& ref) { ref.mChargeInt = charge; }, [&](ESM::CellRef& ref) { ref.mChargeInt = charge; },
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -188,9 +203,9 @@ namespace MWWorld
cellRef3.mChargeIntRemainder = newChargeRemainder; cellRef3.mChargeIntRemainder = newChargeRemainder;
} }
}; };
std::visit( std::visit(ESM::VisitOverload{
ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {}, [&](ESM4::Reference& /*ref*/) {},
[&](ESM4::ActorCharacter&) {},
esm3Visit, esm3Visit,
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -200,6 +215,7 @@ namespace MWWorld
{ {
std::visit(ESM::VisitOverload{ std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {}, [&](ESM4::Reference& /*ref*/) {},
[&](ESM4::ActorCharacter&) {},
[&](ESM::CellRef& ref) { ref.mChargeFloat = charge; }, [&](ESM::CellRef& ref) { ref.mChargeFloat = charge; },
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -209,6 +225,7 @@ namespace MWWorld
{ {
return std::visit(ESM::VisitOverload{ return std::visit(ESM::VisitOverload{
[&](const ESM4::Reference& /*ref*/) -> const std::string& { return emptyString; }, [&](const ESM4::Reference& /*ref*/) -> const std::string& { return emptyString; },
[&](const ESM4::ActorCharacter& /*ref*/) -> const std::string& { return emptyString; },
[&](const ESM::CellRef& ref) -> const std::string& { return ref.mGlobalVariable; }, [&](const ESM::CellRef& ref) -> const std::string& { return ref.mGlobalVariable; },
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -221,6 +238,7 @@ namespace MWWorld
mChanged = true; mChanged = true;
std::visit(ESM::VisitOverload{ std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {}, [&](ESM4::Reference& /*ref*/) {},
[&](ESM4::ActorCharacter& /*ref*/) {},
[&](ESM::CellRef& ref) { ref.mGlobalVariable.erase(); }, [&](ESM::CellRef& ref) { ref.mGlobalVariable.erase(); },
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -232,7 +250,9 @@ namespace MWWorld
if (factionRank != getFactionRank()) if (factionRank != getFactionRank())
{ {
mChanged = true; mChanged = true;
std::visit([&](auto&& ref) { ref.mFactionRank = factionRank; }, mCellRef.mVariant); std::visit(ESM::VisitOverload{
[&](ESM4::ActorCharacter&) {}, [&](auto&& ref) { ref.mFactionRank = factionRank; } },
mCellRef.mVariant);
} }
} }
@ -242,6 +262,7 @@ namespace MWWorld
{ {
std::visit(ESM::VisitOverload{ std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {}, [&](ESM4::Reference& /*ref*/) {},
[&](ESM4::ActorCharacter&) {},
[&](ESM::CellRef& ref) { ref.mOwner = owner; }, [&](ESM::CellRef& ref) { ref.mOwner = owner; },
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -255,6 +276,7 @@ namespace MWWorld
mChanged = true; mChanged = true;
std::visit(ESM::VisitOverload{ std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {}, [&](ESM4::Reference& /*ref*/) {},
[&](ESM4::ActorCharacter&) {},
[&](ESM::CellRef& ref) { ref.mSoul = soul; }, [&](ESM::CellRef& ref) { ref.mSoul = soul; },
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -268,6 +290,7 @@ namespace MWWorld
mChanged = true; mChanged = true;
std::visit(ESM::VisitOverload{ std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {}, [&](ESM4::Reference& /*ref*/) {},
[&](ESM4::ActorCharacter&) {},
[&](ESM::CellRef& ref) { ref.mFaction = faction; }, [&](ESM::CellRef& ref) { ref.mFaction = faction; },
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -279,7 +302,12 @@ namespace MWWorld
if (lockLevel != getLockLevel()) if (lockLevel != getLockLevel())
{ {
mChanged = true; mChanged = true;
std::visit([&](auto&& ref) { ref.mLockLevel = lockLevel; }, mCellRef.mVariant); std::visit(ESM::VisitOverload{
[&](ESM4::Reference& ref) { ref.mLockLevel = lockLevel; },
[&](ESM4::ActorCharacter&) {},
[&](ESM::CellRef& ref) { ref.mLockLevel = lockLevel; },
},
mCellRef.mVariant);
} }
} }
@ -297,12 +325,23 @@ namespace MWWorld
bool CellRef::isLocked() const bool CellRef::isLocked() const
{ {
return std::visit([](auto&& ref) { return ref.mIsLocked; }, mCellRef.mVariant); struct Visitor
{
bool operator()(const ESM::CellRef& ref) { return ref.mIsLocked; }
bool operator()(const ESM4::Reference& ref) { return ref.mIsLocked; }
bool operator()(const ESM4::ActorCharacter&) { throw std::logic_error("Not applicable"); }
};
return std::visit(Visitor(), mCellRef.mVariant);
} }
void CellRef::setLocked(bool locked) void CellRef::setLocked(bool locked)
{ {
std::visit([=](auto&& ref) { ref.mIsLocked = locked; }, mCellRef.mVariant); std::visit(ESM::VisitOverload{
[&](ESM4::Reference& ref) { ref.mIsLocked = locked; },
[&](ESM4::ActorCharacter&) {},
[&](ESM::CellRef& ref) { ref.mIsLocked = locked; },
},
mCellRef.mVariant);
} }
void CellRef::setTrap(const ESM::RefId& trap) void CellRef::setTrap(const ESM::RefId& trap)
@ -312,6 +351,7 @@ namespace MWWorld
mChanged = true; mChanged = true;
std::visit(ESM::VisitOverload{ std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {}, [&](ESM4::Reference& /*ref*/) {},
[&](ESM4::ActorCharacter&) {},
[&](ESM::CellRef& ref) { ref.mTrap = trap; }, [&](ESM::CellRef& ref) { ref.mTrap = trap; },
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -325,6 +365,7 @@ namespace MWWorld
mChanged = true; mChanged = true;
std::visit(ESM::VisitOverload{ std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {}, [&](ESM4::Reference& /*ref*/) {},
[&](ESM4::ActorCharacter&) {},
[&](ESM::CellRef& ref) { ref.mKey = key; }, [&](ESM::CellRef& ref) { ref.mKey = key; },
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -338,6 +379,7 @@ namespace MWWorld
mChanged = true; mChanged = true;
std::visit(ESM::VisitOverload{ std::visit(ESM::VisitOverload{
[&](ESM4::Reference& /*ref*/) {}, [&](ESM4::Reference& /*ref*/) {},
[&](ESM4::ActorCharacter&) {},
[&](ESM::CellRef& ref) { ref.mGoldValue = value; }, [&](ESM::CellRef& ref) { ref.mGoldValue = value; },
}, },
mCellRef.mVariant); mCellRef.mVariant);
@ -348,6 +390,7 @@ namespace MWWorld
{ {
std::visit(ESM::VisitOverload{ std::visit(ESM::VisitOverload{
[&](const ESM4::Reference& /*ref*/) {}, [&](const ESM4::Reference& /*ref*/) {},
[&](const ESM4::ActorCharacter&) {},
[&](const ESM::CellRef& ref) { state.mRef = ref; }, [&](const ESM::CellRef& ref) { state.mRef = ref; },
}, },
mCellRef.mVariant); mCellRef.mVariant);

@ -24,6 +24,7 @@ namespace MWWorld
explicit CellRef(const ESM::CellRef& ref); explicit CellRef(const ESM::CellRef& ref);
explicit CellRef(const ESM4::Reference& ref); explicit CellRef(const ESM4::Reference& ref);
explicit CellRef(const ESM4::ActorCharacter& ref);
// Note: Currently unused for items in containers // Note: Currently unused for items in containers
const ESM::RefNum& getRefNum() const; const ESM::RefNum& getRefNum() const;
@ -47,6 +48,7 @@ namespace MWWorld
{ {
ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mRefID; } ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mRefID; }
ESM::RefId operator()(const ESM4::Reference& ref) { return ref.mBaseObj; } ESM::RefId operator()(const ESM4::Reference& ref) { return ref.mBaseObj; }
ESM::RefId operator()(const ESM4::ActorCharacter& ref) { return ref.mBaseObj; }
}; };
return std::visit(Visitor(), mCellRef.mVariant); return std::visit(Visitor(), mCellRef.mVariant);
} }
@ -59,6 +61,7 @@ namespace MWWorld
{ {
bool operator()(const ESM::CellRef& ref) { return ref.mTeleport; } bool operator()(const ESM::CellRef& ref) { return ref.mTeleport; }
bool operator()(const ESM4::Reference& ref) { return !ref.mDoor.destDoor.isZeroOrUnset(); } bool operator()(const ESM4::Reference& ref) { return !ref.mDoor.destDoor.isZeroOrUnset(); }
bool operator()(const ESM4::ActorCharacter&) { throw std::logic_error("Not applicable"); }
}; };
return std::visit(Visitor(), mCellRef.mVariant); return std::visit(Visitor(), mCellRef.mVariant);
} }
@ -101,6 +104,7 @@ namespace MWWorld
{ {
int operator()(const ESM::CellRef& ref) { return ref.mChargeInt; } int operator()(const ESM::CellRef& ref) { return ref.mChargeInt; }
int operator()(const ESM4::Reference& /*ref*/) { return 0; } int operator()(const ESM4::Reference& /*ref*/) { return 0; }
int operator()(const ESM4::ActorCharacter&) { throw std::logic_error("Not applicable"); }
}; };
return std::visit(Visitor(), mCellRef.mVariant); return std::visit(Visitor(), mCellRef.mVariant);
} }
@ -110,6 +114,7 @@ namespace MWWorld
{ {
float operator()(const ESM::CellRef& ref) { return ref.mChargeFloat; } float operator()(const ESM::CellRef& ref) { return ref.mChargeFloat; }
float operator()(const ESM4::Reference& /*ref*/) { return 0; } float operator()(const ESM4::Reference& /*ref*/) { return 0; }
float operator()(const ESM4::ActorCharacter&) { throw std::logic_error("Not applicable"); }
}; };
return std::visit(Visitor(), mCellRef.mVariant); return std::visit(Visitor(), mCellRef.mVariant);
} // Implemented as union with int charge } // Implemented as union with int charge
@ -123,7 +128,8 @@ namespace MWWorld
struct Visitor struct Visitor
{ {
ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mOwner; } ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mOwner; }
ESM::RefId operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId(); } ESM::RefId operator()(const ESM4::Reference& ref) { return ESM::RefId::formIdRefId(ref.mOwner); }
ESM::RefId operator()(const ESM4::ActorCharacter& ref) { return ESM::RefId::formIdRefId(ref.mOwner); }
}; };
return std::visit(Visitor(), mCellRef.mVariant); return std::visit(Visitor(), mCellRef.mVariant);
} }
@ -143,6 +149,7 @@ namespace MWWorld
{ {
ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mSoul; } ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mSoul; }
ESM::RefId operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId(); } ESM::RefId operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId(); }
ESM::RefId operator()(const ESM4::ActorCharacter&) { throw std::logic_error("Not applicable"); }
}; };
return std::visit(Visitor(), mCellRef.mVariant); return std::visit(Visitor(), mCellRef.mVariant);
} }
@ -156,6 +163,7 @@ namespace MWWorld
{ {
ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mFaction; } ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mFaction; }
ESM::RefId operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId(); } ESM::RefId operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId(); }
ESM::RefId operator()(const ESM4::ActorCharacter& /*ref*/) { return ESM::RefId(); }
}; };
return std::visit(Visitor(), mCellRef.mVariant); return std::visit(Visitor(), mCellRef.mVariant);
} }
@ -165,7 +173,13 @@ namespace MWWorld
void setFactionRank(int factionRank); void setFactionRank(int factionRank);
int getFactionRank() const int getFactionRank() const
{ {
return std::visit([&](auto&& ref) { return ref.mFactionRank; }, mCellRef.mVariant); struct Visitor
{
int operator()(const ESM::CellRef& ref) { return ref.mFactionRank; }
int operator()(const ESM4::Reference& ref) { return ref.mFactionRank; }
int operator()(const ESM4::ActorCharacter&) { throw std::logic_error("Not applicable"); }
};
return std::visit(Visitor(), mCellRef.mVariant);
} }
// Lock level for doors and containers // Lock level for doors and containers
@ -173,7 +187,13 @@ namespace MWWorld
// For an unlocked door, it is set to -(previous locklevel) // For an unlocked door, it is set to -(previous locklevel)
int getLockLevel() const int getLockLevel() const
{ {
return std::visit([](auto&& ref) { return static_cast<int>(ref.mLockLevel); }, mCellRef.mVariant); struct Visitor
{
int operator()(const ESM::CellRef& ref) { return ref.mLockLevel; }
int operator()(const ESM4::Reference& ref) { return ref.mLockLevel; }
int operator()(const ESM4::ActorCharacter&) { throw std::logic_error("Not applicable"); }
};
return std::visit(Visitor(), mCellRef.mVariant);
} }
void setLockLevel(int lockLevel); void setLockLevel(int lockLevel);
void lock(int lockLevel); void lock(int lockLevel);
@ -183,7 +203,13 @@ namespace MWWorld
// Key and trap ID names, if any // Key and trap ID names, if any
ESM::RefId getKey() const ESM::RefId getKey() const
{ {
return std::visit([](auto&& ref) -> const ESM::RefId& { return ref.mKey; }, mCellRef.mVariant); struct Visitor
{
ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mKey; }
ESM::RefId operator()(const ESM4::Reference& ref) { return ref.mKey; }
ESM::RefId operator()(const ESM4::ActorCharacter&) { throw std::logic_error("Not applicable"); }
};
return std::visit(Visitor(), mCellRef.mVariant);
} }
void setKey(const ESM::RefId& key); void setKey(const ESM::RefId& key);
ESM::RefId getTrap() const ESM::RefId getTrap() const
@ -192,6 +218,7 @@ namespace MWWorld
{ {
ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mTrap; } ESM::RefId operator()(const ESM::CellRef& ref) { return ref.mTrap; }
ESM::RefId operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId(); } ESM::RefId operator()(const ESM4::Reference& /*ref*/) { return ESM::RefId(); }
ESM::RefId operator()(const ESM4::ActorCharacter&) { throw std::logic_error("Not applicable"); }
}; };
return std::visit(Visitor(), mCellRef.mVariant); return std::visit(Visitor(), mCellRef.mVariant);
} }
@ -204,6 +231,7 @@ namespace MWWorld
{ {
int operator()(const ESM::CellRef& ref) { return ref.mGoldValue; } int operator()(const ESM::CellRef& ref) { return ref.mGoldValue; }
int operator()(const ESM4::Reference& /*ref*/) { return 0; } int operator()(const ESM4::Reference& /*ref*/) { return 0; }
int operator()(const ESM4::ActorCharacter&) { throw std::logic_error("Not applicable"); }
}; };
return std::visit(Visitor(), mCellRef.mVariant); return std::visit(Visitor(), mCellRef.mVariant);
} }

@ -29,6 +29,7 @@ namespace MWWorld
void load(ESM::CellRef& ref, bool deleted, const MWWorld::ESMStore& esmStore); void load(ESM::CellRef& ref, bool deleted, const MWWorld::ESMStore& esmStore);
void load(const ESM4::Reference& ref, const MWWorld::ESMStore& esmStore); void load(const ESM4::Reference& ref, const MWWorld::ESMStore& esmStore);
void load(const ESM4::ActorCharacter& ref, const MWWorld::ESMStore& esmStore);
LiveRef& insert(const LiveRef& item) LiveRef& insert(const LiveRef& item)
{ {

@ -41,6 +41,7 @@
#include <components/esm3/npcstate.hpp> #include <components/esm3/npcstate.hpp>
#include <components/esm3/objectstate.hpp> #include <components/esm3/objectstate.hpp>
#include <components/esm3/readerscache.hpp> #include <components/esm3/readerscache.hpp>
#include <components/esm4/loadachr.hpp>
#include <components/esm4/loadligh.hpp> #include <components/esm4/loadligh.hpp>
#include <components/esm4/loadrefr.hpp> #include <components/esm4/loadrefr.hpp>
#include <components/esm4/loadstat.hpp> #include <components/esm4/loadstat.hpp>
@ -384,24 +385,32 @@ namespace MWWorld
} }
} }
template <typename X> static constexpr bool isESM4ActorRec(unsigned int rec)
void CellRefList<X>::load(const ESM4::Reference& ref, const MWWorld::ESMStore& esmStore)
{ {
return rec == ESM::REC_NPC_4 || rec == ESM::REC_CREA4 || rec == ESM::REC_LVLN4 || rec == ESM::REC_LVLC4;
}
if constexpr (!ESM::isESM4Rec(X::sRecordId)) template <typename X, typename R>
return; static void loadImpl(const R& ref, const MWWorld::ESMStore& esmStore, auto& list)
{
const MWWorld::Store<X>& store = esmStore.get<X>(); const MWWorld::Store<X>& store = esmStore.get<X>();
if (const X* ptr = store.search(ref.mBaseObj)) if (const X* ptr = store.search(ref.mBaseObj))
{ list.emplace_back(ref, ptr);
LiveRef liveCellRef(ref, ptr);
mList.push_back(liveCellRef);
}
else else
{
Log(Debug::Warning) << "Warning: could not resolve cell reference " << ref.mId << " (dropping reference)"; Log(Debug::Warning) << "Warning: could not resolve cell reference " << ref.mId << " (dropping reference)";
} }
template <typename X>
void CellRefList<X>::load(const ESM4::Reference& ref, const MWWorld::ESMStore& esmStore)
{
if constexpr (ESM::isESM4Rec(X::sRecordId) && !isESM4ActorRec(X::sRecordId))
loadImpl<X>(ref, esmStore, mList);
}
template <typename X>
void CellRefList<X>::load(const ESM4::ActorCharacter& ref, const MWWorld::ESMStore& esmStore)
{
if constexpr (isESM4ActorRec(X::sRecordId))
loadImpl<X>(ref, esmStore, mList);
} }
template <typename X> template <typename X>
@ -769,9 +778,21 @@ namespace MWWorld
invocable(*ref); invocable(*ref);
} }
template <typename ReferenceInvocable>
static void visitCell4ActorReferences(
const ESM4::Cell& cell, const ESMStore& esmStore, ESM::ReadersCache& readers, ReferenceInvocable&& invocable)
{
for (const ESM4::ActorCharacter* ref : esmStore.get<ESM4::ActorCharacter>().getByCell(cell.mId))
invocable(*ref);
for (const ESM4::ActorCharacter* ref : esmStore.get<ESM4::ActorCreature>().getByCell(cell.mId))
invocable(*ref);
}
void CellStore::listRefs(const ESM4::Cell& cell) void CellStore::listRefs(const ESM4::Cell& cell)
{ {
visitCell4References(cell, mStore, mReaders, [&](const ESM4::Reference& ref) { mIds.push_back(ref.mBaseObj); }); visitCell4References(cell, mStore, mReaders, [&](const ESM4::Reference& ref) { mIds.push_back(ref.mBaseObj); });
visitCell4ActorReferences(
cell, mStore, mReaders, [&](const ESM4::ActorCharacter& ref) { mIds.push_back(ref.mBaseObj); });
} }
void CellStore::listRefs() void CellStore::listRefs()
@ -836,6 +857,7 @@ namespace MWWorld
void CellStore::loadRefs(const ESM4::Cell& cell, std::map<ESM::RefNum, ESM::RefId>& refNumToID) void CellStore::loadRefs(const ESM4::Cell& cell, std::map<ESM::RefNum, ESM::RefId>& refNumToID)
{ {
visitCell4References(cell, mStore, mReaders, [&](const ESM4::Reference& ref) { loadRef(ref); }); visitCell4References(cell, mStore, mReaders, [&](const ESM4::Reference& ref) { loadRef(ref); });
visitCell4ActorReferences(cell, mStore, mReaders, [&](const ESM4::ActorCharacter& ref) { loadRef(ref); });
} }
void CellStore::loadRefs() void CellStore::loadRefs()
@ -888,6 +910,16 @@ namespace MWWorld
}); });
} }
void CellStore::loadRef(const ESM4::ActorCharacter& ref)
{
const MWWorld::ESMStore& store = mStore;
ESM::RecNameInts foundType = static_cast<ESM::RecNameInts>(store.find(ref.mBaseObj));
Misc::tupleForEach(this->mCellStoreImp->mRefLists, [&ref, &store, foundType](auto& x) {
recNameSwitcher(x, foundType, [&ref, &store](auto& storeIn) { storeIn.load(ref, store); });
});
}
void CellStore::loadRef(ESM::CellRef& ref, bool deleted, std::map<ESM::RefNum, ESM::RefId>& refNumToID) void CellStore::loadRef(ESM::CellRef& ref, bool deleted, std::map<ESM::RefNum, ESM::RefId>& refNumToID)
{ {
const MWWorld::ESMStore& store = mStore; const MWWorld::ESMStore& store = mStore;

@ -58,6 +58,7 @@ namespace ESM4
class Reader; class Reader;
struct Cell; struct Cell;
struct Reference; struct Reference;
struct ActorCharacter;
struct Static; struct Static;
struct Light; struct Light;
struct Activator; struct Activator;
@ -73,6 +74,8 @@ namespace ESM4
struct MiscItem; struct MiscItem;
struct Tree; struct Tree;
struct Weapon; struct Weapon;
struct Creature;
struct Npc;
} }
namespace MWWorld namespace MWWorld
@ -90,7 +93,8 @@ namespace MWWorld
CellRefList<ESM4::Static>, CellRefList<ESM4::Light>, CellRefList<ESM4::Activator>, CellRefList<ESM4::Potion>, CellRefList<ESM4::Static>, CellRefList<ESM4::Light>, CellRefList<ESM4::Activator>, CellRefList<ESM4::Potion>,
CellRefList<ESM4::Ammunition>, CellRefList<ESM4::Armor>, CellRefList<ESM4::Book>, CellRefList<ESM4::Clothing>, CellRefList<ESM4::Ammunition>, CellRefList<ESM4::Armor>, CellRefList<ESM4::Book>, CellRefList<ESM4::Clothing>,
CellRefList<ESM4::Container>, CellRefList<ESM4::Door>, CellRefList<ESM4::Ingredient>, CellRefList<ESM4::Tree>, CellRefList<ESM4::Container>, CellRefList<ESM4::Door>, CellRefList<ESM4::Ingredient>, CellRefList<ESM4::Tree>,
CellRefList<ESM4::MiscItem>, CellRefList<ESM4::Weapon>, CellRefList<ESM4::Furniture>>; CellRefList<ESM4::MiscItem>, CellRefList<ESM4::Weapon>, CellRefList<ESM4::Furniture>,
CellRefList<ESM4::Creature>, CellRefList<ESM4::Npc>>;
/// \brief Mutable state of a cell /// \brief Mutable state of a cell
class CellStore class CellStore
@ -420,6 +424,7 @@ namespace MWWorld
void loadRefs(); void loadRefs();
void loadRef(const ESM4::Reference& ref); void loadRef(const ESM4::Reference& ref);
void loadRef(const ESM4::ActorCharacter& ref);
void loadRef(ESM::CellRef& ref, bool deleted, std::map<ESM::RefNum, ESM::RefId>& refNumToID); void loadRef(ESM::CellRef& ref, bool deleted, std::map<ESM::RefNum, ESM::RefId>& refNumToID);
///< Make case-adjustments to \a ref and insert it into the respective container. ///< Make case-adjustments to \a ref and insert it into the respective container.
/// ///

@ -278,6 +278,8 @@ namespace MWWorld
case ESM::REC_STAT: case ESM::REC_STAT:
case ESM::REC_WEAP: case ESM::REC_WEAP:
case ESM::REC_BODY: case ESM::REC_BODY:
case ESM::REC_ACHR4:
case ESM::REC_ACRE4:
case ESM::REC_STAT4: case ESM::REC_STAT4:
case ESM::REC_LIGH4: case ESM::REC_LIGH4:
case ESM::REC_ACTI4: case ESM::REC_ACTI4:
@ -286,10 +288,15 @@ namespace MWWorld
case ESM::REC_ARMO4: case ESM::REC_ARMO4:
case ESM::REC_BOOK4: case ESM::REC_BOOK4:
case ESM::REC_CONT4: case ESM::REC_CONT4:
case ESM::REC_CREA4:
case ESM::REC_DOOR4: case ESM::REC_DOOR4:
case ESM::REC_FURN4: case ESM::REC_FURN4:
case ESM::REC_INGR4: case ESM::REC_INGR4:
case ESM::REC_LVLC4:
case ESM::REC_LVLN4:
case ESM::REC_MISC4: case ESM::REC_MISC4:
case ESM::REC_NPC_4:
case ESM::REC_RACE4:
case ESM::REC_TREE4: case ESM::REC_TREE4:
case ESM::REC_WEAP4: case ESM::REC_WEAP4:
return true; return true;
@ -439,6 +446,8 @@ namespace MWWorld
getWritable<ESM::Attribute>().setUp(get<ESM::GameSetting>()); getWritable<ESM::Attribute>().setUp(get<ESM::GameSetting>());
getWritable<ESM4::Land>().updateLandPositions(get<ESM4::Cell>()); getWritable<ESM4::Land>().updateLandPositions(get<ESM4::Cell>());
getWritable<ESM4::Reference>().preprocessReferences(get<ESM4::Cell>()); getWritable<ESM4::Reference>().preprocessReferences(get<ESM4::Cell>());
getWritable<ESM4::ActorCharacter>().preprocessReferences(get<ESM4::Cell>());
getWritable<ESM4::ActorCreature>().preprocessReferences(get<ESM4::Cell>());
rebuildIdsIndex(); rebuildIdsIndex();
mStoreImp->mStaticIds = mStoreImp->mIds; mStoreImp->mStaticIds = mStoreImp->mIds;

@ -31,6 +31,8 @@ namespace ESM4
struct Cell; struct Cell;
struct Light; struct Light;
struct Reference; struct Reference;
struct ActorCharacter;
struct ActorCreature;
struct Activator; struct Activator;
struct Potion; struct Potion;
struct Ammunition; struct Ammunition;
@ -38,10 +40,15 @@ namespace ESM4
struct Book; struct Book;
struct Clothing; struct Clothing;
struct Container; struct Container;
struct Creature;
struct Door; struct Door;
struct Furniture; struct Furniture;
struct Ingredient; struct Ingredient;
struct LevelledCreature;
struct LevelledNpc;
struct MiscItem; struct MiscItem;
struct Npc;
struct Race;
struct Tree; struct Tree;
struct Weapon; struct Weapon;
struct World; struct World;
@ -124,7 +131,9 @@ namespace MWWorld
Store<ESM4::Static>, Store<ESM4::Cell>, Store<ESM4::Light>, Store<ESM4::Reference>, Store<ESM4::Activator>, Store<ESM4::Static>, Store<ESM4::Cell>, Store<ESM4::Light>, Store<ESM4::Reference>, Store<ESM4::Activator>,
Store<ESM4::Potion>, Store<ESM4::Ammunition>, Store<ESM4::Armor>, Store<ESM4::Book>, Store<ESM4::Clothing>, Store<ESM4::Potion>, Store<ESM4::Ammunition>, Store<ESM4::Armor>, Store<ESM4::Book>, Store<ESM4::Clothing>,
Store<ESM4::Container>, Store<ESM4::Door>, Store<ESM4::Ingredient>, Store<ESM4::MiscItem>, Store<ESM4::Container>, Store<ESM4::Door>, Store<ESM4::Ingredient>, Store<ESM4::MiscItem>,
Store<ESM4::Tree>, Store<ESM4::Weapon>, Store<ESM4::World>, Store<ESM4::Furniture>, Store<ESM4::Land>>; Store<ESM4::Tree>, Store<ESM4::Weapon>, Store<ESM4::World>, Store<ESM4::Furniture>, Store<ESM4::Land>,
Store<ESM4::ActorCharacter>, Store<ESM4::ActorCreature>, Store<ESM4::Race>, Store<ESM4::Creature>,
Store<ESM4::LevelledCreature>, Store<ESM4::Npc>, Store<ESM4::LevelledNpc>>;
private: private:
template <typename T> template <typename T>

@ -28,6 +28,13 @@ MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM4::Referen
{ {
} }
MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM4::ActorCharacter& cref)
: mClass(&Class::get(type))
, mRef(cref)
, mData(cref)
{
}
void MWWorld::LiveCellRefBase::loadImp(const ESM::ObjectState& state) void MWWorld::LiveCellRefBase::loadImp(const ESM::ObjectState& state)
{ {
mRef = MWWorld::CellRef(state.mRef); mRef = MWWorld::CellRef(state.mRef);

@ -36,6 +36,7 @@ namespace MWWorld
LiveCellRefBase(unsigned int type, const ESM::CellRef& cref = ESM::CellRef()); LiveCellRefBase(unsigned int type, const ESM::CellRef& cref = ESM::CellRef());
LiveCellRefBase(unsigned int type, const ESM4::Reference& cref); LiveCellRefBase(unsigned int type, const ESM4::Reference& cref);
LiveCellRefBase(unsigned int type, const ESM4::ActorCharacter& cref);
/* Need this for the class to be recognized as polymorphic */ /* Need this for the class to be recognized as polymorphic */
virtual ~LiveCellRefBase() {} virtual ~LiveCellRefBase() {}
@ -122,6 +123,12 @@ namespace MWWorld
{ {
} }
LiveCellRef(const ESM4::ActorCharacter& cref, const X* b = nullptr)
: LiveCellRefBase(X::sRecordId, cref)
, mBase(b)
{
}
LiveCellRef(const X* b = nullptr) LiveCellRef(const X* b = nullptr)
: LiveCellRefBase(X::sRecordId) : LiveCellRefBase(X::sRecordId)
, mBase(b) , mBase(b)

@ -1,6 +1,8 @@
#include "refdata.hpp" #include "refdata.hpp"
#include <components/esm3/objectstate.hpp> #include <components/esm3/objectstate.hpp>
#include <components/esm4/loadachr.hpp>
#include <components/esm4/loadrefr.hpp>
#include <components/sceneutil/positionattitudetransform.hpp> #include <components/sceneutil/positionattitudetransform.hpp>
#include "customdata.hpp" #include "customdata.hpp"
@ -98,6 +100,19 @@ namespace MWWorld
{ {
} }
RefData::RefData(const ESM4::ActorCharacter& ref)
: mBaseNode(nullptr)
, mDeletedByContentFile(ref.mFlags & ESM4::Rec_Deleted)
, mEnabled(!(ref.mFlags & ESM4::Rec_Disabled))
, mPhysicsPostponed(false)
, mCount(mDeletedByContentFile ? 0 : 1)
, mPosition(ref.mPos)
, mCustomData(nullptr)
, mChanged(false)
, mFlags(0)
{
}
RefData::RefData(const ESM::ObjectState& objectState, bool deletedByContentFile) RefData::RefData(const ESM::ObjectState& objectState, bool deletedByContentFile)
: mBaseNode(nullptr) : mBaseNode(nullptr)
, mDeletedByContentFile(deletedByContentFile) , mDeletedByContentFile(deletedByContentFile)

@ -27,6 +27,7 @@ namespace ESM
namespace ESM4 namespace ESM4
{ {
struct ActorCharacter;
struct Reference; struct Reference;
} }
@ -82,6 +83,7 @@ namespace MWWorld
/// to reset the position as the original data is still held in the CellRef /// to reset the position as the original data is still held in the CellRef
RefData(const ESM::CellRef& cellRef); RefData(const ESM::CellRef& cellRef);
RefData(const ESM4::Reference& cellRef); RefData(const ESM4::Reference& cellRef);
RefData(const ESM4::ActorCharacter& cellRef);
RefData(const ESM::ObjectState& objectState, bool deletedByContentFile); RefData(const ESM::ObjectState& objectState, bool deletedByContentFile);
///< Ignores local variables and custom data (not enough context available here to ///< Ignores local variables and custom data (not enough context available here to

@ -1326,33 +1326,6 @@ namespace MWWorld
return nullptr; return nullptr;
return foundLand->second; return foundLand->second;
} }
// ESM4 Reference
//=========================================================================
void Store<ESM4::Reference>::preprocessReferences(const Store<ESM4::Cell>& cells)
{
for (auto& [_, ref] : mStatic)
{
const ESM4::Cell* cell = cells.find(ref.mParent);
if (cell->isExterior() && (cell->mFlags & ESM4::Rec_Persistent))
{
const ESM4::Cell* actualCell = cells.searchExterior(
positionToExteriorCellLocation(ref.mPos.pos[0], ref.mPos.pos[1], cell->mParent));
if (actualCell)
ref.mParent = actualCell->mId;
}
mPerCellReferences[ref.mParent].push_back(&ref);
}
}
std::span<const ESM4::Reference* const> Store<ESM4::Reference>::getByCell(ESM::RefId cellId) const
{
auto it = mPerCellReferences.find(cellId);
if (it == mPerCellReferences.end())
return {};
return it->second;
}
} }
template class MWWorld::TypedDynamicStore<ESM::Activator>; template class MWWorld::TypedDynamicStore<ESM::Activator>;
@ -1405,14 +1378,21 @@ template class MWWorld::TypedDynamicStore<ESM4::Armor>;
template class MWWorld::TypedDynamicStore<ESM4::Book>; template class MWWorld::TypedDynamicStore<ESM4::Book>;
template class MWWorld::TypedDynamicStore<ESM4::Clothing>; template class MWWorld::TypedDynamicStore<ESM4::Clothing>;
template class MWWorld::TypedDynamicStore<ESM4::Container>; template class MWWorld::TypedDynamicStore<ESM4::Container>;
template class MWWorld::TypedDynamicStore<ESM4::Creature>;
template class MWWorld::TypedDynamicStore<ESM4::Door>; template class MWWorld::TypedDynamicStore<ESM4::Door>;
template class MWWorld::TypedDynamicStore<ESM4::Furniture>; template class MWWorld::TypedDynamicStore<ESM4::Furniture>;
template class MWWorld::TypedDynamicStore<ESM4::Ingredient>; template class MWWorld::TypedDynamicStore<ESM4::Ingredient>;
template class MWWorld::TypedDynamicStore<ESM4::MiscItem>; template class MWWorld::TypedDynamicStore<ESM4::MiscItem>;
template class MWWorld::TypedDynamicStore<ESM4::Static>; template class MWWorld::TypedDynamicStore<ESM4::Static>;
template class MWWorld::TypedDynamicStore<ESM4::Tree>; template class MWWorld::TypedDynamicStore<ESM4::Tree>;
template class MWWorld::TypedDynamicStore<ESM4::LevelledCreature>;
template class MWWorld::TypedDynamicStore<ESM4::LevelledNpc>;
template class MWWorld::TypedDynamicStore<ESM4::Light>; template class MWWorld::TypedDynamicStore<ESM4::Light>;
template class MWWorld::TypedDynamicStore<ESM4::Npc>;
template class MWWorld::TypedDynamicStore<ESM4::Reference, ESM::FormId>; template class MWWorld::TypedDynamicStore<ESM4::Reference, ESM::FormId>;
template class MWWorld::TypedDynamicStore<ESM4::ActorCharacter, ESM::FormId>;
template class MWWorld::TypedDynamicStore<ESM4::ActorCreature, ESM::FormId>;
template class MWWorld::TypedDynamicStore<ESM4::Race>;
template class MWWorld::TypedDynamicStore<ESM4::Cell>; template class MWWorld::TypedDynamicStore<ESM4::Cell>;
template class MWWorld::TypedDynamicStore<ESM4::Weapon>; template class MWWorld::TypedDynamicStore<ESM4::Weapon>;
template class MWWorld::TypedDynamicStore<ESM4::World>; template class MWWorld::TypedDynamicStore<ESM4::World>;

@ -18,6 +18,7 @@
#include <components/esm3/loadland.hpp> #include <components/esm3/loadland.hpp>
#include <components/esm3/loadpgrd.hpp> #include <components/esm3/loadpgrd.hpp>
#include <components/esm3/loadskil.hpp> #include <components/esm3/loadskil.hpp>
#include <components/esm4/loadachr.hpp>
#include <components/esm4/loadcell.hpp> #include <components/esm4/loadcell.hpp>
#include <components/esm4/loadland.hpp> #include <components/esm4/loadland.hpp>
#include <components/esm4/loadrefr.hpp> #include <components/esm4/loadrefr.hpp>
@ -581,16 +582,51 @@ namespace MWWorld
const MWDialogue::KeywordSearch<int>& getDialogIdKeywordSearch() const; const MWDialogue::KeywordSearch<int>& getDialogIdKeywordSearch() const;
}; };
template <> template <typename T>
class Store<ESM4::Reference> : public TypedDynamicStore<ESM4::Reference, ESM::FormId> class ESM4RefsStore : public TypedDynamicStore<T, ESM::FormId>
{ {
public: public:
void preprocessReferences(const Store<ESM4::Cell>& cells); void preprocessReferences(const Store<ESM4::Cell>& cells)
{
for (auto& [_, ref] : this->mStatic)
{
const ESM4::Cell* cell = cells.find(ref.mParent);
if (cell->isExterior() && (cell->mFlags & ESM4::Rec_Persistent))
{
const ESM4::Cell* actualCell = cells.searchExterior(
positionToExteriorCellLocation(ref.mPos.pos[0], ref.mPos.pos[1], cell->mParent));
if (actualCell)
ref.mParent = actualCell->mId;
}
mPerCellReferences[ref.mParent].push_back(&ref);
}
}
std::span<const ESM4::Reference* const> getByCell(ESM::RefId cellId) const; std::span<const T* const> getByCell(ESM::RefId cellId) const
{
auto it = mPerCellReferences.find(cellId);
if (it == mPerCellReferences.end())
return {};
return it->second;
}
private: private:
std::unordered_map<ESM::RefId, std::vector<ESM4::Reference*>> mPerCellReferences; std::unordered_map<ESM::RefId, std::vector<T*>> mPerCellReferences;
};
template <>
class Store<ESM4::Reference> : public ESM4RefsStore<ESM4::Reference>
{
};
template <>
class Store<ESM4::ActorCharacter> : public ESM4RefsStore<ESM4::ActorCharacter>
{
};
template <>
class Store<ESM4::ActorCreature> : public ESM4RefsStore<ESM4::ActorCreature>
{
}; };
} // end namespace } // end namespace

@ -6,7 +6,6 @@
#include <components/esm4/inventory.hpp> #include <components/esm4/inventory.hpp>
#include <components/esm4/lighting.hpp> #include <components/esm4/lighting.hpp>
#include <components/esm4/loadachr.hpp> #include <components/esm4/loadachr.hpp>
#include <components/esm4/loadacre.hpp>
#include <components/esm4/loadacti.hpp> #include <components/esm4/loadacti.hpp>
#include <components/esm4/loadalch.hpp> #include <components/esm4/loadalch.hpp>
#include <components/esm4/loadaloc.hpp> #include <components/esm4/loadaloc.hpp>

@ -161,7 +161,6 @@ add_component_dir (esm4
inventory inventory
lighting lighting
loadachr loadachr
loadacre
loadacti loadacti
loadalch loadalch
loadaloc loadaloc

@ -1,10 +1,12 @@
#ifndef COMPONENTS_ESM_ESMBRIDGE #ifndef COMPONENTS_ESM_ESMBRIDGE
#define COMPONENTS_ESM_ESMBRIDGE #define COMPONENTS_ESM_ESMBRIDGE
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <variant> #include <variant>
#include <components/esm3/cellref.hpp> #include <components/esm3/cellref.hpp>
#include <components/esm4/loadachr.hpp>
#include <components/esm4/loadrefr.hpp> #include <components/esm4/loadrefr.hpp>
namespace ESM4 namespace ESM4
@ -52,25 +54,22 @@ namespace ESM
struct ReferenceVariant struct ReferenceVariant
{ {
std::variant<ESM::CellRef, ESM4::Reference> mVariant; std::variant<ESM::CellRef, ESM4::Reference, ESM4::ActorCharacter> mVariant;
explicit ReferenceVariant(const ESM4::Reference& ref) explicit ReferenceVariant(const ESM4::Reference& ref)
: mVariant(ref) : mVariant(ref)
{ {
} }
explicit ReferenceVariant(const ESM::CellRef& ref) explicit ReferenceVariant(const ESM4::ActorCharacter& ref)
: mVariant(ref) : mVariant(ref)
{ {
} }
bool isESM4() const { return std::holds_alternative<ESM4::Reference>(mVariant); } explicit ReferenceVariant(const ESM::CellRef& ref)
: mVariant(ref)
const ESM::CellRef& getEsm3() const { return std::get<ESM::CellRef>(mVariant); } {
const ESM4::Reference& getEsm4() const { return std::get<ESM4::Reference>(mVariant); } }
ESM::CellRef& getEsm3() { return std::get<ESM::CellRef>(mVariant); }
ESM4::Reference& getEsm4() { return std::get<ESM4::Reference>(mVariant); }
}; };
template <class F, class... T> template <class F, class... T>

@ -51,11 +51,16 @@
#include <components/esm4/loadcell.hpp> #include <components/esm4/loadcell.hpp>
#include <components/esm4/loadclot.hpp> #include <components/esm4/loadclot.hpp>
#include <components/esm4/loadcont.hpp> #include <components/esm4/loadcont.hpp>
#include <components/esm4/loadcrea.hpp>
#include <components/esm4/loaddoor.hpp> #include <components/esm4/loaddoor.hpp>
#include <components/esm4/loadfurn.hpp> #include <components/esm4/loadfurn.hpp>
#include <components/esm4/loadingr.hpp> #include <components/esm4/loadingr.hpp>
#include <components/esm4/loadligh.hpp> #include <components/esm4/loadligh.hpp>
#include <components/esm4/loadlvlc.hpp>
#include <components/esm4/loadlvln.hpp>
#include <components/esm4/loadmisc.hpp> #include <components/esm4/loadmisc.hpp>
#include <components/esm4/loadnpc.hpp>
#include <components/esm4/loadrace.hpp>
#include <components/esm4/loadrefr.hpp> #include <components/esm4/loadrefr.hpp>
#include <components/esm4/loadstat.hpp> #include <components/esm4/loadstat.hpp>
#include <components/esm4/loadtree.hpp> #include <components/esm4/loadtree.hpp>

@ -33,10 +33,10 @@
void ESM4::ActorCharacter::load(ESM4::Reader& reader) void ESM4::ActorCharacter::load(ESM4::Reader& reader)
{ {
mFormId = reader.hdr().record.getFormId(); mId = reader.hdr().record.getFormId();
reader.adjustFormId(mFormId); reader.adjustFormId(mId);
mFlags = reader.hdr().record.flags; mFlags = reader.hdr().record.flags;
mParent = reader.currCell(); // NOTE: only for persistent achr? (aren't they all persistent?) mParent = ESM::RefId::formIdRefId(reader.currCell());
while (reader.getSubRecordHeader()) while (reader.getSubRecordHeader())
{ {
@ -50,10 +50,14 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader)
reader.getZString(mFullName); reader.getZString(mFullName);
break; break;
case ESM4::SUB_NAME: case ESM4::SUB_NAME:
reader.getFormId(mBaseObj); {
FormId baseId;
reader.getFormId(baseId);
mBaseObj = ESM::RefId::formIdRefId(baseId);
break; break;
}
case ESM4::SUB_DATA: case ESM4::SUB_DATA:
reader.get(mPlacement); reader.get(mPos);
break; break;
case ESM4::SUB_XSCL: case ESM4::SUB_XSCL:
reader.get(mScale); reader.get(mScale);
@ -97,7 +101,7 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader)
reader.skipSubRecordData(); reader.skipSubRecordData();
break; break;
default: default:
throw std::runtime_error("ESM4::ACHR::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); throw std::runtime_error("ESM4 ACHR/ACRE load - Unknown subrecord " + ESM::printName(subHdr.typeId));
} }
} }
} }

@ -29,6 +29,9 @@
#include <cstdint> #include <cstdint>
#include <components/esm/defs.hpp>
#include <components/esm/refid.hpp>
#include "reference.hpp" // FormId, Placement, EnableParent #include "reference.hpp" // FormId, Placement, EnableParent
namespace ESM4 namespace ESM4
@ -38,22 +41,19 @@ namespace ESM4
struct ActorCharacter struct ActorCharacter
{ {
FormId mParent; // cell formId, from the loading sequence ESM::FormId mId; // from the header
ESM::RefId mParent; // cell FormId , from the loading sequence
// NOTE: for exterior cells it will be the dummy cell FormId // NOTE: for exterior cells it will be the dummy cell FormId
FormId mFormId; // from the header
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
std::string mEditorId; std::string mEditorId;
std::string mFullName; std::string mFullName;
FormId mBaseObj; ESM::RefId mBaseObj;
Placement mPlacement; ESM::Position mPos;
float mScale = 1.0f; float mScale = 1.0f;
FormId mOwner; ESM::FormId mOwner;
FormId mGlobal; ESM::FormId mGlobal;
bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled)
EnableParent mEsp; EnableParent mEsp;
@ -61,6 +61,12 @@ namespace ESM4
// void save(ESM4::Writer& writer) const; // void save(ESM4::Writer& writer) const;
// void blank(); // void blank();
static constexpr ESM::RecNameInts sRecordId = ESM::REC_ACHR4;
};
struct ActorCreature : public ActorCharacter
{
static constexpr ESM::RecNameInts sRecordId = ESM::REC_ACRE4;
}; };
} }

@ -1,96 +0,0 @@
/*
Copyright (C) 2016, 2018, 2020 cc9cii
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
cc9cii cc9c@iinet.net.au
Much of the information on the data structures are based on the information
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
trial & error. See http://en.uesp.net/wiki for details.
*/
#include "loadacre.hpp"
#include <stdexcept>
#include "reader.hpp"
//#include "writer.hpp"
void ESM4::ActorCreature::load(ESM4::Reader& reader)
{
mFormId = reader.hdr().record.getFormId();
reader.adjustFormId(mFormId);
mFlags = reader.hdr().record.flags;
while (reader.getSubRecordHeader())
{
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
switch (subHdr.typeId)
{
case ESM4::SUB_EDID:
reader.getZString(mEditorId);
break;
case ESM4::SUB_NAME:
reader.getFormId(mBaseObj);
break;
case ESM4::SUB_DATA:
reader.get(mPlacement);
break;
case ESM4::SUB_XSCL:
reader.get(mScale);
break;
case ESM4::SUB_XESP:
reader.getFormId(mEsp.parent);
reader.get(mEsp.flags);
break;
case ESM4::SUB_XOWN:
reader.getFormId(mOwner);
break;
case ESM4::SUB_XGLB:
reader.getFormId(mGlobal);
break;
case ESM4::SUB_XRNK:
reader.get(mFactionRank);
break;
case ESM4::SUB_XLKR: // FO3
case ESM4::SUB_XLCM: // FO3
case ESM4::SUB_XEZN: // FO3
case ESM4::SUB_XMRC: // FO3
case ESM4::SUB_XAPD: // FO3
case ESM4::SUB_XAPR: // FO3
case ESM4::SUB_XRDS: // FO3
case ESM4::SUB_XPRD: // FO3
case ESM4::SUB_XATO: // FONV
// seems to occur only for dead bodies, e.g. DeadMuffy, DeadDogVicious
case ESM4::SUB_XRGD: // ragdoll
case ESM4::SUB_XRGB: // ragdoll biped
reader.skipSubRecordData();
break;
default:
throw std::runtime_error("ESM4::ACRE::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
}
}
}
// void ESM4::ActorCreature::save(ESM4::Writer& writer) const
//{
// }
// void ESM4::ActorCreature::blank()
//{
// }

@ -1,64 +0,0 @@
/*
Copyright (C) 2016, 2018, 2020 cc9cii
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
cc9cii cc9c@iinet.net.au
Much of the information on the data structures are based on the information
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
trial & error. See http://en.uesp.net/wiki for details.
*/
#ifndef ESM4_ACRE_H
#define ESM4_ACRE_H
#include <cstdint>
#include "reference.hpp" // FormId, Placement, EnableParent
namespace ESM4
{
class Reader;
class Writer;
struct ActorCreature
{
FormId mFormId; // from the header
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
std::string mEditorId;
FormId mBaseObj;
Placement mPlacement;
float mScale = 1.0f;
FormId mOwner;
FormId mGlobal;
std::uint32_t mFactionRank;
bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled)
EnableParent mEsp;
void load(ESM4::Reader& reader);
// void save(ESM4::Writer& writer) const;
// void blank();
};
}
#endif // ESM4_ACRE_H

@ -37,8 +37,7 @@
void ESM4::Creature::load(ESM4::Reader& reader) void ESM4::Creature::load(ESM4::Reader& reader)
{ {
mFormId = reader.hdr().record.getFormId(); mId = reader.getRefIdFromHeader();
reader.adjustFormId(mFormId);
mFlags = reader.hdr().record.flags; mFlags = reader.hdr().record.flags;
while (reader.getSubRecordHeader()) while (reader.getSubRecordHeader())
@ -153,7 +152,7 @@ void ESM4::Creature::load(ESM4::Reader& reader)
std::uint32_t nift; std::uint32_t nift;
reader.get(nift); reader.get(nift);
if (nift) if (nift)
Log(Debug::Verbose) << "CREA NIFT " << mFormId << ", non-zero " << nift; Log(Debug::Verbose) << "CREA NIFT " << mId << ", non-zero " << nift;
break; break;
} }
case ESM4::SUB_KFFZ: case ESM4::SUB_KFFZ:

@ -31,6 +31,9 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <components/esm/defs.hpp>
#include <components/esm/refid.hpp>
#include "actor.hpp" #include "actor.hpp"
#include "inventory.hpp" #include "inventory.hpp"
@ -103,7 +106,7 @@ namespace ESM4
}; };
#pragma pack(pop) #pragma pack(pop)
FormId mFormId; // from the header ESM::RefId mId; // from the header
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
std::string mEditorId; std::string mEditorId;
@ -142,6 +145,7 @@ namespace ESM4
// void save(ESM4::Writer& writer) const; // void save(ESM4::Writer& writer) const;
// void blank(); // void blank();
static constexpr ESM::RecNameInts sRecordId = ESM::RecNameInts::REC_CREA4;
}; };
} }

@ -33,8 +33,7 @@
void ESM4::LevelledCreature::load(ESM4::Reader& reader) void ESM4::LevelledCreature::load(ESM4::Reader& reader)
{ {
mFormId = reader.hdr().record.getFormId(); mId = reader.getRefIdFromHeader();
reader.adjustFormId(mFormId);
mFlags = reader.hdr().record.flags; mFlags = reader.hdr().record.flags;
while (reader.getSubRecordHeader()) while (reader.getSubRecordHeader())

@ -30,6 +30,9 @@
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include <components/esm/defs.hpp>
#include <components/esm/refid.hpp>
#include "formid.hpp" #include "formid.hpp"
#include "inventory.hpp" #include "inventory.hpp"
@ -40,7 +43,7 @@ namespace ESM4
struct LevelledCreature struct LevelledCreature
{ {
FormId mFormId; // from the header ESM::RefId mId; // from the header
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
std::string mEditorId; std::string mEditorId;
@ -62,6 +65,7 @@ namespace ESM4
// void save(ESM4::Writer& writer) const; // void save(ESM4::Writer& writer) const;
// void blank(); // void blank();
static constexpr ESM::RecNameInts sRecordId = ESM::RecNameInts::REC_LVLC4;
}; };
} }

@ -33,8 +33,7 @@
void ESM4::LevelledNpc::load(ESM4::Reader& reader) void ESM4::LevelledNpc::load(ESM4::Reader& reader)
{ {
mFormId = reader.hdr().record.getFormId(); mId = reader.getRefIdFromHeader();
reader.adjustFormId(mFormId);
mFlags = reader.hdr().record.flags; mFlags = reader.hdr().record.flags;
// std::uint32_t esmVer = reader.esmVersion(); // currently unused // std::uint32_t esmVer = reader.esmVersion(); // currently unused

@ -31,6 +31,9 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <components/esm/defs.hpp>
#include <components/esm/refid.hpp>
#include "formid.hpp" #include "formid.hpp"
#include "inventory.hpp" // LVLO #include "inventory.hpp" // LVLO
@ -41,7 +44,7 @@ namespace ESM4
struct LevelledNpc struct LevelledNpc
{ {
FormId mFormId; // from the header ESM::RefId mId; // from the header
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
std::string mEditorId; std::string mEditorId;
@ -61,6 +64,7 @@ namespace ESM4
// void save(ESM4::Writer& writer) const; // void save(ESM4::Writer& writer) const;
// void blank(); // void blank();
static constexpr ESM::RecNameInts sRecordId = ESM::RecNameInts::REC_LVLN4;
}; };
} }

@ -36,8 +36,7 @@
void ESM4::Npc::load(ESM4::Reader& reader) void ESM4::Npc::load(ESM4::Reader& reader)
{ {
mFormId = reader.hdr().record.getFormId(); mId = reader.getRefIdFromHeader();
reader.adjustFormId(mFormId);
mFlags = reader.hdr().record.flags; mFlags = reader.hdr().record.flags;
std::uint32_t esmVer = reader.esmVersion(); std::uint32_t esmVer = reader.esmVersion();

@ -31,6 +31,9 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <components/esm/defs.hpp>
#include <components/esm/refid.hpp>
#include "actor.hpp" #include "actor.hpp"
#include "inventory.hpp" #include "inventory.hpp"
@ -164,7 +167,7 @@ namespace ESM4
#pragma pack(pop) #pragma pack(pop)
FormId mFormId; // from the header ESM::RefId mId; // from the header
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
bool mIsTES4; bool mIsTES4;
@ -221,6 +224,7 @@ namespace ESM4
// void save(ESM4::Writer& writer) const; // void save(ESM4::Writer& writer) const;
// void blank(); // void blank();
static constexpr ESM::RecNameInts sRecordId = ESM::RecNameInts::REC_NPC_4;
}; };
} }

@ -35,8 +35,7 @@
void ESM4::Race::load(ESM4::Reader& reader) void ESM4::Race::load(ESM4::Reader& reader)
{ {
mFormId = reader.hdr().record.getFormId(); mId = reader.getRefIdFromHeader();
reader.adjustFormId(mFormId);
mFlags = reader.hdr().record.flags; mFlags = reader.hdr().record.flags;
std::uint32_t esmVer = reader.esmVersion(); std::uint32_t esmVer = reader.esmVersion();

@ -32,6 +32,9 @@
#include <map> #include <map>
#include <vector> #include <vector>
#include <components/esm/defs.hpp>
#include <components/esm/refid.hpp>
#include "actor.hpp" // AttributeValues, BodyTemplate #include "actor.hpp" // AttributeValues, BodyTemplate
#include "formid.hpp" #include "formid.hpp"
@ -106,7 +109,7 @@ namespace ESM4
std::string texture; // can be empty e.g. eye left, eye right std::string texture; // can be empty e.g. eye left, eye right
}; };
FormId mFormId; // from the header ESM::RefId mId; // from the header
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
bool mIsTES5; bool mIsTES5;
@ -165,6 +168,7 @@ namespace ESM4
// void save(ESM4::Writer& writer) const; // void save(ESM4::Writer& writer) const;
// void blank(); // void blank();
static constexpr ESM::RecNameInts sRecordId = ESM::RecNameInts::REC_RACE4;
}; };
} }

@ -36,11 +36,8 @@ void ESM4::Reference::load(ESM4::Reader& reader)
mId = reader.hdr().record.getFormId(); mId = reader.hdr().record.getFormId();
reader.adjustFormId(mId); reader.adjustFormId(mId);
mFlags = reader.hdr().record.flags; mFlags = reader.hdr().record.flags;
mParentFormId = reader.currCell(); // NOTE: only for persistent refs? mParent = ESM::RefId::formIdRefId(reader.currCell());
mParent = ESM::RefId::formIdRefId(mParentFormId);
// TODO: Let the engine apply this? Saved games?
// mInitiallyDisabled = ((mFlags & ESM4::Rec_Disabled) != 0) ? true : false;
std::uint32_t esmVer = reader.esmVersion(); std::uint32_t esmVer = reader.esmVersion();
bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134;
@ -63,15 +60,6 @@ void ESM4::Reference::load(ESM4::Reader& reader)
FormId BaseId; FormId BaseId;
reader.getFormId(BaseId); reader.getFormId(BaseId);
mBaseObj = ESM::RefId::formIdRefId(BaseId); mBaseObj = ESM::RefId::formIdRefId(BaseId);
#if 0
if (mFlags & ESM4::Rec_Disabled)
std::cout << "REFR disable at start " << formIdToString(mFormId) <<
" baseobj " << formIdToString(mBaseObj) <<
" " << (mEditorId.empty() ? "" : mEditorId) << std::endl; // FIXME
#endif
// if (mBaseObj == 0x20) // e.g. FO3 mFormId == 0x0007E90F
// if (mBaseObj == 0x17)
// std::cout << mEditorId << std::endl;
break; break;
} }
case ESM4::SUB_DATA: case ESM4::SUB_DATA:

@ -29,7 +29,7 @@
#include <cstdint> #include <cstdint>
#include "reference.hpp" // FormId, Placement, EnableParent #include "reference.hpp" // EnableParent
#include <components/esm/defs.hpp> #include <components/esm/defs.hpp>
#include <components/esm/refid.hpp> #include <components/esm/refid.hpp>
@ -58,7 +58,7 @@ namespace ESM4
struct TeleportDest struct TeleportDest
{ {
FormId destDoor; ESM::FormId destDoor;
ESM::Position destPos; ESM::Position destPos;
std::uint32_t flags = 0; // 0x01 no alarm (only in TES5) std::uint32_t flags = 0; // 0x01 no alarm (only in TES5)
}; };
@ -69,16 +69,15 @@ namespace ESM4
// 0 radius, 1 everywhere, 2 worldspace and linked int, 3 linked int, 4 current cell only // 0 radius, 1 everywhere, 2 worldspace and linked int, 3 linked int, 4 current cell only
std::uint32_t broadcastRange; std::uint32_t broadcastRange;
float staticPercentage; float staticPercentage;
FormId posReference; // only used if broadcastRange == 0 ESM::FormId posReference; // only used if broadcastRange == 0
}; };
struct Reference struct Reference
{ {
FormId mId; // from the header ESM::FormId mId; // from the header
FormId mParentFormId; // cell FormId (currently persistent refs only), from the loading sequence ESM::RefId mParent; // cell FormId, from the loading sequence
// NOTE: for exterior cells it will be the dummy cell FormId // NOTE: for exterior cells it will be the dummy cell FormId
ESM::RefId mParent;
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
@ -88,19 +87,18 @@ namespace ESM4
ESM::Position mPos; ESM::Position mPos;
float mScale = 1.0f; float mScale = 1.0f;
FormId mOwner; ESM::FormId mOwner;
FormId mGlobal; ESM::FormId mGlobal;
std::int32_t mFactionRank = -1; std::int32_t mFactionRank = -1;
bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled) bool mIsMapMarker = false;
bool mIsMapMarker;
std::uint16_t mMapMarker; std::uint16_t mMapMarker;
EnableParent mEsp; EnableParent mEsp;
std::uint32_t mCount = 1; // only if > 1 std::uint32_t mCount = 1; // only if > 1
FormId mAudioLocation; ESM::FormId mAudioLocation;
RadioStationData mRadio; RadioStationData mRadio;
@ -109,7 +107,7 @@ namespace ESM4
std::int8_t mLockLevel; std::int8_t mLockLevel;
ESM::RefId mKey; ESM::RefId mKey;
FormId mTargetRef; ESM::FormId mTargetRef;
void load(ESM4::Reader& reader); void load(ESM4::Reader& reader);
// void save(ESM4::Writer& writer) const; // void save(ESM4::Writer& writer) const;

@ -2,7 +2,6 @@
#define COMPONENTS_ESM4_RECORDS_H #define COMPONENTS_ESM4_RECORDS_H
#include <components/esm4/loadachr.hpp> #include <components/esm4/loadachr.hpp>
#include <components/esm4/loadacre.hpp>
#include <components/esm4/loadacti.hpp> #include <components/esm4/loadacti.hpp>
#include <components/esm4/loadalch.hpp> #include <components/esm4/loadalch.hpp>
#include <components/esm4/loadaloc.hpp> #include <components/esm4/loadaloc.hpp>

@ -30,37 +30,22 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include "formid.hpp" #include <components/esm/defs.hpp>
#include <components/esm/formid.hpp>
namespace ESM4 namespace ESM4
{ {
#pragma pack(push, 1)
struct Vector3
{
float x;
float y;
float z;
};
// REFR, ACHR, ACRE
struct Placement
{
Vector3 pos;
Vector3 rot; // angles are in radian, rz applied first and rx applied last
};
#pragma pack(pop)
// REFR, ACHR, ACRE // REFR, ACHR, ACRE
struct EnableParent struct EnableParent
{ {
FormId parent; ESM::FormId parent;
std::uint32_t flags; // 0x0001 = Set Enable State Opposite Parent, 0x0002 = Pop In std::uint32_t flags; // 0x0001 = Set Enable State Opposite Parent, 0x0002 = Pop In
}; };
struct LODReference struct LODReference
{ {
FormId baseObj; ESM::FormId baseObj;
Placement placement; ESM::Position placement;
float scale; float scale;
}; };
} }

Loading…
Cancel
Save