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) {