#include "cellref.hpp" #include #include #include #include "esmreader.hpp" #include "esmwriter.hpp" namespace { constexpr int ZeroLock = std::numeric_limits::max(); } namespace ESM { namespace { template void loadIdImpl(ESMReader& esm, bool wideRefNum, CellRef& cellRef) { // According to Hrnchamd, this does not belong to the actual ref. Instead, it is a marker indicating that // the following refs are part of a "temp refs" section. A temp ref is not being tracked by the moved // references system. Its only purpose is a performance optimization for "immovable" things. We don't need // this, and it's problematic anyway, because any item can theoretically be moved by a script. if (esm.isNextSub("NAM0")) esm.skipHSub(); if constexpr (load) { cellRef.blank(); cellRef.mRefNum = esm.getFormId(wideRefNum); cellRef.mRefID = esm.getHNORefId("NAME"); if (cellRef.mRefID.empty()) Log(Debug::Warning) << "Warning: got CellRef with empty RefId in " << esm.getName() << " 0x" << std::hex << esm.getFileOffset(); } else { esm.getFormId(wideRefNum); esm.skipHNORefId("NAME"); } } template void loadDataImpl(ESMReader& esm, bool& isDeleted, CellRef& cellRef) { const auto getRefIdOrSkip = [&](ESM::RefId& refId) { if constexpr (load) refId = esm.getRefId(); else esm.skipHRefId(); }; const auto getHStringOrSkip = [&](std::string& value) { if constexpr (load) value = esm.getHString(); else esm.skipHString(); }; const auto getHTOrSkip = [&](auto& value) { if constexpr (load) esm.getHT(value); else esm.skipHT>(); }; if constexpr (load) isDeleted = false; bool isLoaded = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case fourCC("UNAM"): getHTOrSkip(cellRef.mReferenceBlocked); break; case fourCC("XSCL"): getHTOrSkip(cellRef.mScale); if constexpr (load) cellRef.mScale = std::clamp(cellRef.mScale, 0.5f, 2.0f); break; case fourCC("ANAM"): getRefIdOrSkip(cellRef.mOwner); break; case fourCC("BNAM"): getHStringOrSkip(cellRef.mGlobalVariable); break; case fourCC("XSOL"): getRefIdOrSkip(cellRef.mSoul); break; case fourCC("CNAM"): getRefIdOrSkip(cellRef.mFaction); break; case fourCC("INDX"): getHTOrSkip(cellRef.mFactionRank); break; case fourCC("XCHG"): getHTOrSkip(cellRef.mEnchantmentCharge); break; case fourCC("INTV"): getHTOrSkip(cellRef.mChargeInt); break; case fourCC("NAM9"): getHTOrSkip(cellRef.mCount); break; case fourCC("DODT"): if constexpr (load) { esm.getSubComposite(cellRef.mDoorDest); cellRef.mTeleport = true; } else esm.skipHSub(); break; case fourCC("DNAM"): getHStringOrSkip(cellRef.mDestCell); break; case fourCC("FLTV"): getHTOrSkip(cellRef.mLockLevel); break; case fourCC("KNAM"): getRefIdOrSkip(cellRef.mKey); break; case fourCC("TNAM"): getRefIdOrSkip(cellRef.mTrap); break; case fourCC("DATA"): if constexpr (load) esm.getSubComposite(cellRef.mPos); else esm.skipHSub(); break; case fourCC("NAM0"): { esm.skipHSub(); break; } case SREC_DELE: esm.skipHSub(); if constexpr (load) isDeleted = true; break; default: esm.cacheSubName(); isLoaded = true; break; } } if constexpr (load) { if (esm.getFormatVersion() == DefaultFormatVersion) // loading a content file cellRef.mIsLocked = !cellRef.mKey.empty() || cellRef.mLockLevel > 0; else cellRef.mIsLocked = cellRef.mLockLevel > 0; if (cellRef.mLockLevel == ZeroLock) cellRef.mLockLevel = 0; } } } void CellRef::load(ESMReader& esm, bool& isDeleted, bool wideRefNum) { loadId(esm, wideRefNum); loadData(esm, isDeleted); } void CellRef::loadId(ESMReader& esm, bool wideRefNum) { loadIdImpl(esm, wideRefNum, *this); } void CellRef::loadData(ESMReader& esm, bool& isDeleted) { loadDataImpl(esm, isDeleted, *this); } void CellRef::save(ESMWriter& esm, bool wideRefNum, bool inInventory, bool isDeleted) const { esm.writeFormId(mRefNum, wideRefNum); esm.writeHNCRefId("NAME", mRefID); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } if (mScale != 1.0) { esm.writeHNT("XSCL", std::clamp(mScale, 0.5f, 2.0f)); } if (!inInventory) esm.writeHNOCRefId("ANAM", mOwner); esm.writeHNOCString("BNAM", mGlobalVariable); esm.writeHNOCRefId("XSOL", mSoul); if (!inInventory) { esm.writeHNOCRefId("CNAM", mFaction); if (mFactionRank != -2) { esm.writeHNT("INDX", mFactionRank); } } if (mEnchantmentCharge != -1) esm.writeHNT("XCHG", mEnchantmentCharge); if (mChargeInt != -1) esm.writeHNT("INTV", mChargeInt); if (mCount != 1) esm.writeHNT("NAM9", mCount); if (!inInventory && mTeleport) { esm.writeNamedComposite("DODT", mDoorDest); esm.writeHNOCString("DNAM", mDestCell); } if (!inInventory) { if (mIsLocked) { int lockLevel = mLockLevel; if (lockLevel == 0) lockLevel = ZeroLock; esm.writeHNT("FLTV", lockLevel); esm.writeHNOCRefId("KNAM", mKey); } esm.writeHNOCRefId("TNAM", mTrap); } if (mReferenceBlocked != -1) esm.writeHNT("UNAM", mReferenceBlocked); if (!inInventory) esm.writeNamedComposite("DATA", mPos); } void CellRef::blank() { mRefNum = RefNum{}; mRefID = ESM::RefId(); mScale = 1; mOwner = ESM::RefId(); mGlobalVariable.clear(); mSoul = ESM::RefId(); mFaction = ESM::RefId(); mFactionRank = -2; mChargeInt = -1; mChargeIntRemainder = 0.0f; mEnchantmentCharge = -1; mCount = 1; mDestCell.clear(); mLockLevel = 0; mIsLocked = false; mKey = ESM::RefId(); mTrap = ESM::RefId(); mReferenceBlocked = -1; mTeleport = false; for (int i = 0; i < 3; ++i) { mDoorDest.pos[i] = 0; mDoorDest.rot[i] = 0; mPos.pos[i] = 0; mPos.rot[i] = 0; } } void skipLoadCellRef(ESMReader& esm, bool wideRefNum) { CellRef cellRef; loadIdImpl(esm, wideRefNum, cellRef); bool isDeleted; loadDataImpl(esm, isDeleted, cellRef); } CellRef makeBlankCellRef() { CellRef result; result.blank(); return result; } }