#include "loadcrea.hpp" #include #include #include #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { namespace { struct EsmNPDTstruct { int32_t mType; int32_t mLevel; std::array mAttributes; int32_t mHealth, mMana, mFatigue; int32_t mSoul; int32_t mCombat, mMagic, mStealth; int32_t mAttack[6]; int32_t mGold; }; void toBinary(const Creature::NPDTstruct& src, EsmNPDTstruct& dst) { dst.mType = src.mType; dst.mLevel = src.mLevel; for (int i = 0; i < ESM::Attribute::Length; ++i) { const ESM::RefId id = ESM::Attribute::indexToRefId(i); dst.mAttributes[i] = src.getAttribute(id); } dst.mHealth = src.mHealth; dst.mMana = src.mMana; dst.mFatigue = src.mFatigue; dst.mSoul = src.mSoul; dst.mCombat = src.mCombat; dst.mMagic = src.mMagic; dst.mStealth = src.mStealth; for (std::size_t i = 0; i < std::size(dst.mAttack); ++i) dst.mAttack[i] = src.mAttack[i]; dst.mGold = src.mGold; } void fromBinary(const EsmNPDTstruct& src, Creature::NPDTstruct& dst) { dst.mType = src.mType; dst.mLevel = src.mLevel; dst.mAttributes.clear(); for (int i = 0; i < ESM::Attribute::Length; ++i) { const ESM::RefId id = ESM::Attribute::indexToRefId(i); dst.mAttributes.emplace(id, src.mAttributes[i]); } dst.mHealth = src.mHealth; dst.mMana = src.mMana; dst.mFatigue = src.mFatigue; dst.mSoul = src.mSoul; dst.mCombat = src.mCombat; dst.mMagic = src.mMagic; dst.mStealth = src.mStealth; for (std::size_t i = 0; i < std::size(dst.mAttack); ++i) dst.mAttack[i] = src.mAttack[i]; dst.mGold = src.mGold; } } template T> void decompose(T&& v, const auto& f) { f(v.mType, v.mLevel, v.mAttributes, v.mHealth, v.mMana, v.mFatigue, v.mSoul, v.mCombat, v.mMagic, v.mStealth, v.mAttack, v.mGold); } template T> void decompose(T&& v, const auto& f) { f(v.mType, v.mLevel, v.mHealth, v.mMana, v.mFatigue, v.mSoul, v.mCombat, v.mMagic, v.mStealth, v.mAttack, v.mGold); } void Creature::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; mRecordFlags = esm.getRecordFlags(); mAiPackage.mList.clear(); mInventory.mList.clear(); mSpells.mList.clear(); mTransport.mList.clear(); mScale = 1.f; mAiData.blank(); mAiData.mFight = 90; mAiData.mFlee = 20; bool hasName = false; bool hasNpdt = false; bool hasFlags = false; while (esm.hasMoreSubs()) { esm.getSubName(); switch (esm.retSubName().toInt()) { case SREC_NAME: mId = esm.getRefId(); hasName = true; break; case fourCC("MODL"): mModel = esm.getHString(); break; case fourCC("CNAM"): mOriginal = esm.getRefId(); break; case fourCC("FNAM"): mName = esm.getHString(); break; case fourCC("SCRI"): mScript = esm.getRefId(); break; case fourCC("NPDT"): { if (esm.getFormatVersion() <= MaxFixedStatsFormatVersion) { EsmNPDTstruct data; esm.getSubComposite(data); fromBinary(data, mData); } else { esm.getSubComposite(mData); while (esm.isNextSub("ATTR")) { esm.getSubHeader(); int32_t value; esm.getT(value); ESM::RefId attribute = esm.getRefId(esm.getSubSize() - sizeof(value)); mData.mAttributes.emplace(attribute, value); } } hasNpdt = true; break; } case fourCC("FLAG"): int flags; esm.getHT(flags); mFlags = flags & 0xFF; mBloodType = ((flags >> 8) & 0xFF) >> 2; hasFlags = true; break; case fourCC("XSCL"): esm.getHT(mScale); break; case fourCC("NPCO"): mInventory.add(esm); break; case fourCC("NPCS"): mSpells.add(esm); break; case fourCC("AIDT"): esm.getSubComposite(mAiData); break; case fourCC("DODT"): case fourCC("DNAM"): mTransport.add(esm); break; case AI_Wander: case AI_Activate: case AI_Escort: case AI_Follow: case AI_Travel: case AI_CNDT: mAiPackage.add(esm); break; case SREC_DELE: esm.skipHSub(); isDeleted = true; break; case fourCC("INDX"): // seems to occur only in .ESS files, unsure of purpose int index; esm.getHT(index); Log(Debug::Warning) << "Creature::load: Unhandled INDX " << index; break; default: esm.fail("Unknown subrecord"); break; } } if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasNpdt && !isDeleted) esm.fail("Missing NPDT subrecord"); if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } void Creature::save(ESMWriter& esm, bool isDeleted) const { esm.writeHNCRefId("NAME", mId); if (isDeleted) { esm.writeHNString("DELE", "", 3); return; } esm.writeHNCString("MODL", mModel.getOriginal()); esm.writeHNOCRefId("CNAM", mOriginal); esm.writeHNOCString("FNAM", mName); esm.writeHNOCRefId("SCRI", mScript); if (esm.getFormatVersion() <= MaxFixedStatsFormatVersion) { EsmNPDTstruct data; toBinary(mData, data); esm.writeNamedComposite("NPDT", data); } else { esm.writeNamedComposite("NPDT", mData); for (const auto& [attribute, value] : mData.mAttributes) { if (!attribute.empty() && value != 0) { esm.startSubRecord("ATTR"); esm.writeT(value); esm.writeHRefId(attribute); esm.endRecord("ATTR"); } } } esm.writeHNT("FLAG", ((mBloodType << 10) + mFlags)); if (mScale != 1.0) { esm.writeHNT("XSCL", mScale); } mInventory.save(esm); mSpells.save(esm); esm.writeNamedComposite("AIDT", mAiData); mTransport.save(esm); mAiPackage.save(esm); } void Creature::blank() { mRecordFlags = 0; mData.mType = 0; mData.mLevel = 0; mData.mAttributes.clear(); mData.mHealth = mData.mMana = mData.mFatigue = 0; mData.mSoul = 0; mData.mCombat = mData.mMagic = mData.mStealth = 0; for (int i = 0; i < 6; ++i) mData.mAttack[i] = 0; mData.mGold = 0; mBloodType = 0; mFlags = 0; mScale = 1.f; mModel.clear(); mName.clear(); mScript = ESM::RefId(); mOriginal = ESM::RefId(); mInventory.mList.clear(); mSpells.mList.clear(); mAiData.blank(); mAiData.mFight = 90; mAiData.mFlee = 20; mAiPackage.mList.clear(); mTransport.mList.clear(); } const std::vector& Creature::getTransport() const { return mTransport.mList; } int32_t Creature::NPDTstruct::getAttribute(ESM::RefId id) const { const auto it = mAttributes.find(id); if (it != mAttributes.end()) return it->second; return 0; } }