Mutate base records when adding/removing spells

pull/2954/head
Evil Eye 5 years ago
parent ae1af0e103
commit 6ad20ec9c7

@ -82,7 +82,7 @@ add_openmw_dir (mwclass
) )
add_openmw_dir (mwmechanics 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 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 aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning

@ -141,14 +141,9 @@ namespace MWClass
data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr)); data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr));
// spells // spells
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin()); bool spellsInitialised = data->mCreatureStats.getSpells().setSpells(ref->mBase->mId);
iter!=ref->mBase->mSpells.mList.end(); ++iter) if (!spellsInitialised)
{ data->mCreatureStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList);
if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().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 << "'";
}
// inventory // inventory
bool hasInventory = hasInventoryStore(ptr); bool hasInventory = hasInventoryStore(ptr);
@ -781,6 +776,9 @@ namespace MWClass
CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
const ESM::CreatureState& creatureState = state.asCreatureState(); const ESM::CreatureState& creatureState = state.asCreatureState();
customData.mContainerStore->readState (creatureState.mInventory); customData.mContainerStore->readState (creatureState.mInventory);
bool spellsInitialised = customData.mCreatureStats.getSpells().setSpells(ptr.get<ESM::Creature>()->mBase->mId);
if(spellsInitialised)
customData.mCreatureStats.getSpells().clear();
customData.mCreatureStats.readState (creatureState.mCreatureStats); customData.mCreatureStats.readState (creatureState.mCreatureStats);
} }

@ -158,7 +158,7 @@ namespace
* *
* and by adding class, race, specialization bonus. * 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_ = const ESM::Class *class_ =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass); MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);
@ -235,9 +235,11 @@ namespace
for (int i=0; i<ESM::Attribute::Length; ++i) for (int i=0; i<ESM::Attribute::Length; ++i)
attributes[i] = npcStats.getAttribute(i).getBase(); attributes[i] = npcStats.getAttribute(i).getBase();
if (!spellsInitialised)
{
std::vector<std::string> spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); std::vector<std::string> spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race);
for (std::vector<std::string>::iterator it = spells.begin(); it != spells.end(); ++it) npcStats.getSpells().addAllToInstance(spells);
npcStats.getSpells().add(*it); }
} }
} }
@ -311,6 +313,8 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
bool spellsInitialised = data->mNpcStats.getSpells().setSpells(ref->mBase->mId);
// creature stats // creature stats
int gold=0; int gold=0;
if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
@ -351,7 +355,7 @@ namespace MWClass
data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation); data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation);
autoCalculateAttributes(ref->mBase, data->mNpcStats); autoCalculateAttributes(ref->mBase, data->mNpcStats);
autoCalculateSkills(ref->mBase, data->mNpcStats, ptr); autoCalculateSkills(ref->mBase, data->mNpcStats, ptr, spellsInitialised);
data->mNpcStats.setNeedRecalcDynamicStats(true); data->mNpcStats.setNeedRecalcDynamicStats(true);
} }
@ -362,14 +366,7 @@ namespace MWClass
// race powers // race powers
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace); const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
for (std::vector<std::string>::const_iterator iter (race->mPowers.mList.begin()); data->mNpcStats.getSpells().addAllToInstance(race->mPowers.mList);
iter!=race->mPowers.mList.end(); ++iter)
{
if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
data->mNpcStats.getSpells().add (spell);
else
Log(Debug::Warning) << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'";
}
if (!ref->mBase->mFaction.empty()) if (!ref->mBase->mFaction.empty())
{ {
@ -390,17 +387,8 @@ namespace MWClass
data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm);
// spells // spells
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin()); if (!spellsInitialised)
iter!=ref->mBase->mSpells.mList.end(); ++iter) data->mNpcStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList);
{
if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().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 << "'";
}
}
// inventory // inventory
// setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items // 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(); const ESM::NpcState& npcState = state.asNpcState();
customData.mInventoryStore.readState (npcState.mInventory); customData.mInventoryStore.readState (npcState.mInventory);
customData.mNpcStats.readState (npcState.mNpcStats); customData.mNpcStats.readState (npcState.mNpcStats);
bool spellsInitialised = customData.mNpcStats.getSpells().setSpells(ptr.get<ESM::NPC>()->mBase->mId);
if(spellsInitialised)
customData.mNpcStats.getSpells().clear();
customData.mNpcStats.readState (npcState.mCreatureStats); customData.mNpcStats.readState (npcState.mCreatureStats);
} }

@ -1973,10 +1973,6 @@ namespace MWMechanics
// One case where we need this is to make sure bound items are removed upon death // One case where we need this is to make sure bound items are removed upon death
stats.modifyMagicEffects(MWMechanics::MagicEffects()); stats.modifyMagicEffects(MWMechanics::MagicEffects());
stats.getActiveSpells().clear(); stats.getActiveSpells().clear();
if (!isPlayer)
stats.getSpells().clear();
// Make sure spell effects are removed // Make sure spell effects are removed
purgeSpellEffects(stats.getActorId()); purgeSpellEffects(stats.getActorId());

@ -40,7 +40,7 @@ namespace MWMechanics
continue; continue;
float resist = 0.f; float resist = 0.f;
if (spells.hasCorprusEffect(spell)) if (Spells::hasCorprusEffect(spell))
resist = 1.f - 0.01f * (actorEffects.get(ESM::MagicEffect::ResistCorprusDisease).getMagnitude() resist = 1.f - 0.01f * (actorEffects.get(ESM::MagicEffect::ResistCorprusDisease).getMagnitude()
- actorEffects.get(ESM::MagicEffect::WeaknessToCorprusDisease).getMagnitude()); - actorEffects.get(ESM::MagicEffect::WeaknessToCorprusDisease).getMagnitude());
else if (spell->mData.mType == ESM::Spell::ST_Disease) else if (spell->mData.mType == ESM::Spell::ST_Disease)

@ -83,7 +83,7 @@ namespace MWMechanics
// reset // reset
creatureStats.setLevel(player->mNpdt.mLevel); creatureStats.setLevel(player->mNpdt.mLevel);
creatureStats.getSpells().clear(); creatureStats.getSpells().clear(true);
creatureStats.modifyMagicEffects(MagicEffects()); creatureStats.modifyMagicEffects(MagicEffects());
for (int i=0; i<27; ++i) for (int i=0; i<27; ++i)

@ -1,8 +1,10 @@
#include "spells.hpp" #include "spells.hpp"
#include <components/debug/debuglog.hpp>
#include <components/esm/loadspel.hpp> #include <components/esm/loadspel.hpp>
#include <components/esm/spellstate.hpp> #include <components/esm/spellstate.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/misc/stringops.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -22,46 +24,41 @@ namespace MWMechanics
{ {
} }
Spells::TIterator Spells::begin() const std::map<const ESM::Spell*, SpellParams>::const_iterator Spells::begin() const
{ {
return mSpells.begin(); return mSpells.begin();
} }
Spells::TIterator Spells::end() const std::map<const ESM::Spell*, SpellParams>::const_iterator Spells::end() const
{ {
return mSpells.end(); return mSpells.end();
} }
const ESM::Spell* Spells::getSpell(const std::string& id) const
{
return MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(id);
}
void Spells::rebuildEffects() const void Spells::rebuildEffects() const
{ {
mEffects = MagicEffects(); mEffects = MagicEffects();
mSourcedEffects.clear(); 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 || 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) spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse)
{ {
int i=0; int i=0;
for (std::vector<ESM::ENAMstruct>::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 continue; // effect was purged
float random = 1.f; float random = 1.f;
if (iter->second.mEffectRands.find(i) != iter->second.mEffectRands.end()) if (iter.second.mEffectRands.find(i) != iter.second.mEffectRands.end())
random = iter->second.mEffectRands.at(i); random = iter.second.mEffectRands.at(i);
float magnitude = it->mMagnMin + (it->mMagnMax - it->mMagnMin) * random; float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * random;
mEffects.add (*it, magnitude); mEffects.add (effect, magnitude);
mSourcedEffects[spell].add(MWMechanics::EffectKey(*it), magnitude); mSourcedEffects[spell].add(MWMechanics::EffectKey(effect), magnitude);
++i; ++i;
} }
@ -71,7 +68,7 @@ namespace MWMechanics
bool Spells::hasSpell(const std::string &spell) const 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 bool Spells::hasSpell(const ESM::Spell *spell) const
@ -80,6 +77,16 @@ namespace MWMechanics
} }
void Spells::add (const ESM::Spell* spell) 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()) 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); const auto it = mSpells.find(spell);
TContainer::iterator iter = mSpells.find (spell); if(it != mSpells.end())
if (iter!=mSpells.end())
{ {
mSpells.erase (iter); mSpells.erase(it);
mSpellsChanged = true; mSpellsChanged = true;
} }
if (spellId==mSelectedSpell)
mSelectedSpell.clear();
} }
MagicEffects Spells::getMagicEffects() const MagicEffects Spells::getMagicEffects() const
@ -135,12 +142,19 @@ namespace MWMechanics
return mEffects; return mEffects;
} }
void Spells::clear() void Spells::removeAllSpells()
{ {
mSpells.clear(); mSpells.clear();
mSpellsChanged = true; mSpellsChanged = true;
} }
void Spells::clear(bool modifyBase)
{
removeAllSpells();
if(modifyBase)
mSpellList->clear();
}
void Spells::setSelectedSpell (const std::string& spellId) void Spells::setSelectedSpell (const std::string& spellId)
{ {
mSelectedSpell = spellId; mSelectedSpell = spellId;
@ -166,101 +180,78 @@ namespace MWMechanics
return false; 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; const ESM::Spell *spell = iter.first;
if (spell->mData.mType == ESM::Spell::ST_Disease) if (spell->mData.mType == type)
return true; return true;
} }
return false; 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; return hasDisease(ESM::Spell::ST_Disease);
if (spell->mData.mType == ESM::Spell::ST_Blight)
return true;
} }
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<std::string> purged;
for (auto iter = mSpells.begin(); iter!=mSpells.end();)
{ {
const ESM::Spell *spell = iter->first; const ESM::Spell *spell = iter->first;
if (spell->mData.mType == ESM::Spell::ST_Disease) if (filter(spell))
{ {
mSpells.erase(iter++); mSpells.erase(iter++);
purged.push_back(spell->mId);
mSpellsChanged = true; mSpellsChanged = true;
} }
else else
++iter; ++iter;
} }
if(!purged.empty())
mSpellList->removeAll(purged);
} }
void Spells::purgeBlightDisease() 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_Blight && !hasCorprusEffect(spell))
{ {
mSpells.erase(iter++); purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Disease; });
mSpellsChanged = true;
}
else
++iter;
} }
void Spells::purgeBlightDisease()
{
purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Blight && !hasCorprusEffect(spell); });
} }
void Spells::purgeCorprusDisease() void Spells::purgeCorprusDisease()
{ {
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) purge(&hasCorprusEffect);
{
const ESM::Spell *spell = iter->first;
if (hasCorprusEffect(spell))
{
mSpells.erase(iter++);
mSpellsChanged = true;
}
else
++iter;
}
} }
void Spells::purgeCurses() void Spells::purgeCurses()
{ {
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Curse; });
{
const ESM::Spell *spell = iter->first;
if (spell->mData.mType == ESM::Spell::ST_Curse)
{
mSpells.erase(iter++);
mSpellsChanged = true;
}
else
++iter;
}
} }
void Spells::removeEffects(const std::string &id) void Spells::removeEffects(const std::string &id)
{ {
if (isSpellActive(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; mSpellsChanged = false;
} }
for (std::map<SpellKey, MagicEffects>::const_iterator it = mSourcedEffects.begin(); for (const auto& it : mSourcedEffects)
it != mSourcedEffects.end(); ++it)
{ {
const ESM::Spell * spell = it->first; const ESM::Spell * spell = it.first;
for (MagicEffects::Collection::const_iterator effectIt = it->second.begin(); for (const auto& effectIt : it.second)
effectIt != it->second.end(); ++effectIt)
{ {
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) bool Spells::hasCorprusEffect(const ESM::Spell *spell)
{ {
for (std::vector<ESM::ENAMstruct>::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; return true;
} }
@ -302,14 +291,14 @@ namespace MWMechanics
void Spells::purgeEffect(int effectId) void Spells::purgeEffect(int effectId)
{ {
for (TContainer::iterator spellIt = mSpells.begin(); spellIt != mSpells.end(); ++spellIt) for (auto& spellIt : mSpells)
{ {
int i = 0; int i = 0;
for (std::vector<ESM::ENAMstruct>::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; mSpellsChanged = true;
} }
++i; ++i;
@ -319,15 +308,15 @@ namespace MWMechanics
void Spells::purgeEffect(int effectId, const std::string & sourceId) void Spells::purgeEffect(int effectId, const std::string & sourceId)
{ {
const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(sourceId); const ESM::Spell * spell = SpellList::getSpell(sourceId);
TContainer::iterator spellIt = mSpells.find(spell); auto spellIt = mSpells.find(spell);
if (spellIt == mSpells.end()) if (spellIt == mSpells.end())
return; return;
int i = 0; int i = 0;
for (std::vector<ESM::ENAMstruct>::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; mSpellsChanged = true;
@ -338,11 +327,8 @@ namespace MWMechanics
bool Spells::canUsePower(const ESM::Spell* spell) const bool Spells::canUsePower(const ESM::Spell* spell) const
{ {
std::map<SpellKey, MWWorld::TimeStamp>::const_iterator it = mUsedPowers.find(spell); const auto it = mUsedPowers.find(spell);
if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp()) return it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp();
return true;
else
return false;
} }
void Spells::usePower(const ESM::Spell* spell) void Spells::usePower(const ESM::Spell* spell)
@ -352,6 +338,8 @@ namespace MWMechanics
void Spells::readState(const ESM::SpellState &state, CreatureStats* creatureStats) 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) 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 // Discard spells that are no longer available due to changed content files
@ -365,6 +353,13 @@ namespace MWMechanics
mSelectedSpell = it->first; 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<ESM::Spell>().search(id);
if(spell)
addSpell(spell);
}
for (std::map<std::string, ESM::TimeStamp>::const_iterator it = state.mUsedPowers.begin(); it != state.mUsedPowers.end(); ++it) for (std::map<std::string, ESM::TimeStamp>::const_iterator it = state.mUsedPowers.begin(); it != state.mUsedPowers.end(); ++it)
{ {
@ -436,17 +431,50 @@ namespace MWMechanics
void Spells::writeState(ESM::SpellState &state) const 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)
{
//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; ESM::SpellState::SpellParams params;
params.mEffectRands = it->second.mEffectRands; params.mEffectRands = it.second.mEffectRands;
params.mPurgedEffects = it->second.mPurgedEffects; params.mPurgedEffects = it.second.mPurgedEffects;
state.mSpells.insert(std::make_pair(it->first->mId, params)); state.mSpells.insert(std::make_pair(it.first->mId, params));
}
} }
state.mSelectedSpell = mSelectedSpell; state.mSelectedSpell = mSelectedSpell;
for (std::map<SpellKey, MWWorld::TimeStamp>::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) for (const auto& it : mUsedPowers)
state.mUsedPowers[it->first->mId] = it->second.toEsm(); 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<std::string>& spells)
{
for(const std::string& id : spells)
{
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(id);
if(spell)
addSpell(spell);
else
Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << id << "'";
}
}
Spells::~Spells()
{
if(mSpellList)
mSpellList->removeListener(this);
} }
} }

@ -1,22 +1,19 @@
#ifndef GAME_MWMECHANICS_SPELLS_H #ifndef GAME_MWMECHANICS_SPELLS_H
#define GAME_MWMECHANICS_SPELLS_H #define GAME_MWMECHANICS_SPELLS_H
#include <memory>
#include <map> #include <map>
#include <string> #include <string>
#include <set> #include <set>
#include <vector>
#include <components/misc/stringops.hpp>
#include "../mwworld/ptr.hpp"
#include "../mwworld/timestamp.hpp" #include "../mwworld/timestamp.hpp"
#include "magiceffects.hpp" #include "magiceffects.hpp"
#include "spelllist.hpp"
namespace ESM namespace ESM
{ {
struct Spell;
struct SpellState; struct SpellState;
} }
@ -32,37 +29,36 @@ namespace MWMechanics
/// diseases. It also keeps track of used powers (which can only be used every 24h). /// diseases. It also keeps track of used powers (which can only be used every 24h).
class Spells class Spells
{ {
public: std::shared_ptr<SpellList> mSpellList;
std::map<const ESM::Spell*, SpellParams> mSpells;
typedef const ESM::Spell* SpellKey;
struct SpellParams
{
std::map<int, float> mEffectRands; // <effect index, normalised random magnitude>
std::set<int> mPurgedEffects; // indices of purged effects
};
typedef std::map<SpellKey, SpellParams> TContainer;
typedef TContainer::const_iterator TIterator;
private:
TContainer mSpells;
// Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) // 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::string mSelectedSpell;
std::map<SpellKey, MWWorld::TimeStamp> mUsedPowers; std::map<const ESM::Spell*, MWWorld::TimeStamp> mUsedPowers;
mutable bool mSpellsChanged; mutable bool mSpellsChanged;
mutable MagicEffects mEffects; mutable MagicEffects mEffects;
mutable std::map<SpellKey, MagicEffects> mSourcedEffects; mutable std::map<const ESM::Spell*, MagicEffects> mSourcedEffects;
void rebuildEffects() const; void rebuildEffects() const;
/// Get spell from ID, throws exception if not found bool hasDisease(const ESM::Spell::SpellType type) const;
const ESM::Spell* getSpell(const std::string& id) 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: public:
using TIterator = std::map<const ESM::Spell*, SpellParams>::const_iterator;
Spells(); Spells();
~Spells();
static bool hasCorprusEffect(const ESM::Spell *spell); static bool hasCorprusEffect(const ESM::Spell *spell);
void purgeEffect(int effectId); void purgeEffect(int effectId);
@ -96,7 +92,7 @@ namespace MWMechanics
MagicEffects getMagicEffects() const; MagicEffects getMagicEffects() const;
///< Return sum of magic effects resulting from abilities, blights, deseases and curses. ///< 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. ///< Remove all spells of al types.
void setSelectedSpell (const std::string& spellId); void setSelectedSpell (const std::string& spellId);
@ -118,6 +114,10 @@ namespace MWMechanics
void readState (const ESM::SpellState& state, CreatureStats* creatureStats); void readState (const ESM::SpellState& state, CreatureStats* creatureStats);
void writeState (ESM::SpellState& state) const; void writeState (ESM::SpellState& state) const;
bool setSpells(const std::string& id);
void addAllToInstance(const std::vector<std::string>& spells);
}; };
} }

@ -9,6 +9,8 @@
#include <components/esm/esmreader.hpp> #include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp> #include <components/esm/esmwriter.hpp>
#include "../mwmechanics/spelllist.hpp"
namespace namespace
{ {
void readRefs(const ESM::Cell& cell, std::map<ESM::RefNum, std::string>& refs, std::vector<ESM::ESMReader>& readers) void readRefs(const ESM::Cell& cell, std::map<ESM::RefNum, std::string>& refs, std::vector<ESM::ESMReader>& readers)
@ -409,4 +411,23 @@ void ESMStore::validate()
throw std::runtime_error ("Invalid player record (race or class unavailable"); throw std::runtime_error ("Invalid player record (race or class unavailable");
} }
std::pair<std::shared_ptr<MWMechanics::SpellList>, bool> ESMStore::getSpellList(const std::string& originalId) const
{
const std::string id = Misc::StringUtils::lowerCase(originalId);
auto result = mSpellListCache.find(id);
std::shared_ptr<MWMechanics::SpellList> ptr;
if (result != mSpellListCache.end())
ptr = result->second.lock();
if (!ptr)
{
int type = find(id);
ptr = std::make_shared<MWMechanics::SpellList>(id, type);
if (result != mSpellListCache.end())
result->second = ptr;
else
mSpellListCache.insert({id, ptr});
return {ptr, false};
}
return {ptr, true};
}
} // end namespace } // end namespace

@ -1,6 +1,7 @@
#ifndef OPENMW_MWWORLD_ESMSTORE_H #ifndef OPENMW_MWWORLD_ESMSTORE_H
#define OPENMW_MWWORLD_ESMSTORE_H #define OPENMW_MWWORLD_ESMSTORE_H
#include <memory>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
@ -12,6 +13,11 @@ namespace Loading
class Listener; class Listener;
} }
namespace MWMechanics
{
class SpellList;
}
namespace MWWorld namespace MWWorld
{ {
class ESMStore class ESMStore
@ -78,6 +84,8 @@ namespace MWWorld
unsigned int mDynamicCount; unsigned int mDynamicCount;
mutable std::map<std::string, std::weak_ptr<MWMechanics::SpellList> > mSpellListCache;
/// Validate entries in store after setup /// Validate entries in store after setup
void validate(); void validate();
@ -257,6 +265,8 @@ namespace MWWorld
void checkPlayer(); void checkPlayer();
int getRefCount(const std::string& id) const; int getRefCount(const std::string& id) const;
std::pair<std::shared_ptr<MWMechanics::SpellList>, bool> getSpellList(const std::string& id) const;
}; };
template <> template <>

@ -8,6 +8,12 @@
#include <components/loadinglistener/loadinglistener.hpp> #include <components/loadinglistener/loadinglistener.hpp>
#include "apps/openmw/mwworld/esmstore.hpp" #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; static Loading::Listener dummyListener;

@ -4,7 +4,7 @@
#include "esmwriter.hpp" #include "esmwriter.hpp"
unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE;
int ESM::SavedGame::sCurrentFormat = 12; int ESM::SavedGame::sCurrentFormat = 13;
void ESM::SavedGame::load (ESMReader &esm) void ESM::SavedGame::load (ESMReader &esm)
{ {

Loading…
Cancel
Save