From 83819b2894371d25f4be0ec79c108bda5ec06178 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 12 Jul 2014 08:05:10 +0200 Subject: [PATCH] Traverse spells in record order from content files. bronrod_the_roarer is perfect now. Other NPCs have some differences. --- apps/openmw/mwclass/npc.cpp | 28 ++---------------- apps/openmw/mwmechanics/autocalcspell.cpp | 32 +++++++++++++++----- apps/openmw/mwmechanics/autocalcspell.hpp | 2 +- apps/openmw/mwworld/store.hpp | 36 +++++++++++++++++++---- 4 files changed, 59 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 80900d4655..31e09bbacf 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -221,33 +221,9 @@ namespace for (int i=0; imId << std::endl; - std::cout << "Skills: " << std::endl; - for (int i=0; i spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); - std::cout << "Spells: " << spells.size() << std::endl; - for (std::set::iterator it = spells.begin(); it != spells.end(); ++it) - { - std::cout << *it << ", "; + std::vector spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); + for (std::vector::iterator it = spells.begin(); it != spells.end(); ++it) npcStats.getSpells().add(*it); - } - std::cout << std::endl; - - const char* compare[] = { "weary","dire noise","reflect","weak spelldrinker","absorb endurance","absorb personality","absorb speed","absorb strength","absorb willpower","fortify alteration skill","fortify illusion skill","fortify unarmored skill","fortify mysticism skill","fortify restoration skill","assured sublime wisdom","surpassing sublime wisdom","surpassing golden wisdom","blood gift","surpassing silver wisdom","surpassing unseen wisdom","surpassing green wisdom","powerwell","orc's strength","surpassing fluid evasion","poet's whim","rapid regenerate","dispel","shadow weave" }; - int n = sizeof(compare) / sizeof(compare[0]); - std::set compareSet; - for (int i=0; i::iterator it = compareSet.begin(); it != compareSet.end(); ++it) - { - std::cout << *it << ", "; - } - std::cout << std::endl; } } diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index d2b6c46e3b..255decdf75 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -20,7 +20,7 @@ namespace MWMechanics std::string mWeakestSpell; }; - std::set autoCalcNpcSpells(const int *actorSkills, const int *actorAttributes, const ESM::Race* race) + std::vector autoCalcNpcSpells(const int *actorSkills, const int *actorAttributes, const ESM::Race* race) { const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fNPCbaseMagickaMult = gmst.find("fNPCbaseMagickaMult")->getFloat(); @@ -53,10 +53,13 @@ namespace MWMechanics schoolCaps[i] = caps; } - std::set selectedSpells; + std::vector selectedSpells; const MWWorld::Store &spells = MWBase::Environment::get().getWorld()->getStore().get(); + + // Note: the algorithm heavily depends on the traversal order of the spells. For vanilla-compatible results the + // Store must preserve the record ordering as it was in the content files. for (MWWorld::Store::iterator iter = spells.begin(); iter != spells.end(); ++iter) { const ESM::Spell* spell = &*iter; @@ -88,18 +91,32 @@ namespace MWMechanics if (calcAutoCastChance(spell, actorSkills, actorAttributes, school) < fAutoSpellChance) continue; - selectedSpells.insert(spell->mId); + selectedSpells.push_back(spell->mId); if (cap.mReachedLimit) { - selectedSpells.erase(cap.mWeakestSpell); + std::vector::iterator found = std::find(selectedSpells.begin(), selectedSpells.end(), cap.mWeakestSpell); + if (found != selectedSpells.end()) + selectedSpells.erase(found); - // Note: not school specific cap.mMinCost = INT_MAX; - for (std::set::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt) + for (std::vector::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt) { const ESM::Spell* testSpell = spells.find(*weakIt); - if (testSpell->mData.mCost < cap.mMinCost) // XXX what if 2 candidates have the same cost? + + //int testSchool; + //float dummySkillTerm; + //calcWeakestSchool(testSpell, actorSkills, testSchool, dummySkillTerm); + + // Note: if there are multiple spells with the same cost, we pick the first one we found. + // So the algorithm depends on the iteration order of the outer loop. + if ( + // There is a huge bug here. It is not checked that weakestSpell is of the correct school. + // As result multiple SchoolCaps could have the same mWeakestSpell. Erasing the weakest spell would then fail if another school + // already erased it, and so the number of spells would often exceed the sum of limits. + // This bug cannot be fixed without significantly changing the results of the spell autocalc, which will not have been playtested. + //testSchool == school && + testSpell->mData.mCost < cap.mMinCost) { cap.mMinCost = testSpell->mData.mCost; cap.mWeakestSpell = testSpell->mId; @@ -119,6 +136,7 @@ namespace MWMechanics } } } + return selectedSpells; } diff --git a/apps/openmw/mwmechanics/autocalcspell.hpp b/apps/openmw/mwmechanics/autocalcspell.hpp index 8ba7d833f7..1912c75c42 100644 --- a/apps/openmw/mwmechanics/autocalcspell.hpp +++ b/apps/openmw/mwmechanics/autocalcspell.hpp @@ -14,7 +14,7 @@ namespace MWMechanics /// Contains algorithm for calculating an NPC's spells based on stats /// @note We might want to move this code to a component later, so the editor can use it for preview purposes -std::set autoCalcNpcSpells(const int* actorSkills, const int* actorAttributes, const ESM::Race* race); +std::vector autoCalcNpcSpells(const int* actorSkills, const int* actorAttributes, const ESM::Race* race); // Helpers diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 3e7e7a5f98..9a442387b3 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -209,8 +209,6 @@ namespace MWWorld } void setUp() { - //std::sort(mStatic.begin(), mStatic.end(), RecordCmp()); - mShared.clear(); mShared.reserve(mStatic.size()); typename std::map::iterator it = mStatic.begin(); @@ -675,18 +673,15 @@ namespace MWWorld } void setUp() { - //typedef std::vector::iterator Iterator; typedef DynamicExt::iterator ExtIterator; typedef std::map::iterator IntIterator; - //std::sort(mInt.begin(), mInt.end(), RecordCmp()); mSharedInt.clear(); mSharedInt.reserve(mInt.size()); for (IntIterator it = mInt.begin(); it != mInt.end(); ++it) { mSharedInt.push_back(&(it->second)); } - //std::sort(mExt.begin(), mExt.end(), ExtCmp()); mSharedExt.clear(); mSharedExt.reserve(mExt.size()); for (ExtIterator it = mExt.begin(); it != mExt.end(); ++it) { @@ -1147,6 +1142,37 @@ namespace MWWorld } }; + + // Specialisation for ESM::Spell to preserve record order as it was in the content files. + // The NPC spell autocalc code heavily depends on this order. + // We could also do this in the base class, but it's usually not a good idea to depend on record order. + template<> + inline void Store::clearDynamic() + { + // remove the dynamic part of mShared + mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); + mDynamic.clear(); + } + + template<> + inline void Store::load(ESM::ESMReader &esm, const std::string &id) { + std::string idLower = Misc::StringUtils::lowerCase(id); + + std::pair inserted = mStatic.insert(std::make_pair(idLower, ESM::Spell())); + if (inserted.second) + mShared.push_back(&mStatic[idLower]); + + inserted.first->second.mId = idLower; + inserted.first->second.load(esm); + } + + template<> + inline void Store::setUp() + { + // remove the dynamic part of mShared + mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); + } + } //end namespace #endif