diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 01d270f82..d8fdf7e33 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 4f411ad81..15e0fa6ab 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 bc29d0973..0c00d3bd1 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 93b0e0a89..b91b01e4a 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 0c5706775..7933c927e 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 fd8902b37..466a3bcc8 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 ae7454f19..4bffcab9b 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,24 +113,24 @@ namespace MWMechanics } } - void Spells::add (const std::string& spellId) + void Spells::remove (const std::string& spellId) { - add(getSpell(spellId)); + const auto spell = SpellList::getSpell(spellId); + removeSpell(spell); + mSpellList->remove(spell); + + if (spellId==mSelectedSpell) + mSelectedSpell.clear(); } - void Spells::remove (const std::string& spellId) + void Spells::removeSpell(const ESM::Spell* spell) { - const ESM::Spell* spell = getSpell(spellId); - TContainer::iterator iter = mSpells.find (spell); - - if (iter!=mSpells.end()) + const auto it = mSpells.find(spell); + if(it != mSpells.end()) { - mSpells.erase (iter); + mSpells.erase(it); mSpellsChanged = true; } - - if (spellId==mSelectedSpell) - mSelectedSpell.clear(); } MagicEffects Spells::getMagicEffects() const @@ -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::hasBlightDisease() const + bool Spells::hasCommonDisease() const { - for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) - { - const ESM::Spell *spell = iter->first; - if (spell->mData.mType == ESM::Spell::ST_Blight) - return true; - } + return hasDisease(ESM::Spell::ST_Disease); + } - return false; + bool Spells::hasBlightDisease() const + { + return hasDisease(ESM::Spell::ST_Blight); } - void Spells::purgeCommonDisease() + void Spells::purge(const SpellFilter& filter) { - for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + std::vector purged; + for (auto iter = mSpells.begin(); iter!=mSpells.end();) { const ESM::Spell *spell = iter->first; - if (spell->mData.mType == ESM::Spell::ST_Disease) + if (filter(spell)) { mSpells.erase(iter++); + purged.push_back(spell->mId); mSpellsChanged = true; } else ++iter; } + if(!purged.empty()) + mSpellList->removeAll(purged); + } + + void Spells::purgeCommonDisease() + { + 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 a4a599f8b..2f4049d2e 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 f6fccba92..aea9a5e4f 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 b6c78f042..ceb05ca80 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 63e4bd6af..60f60adb3 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 76695dbe8..4b0529703 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) {