From 6ad20ec9c782829694214a2f5702b8c985bd31b8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 28 Jul 2020 08:33:28 +0200 Subject: [PATCH 1/2] Mutate base records when adding/removing spells --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwclass/creature.cpp | 14 +- apps/openmw/mwclass/npc.cpp | 39 +-- apps/openmw/mwmechanics/actors.cpp | 4 - apps/openmw/mwmechanics/disease.hpp | 2 +- .../mwmechanics/mechanicsmanagerimp.cpp | 2 +- apps/openmw/mwmechanics/spells.cpp | 260 ++++++++++-------- apps/openmw/mwmechanics/spells.hpp | 50 ++-- apps/openmw/mwworld/esmstore.cpp | 21 ++ apps/openmw/mwworld/esmstore.hpp | 10 + apps/openmw_test_suite/mwworld/test_store.cpp | 6 + components/esm/savedgame.cpp | 2 +- 12 files changed, 231 insertions(+), 181 deletions(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 01d270f829..d8fdf7e338 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -82,7 +82,7 @@ add_openmw_dir (mwclass ) add_openmw_dir (mwmechanics - mechanicsmanagerimp stat creaturestats magiceffects movement actorutil + mechanicsmanagerimp stat creaturestats magiceffects movement actorutil spelllist drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 4f411ad813..15e0fa6abb 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -141,14 +141,9 @@ namespace MWClass data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr)); // spells - for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); - iter!=ref->mBase->mSpells.mList.end(); ++iter) - { - if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter)) - data->mCreatureStats.getSpells().add (spell); - else /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility - Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << *iter << "' on creature '" << ref->mBase->mId << "'"; - } + bool spellsInitialised = data->mCreatureStats.getSpells().setSpells(ref->mBase->mId); + if (!spellsInitialised) + data->mCreatureStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList); // inventory bool hasInventory = hasInventoryStore(ptr); @@ -781,6 +776,9 @@ namespace MWClass CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); const ESM::CreatureState& creatureState = state.asCreatureState(); customData.mContainerStore->readState (creatureState.mInventory); + bool spellsInitialised = customData.mCreatureStats.getSpells().setSpells(ptr.get()->mBase->mId); + if(spellsInitialised) + customData.mCreatureStats.getSpells().clear(); customData.mCreatureStats.readState (creatureState.mCreatureStats); } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index bc29d09730..0c00d3bd12 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -158,7 +158,7 @@ namespace * * and by adding class, race, specialization bonus. */ - void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr) + void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr, bool spellsInitialised) { const ESM::Class *class_ = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); @@ -235,9 +235,11 @@ namespace for (int i=0; i spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); - for (std::vector::iterator it = spells.begin(); it != spells.end(); ++it) - npcStats.getSpells().add(*it); + if (!spellsInitialised) + { + std::vector spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); + npcStats.getSpells().addAllToInstance(spells); + } } } @@ -311,6 +313,8 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); + bool spellsInitialised = data->mNpcStats.getSpells().setSpells(ref->mBase->mId); + // creature stats int gold=0; if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) @@ -351,7 +355,7 @@ namespace MWClass data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation); autoCalculateAttributes(ref->mBase, data->mNpcStats); - autoCalculateSkills(ref->mBase, data->mNpcStats, ptr); + autoCalculateSkills(ref->mBase, data->mNpcStats, ptr, spellsInitialised); data->mNpcStats.setNeedRecalcDynamicStats(true); } @@ -362,14 +366,7 @@ namespace MWClass // race powers const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); - for (std::vector::const_iterator iter (race->mPowers.mList.begin()); - iter!=race->mPowers.mList.end(); ++iter) - { - if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter)) - data->mNpcStats.getSpells().add (spell); - else - Log(Debug::Warning) << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'"; - } + data->mNpcStats.getSpells().addAllToInstance(race->mPowers.mList); if (!ref->mBase->mFaction.empty()) { @@ -390,17 +387,8 @@ namespace MWClass data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); // spells - for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); - iter!=ref->mBase->mSpells.mList.end(); ++iter) - { - if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter)) - data->mNpcStats.getSpells().add (spell); - else - { - /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility - Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << *iter << "' on NPC '" << ref->mBase->mId << "'"; - } - } + if (!spellsInitialised) + data->mNpcStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList); // inventory // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items @@ -1326,6 +1314,9 @@ namespace MWClass const ESM::NpcState& npcState = state.asNpcState(); customData.mInventoryStore.readState (npcState.mInventory); customData.mNpcStats.readState (npcState.mNpcStats); + bool spellsInitialised = customData.mNpcStats.getSpells().setSpells(ptr.get()->mBase->mId); + if(spellsInitialised) + customData.mNpcStats.getSpells().clear(); customData.mNpcStats.readState (npcState.mCreatureStats); } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 93b0e0a89d..b91b01e4a3 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1973,10 +1973,6 @@ namespace MWMechanics // One case where we need this is to make sure bound items are removed upon death stats.modifyMagicEffects(MWMechanics::MagicEffects()); stats.getActiveSpells().clear(); - - if (!isPlayer) - stats.getSpells().clear(); - // Make sure spell effects are removed purgeSpellEffects(stats.getActorId()); diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 0c57067754..7933c927e5 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -40,7 +40,7 @@ namespace MWMechanics continue; float resist = 0.f; - if (spells.hasCorprusEffect(spell)) + if (Spells::hasCorprusEffect(spell)) resist = 1.f - 0.01f * (actorEffects.get(ESM::MagicEffect::ResistCorprusDisease).getMagnitude() - actorEffects.get(ESM::MagicEffect::WeaknessToCorprusDisease).getMagnitude()); else if (spell->mData.mType == ESM::Spell::ST_Disease) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index fd8902b37f..466a3bcc8b 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -83,7 +83,7 @@ namespace MWMechanics // reset creatureStats.setLevel(player->mNpdt.mLevel); - creatureStats.getSpells().clear(); + creatureStats.getSpells().clear(true); creatureStats.modifyMagicEffects(MagicEffects()); for (int i=0; i<27; ++i) diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index ae7454f194..4bffcab9b8 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -1,8 +1,10 @@ #include "spells.hpp" +#include #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -22,46 +24,41 @@ namespace MWMechanics { } - Spells::TIterator Spells::begin() const + std::map::const_iterator Spells::begin() const { return mSpells.begin(); } - Spells::TIterator Spells::end() const + std::map::const_iterator Spells::end() const { return mSpells.end(); } - const ESM::Spell* Spells::getSpell(const std::string& id) const - { - return MWBase::Environment::get().getWorld()->getStore().get().find(id); - } - void Spells::rebuildEffects() const { mEffects = MagicEffects(); mSourcedEffects.clear(); - for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) + for (const auto& iter : mSpells) { - const ESM::Spell *spell = iter->first; + const ESM::Spell *spell = iter.first; if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse) { int i=0; - for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) + for (const auto& effect : spell->mEffects.mList) { - if (iter->second.mPurgedEffects.find(i) != iter->second.mPurgedEffects.end()) + if (iter.second.mPurgedEffects.find(i) != iter.second.mPurgedEffects.end()) continue; // effect was purged float random = 1.f; - if (iter->second.mEffectRands.find(i) != iter->second.mEffectRands.end()) - random = iter->second.mEffectRands.at(i); + if (iter.second.mEffectRands.find(i) != iter.second.mEffectRands.end()) + random = iter.second.mEffectRands.at(i); - float magnitude = it->mMagnMin + (it->mMagnMax - it->mMagnMin) * random; - mEffects.add (*it, magnitude); - mSourcedEffects[spell].add(MWMechanics::EffectKey(*it), magnitude); + float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * random; + mEffects.add (effect, magnitude); + mSourcedEffects[spell].add(MWMechanics::EffectKey(effect), magnitude); ++i; } @@ -71,7 +68,7 @@ namespace MWMechanics bool Spells::hasSpell(const std::string &spell) const { - return hasSpell(getSpell(spell)); + return hasSpell(SpellList::getSpell(spell)); } bool Spells::hasSpell(const ESM::Spell *spell) const @@ -80,6 +77,16 @@ namespace MWMechanics } void Spells::add (const ESM::Spell* spell) + { + mSpellList->add(spell); + } + + void Spells::add (const std::string& spellId) + { + add(SpellList::getSpell(spellId)); + } + + void Spells::addSpell(const ESM::Spell* spell) { if (mSpells.find (spell)==mSpells.end()) { @@ -106,26 +113,26 @@ namespace MWMechanics } } - void Spells::add (const std::string& spellId) - { - add(getSpell(spellId)); - } - void Spells::remove (const std::string& spellId) { - const ESM::Spell* spell = getSpell(spellId); - TContainer::iterator iter = mSpells.find (spell); - - if (iter!=mSpells.end()) - { - mSpells.erase (iter); - mSpellsChanged = true; - } + const auto spell = SpellList::getSpell(spellId); + removeSpell(spell); + mSpellList->remove(spell); if (spellId==mSelectedSpell) mSelectedSpell.clear(); } + void Spells::removeSpell(const ESM::Spell* spell) + { + const auto it = mSpells.find(spell); + if(it != mSpells.end()) + { + mSpells.erase(it); + mSpellsChanged = true; + } + } + MagicEffects Spells::getMagicEffects() const { if (mSpellsChanged) { @@ -135,12 +142,19 @@ namespace MWMechanics return mEffects; } - void Spells::clear() + void Spells::removeAllSpells() { mSpells.clear(); mSpellsChanged = true; } + void Spells::clear(bool modifyBase) + { + removeAllSpells(); + if(modifyBase) + mSpellList->clear(); + } + void Spells::setSelectedSpell (const std::string& spellId) { mSelectedSpell = spellId; @@ -166,101 +180,78 @@ namespace MWMechanics return false; } - bool Spells::hasCommonDisease() const + bool Spells::hasDisease(const ESM::Spell::SpellType type) const { - for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) + for (const auto& iter : mSpells) { - const ESM::Spell *spell = iter->first; - if (spell->mData.mType == ESM::Spell::ST_Disease) + const ESM::Spell *spell = iter.first; + if (spell->mData.mType == type) return true; } return false; } + bool Spells::hasCommonDisease() const + { + return hasDisease(ESM::Spell::ST_Disease); + } + bool Spells::hasBlightDisease() const { - for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) + return hasDisease(ESM::Spell::ST_Blight); + } + + void Spells::purge(const SpellFilter& filter) + { + std::vector purged; + for (auto iter = mSpells.begin(); iter!=mSpells.end();) { const ESM::Spell *spell = iter->first; - if (spell->mData.mType == ESM::Spell::ST_Blight) - return true; + if (filter(spell)) + { + mSpells.erase(iter++); + purged.push_back(spell->mId); + mSpellsChanged = true; + } + else + ++iter; } - - return false; + if(!purged.empty()) + mSpellList->removeAll(purged); } void Spells::purgeCommonDisease() { - for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) - { - const ESM::Spell *spell = iter->first; - if (spell->mData.mType == ESM::Spell::ST_Disease) - { - mSpells.erase(iter++); - mSpellsChanged = true; - } - else - ++iter; - } + purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Disease; }); } void Spells::purgeBlightDisease() { - for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) - { - const ESM::Spell *spell = iter->first; - if (spell->mData.mType == ESM::Spell::ST_Blight && !hasCorprusEffect(spell)) - { - mSpells.erase(iter++); - mSpellsChanged = true; - } - else - ++iter; - } + purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Blight && !hasCorprusEffect(spell); }); } void Spells::purgeCorprusDisease() { - for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) - { - const ESM::Spell *spell = iter->first; - if (hasCorprusEffect(spell)) - { - mSpells.erase(iter++); - mSpellsChanged = true; - } - else - ++iter; - } + purge(&hasCorprusEffect); } void Spells::purgeCurses() { - for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) - { - const ESM::Spell *spell = iter->first; - if (spell->mData.mType == ESM::Spell::ST_Curse) - { - mSpells.erase(iter++); - mSpellsChanged = true; - } - else - ++iter; - } + purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Curse; }); } void Spells::removeEffects(const std::string &id) { if (isSpellActive(id)) { - for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell) + for (auto& spell : mSpells) { - if (spell->first == getSpell(id)) + if (spell.first == SpellList::getSpell(id)) { - for (long unsigned int i = 0; i != spell->first->mEffects.mList.size(); i++) + for (long unsigned int i = 0; i != spell.first->mEffects.mList.size(); i++) { - spell->second.mPurgedEffects.insert(i); + spell.second.mPurgedEffects.insert(i); } } } @@ -276,23 +267,21 @@ namespace MWMechanics mSpellsChanged = false; } - for (std::map::const_iterator it = mSourcedEffects.begin(); - it != mSourcedEffects.end(); ++it) + for (const auto& it : mSourcedEffects) { - const ESM::Spell * spell = it->first; - for (MagicEffects::Collection::const_iterator effectIt = it->second.begin(); - effectIt != it->second.end(); ++effectIt) + const ESM::Spell * spell = it.first; + for (const auto& effectIt : it.second) { - visitor.visit(effectIt->first, spell->mName, spell->mId, -1, effectIt->second.getMagnitude()); + visitor.visit(effectIt.first, spell->mName, spell->mId, -1, effectIt.second.getMagnitude()); } } } bool Spells::hasCorprusEffect(const ESM::Spell *spell) { - for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) + for (const auto& effectIt : spell->mEffects.mList) { - if (effectIt->mEffectID == ESM::MagicEffect::Corprus) + if (effectIt.mEffectID == ESM::MagicEffect::Corprus) { return true; } @@ -302,14 +291,14 @@ namespace MWMechanics void Spells::purgeEffect(int effectId) { - for (TContainer::iterator spellIt = mSpells.begin(); spellIt != mSpells.end(); ++spellIt) + for (auto& spellIt : mSpells) { int i = 0; - for (std::vector::const_iterator effectIt = spellIt->first->mEffects.mList.begin(); effectIt != spellIt->first->mEffects.mList.end(); ++effectIt) + for (auto& effectIt : spellIt.first->mEffects.mList) { - if (effectIt->mEffectID == effectId) + if (effectIt.mEffectID == effectId) { - spellIt->second.mPurgedEffects.insert(i); + spellIt.second.mPurgedEffects.insert(i); mSpellsChanged = true; } ++i; @@ -319,15 +308,15 @@ namespace MWMechanics void Spells::purgeEffect(int effectId, const std::string & sourceId) { - const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().find(sourceId); - TContainer::iterator spellIt = mSpells.find(spell); + const ESM::Spell * spell = SpellList::getSpell(sourceId); + auto spellIt = mSpells.find(spell); if (spellIt == mSpells.end()) return; int i = 0; - for (std::vector::const_iterator effectIt = spellIt->first->mEffects.mList.begin(); effectIt != spellIt->first->mEffects.mList.end(); ++effectIt) + for (auto& effectIt : spellIt->first->mEffects.mList) { - if (effectIt->mEffectID == effectId) + if (effectIt.mEffectID == effectId) { spellIt->second.mPurgedEffects.insert(i); mSpellsChanged = true; @@ -338,11 +327,8 @@ namespace MWMechanics bool Spells::canUsePower(const ESM::Spell* spell) const { - std::map::const_iterator it = mUsedPowers.find(spell); - if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp()) - return true; - else - return false; + const auto it = mUsedPowers.find(spell); + return it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp(); } void Spells::usePower(const ESM::Spell* spell) @@ -352,6 +338,8 @@ namespace MWMechanics void Spells::readState(const ESM::SpellState &state, CreatureStats* creatureStats) { + const auto& baseSpells = mSpellList->getSpells(); + for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) { // Discard spells that are no longer available due to changed content files @@ -365,6 +353,13 @@ namespace MWMechanics mSelectedSpell = it->first; } } + // Add spells from the base record + for(const std::string& id : baseSpells) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); + if(spell) + addSpell(spell); + } for (std::map::const_iterator it = state.mUsedPowers.begin(); it != state.mUsedPowers.end(); ++it) { @@ -436,17 +431,50 @@ namespace MWMechanics void Spells::writeState(ESM::SpellState &state) const { - for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) + const auto& baseSpells = mSpellList->getSpells(); + for (const auto& it : mSpells) { - ESM::SpellState::SpellParams params; - params.mEffectRands = it->second.mEffectRands; - params.mPurgedEffects = it->second.mPurgedEffects; - state.mSpells.insert(std::make_pair(it->first->mId, params)); + //Don't save spells stored in the base record + if(std::find(baseSpells.begin(), baseSpells.end(), it.first->mId) == baseSpells.end()) + { + ESM::SpellState::SpellParams params; + params.mEffectRands = it.second.mEffectRands; + params.mPurgedEffects = it.second.mPurgedEffects; + state.mSpells.insert(std::make_pair(it.first->mId, params)); + } } state.mSelectedSpell = mSelectedSpell; - for (std::map::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) - state.mUsedPowers[it->first->mId] = it->second.toEsm(); + for (const auto& it : mUsedPowers) + state.mUsedPowers[it.first->mId] = it.second.toEsm(); + } + + bool Spells::setSpells(const std::string& actorId) + { + bool result; + std::tie(mSpellList, result) = MWBase::Environment::get().getWorld()->getStore().getSpellList(actorId); + mSpellList->addListener(this); + for(const auto& id : mSpellList->getSpells()) + addSpell(SpellList::getSpell(id)); + return result; + } + + void Spells::addAllToInstance(const std::vector& spells) + { + for(const std::string& id : spells) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); + if(spell) + addSpell(spell); + else + Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << id << "'"; + } + } + + Spells::~Spells() + { + if(mSpellList) + mSpellList->removeListener(this); } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index a4a599f8b5..2f4049d2e0 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -1,22 +1,19 @@ #ifndef GAME_MWMECHANICS_SPELLS_H #define GAME_MWMECHANICS_SPELLS_H +#include #include #include #include +#include -#include - -#include "../mwworld/ptr.hpp" #include "../mwworld/timestamp.hpp" #include "magiceffects.hpp" - +#include "spelllist.hpp" namespace ESM { - struct Spell; - struct SpellState; } @@ -32,37 +29,36 @@ namespace MWMechanics /// diseases. It also keeps track of used powers (which can only be used every 24h). class Spells { - public: - - typedef const ESM::Spell* SpellKey; - struct SpellParams - { - std::map mEffectRands; // - std::set mPurgedEffects; // indices of purged effects - }; - - typedef std::map TContainer; - typedef TContainer::const_iterator TIterator; - - private: - TContainer mSpells; + std::shared_ptr mSpellList; + std::map mSpells; // Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) std::string mSelectedSpell; - std::map mUsedPowers; + std::map mUsedPowers; mutable bool mSpellsChanged; mutable MagicEffects mEffects; - mutable std::map mSourcedEffects; + mutable std::map mSourcedEffects; void rebuildEffects() const; - /// Get spell from ID, throws exception if not found - const ESM::Spell* getSpell(const std::string& id) const; + bool hasDisease(const ESM::Spell::SpellType type) const; + using SpellFilter = bool (*)(const ESM::Spell*); + void purge(const SpellFilter& filter); + + void addSpell(const ESM::Spell* spell); + void removeSpell(const ESM::Spell* spell); + void removeAllSpells(); + + friend class SpellList; public: + using TIterator = std::map::const_iterator; + Spells(); + ~Spells(); + static bool hasCorprusEffect(const ESM::Spell *spell); void purgeEffect(int effectId); @@ -96,7 +92,7 @@ namespace MWMechanics MagicEffects getMagicEffects() const; ///< Return sum of magic effects resulting from abilities, blights, deseases and curses. - void clear(); + void clear(bool modifyBase = false); ///< Remove all spells of al types. void setSelectedSpell (const std::string& spellId); @@ -118,6 +114,10 @@ namespace MWMechanics void readState (const ESM::SpellState& state, CreatureStats* creatureStats); void writeState (ESM::SpellState& state) const; + + bool setSpells(const std::string& id); + + void addAllToInstance(const std::vector& spells); }; } diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index f6fccba92a..aea9a5e4ff 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -9,6 +9,8 @@ #include #include +#include "../mwmechanics/spelllist.hpp" + namespace { void readRefs(const ESM::Cell& cell, std::map& refs, std::vector& readers) @@ -409,4 +411,23 @@ void ESMStore::validate() throw std::runtime_error ("Invalid player record (race or class unavailable"); } + std::pair, bool> ESMStore::getSpellList(const std::string& originalId) const + { + const std::string id = Misc::StringUtils::lowerCase(originalId); + auto result = mSpellListCache.find(id); + std::shared_ptr ptr; + if (result != mSpellListCache.end()) + ptr = result->second.lock(); + if (!ptr) + { + int type = find(id); + ptr = std::make_shared(id, type); + if (result != mSpellListCache.end()) + result->second = ptr; + else + mSpellListCache.insert({id, ptr}); + return {ptr, false}; + } + return {ptr, true}; + } } // end namespace diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index b6c78f0429..ceb05ca806 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_MWWORLD_ESMSTORE_H #define OPENMW_MWWORLD_ESMSTORE_H +#include #include #include @@ -12,6 +13,11 @@ namespace Loading class Listener; } +namespace MWMechanics +{ + class SpellList; +} + namespace MWWorld { class ESMStore @@ -78,6 +84,8 @@ namespace MWWorld unsigned int mDynamicCount; + mutable std::map > mSpellListCache; + /// Validate entries in store after setup void validate(); @@ -257,6 +265,8 @@ namespace MWWorld void checkPlayer(); int getRefCount(const std::string& id) const; + + std::pair, bool> getSpellList(const std::string& id) const; }; template <> diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index 63e4bd6af4..60f60adb31 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -8,6 +8,12 @@ #include #include "apps/openmw/mwworld/esmstore.hpp" +#include "apps/openmw/mwmechanics/spelllist.hpp" + +namespace MWMechanics +{ + SpellList::SpellList(const std::string& id, int type) : mId(id), mType(type) {} +} static Loading::Listener dummyListener; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 76695dbe8b..4b0529703d 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -4,7 +4,7 @@ #include "esmwriter.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 12; +int ESM::SavedGame::sCurrentFormat = 13; void ESM::SavedGame::load (ESMReader &esm) { From e27858cfabf2e8489787e93a32595cc63b16730b Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 28 Jul 2020 18:24:09 +0200 Subject: [PATCH 2/2] these were supposed to be included --- apps/openmw/mwmechanics/spelllist.cpp | 175 ++++++++++++++++++++++++++ apps/openmw/mwmechanics/spelllist.hpp | 60 +++++++++ 2 files changed, 235 insertions(+) create mode 100644 apps/openmw/mwmechanics/spelllist.cpp create mode 100644 apps/openmw/mwmechanics/spelllist.hpp diff --git a/apps/openmw/mwmechanics/spelllist.cpp b/apps/openmw/mwmechanics/spelllist.cpp new file mode 100644 index 0000000000..891b286196 --- /dev/null +++ b/apps/openmw/mwmechanics/spelllist.cpp @@ -0,0 +1,175 @@ +#include "spelllist.hpp" + +#include + +#include +#include + +#include "spells.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/esmstore.hpp" + +namespace +{ + template + const std::vector getSpellList(const std::string& id) + { + return MWBase::Environment::get().getWorld()->getStore().get().find(id)->mSpells.mList; + } + + template + bool withBaseRecord(const std::string& id, const std::function&)>& function) + { + T copy = *MWBase::Environment::get().getWorld()->getStore().get().find(id); + bool changed = function(copy.mSpells.mList); + if(changed) + MWBase::Environment::get().getWorld()->createOverrideRecord(copy); + return changed; + } +} + +namespace MWMechanics +{ + SpellList::SpellList(const std::string& id, int type) : mId(id), mType(type) {} + + bool SpellList::withBaseRecord(const std::function&)>& function) + { + switch(mType) + { + case ESM::REC_CREA: + return ::withBaseRecord(mId, function); + case ESM::REC_NPC_: + return ::withBaseRecord(mId, function); + default: + throw std::logic_error("failed to update base record for " + mId); + } + } + + const std::vector SpellList::getSpells() const + { + switch(mType) + { + case ESM::REC_CREA: + return getSpellList(mId); + case ESM::REC_NPC_: + return getSpellList(mId); + default: + throw std::logic_error("failed to get spell list for " + mId); + } + } + + const ESM::Spell* SpellList::getSpell(const std::string& id) + { + return MWBase::Environment::get().getWorld()->getStore().get().find(id); + } + + void SpellList::add (const ESM::Spell* spell) + { + auto& id = spell->mId; + bool changed = withBaseRecord([&] (auto& spells) + { + for(auto it : spells) + { + if(Misc::StringUtils::ciEqual(id, it)) + return false; + } + spells.push_back(id); + return true; + }); + if(changed) + { + for(auto listener : mListeners) + listener->addSpell(spell); + } + } + + void SpellList::remove (const ESM::Spell* spell) + { + auto& id = spell->mId; + bool changed = withBaseRecord([&] (auto& spells) + { + for(auto it = spells.begin(); it != spells.end(); it++) + { + if(Misc::StringUtils::ciEqual(id, *it)) + { + spells.erase(it); + return true; + } + } + return false; + }); + if(changed) + { + for(auto listener : mListeners) + listener->removeSpell(spell); + } + } + + void SpellList::removeAll (const std::vector& ids) + { + bool changed = withBaseRecord([&] (auto& spells) + { + const auto it = std::remove_if(spells.begin(), spells.end(), [&] (const auto& spell) + { + const auto isSpell = [&] (const auto& id) { return Misc::StringUtils::ciEqual(spell, id); }; + return ids.end() != std::find_if(ids.begin(), ids.end(), isSpell); + }); + if (it == spells.end()) + return false; + spells.erase(it, spells.end()); + return true; + }); + if(changed) + { + for(auto listener : mListeners) + { + for(auto& id : ids) + { + const auto spell = getSpell(id); + listener->removeSpell(spell); + } + } + } + } + + void SpellList::clear() + { + bool changed = withBaseRecord([] (auto& spells) + { + if(spells.empty()) + return false; + spells.clear(); + return true; + }); + if(changed) + { + for(auto listener : mListeners) + listener->removeAllSpells(); + } + } + + void SpellList::addListener(Spells* spells) + { + for(const auto ptr : mListeners) + { + if(ptr == spells) + return; + } + mListeners.push_back(spells); + } + + void SpellList::removeListener(Spells* spells) + { + for(auto it = mListeners.begin(); it != mListeners.end(); it++) + { + if(*it == spells) + { + mListeners.erase(it); + break; + } + } + } +} diff --git a/apps/openmw/mwmechanics/spelllist.hpp b/apps/openmw/mwmechanics/spelllist.hpp new file mode 100644 index 0000000000..87420082fc --- /dev/null +++ b/apps/openmw/mwmechanics/spelllist.hpp @@ -0,0 +1,60 @@ +#ifndef GAME_MWMECHANICS_SPELLLIST_H +#define GAME_MWMECHANICS_SPELLLIST_H + +#include +#include +#include +#include +#include + +#include + +#include "magiceffects.hpp" + +namespace ESM +{ + struct SpellState; +} + +namespace MWMechanics +{ + struct SpellParams + { + std::map mEffectRands; // + std::set mPurgedEffects; // indices of purged effects + }; + + class Spells; + + class SpellList + { + const std::string mId; + const int mType; + std::vector mListeners; + + bool withBaseRecord(const std::function&)>& function); + public: + SpellList(const std::string& id, int type); + + /// Get spell from ID, throws exception if not found + static const ESM::Spell* getSpell(const std::string& id); + + void add (const ESM::Spell* spell); + ///< Adding a spell that is already listed in *this is a no-op. + + void remove (const ESM::Spell* spell); + + void removeAll(const std::vector& spells); + + void clear(); + ///< Remove all spells of all types. + + void addListener(Spells* spells); + + void removeListener(Spells* spells); + + const std::vector getSpells() const; + }; +} + +#endif