#include "cellref.hpp" #include #include #include #include #include #include #include "apps/openmw/mwbase/environment.hpp" #include "apps/openmw/mwbase/world.hpp" #include "apps/openmw/mwmechanics/spellutil.hpp" #include "apps/openmw/mwworld/esmstore.hpp" namespace MWWorld { CellRef::CellRef(const ESM::CellRef& ref) : mCellRef(ESM::ReferenceVariant(ref)) { } CellRef::CellRef(const ESM4::Reference& ref) : mCellRef(ESM::ReferenceVariant(ref)) { } CellRef::CellRef(const ESM4::ActorCharacter& ref) : mCellRef(ESM::ReferenceVariant(ref)) { } ESM::RefNum CellRef::getRefNum() const noexcept { return std::visit(ESM::VisitOverload{ [&](const ESM4::Reference& ref) -> ESM::RefNum { return ref.mId; }, [&](const ESM4::ActorCharacter& ref) -> ESM::RefNum { return ref.mId; }, [&](const ESM::CellRef& ref) -> ESM::RefNum { return ref.mRefNum; }, }, mCellRef.mVariant); } ESM::RefNum CellRef::getOrAssignRefNum(ESM::RefNum& lastAssignedRefNum) { ESM::RefNum& refNum = std::visit(ESM::VisitOverload{ [&](ESM4::Reference& ref) -> ESM::RefNum& { return ref.mId; }, [&](ESM4::ActorCharacter& ref) -> ESM::RefNum& { return ref.mId; }, [&](ESM::CellRef& ref) -> ESM::RefNum& { return ref.mRefNum; }, }, mCellRef.mVariant); if (!refNum.isSet()) { // Generated RefNums have negative mContentFile assert(lastAssignedRefNum.mContentFile < 0); lastAssignedRefNum.mIndex++; if (lastAssignedRefNum.mIndex == 0) // mIndex overflow, so mContentFile should be changed { if (lastAssignedRefNum.mContentFile > std::numeric_limits::min()) lastAssignedRefNum.mContentFile--; else Log(Debug::Error) << "RefNum counter overflow in CellRef::getOrAssignRefNum"; } refNum = lastAssignedRefNum; mChanged = true; } return refNum; } void CellRef::setRefNum(ESM::RefNum refNum) { std::visit(ESM::VisitOverload{ [&](ESM4::Reference& ref) { ref.mId = refNum; }, [&](ESM4::ActorCharacter& ref) { ref.mId = refNum; }, [&](ESM::CellRef& ref) { ref.mRefNum = refNum; }, }, mCellRef.mVariant); } static const std::string emptyString = ""; ESM::Position CellRef::getDoorDest() const { return std::visit( ESM::VisitOverload{ [&](const ESM4::Reference& ref) { return ref.mDoor.destPos; }, [&](const ESM::CellRef& ref) -> ESM::Position { return ref.mDoorDest; }, [&](const ESM4::ActorCharacter&) -> ESM::Position { throw std::logic_error("Not applicable"); }, }, mCellRef.mVariant); } ESM::RefId CellRef::getDestCell() const { auto esm3Visit = [&](const ESM::CellRef& ref) -> ESM::RefId { if (!ref.mDestCell.empty()) { return ESM::RefId::stringRefId(ref.mDestCell); } else { const auto cellPos = ESM::positionToExteriorCellLocation(ref.mDoorDest.pos[0], ref.mDoorDest.pos[1]); return ESM::RefId::esm3ExteriorCell(cellPos.mX, cellPos.mY); } }; auto esm4Visit = [&](const ESM4::Reference& ref) -> ESM::RefId { if (ref.mDoor.destDoor.isZeroOrUnset()) return ESM::RefId(); const ESM4::Reference* refDest = MWBase::Environment::get().getESMStore()->get().searchStatic(ref.mDoor.destDoor); if (refDest) return refDest->mParent; return ESM::RefId(); }; auto actorDestCell = [&](const ESM4::ActorCharacter&) -> ESM::RefId { throw std::logic_error("Not applicable"); }; return std::visit(ESM::VisitOverload{ esm3Visit, esm4Visit, actorDestCell }, mCellRef.mVariant); } void CellRef::setScale(float scale) { if (scale != getScale()) { mChanged = true; std::visit([scale](auto&& ref) { ref.mScale = scale; }, mCellRef.mVariant); } } void CellRef::setPosition(const ESM::Position& position) { mChanged = true; std::visit([&position](auto&& ref) { ref.mPos = position; }, mCellRef.mVariant); } float CellRef::getEnchantmentCharge() const { return std::visit(ESM::VisitOverload{ [&](const ESM4::Reference& /*ref*/) { return 0.f; }, [&](const ESM::CellRef& ref) { return ref.mEnchantmentCharge; }, [&](const ESM4::ActorCharacter&) -> float { throw std::logic_error("Not applicable"); }, }, mCellRef.mVariant); } float CellRef::getNormalizedEnchantmentCharge(const ESM::Enchantment& enchantment) const { const int maxCharge = MWMechanics::getEnchantmentCharge(enchantment); if (maxCharge == 0) { return 0; } else if (getEnchantmentCharge() == -1) { return 1; } else { return getEnchantmentCharge() / static_cast(maxCharge); } } void CellRef::setEnchantmentCharge(float charge) { if (charge != getEnchantmentCharge()) { mChanged = true; std::visit(ESM::VisitOverload{ [&](ESM4::Reference& /*ref*/) {}, [&](ESM4::ActorCharacter&) {}, [&](ESM::CellRef& ref) { ref.mEnchantmentCharge = charge; }, }, mCellRef.mVariant); } } void CellRef::setCharge(int charge) { std::visit(ESM::VisitOverload{ [&](ESM4::Reference& /*ref*/) {}, [&](ESM4::ActorCharacter&) {}, [&](ESM::CellRef& ref) { ref.mChargeInt = charge; }, }, mCellRef.mVariant); } void CellRef::applyChargeRemainderToBeSubtracted(float chargeRemainder) { auto esm3Visit = [&](ESM::CellRef& cellRef3) { cellRef3.mChargeIntRemainder -= std::abs(chargeRemainder); if (cellRef3.mChargeIntRemainder <= -1.0f) { float newChargeRemainder = std::modf(cellRef3.mChargeIntRemainder, &cellRef3.mChargeIntRemainder); cellRef3.mChargeInt += static_cast(cellRef3.mChargeIntRemainder); cellRef3.mChargeIntRemainder = newChargeRemainder; if (cellRef3.mChargeInt < 0) cellRef3.mChargeInt = 0; } }; std::visit(ESM::VisitOverload{ [&](ESM4::Reference& /*ref*/) {}, [&](ESM4::ActorCharacter&) {}, esm3Visit, }, mCellRef.mVariant); } void CellRef::setChargeIntRemainder(float chargeRemainder) { std::visit(ESM::VisitOverload{ [&](ESM4::Reference& /*ref*/) {}, [&](ESM4::ActorCharacter&) {}, [&](ESM::CellRef& ref) { ref.mChargeIntRemainder = chargeRemainder; }, }, mCellRef.mVariant); } void CellRef::setChargeFloat(float charge) { std::visit(ESM::VisitOverload{ [&](ESM4::Reference& /*ref*/) {}, [&](ESM4::ActorCharacter&) {}, [&](ESM::CellRef& ref) { ref.mChargeFloat = charge; }, }, mCellRef.mVariant); } const std::string& CellRef::getGlobalVariable() const { return std::visit(ESM::VisitOverload{ [&](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; }, }, mCellRef.mVariant); } void CellRef::resetGlobalVariable() { if (!getGlobalVariable().empty()) { mChanged = true; std::visit(ESM::VisitOverload{ [&](ESM4::Reference& /*ref*/) {}, [&](ESM4::ActorCharacter& /*ref*/) {}, [&](ESM::CellRef& ref) { ref.mGlobalVariable.erase(); }, }, mCellRef.mVariant); } } void CellRef::setFactionRank(int factionRank) { if (factionRank != getFactionRank()) { mChanged = true; std::visit(ESM::VisitOverload{ [&](ESM4::ActorCharacter&) {}, [&](auto&& ref) { ref.mFactionRank = factionRank; } }, mCellRef.mVariant); } } void CellRef::setOwner(const ESM::RefId& owner) { if (owner != getOwner()) { std::visit(ESM::VisitOverload{ [&](ESM4::Reference& /*ref*/) {}, [&](ESM4::ActorCharacter&) {}, [&](ESM::CellRef& ref) { ref.mOwner = owner; }, }, mCellRef.mVariant); } } void CellRef::setSoul(const ESM::RefId& soul) { if (soul != getSoul()) { mChanged = true; std::visit(ESM::VisitOverload{ [&](ESM4::Reference& /*ref*/) {}, [&](ESM4::ActorCharacter&) {}, [&](ESM::CellRef& ref) { ref.mSoul = soul; }, }, mCellRef.mVariant); } } void CellRef::setFaction(const ESM::RefId& faction) { if (faction != getFaction()) { mChanged = true; std::visit(ESM::VisitOverload{ [&](ESM4::Reference& /*ref*/) {}, [&](ESM4::ActorCharacter&) {}, [&](ESM::CellRef& ref) { ref.mFaction = faction; }, }, mCellRef.mVariant); } } void CellRef::setLockLevel(int lockLevel) { if (lockLevel != getLockLevel()) { mChanged = true; std::visit(ESM::VisitOverload{ [&](ESM4::Reference& ref) { ref.mLockLevel = lockLevel; }, [&](ESM4::ActorCharacter&) {}, [&](ESM::CellRef& ref) { ref.mLockLevel = lockLevel; }, }, mCellRef.mVariant); } } void CellRef::lock(int lockLevel) { setLockLevel(lockLevel); setLocked(true); } void CellRef::unlock() { setLockLevel(-getLockLevel()); setLocked(false); } bool CellRef::isLocked() const { 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) { 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) { if (trap != getTrap()) { mChanged = true; std::visit(ESM::VisitOverload{ [&](ESM4::Reference& /*ref*/) {}, [&](ESM4::ActorCharacter&) {}, [&](ESM::CellRef& ref) { ref.mTrap = trap; }, }, mCellRef.mVariant); } } void CellRef::setKey(const ESM::RefId& key) { if (key != getKey()) { mChanged = true; std::visit(ESM::VisitOverload{ [&](ESM4::Reference& /*ref*/) {}, [&](ESM4::ActorCharacter&) {}, [&](ESM::CellRef& ref) { ref.mKey = key; }, }, mCellRef.mVariant); } } void CellRef::setCount(int value) { if (value != getCount(false)) { mChanged = true; std::visit(ESM::VisitOverload{ [&](ESM4::Reference& ref) { ref.mCount = value; }, [&](ESM4::ActorCharacter& ref) { ref.mCount = value; }, [&](ESM::CellRef& ref) { ref.mCount = value; }, }, mCellRef.mVariant); if (value == 0) MWBase::Environment::get().getWorld()->removeRefScript(this); } } void CellRef::writeState(ESM::ObjectState& state) const { std::visit(ESM::VisitOverload{ [&](const ESM4::Reference& /*ref*/) {}, [&](const ESM4::ActorCharacter&) {}, [&](const ESM::CellRef& ref) { state.mRef = ref; }, }, mCellRef.mVariant); } }