#include "npcstats.hpp" #include #include #include #include #include #include #include #include #include #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" MWMechanics::NpcStats::NpcStats() : mDisposition(0) , mCrimeDispositionModifier(0) , mReputation(0) , mCrimeId(-1) , mBounty(0) , mWerewolfKills(0) , mLevelProgress(0) , mTimeToStartDrowning(-1.0) // set breath to special value, it will be replaced during actor update , mIsWerewolf(false) { mSpecIncreases.resize(3, 0); for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get()) mSkills.emplace(skill.mId, SkillValue{}); } int MWMechanics::NpcStats::getBaseDisposition() const { return mDisposition; } void MWMechanics::NpcStats::setBaseDisposition(int disposition) { mDisposition = disposition; } int MWMechanics::NpcStats::getCrimeDispositionModifier() const { return mCrimeDispositionModifier; } void MWMechanics::NpcStats::setCrimeDispositionModifier(int value) { mCrimeDispositionModifier = value; } void MWMechanics::NpcStats::modCrimeDispositionModifier(int value) { mCrimeDispositionModifier += value; } const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill(ESM::RefId id) const { auto it = mSkills.find(id); if (it == mSkills.end()) throw std::runtime_error("skill not found"); return it->second; } MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill(ESM::RefId id) { auto it = mSkills.find(id); if (it == mSkills.end()) throw std::runtime_error("skill not found"); return it->second; } void MWMechanics::NpcStats::setSkill(ESM::RefId id, const MWMechanics::SkillValue& value) { auto it = mSkills.find(id); if (it == mSkills.end()) throw std::runtime_error("skill not found"); it->second = value; } const std::map& MWMechanics::NpcStats::getFactionRanks() const { return mFactionRank; } int MWMechanics::NpcStats::getFactionRank(const ESM::RefId& faction) const { auto it = mFactionRank.find(faction); if (it != mFactionRank.end()) return it->second; return -1; } void MWMechanics::NpcStats::joinFaction(const ESM::RefId& faction) { auto it = mFactionRank.find(faction); if (it == mFactionRank.end()) mFactionRank[faction] = 0; } void MWMechanics::NpcStats::setFactionRank(const ESM::RefId& faction, int newRank) { auto it = mFactionRank.find(faction); if (it != mFactionRank.end()) { const ESM::Faction* factionPtr = MWBase::Environment::get().getESMStore()->get().find(faction); if (newRank < 0) { mFactionRank.erase(it); mExpelled.erase(faction); } else if (newRank < static_cast(factionPtr->mData.mRankData.size())) do it->second = newRank; // Does the new rank exist? while (newRank > 0 && factionPtr->mRanks[newRank--].empty()); } } bool MWMechanics::NpcStats::getExpelled(const ESM::RefId& factionID) const { return mExpelled.find(factionID) != mExpelled.end(); } void MWMechanics::NpcStats::expell(const ESM::RefId& factionID, bool printMessage) { if (mExpelled.find(factionID) == mExpelled.end()) { mExpelled.insert(factionID); if (!printMessage) return; std::string message = "#{sExpelledMessage}"; message += MWBase::Environment::get().getESMStore()->get().find(factionID)->mName; MWBase::Environment::get().getWindowManager()->messageBox(message); } } void MWMechanics::NpcStats::clearExpelled(const ESM::RefId& factionID) { mExpelled.erase(factionID); } bool MWMechanics::NpcStats::isInFaction(const ESM::RefId& faction) const { return (mFactionRank.find(faction) != mFactionRank.end()); } int MWMechanics::NpcStats::getFactionReputation(const ESM::RefId& faction) const { auto iter = mFactionReputation.find(faction); if (iter == mFactionReputation.end()) return 0; return iter->second; } void MWMechanics::NpcStats::setFactionReputation(const ESM::RefId& faction, int value) { mFactionReputation[faction] = value; } float MWMechanics::NpcStats::getSkillProgressRequirement(ESM::RefId id, const ESM::Class& class_) const { float progressRequirement = 1.f + getSkill(id).getBase(); const MWWorld::Store& gmst = MWBase::Environment::get().getESMStore()->get(); const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get().find(id); float typeFactor = gmst.find("fMiscSkillBonus")->mValue.getFloat(); int index = ESM::Skill::refIdToIndex(skill->mId); for (const auto& skills : class_.mData.mSkills) { if (skills[0] == index) { typeFactor = gmst.find("fMinorSkillBonus")->mValue.getFloat(); break; } else if (skills[1] == index) { typeFactor = gmst.find("fMajorSkillBonus")->mValue.getFloat(); break; } } progressRequirement *= typeFactor; if (typeFactor <= 0) throw std::runtime_error("invalid skill type factor"); float specialisationFactor = 1; if (skill->mData.mSpecialization == class_.mData.mSpecialization) { specialisationFactor = gmst.find("fSpecialSkillBonus")->mValue.getFloat(); if (specialisationFactor <= 0) throw std::runtime_error("invalid skill specialisation factor"); } progressRequirement *= specialisationFactor; return progressRequirement; } int MWMechanics::NpcStats::getLevelProgress() const { return mLevelProgress; } void MWMechanics::NpcStats::setLevelProgress(int progress) { mLevelProgress = progress; } void MWMechanics::NpcStats::levelUp() { const MWWorld::Store& gmst = MWBase::Environment::get().getESMStore()->get(); mLevelProgress -= gmst.find("iLevelUpTotal")->mValue.getInteger(); mLevelProgress = std::max(0, mLevelProgress); // might be necessary when levelup was invoked via console mSkillIncreases.clear(); const float endurance = getAttribute(ESM::Attribute::Endurance).getBase(); // "When you gain a level, in addition to increasing three primary attributes, your Health // will automatically increase by 10% of your Endurance attribute. If you increased Endurance this level, // the Health increase is calculated from the increased Endurance" // Note: we should add bonus Health points to current level too. float healthGain = endurance * gmst.find("fLevelUpHealthEndMult")->mValue.getFloat(); MWMechanics::DynamicStat health(getHealth()); health.setBase(getHealth().getBase() + healthGain); health.setCurrent(std::max(1.f, getHealth().getCurrent() + healthGain)); setHealth(health); setLevel(getLevel() + 1); } void MWMechanics::NpcStats::updateHealth() { const float endurance = getAttribute(ESM::Attribute::Endurance).getBase(); const float strength = getAttribute(ESM::Attribute::Strength).getBase(); setHealth(floor(0.5f * (strength + endurance))); } int MWMechanics::NpcStats::getLevelupAttributeMultiplier(ESM::Attribute::AttributeID attribute) const { auto it = mSkillIncreases.find(attribute); if (it == mSkillIncreases.end() || it->second == 0) return 1; int num = std::min(10, it->second); // iLevelUp01Mult - iLevelUp10Mult std::stringstream gmst; gmst << "iLevelUp" << std::setfill('0') << std::setw(2) << num << "Mult"; return MWBase::Environment::get().getESMStore()->get().find(gmst.str())->mValue.getInteger(); } int MWMechanics::NpcStats::getSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute) const { auto it = mSkillIncreases.find(attribute); if (it == mSkillIncreases.end()) return 0; return it->second; } void MWMechanics::NpcStats::setSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute, int increases) { if (increases == 0) mSkillIncreases.erase(attribute); else mSkillIncreases[attribute] = increases; } int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(ESM::Class::Specialization spec) const { return mSpecIncreases[spec]; } void MWMechanics::NpcStats::setSkillIncreasesForSpecialization(ESM::Class::Specialization spec, int increases) { assert(spec >= 0 && spec < 3); mSpecIncreases[spec] = increases; } void MWMechanics::NpcStats::flagAsUsed(const ESM::RefId& id) { mUsedIds.insert(id); } bool MWMechanics::NpcStats::hasBeenUsed(const ESM::RefId& id) const { return mUsedIds.find(id) != mUsedIds.end(); } int MWMechanics::NpcStats::getBounty() const { return mBounty; } void MWMechanics::NpcStats::setBounty(int bounty) { mBounty = bounty; } int MWMechanics::NpcStats::getReputation() const { return mReputation; } void MWMechanics::NpcStats::setReputation(int reputation) { // Reputation is capped in original engine mReputation = std::clamp(reputation, 0, 255); } int MWMechanics::NpcStats::getCrimeId() const { return mCrimeId; } void MWMechanics::NpcStats::setCrimeId(int id) { mCrimeId = id; } bool MWMechanics::NpcStats::hasSkillsForRank(const ESM::RefId& factionId, int rank) const { const ESM::Faction& faction = *MWBase::Environment::get().getESMStore()->get().find(factionId); const ESM::RankData& rankData = faction.mData.mRankData.at(rank); std::vector skills; for (int index : faction.mData.mSkills) { ESM::RefId id = ESM::Skill::indexToRefId(index); if (!id.empty()) skills.push_back(static_cast(getSkill(id).getBase())); } if (skills.empty()) return true; std::sort(skills.begin(), skills.end()); std::vector::const_reverse_iterator iter = skills.rbegin(); if (*iter < rankData.mPrimarySkill) return false; if (skills.size() < 2) return true; iter++; if (*iter < rankData.mFavouredSkill) return false; if (skills.size() < 3) return true; iter++; if (*iter < rankData.mFavouredSkill) return false; return true; } bool MWMechanics::NpcStats::isWerewolf() const { return mIsWerewolf; } void MWMechanics::NpcStats::setWerewolf(bool set) { if (mIsWerewolf == set) return; if (set != false) { mWerewolfKills = 0; } mIsWerewolf = set; } int MWMechanics::NpcStats::getWerewolfKills() const { return mWerewolfKills; } void MWMechanics::NpcStats::addWerewolfKill() { ++mWerewolfKills; } float MWMechanics::NpcStats::getTimeToStartDrowning() const { return mTimeToStartDrowning; } void MWMechanics::NpcStats::setTimeToStartDrowning(float time) { mTimeToStartDrowning = time; } void MWMechanics::NpcStats::writeState(ESM::CreatureStats& state) const { CreatureStats::writeState(state); } void MWMechanics::NpcStats::writeState(ESM::NpcStats& state) const { for (std::map::const_iterator iter(mFactionRank.begin()); iter != mFactionRank.end(); ++iter) state.mFactions[iter->first].mRank = iter->second; state.mDisposition = mDisposition; state.mCrimeDispositionModifier = mCrimeDispositionModifier; for (const auto& [id, value] : mSkills) { // TODO extend format auto index = ESM::Skill::refIdToIndex(id); assert(index >= 0); value.writeState(state.mSkills[static_cast(index)]); } state.mIsWerewolf = mIsWerewolf; state.mCrimeId = mCrimeId; state.mBounty = mBounty; for (auto iter(mExpelled.begin()); iter != mExpelled.end(); ++iter) state.mFactions[*iter].mExpelled = true; for (auto iter(mFactionReputation.begin()); iter != mFactionReputation.end(); ++iter) state.mFactions[iter->first].mReputation = iter->second; state.mReputation = mReputation; state.mWerewolfKills = mWerewolfKills; state.mLevelProgress = mLevelProgress; state.mSkillIncrease.fill(0); for (const auto& [key, value] : mSkillIncreases) { // TODO extend format auto index = ESM::Attribute::refIdToIndex(key); assert(index >= 0); state.mSkillIncrease[static_cast(index)] = value; } for (size_t i = 0; i < state.mSpecIncreases.size(); ++i) state.mSpecIncreases[i] = mSpecIncreases[i]; std::copy(mUsedIds.begin(), mUsedIds.end(), std::back_inserter(state.mUsedIds)); state.mTimeToStartDrowning = mTimeToStartDrowning; } void MWMechanics::NpcStats::readState(const ESM::CreatureStats& state) { CreatureStats::readState(state); } void MWMechanics::NpcStats::readState(const ESM::NpcStats& state) { const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); for (auto iter(state.mFactions.begin()); iter != state.mFactions.end(); ++iter) if (store.get().search(iter->first)) { if (iter->second.mExpelled) mExpelled.insert(iter->first); if (iter->second.mRank >= 0) mFactionRank[iter->first] = iter->second.mRank; if (iter->second.mReputation) mFactionReputation[iter->first] = iter->second.mReputation; } mDisposition = state.mDisposition; mCrimeDispositionModifier = state.mCrimeDispositionModifier; for (size_t i = 0; i < state.mSkills.size(); ++i) { // TODO extend format ESM::RefId id = ESM::Skill::indexToRefId(i); assert(!id.empty()); mSkills[id].readState(state.mSkills[i]); } mIsWerewolf = state.mIsWerewolf; mCrimeId = state.mCrimeId; mBounty = state.mBounty; mReputation = state.mReputation; mWerewolfKills = state.mWerewolfKills; mLevelProgress = state.mLevelProgress; for (size_t i = 0; i < state.mSkillIncrease.size(); ++i) mSkillIncreases[ESM::Attribute::indexToRefId(i)] = state.mSkillIncrease[i]; for (size_t i = 0; i < state.mSpecIncreases.size(); ++i) mSpecIncreases[i] = state.mSpecIncreases[i]; for (auto iter(state.mUsedIds.begin()); iter != state.mUsedIds.end(); ++iter) if (store.find(*iter)) mUsedIds.insert(*iter); mTimeToStartDrowning = state.mTimeToStartDrowning; }