From ee81698089652ae92c78967570a41c4b21bddbbb Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 3 Jul 2014 17:40:44 +0200 Subject: [PATCH 01/23] Always control movement by animation except for first person or in-air --- apps/openmw/mwmechanics/character.cpp | 19 ++++++++++++++----- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2c5d68ceb7..2f33c05916 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -361,9 +361,10 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat * beginning. */ int mode = ((movement == mCurrentMovement) ? 2 : 1); + mMovementAnimationControlled = true; + mAnimation->disable(mCurrentMovement); mCurrentMovement = movement; - mMovementAnimVelocity = 0.0f; if(!mCurrentMovement.empty()) { float vel, speedmult = 1.0f; @@ -383,16 +384,18 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) { - mMovementAnimVelocity = vel; speedmult = mMovementSpeed / vel; } else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed else if (mMovementSpeed > 0.0f) + { // The first person anims don't have any velocity to calculate a speed multiplier from. // We use the third person velocities instead. // FIXME: should be pulled from the actual animation, but it is not presently loaded. speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f); + mMovementAnimationControlled = false; + } mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); } @@ -506,6 +509,7 @@ void CharacterController::playDeath(float startpoint, CharacterState death) mJumpState = JumpState_None; mAnimation->disable(mCurrentJump); mCurrentJump = ""; + mMovementAnimationControlled = true; mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, false, 1.0f, "start", "stop", startpoint, 0); @@ -547,7 +551,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mIdleState(CharState_None) , mMovementState(CharState_None) , mMovementSpeed(0.0f) - , mMovementAnimVelocity(0.0f) + , mMovementAnimationControlled(true) , mDeathState(CharState_None) , mHitState(CharState_None) , mUpperBodyState(UpperCharState_Nothing) @@ -1241,6 +1245,7 @@ void CharacterController::update(float duration) if (inwater || flying) cls.getCreatureStats(mPtr).land(); + bool inJump = true; if(!onground && !flying && !inwater) { // In the air (either getting up —ascending part of jump— or falling). @@ -1330,6 +1335,8 @@ void CharacterController::update(float duration) mJumpState = JumpState_None; vec.z = 0.0f; + inJump = false; + if(std::abs(vec.x/2.0f) > std::abs(vec.y)) { if(vec.x > 0.0f) @@ -1391,6 +1398,8 @@ void CharacterController::update(float duration) forcestateupdate = updateCreatureState() || forcestateupdate; refreshCurrentAnims(idlestate, movestate, forcestateupdate); + if (inJump) + mMovementAnimationControlled = false; if (!mSkipAnim) { @@ -1402,7 +1411,7 @@ void CharacterController::update(float duration) else //avoid z-rotating for knockdown world->rotateObject(mPtr, rot.x, rot.y, 0.0f, true); - if (mMovementAnimVelocity == 0) + if (!mMovementAnimationControlled) world->queueMovement(mPtr, vec); } else @@ -1446,7 +1455,7 @@ void CharacterController::update(float duration) } // Update movement - if(mMovementAnimVelocity > 0) + if(mMovementAnimationControlled && mPtr.getClass().isActor()) world->queueMovement(mPtr, moved); } else if (mAnimation) diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 59c20db8fe..b1e1738bd9 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -147,7 +147,7 @@ class CharacterController CharacterState mMovementState; std::string mCurrentMovement; float mMovementSpeed; - float mMovementAnimVelocity; + bool mMovementAnimationControlled; CharacterState mDeathState; std::string mCurrentDeath; From 0a2d5d34bcf5059536dd1fcacda77e2b403c4016 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 3 Jul 2014 19:37:12 +0200 Subject: [PATCH 02/23] Cancel queued view mode switch when switching view mode (Fixes #1618) --- apps/openmw/mwrender/camera.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 9e683cc159..4580bae70b 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -149,6 +149,8 @@ namespace MWRender mViewModeToggleQueued = true; return; } + else + mViewModeToggleQueued = false; mFirstPersonView = !mFirstPersonView; processViewChange(); From a3a8f4da705560545ea220b4d5c26a1a8e8cf804 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 3 Jul 2014 19:56:38 +0200 Subject: [PATCH 03/23] More font hackery. Will it ever end? Fixes #1606 --- apps/openmw/mwgui/fontloader.cpp | 35 +++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwgui/fontloader.cpp b/apps/openmw/mwgui/fontloader.cpp index b7c2007b46..92d9a25b61 100644 --- a/apps/openmw/mwgui/fontloader.cpp +++ b/apps/openmw/mwgui/fontloader.cpp @@ -260,21 +260,28 @@ namespace MWGui // More hacks! The french game uses several win1252 characters that are not included // in the cp437 encoding of the font. Fall back to similar available characters. - // Same for U+2013 - std::map additional; - additional[39] = 0x2019; // apostrophe - additional[45] = 0x2013; // dash - if (additional.find(i) != additional.end() && mEncoding == ToUTF8::CP437) + if (mEncoding == ToUTF8::CP437) { - MyGUI::xml::ElementPtr code = codes->createChild("Code"); - code->addAttribute("index", additional[i]); - code->addAttribute("coord", MyGUI::utility::toString(x1) + " " - + MyGUI::utility::toString(y1) + " " - + MyGUI::utility::toString(w) + " " - + MyGUI::utility::toString(h)); - code->addAttribute("advance", data[i].width); - code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " - + MyGUI::utility::toString((fontSize-data[i].ascent))); + std::multimap additional; + additional.insert(std::make_pair(39, 0x2019)); // apostrophe + additional.insert(std::make_pair(45, 0x2013)); // dash + additional.insert(std::make_pair(34, 0x201D)); // right double quotation mark + additional.insert(std::make_pair(34, 0x201C)); // left double quotation mark + for (std::multimap::iterator it = additional.begin(); it != additional.end(); ++it) + { + if (it->first != i) + continue; + + MyGUI::xml::ElementPtr code = codes->createChild("Code"); + code->addAttribute("index", it->second); + code->addAttribute("coord", MyGUI::utility::toString(x1) + " " + + MyGUI::utility::toString(y1) + " " + + MyGUI::utility::toString(w) + " " + + MyGUI::utility::toString(h)); + code->addAttribute("advance", data[i].width); + code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + + MyGUI::utility::toString((fontSize-data[i].ascent))); + } } // ASCII vertical bar, use this as text input cursor From 742d008765f1ba2aa0b9ad94ccd7cf29d82fbca5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 3 Jul 2014 20:28:52 +0200 Subject: [PATCH 04/23] Fix initializing CharacterController with fists or spell equipped --- apps/openmw/mwmechanics/character.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2f33c05916..d4ddf53cd0 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -574,10 +574,14 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim if (cls.hasInventoryStore(mPtr)) { getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType); + if (mWeaponType != WeapType_None) + { + mUpperBodyState = UpperCharState_WeapEquiped; + getWeaponGroup(mWeaponType, mCurrentWeapon); + } + if(mWeaponType != WeapType_None && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) { - getWeaponGroup(mWeaponType, mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; mAnimation->showWeapons(true); mAnimation->setWeaponGroup(mCurrentWeapon); } From ad90d20ad8dc6e1d3f3fa28f0ecf3ef1bf986297 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 3 Jul 2014 21:00:12 +0200 Subject: [PATCH 05/23] Don't silently skip unknown NPC subrecords --- components/esm/loadnpc.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index e5b851bf0c..2fe9fe3c11 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -10,8 +10,6 @@ namespace ESM void NPC::load(ESMReader &esm) { - //mNpdt52.mGold = -10; - mPersistent = esm.getRecordFlags() & 0x0400; mModel = esm.getHNOString("MODL"); @@ -63,7 +61,6 @@ void NPC::load(ESMReader &esm) } } mAiPackage.load(esm); - esm.skipRecord(); } void NPC::save(ESMWriter &esm) const { From df80c436fb5d46773a29d3306db382534faa1f3b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 5 Jul 2014 18:24:55 +0200 Subject: [PATCH 06/23] Fix compile error for OPENMW_USE_FFMPEG=0 --- apps/openmw/mwrender/videoplayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index 03e74697c1..409e273887 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -1088,7 +1088,7 @@ public: void close() { } - bool update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height) + bool update() { return false; } }; From 0704fa2b3dda4945b392139b220e49d40a19b695 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 9 Jul 2014 10:23:01 +0200 Subject: [PATCH 07/23] Reset local rotation axis in SetAngle (Fixes #1630) --- apps/openmw/mwscript/transformationextensions.cpp | 8 ++++++++ apps/openmw/mwworld/worldimp.cpp | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 3860257ad6..a041049ca4 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -86,16 +86,24 @@ namespace MWScript float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); float az = Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees(); + MWWorld::LocalRotation localRot = ptr.getRefData().getLocalRotation(); + if (axis == "x") { + localRot.rot[0] = 0; + ptr.getRefData().setLocalRotation(localRot); MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az); } else if (axis == "y") { + localRot.rot[1] = 0; + ptr.getRefData().setLocalRotation(localRot); MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az); } else if (axis == "z") { + localRot.rot[2] = 0; + ptr.getRefData().setLocalRotation(localRot); MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle); } else diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 33405b4d86..30c90cfedc 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1123,7 +1123,10 @@ namespace MWWorld ptr.getRefData().setPosition(pos); - mWorldScene->updateObjectRotation(ptr); + if (ptr.getClass().isActor()) + mWorldScene->updateObjectRotation(ptr); + else + mWorldScene->updateObjectLocalRotation(ptr); } void World::localRotateObject (const Ptr& ptr, float x, float y, float z) From 53c70dbdaac4a1eac7c83c6f3736fd4a41cdb9f5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 11 Jul 2014 04:25:56 +0200 Subject: [PATCH 08/23] Exit dialogue when going to jail (Fixes #1635) --- apps/openmw/mwworld/worldimp.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 30c90cfedc..3868348820 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2652,6 +2652,8 @@ namespace MWWorld { mGoToJail = false; + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); + MWWorld::Ptr player = getPlayerPtr(); teleportToClosestMarker(player, "prisonmarker"); int bounty = player.getClass().getNpcStats(player).getBounty(); From 5fbce67b9d2a63800d406d3d83dfa8ae805f73f5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 11 Jul 2014 04:30:44 +0200 Subject: [PATCH 09/23] Don't redraw ItemView if the size did not change --- apps/openmw/mwgui/itemview.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index fdaf930399..b86f610341 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -140,26 +140,28 @@ void ItemView::onMouseWheel(MyGUI::Widget *_sender, int _rel) void ItemView::setSize(const MyGUI::IntSize &_value) { + bool changed = (_value.width != getWidth() || _value.height != getHeight()); Base::setSize(_value); - update(); + if (changed) + update(); } void ItemView::setSize(int _width, int _height) { - Base::setSize(_width, _height); - update(); + setSize(MyGUI::IntSize(_width, _height)); } void ItemView::setCoord(const MyGUI::IntCoord &_value) { + bool changed = (_value.width != getWidth() || _value.height != getHeight()); Base::setCoord(_value); - update(); + if (changed) + update(); } void ItemView::setCoord(int _left, int _top, int _width, int _height) { - Base::setCoord(_left, _top, _width, _height); - update(); + setCoord(MyGUI::IntCoord(_left, _top, _width, _height)); } void ItemView::registerComponents() From 048d07b29905522b83336cf097b2fbe704de3f59 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 11 Jul 2014 11:57:21 +0200 Subject: [PATCH 10/23] Show owner of doors in tooltip --- apps/openmw/mwclass/door.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index e435511b99..677ad462e0 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -250,8 +250,11 @@ namespace MWClass text += "\n#{sTrapped}"; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + { text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); - + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); + } info.text = text; return info; From d91d5992697e3edf9f93ccfb85ae34b0a95436b1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 12 Jul 2014 06:43:04 +0200 Subject: [PATCH 11/23] Disable quickload when in modal dialog (Fixes #1641) --- apps/openmw/mwinput/inputmanagerimp.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index afdde6fb01..8d4c53921c 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -707,11 +707,13 @@ namespace MWInput } void InputManager::quickLoad() { - MWBase::Environment::get().getStateManager()->quickLoad(); + if (!MyGUI::InputManager::getInstance().isModalAny()) + MWBase::Environment::get().getStateManager()->quickLoad(); } void InputManager::quickSave() { - MWBase::Environment::get().getStateManager()->quickSave(); + if (!MyGUI::InputManager::getInstance().isModalAny()) + MWBase::Environment::get().getStateManager()->quickSave(); } void InputManager::toggleSpell() { From 1c41ce9b9d95e09f68cf304bd2342617742eceaa Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 11 Jul 2014 07:31:18 +0200 Subject: [PATCH 12/23] Implement Hrnchamd's player and NPC autocalc spells (Some unclarities remaining, XXX) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwclass/npc.cpp | 49 +++- apps/openmw/mwmechanics/autocalcspell.cpp | 214 ++++++++++++++++++ apps/openmw/mwmechanics/autocalcspell.hpp | 31 +++ .../mwmechanics/mechanicsmanagerimp.cpp | 96 ++++++-- components/esm/loadspel.hpp | 4 +- 6 files changed, 368 insertions(+), 28 deletions(-) create mode 100644 apps/openmw/mwmechanics/autocalcspell.cpp create mode 100644 apps/openmw/mwmechanics/autocalcspell.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 5c5b0d16ca..23ba78dbad 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -69,7 +69,7 @@ add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting - disease pickpocket levelledlist combat steering obstacle + disease pickpocket levelledlist combat steering obstacle autocalcspell ) add_openmw_dir (mwstate diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index c003e0b227..80900d4655 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -22,6 +22,7 @@ #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/disease.hpp" #include "../mwmechanics/combat.hpp" +#include "../mwmechanics/autocalcspell.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actiontalk.hpp" @@ -193,18 +194,6 @@ namespace majorMultiplier = 1.0f; break; } - if (class_->mData.mSkills[k][1] == skillIndex) - { - // Major skill -> add starting spells for this skill if existing - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - MWWorld::Store::iterator it = store.get().begin(); - for (; it != store.get().end(); ++it) - { - if (it->mData.mFlags & ESM::Spell::F_Autocalc - && MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(&*it, ptr)) == skillIndex) - npcStats.getSpells().add(it->mId); - } - } } // is this skill in the same Specialization as the class? @@ -223,6 +212,42 @@ namespace + specBonus + static_cast((level-1) * (majorMultiplier + specMultiplier)), 100)); } + + int skills[ESM::Skill::Length]; + 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 << ", "; + 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 new file mode 100644 index 0000000000..5659959e84 --- /dev/null +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -0,0 +1,214 @@ +#include "autocalcspell.hpp" + +#include + +#include "../mwworld/esmstore.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + + +namespace MWMechanics +{ + + struct SchoolCaps + { + int mCount; + int mLimit; + bool mReachedLimit; + int mMinCost; + std::string mWeakestSpell; + }; + + std::set 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(); + float baseMagicka = fNPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence]; + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + static int iAutoSpellSchoolMax[6]; + static bool init = false; + if (!init) + { + for (int i=0; i<6; ++i) + { + const std::string& gmstName = "iAutoSpell" + schools[i] + "Max"; + iAutoSpellSchoolMax[i] = gmst.find(gmstName)->getInt(); + } + init = true; + } + + std::map schoolCaps; + for (int i=0; i<6; ++i) + { + SchoolCaps caps; + caps.mCount = 0; + caps.mLimit = iAutoSpellSchoolMax[i]; + caps.mReachedLimit = iAutoSpellSchoolMax[i] <= 0; + caps.mMinCost = INT_MAX; + caps.mWeakestSpell.clear(); + schoolCaps[i] = caps; + } + + std::set selectedSpells; + + const MWWorld::Store &spells = + MWBase::Environment::get().getWorld()->getStore().get(); + for (MWWorld::Store::iterator iter = spells.begin(); iter != spells.end(); ++iter) + { + const ESM::Spell* spell = &*iter; + + if (spell->mData.mType != ESM::Spell::ST_Spell) + continue; + if (!(spell->mData.mFlags & ESM::Spell::F_Autocalc)) + continue; + static const int iAutoSpellTimesCanCast = gmst.find("iAutoSpellTimesCanCast")->getInt(); + if (baseMagicka < iAutoSpellTimesCanCast * spell->mData.mCost) + continue; + + if (race && std::find(race->mPowers.mList.begin(), race->mPowers.mList.end(), spell->mId) != race->mPowers.mList.end()) + continue; + + if (!attrSkillCheck(spell, actorSkills, actorAttributes)) + continue; + + int school; + float skillTerm; + calcWeakestSchool(spell, actorSkills, school, skillTerm); + assert(school >= 0 && school < 6); + SchoolCaps& cap = schoolCaps[school]; + + if (cap.mReachedLimit && spell->mData.mCost <= cap.mMinCost) + continue; + + static const float fAutoSpellChance = gmst.find("fAutoSpellChance")->getFloat(); + if (calcAutoCastChance(spell, actorSkills, actorAttributes, school) < fAutoSpellChance) + continue; + + selectedSpells.insert(spell->mId); + + if (cap.mReachedLimit) + { + selectedSpells.erase(cap.mWeakestSpell); + + // Note: not school specific + cap.mMinCost = INT_MAX; + for (std::set::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? + { + cap.mMinCost = testSpell->mData.mCost; + cap.mWeakestSpell = testSpell->mId; + } + } + } + else + { + cap.mCount += 1; + if (cap.mCount == cap.mLimit) + cap.mReachedLimit = true; + + if (spell->mData.mCost < cap.mMinCost) + { + cap.mWeakestSpell = spell->mId; + cap.mMinCost = spell->mData.mCost; + } + } + } + return selectedSpells; + } + + bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes) + { + const std::vector& effects = spell->mEffects.mList; + for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) + { + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); + static const int iAutoSpellAttSkillMin = MWBase::Environment::get().getWorld()->getStore().get().find("iAutoSpellAttSkillMin")->getInt(); + + if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)) + { + assert (effectIt->mSkill >= 0 && effectIt->mSkill < ESM::Skill::Length); + if (actorSkills[effectIt->mSkill] < iAutoSpellAttSkillMin) + return false; + } + + if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)) + { + assert (effectIt->mAttribute >= 0 && effectIt->mAttribute < ESM::Attribute::Length); + if (actorAttributes[effectIt->mAttribute] < iAutoSpellAttSkillMin) + return false; + } + } + + return true; + } + + ESM::Skill::SkillEnum mapSchoolToSkill(int school) + { + std::map schoolSkillMap; // maps spell school to skill id + schoolSkillMap[0] = ESM::Skill::Alteration; + schoolSkillMap[1] = ESM::Skill::Conjuration; + schoolSkillMap[3] = ESM::Skill::Illusion; + schoolSkillMap[2] = ESM::Skill::Destruction; + schoolSkillMap[4] = ESM::Skill::Mysticism; + schoolSkillMap[5] = ESM::Skill::Restoration; + assert(schoolSkillMap.find(school) != schoolSkillMap.end()); + return schoolSkillMap[school]; + } + + void calcWeakestSchool (const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm) + { + float minChance = FLT_MAX; + + const ESM::EffectList& effects = spell->mEffects; + for (std::vector::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) + { + const ESM::ENAMstruct& effect = *it; + float x = effect.mDuration; + + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) + x = std::max(1.f, x); + + x *= 0.1f * magicEffect->mData.mBaseCost; + x *= 0.5f * (effect.mMagnMin + effect.mMagnMax); + x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; // XXX spell.radius + if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) // XXX effect.flags & CAST_TARGET + x *= 1.5f; + + static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find("fEffectCostMult")->getFloat(); + x *= fEffectCostMult; + + float s = 2.f * actorSkills[mapSchoolToSkill(magicEffect->mData.mSchool)]; + if (s - x < minChance) + { + minChance = s - x; + effectiveSchool = magicEffect->mData.mSchool; + skillTerm = s; + } + } + } + + float calcAutoCastChance(const ESM::Spell *spell, const int *actorSkills, const int *actorAttributes, int effectiveSchool) + { + if (spell->mData.mType != ESM::Spell::ST_Spell) + return 100.f; + + if (spell->mData.mFlags & ESM::Spell::F_Always) + return 100.f; + + float skillTerm; + if (effectiveSchool != -1) + skillTerm = 2.f * actorSkills[mapSchoolToSkill(effectiveSchool)]; + else + calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this + + float castChance = skillTerm - spell->mData.mCost + 0.2f * actorAttributes[ESM::Attribute::Willpower] + 0.1f * actorAttributes[ESM::Attribute::Luck]; + return castChance; + } +} diff --git a/apps/openmw/mwmechanics/autocalcspell.hpp b/apps/openmw/mwmechanics/autocalcspell.hpp new file mode 100644 index 0000000000..8ba7d833f7 --- /dev/null +++ b/apps/openmw/mwmechanics/autocalcspell.hpp @@ -0,0 +1,31 @@ +#ifndef OPENMW_AUTOCALCSPELL_H +#define OPENMW_AUTOCALCSPELL_H + +#include +#include + +#include +#include +#include + +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); + +// Helpers + +bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes); + +ESM::Skill::SkillEnum mapSchoolToSkill(int school); + +void calcWeakestSchool(const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm); + +float calcAutoCastChance(const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, int effectiveSchool); + +} + +#endif diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 6e515142de..073cb5f74c 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -19,6 +19,7 @@ #include #include "spellcasting.hpp" +#include "autocalcspell.hpp" namespace { @@ -155,19 +156,6 @@ namespace MWMechanics npcStats.getSkill (index).setBase ( npcStats.getSkill (index).getBase() + bonus); } - - if (i==1) - { - // Major skill - add starting spells for this skill if existing - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - MWWorld::Store::iterator it = store.get().begin(); - for (; it != store.get().end(); ++it) - { - if (it->mData.mFlags & ESM::Spell::F_PCStart - && spellSchoolToSkill(getSpellSchool(&*it, ptr)) == index) - creatureStats.getSpells().add(it->mId); - } - } } } @@ -190,6 +178,88 @@ namespace MWMechanics } } + // F_PCStart spells + static const float fPCbaseMagickaMult = esmStore.get().find("fPCbaseMagickaMult")->getFloat(); + + float baseMagicka = fPCbaseMagickaMult * creatureStats.getAttribute(ESM::Attribute::Intelligence).getBase(); + bool reachedLimit = false; + const ESM::Spell* weakestSpell = NULL; + int minCost = INT_MAX; + + std::set selectedSpells; + + const ESM::Race* race = NULL; + if (mRaceSelected) + race = esmStore.get().find(player->mRace); + + int skills[ESM::Skill::Length]; + for (int i=0; i &spells = + esmStore.get(); + for (MWWorld::Store::iterator iter = spells.begin(); iter != spells.end(); ++iter) + { + const ESM::Spell* spell = &*iter; + + if (spell->mData.mType != ESM::Spell::ST_Spell) + continue; + if (!(spell->mData.mFlags & ESM::Spell::F_PCStart)) + continue; + if (reachedLimit && spell->mData.mCost <= minCost) + continue; + if (selectedSpells.find(spell->mId) != selectedSpells.end()) + continue; + if (race && std::find(race->mPowers.mList.begin(), race->mPowers.mList.end(), spell->mId) != race->mPowers.mList.end()) + continue; + if (baseMagicka < spell->mData.mCost) + continue; + + static const float fAutoPCSpellChance = esmStore.get().find("fAutoPCSpellChance")->getFloat(); + if (calcAutoCastChance(spell, skills, attributes, -1) < fAutoPCSpellChance) + continue; + + if (!attrSkillCheck(spell, skills, attributes)) + continue; + + selectedSpells.insert(spell->mId); + + if (reachedLimit) + { + selectedSpells.erase(weakestSpell->mId); + + minCost = INT_MAX; + for (std::set::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt) + { + const ESM::Spell* testSpell = esmStore.get().find(*weakIt); + if (testSpell->mData.mCost < minCost) // XXX what if 2 candidates have the same cost? + // Note iAutoPCSpellMax is 100 by default, so reachedLimit is very unlikely to happen + { + minCost = testSpell->mData.mCost; + weakestSpell = testSpell; + } + } + } + else + { + if (spell->mData.mCost < minCost) + { + weakestSpell = spell; + minCost = weakestSpell->mData.mCost; + } + static const unsigned int iAutoPCSpellMax = esmStore.get().find("iAutoPCSpellMax")->getInt(); + if (selectedSpells.size() == iAutoPCSpellMax) + reachedLimit = true; + } + } + + for (std::set::iterator it = selectedSpells.begin(); it != selectedSpells.end(); ++it) + creatureStats.getSpells().add(*it); + // forced update and current value adjustments mActors.updateActor (ptr, 0); diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index cbf5366c4b..4bd2210ec0 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -27,8 +27,8 @@ struct Spell enum Flags { - F_Autocalc = 1, - F_PCStart = 2, + F_Autocalc = 1, // Can be selected by NPC spells auto-calc + F_PCStart = 2, // Can be selected by player spells auto-calc F_Always = 4 // Casting always succeeds }; From b4f8edc186772f029411cbd317b1bf6551a2f436 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 11 Jul 2014 18:25:29 +0200 Subject: [PATCH 13/23] Use spell effect range type, not base effect --- apps/openmw/mwmechanics/autocalcspell.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index 5659959e84..d2b6c46e3b 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -177,8 +177,8 @@ namespace MWMechanics x *= 0.1f * magicEffect->mData.mBaseCost; x *= 0.5f * (effect.mMagnMin + effect.mMagnMax); - x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; // XXX spell.radius - if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) // XXX effect.flags & CAST_TARGET + x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; + if (effect.mRange == ESM::RT_Target) x *= 1.5f; static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find("fEffectCostMult")->getFloat(); From 479f248c1ddebc532edae65ae954c3be1b367407 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 11 Jul 2014 18:33:39 +0200 Subject: [PATCH 14/23] Use spell effect range type, not base effect (applied to spellcasting code) --- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 6467730dda..1b0c444ab7 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -92,7 +92,7 @@ namespace MWMechanics x *= 0.1 * magicEffect->mData.mBaseCost; x *= 0.5 * (it->mMagnMin + it->mMagnMax); x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost; - if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) + if (it->mRange == ESM::RT_Target) x *= 1.5; static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( "fEffectCostMult")->getFloat(); From 83819b2894371d25f4be0ec79c108bda5ec06178 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 12 Jul 2014 08:05:10 +0200 Subject: [PATCH 15/23] 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 From a1639371d3f2e637036c40222082dc85f4775072 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 12 Jul 2014 14:05:57 +0200 Subject: [PATCH 16/23] Fix rounding for skill/attribute autocalc --- apps/openmw/mwclass/npc.cpp | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 31e09bbacf..b05bc438cd 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -54,6 +54,24 @@ namespace return new NpcCustomData (*this); } + int is_even(double d) { + double int_part; + modf(d / 2.0, &int_part); + return 2.0 * int_part == d; + } + + int round_ieee_754(double d) { + double i = floor(d); + d -= i; + if(d < 0.5) + return i; + if(d > 0.5) + return i + 1.0; + if(is_even(i)) + return i; + return i + 1.0; + } + void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats) { // race bonus @@ -109,8 +127,9 @@ namespace } modifierSum += add; } - creatureStats.setAttribute(attribute, std::min(creatureStats.getAttribute(attribute).getBase() - + static_cast((level-1) * modifierSum+0.5), 100) ); + creatureStats.setAttribute(attribute, std::min( + round_ieee_754(creatureStats.getAttribute(attribute).getBase() + + (level-1) * modifierSum), 100) ); } // initial health @@ -206,11 +225,12 @@ namespace npcStats.getSkill(skillIndex).setBase( std::min( - npcStats.getSkill(skillIndex).getBase() + round_ieee_754( + npcStats.getSkill(skillIndex).getBase() + 5 + raceBonus + specBonus - + static_cast((level-1) * (majorMultiplier + specMultiplier)), 100)); + + (level-1) * (majorMultiplier + specMultiplier)), 100)); } int skills[ESM::Skill::Length]; From fd3f1093626ed666ae177e0bb8febd2a0c09db8b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 12 Jul 2014 14:43:57 +0200 Subject: [PATCH 17/23] Fix underflow for NPCs with level 0 --- apps/openmw/mwclass/npc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b05bc438cd..e2cb8ba7af 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -230,7 +230,7 @@ namespace + 5 + raceBonus + specBonus - + (level-1) * (majorMultiplier + specMultiplier)), 100)); + +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0 } int skills[ESM::Skill::Length]; From d63dd881130d87ef11463b72796eacbad7dd5c63 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 12 Jul 2014 17:35:38 +0200 Subject: [PATCH 18/23] Apply autocalc corrections to player starting spells --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 073cb5f74c..d7c3e2f56d 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -186,7 +186,7 @@ namespace MWMechanics const ESM::Spell* weakestSpell = NULL; int minCost = INT_MAX; - std::set selectedSpells; + std::vector selectedSpells; const ESM::Race* race = NULL; if (mRaceSelected) @@ -212,8 +212,6 @@ namespace MWMechanics continue; if (reachedLimit && spell->mData.mCost <= minCost) continue; - if (selectedSpells.find(spell->mId) != selectedSpells.end()) - continue; if (race && std::find(race->mPowers.mList.begin(), race->mPowers.mList.end(), spell->mId) != race->mPowers.mList.end()) continue; if (baseMagicka < spell->mData.mCost) @@ -226,18 +224,19 @@ namespace MWMechanics if (!attrSkillCheck(spell, skills, attributes)) continue; - selectedSpells.insert(spell->mId); + selectedSpells.push_back(spell->mId); if (reachedLimit) { - selectedSpells.erase(weakestSpell->mId); + std::vector::iterator it = std::find(selectedSpells.begin(), selectedSpells.end(), weakestSpell->mId); + if (it != selectedSpells.end()) + selectedSpells.erase(it); minCost = 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 = esmStore.get().find(*weakIt); - if (testSpell->mData.mCost < minCost) // XXX what if 2 candidates have the same cost? - // Note iAutoPCSpellMax is 100 by default, so reachedLimit is very unlikely to happen + if (testSpell->mData.mCost < minCost) { minCost = testSpell->mData.mCost; weakestSpell = testSpell; @@ -257,7 +256,7 @@ namespace MWMechanics } } - for (std::set::iterator it = selectedSpells.begin(); it != selectedSpells.end(); ++it) + for (std::vector::iterator it = selectedSpells.begin(); it != selectedSpells.end(); ++it) creatureStats.getSpells().add(*it); // forced update and current value adjustments From 7316f4dde464a58a6ec91a4a292ce3c7558d3584 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 3 Jul 2014 12:17:24 +0200 Subject: [PATCH 19/23] increased version number --- CMakeLists.txt | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9de8efeb54..cb9a54a6cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 30) +set(OPENMW_VERSION_MINOR 31) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") diff --git a/readme.txt b/readme.txt index 92cb35f31f..90f223a646 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind OpenMW is an attempt at recreating the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. -Version: 0.30.0 +Version: 0.31.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org From 7cd819f24f3b213c4407ff1fbb5bdb82428e0d84 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 4 Jul 2014 08:58:54 +0200 Subject: [PATCH 20/23] updated changelog --- readme.txt | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/readme.txt b/readme.txt index 90f223a646..bafca48a47 100644 --- a/readme.txt +++ b/readme.txt @@ -96,6 +96,188 @@ Allowed options: CHANGELOG +0.31.0 + +Bug #245: Cloud direction and weather systems differ from Morrowind +Bug #275: Local Map does not always show objects that span multiple cells +Bug #538: Update CenterOnCell (COC) function behavior +Bug #618: Local and World Map Textures are sometimes Black +Bug #640: Water behaviour at night +Bug #668: OpenMW doesn't support non-latin paths on Windows +Bug #746: OpenMW doesn't check if the background music was already played +Bug #747: Door is stuck if cell is left before animation finishes +Bug #772: Disabled statics are visible on map +Bug #829: OpenMW uses up all available vram, when playing for extended time +Bug #869: Dead bodies don't collide with anything +Bug #894: Various character creation issues +Bug #897/#1369: opencs Segmentation Fault after "new" or "load" +Bug #899: Various jumping issues +Bug #952: Reflection effects are one frame delayed +Bug #993: Able to interact with world during Wait/Rest dialog +Bug #995: Dropped items can be placed inside the wall +Bug #1008: Corpses always face up upon reentering the cell +Bug #1035: Random colour patterns appearing in automap +Bug #1037: Footstep volume issues +Bug #1047: Creation of wrong links in dialogue window +Bug #1129: Summoned creature time life duration seems infinite +Bug #1134: Crimes can be committed against hostile NPCs +Bug #1136: Creature run speed formula is incorrect +Bug #1150: Weakness to Fire doesn't apply to Fire Damage in the same spell +Bug #1155: NPCs killing each other +Bug #1166: Bittercup script still does not work +Bug #1178: .bsa file names are case sensitive. +Bug #1179: Crash after trying to load game after being killed +Bug #1180: Changing footstep sound location +Bug #1196: Jumping not disabled when showing messageboxes +Bug #1202: "strange" keys are not shown in binding menu, and are not saved either, but works +Bug #1217: Container content changes based on the current position of the mouse +Bug #1234: Loading/saving issues with dynamic records +Bug #1277: Text pasted into the console appears twice +Bug #1284: Crash on New Game +Bug #1303: It's possible to skip the chargen +Bug #1304: Slaughterfish should not detect the player unless the player is in the water +Bug #1311: Editor: deleting Record Filter line does not reset the filter +Bug #1324: ERROR: ESM Error: String table overflow when loading Animated Morrowind.esp +Bug #1328: Editor: Bogus Filter created when dragging multiple records to filter bar of non-applicable table +Bug #1331: Walking/running sound persist after killing NPC`s that are walking/running. +Bug #1334: Previously equipped items not shown as unequipped after attempting to sell them. +Bug #1335: Actors ignore vertical axis when deciding to attack +Bug #1338: Unknown toggle option for shadows +Bug #1339: "Ashlands Region" is visible when beginning new game during "Loading Area" process +Bug #1340: Guards prompt Player with punishment options after resisting arrest with another guard. +Bug #1348: Regression: Bug #1098 has returned with a vengeance +Bug #1349: [TR] TR_Data mesh tr_ex_imp_gatejamb01 cannot be activated +Bug #1352: Disabling an ESX file does not disable dependent ESX files +Bug #1355: CppCat Checks OpenMW +Bug #1356: Incorrect voice type filtering for sleep interrupts +Bug #1357: Restarting the game clears saves +Bug #1360: Seyda Neen silk rider dialog problem +Bug #1361: Some lights don't work +Bug #1364: It is difficult to bind "Mouse 1" to an action in the options menu +Bug #1370: Animation compilation mod does not work properly +Bug #1371: SL_Pick01.nif from third party fails to load in openmw, but works in Vanilla +Bug #1373: When stealing in front of Sellus Gravius cannot exit the dialog +Bug #1378: Installs to /usr/local are not working +Bug #1380: Loading a save file fail if one of the content files is disabled +Bug #1382: "getHExact() size mismatch" crash on loading official plugin "Siege at Firemoth.esp" +Bug #1386: Arkngthand door will not open +Bug #1388: Segfault when modifying View Distance in Menu options +Bug #1389: Crash when loading a save after dying +Bug #1390: Apostrophe characters not displayed [French version] +Bug #1391: Custom made icon background texture for magical weapons and stuff isn't scaled properly on GUI. +Bug #1393: Coin icon during the level up dialogue are off of the background +Bug #1394: Alt+F4 doesn't work on Win version +Bug #1395: Changing rings switches only the last one put on +Bug #1396: Pauldron parts aren't showing when the robe is equipped +Bug #1402: Dialogue of some shrines have wrong button orientation +Bug #1403: Items are floating in the air when they're dropped onto dead bodies. +Bug #1404: Forearms are not rendered on Argonian females +Bug #1407: Alchemy allows making potions from two of the same item +Bug #1408: "Max sale" button gives you all the items AND all the trader's gold +Bug #1409: Rest "Until Healed" broken for characters with stunted magicka. +Bug #1412: Empty travel window opens while playing through start game +Bug #1413: Save game ignores missing writing permission +Bug #1414: The Underground 2 ESM Error +Bug #1416: Not all splash screens in the Splash directory are used +Bug #1417: Loading saved game does not terminate +Bug #1419: Skyrim: Home of the Nords error +Bug #1422: ClearInfoActor +Bug #1423: ForceGreeting closes existing dialogue windows +Bug #1425: Cannot load save game +Bug #1426: Read skill books aren't stored in savegame +Bug #1427: Useless items can be set under hotkeys +Bug #1429: Text variables in journal +Bug #1432: When attacking friendly NPC, the crime is reported and bounty is raised after each swing +Bug #1435: Stealing priceless items is without punishment +Bug #1437: Door marker at Jobasha's Rare Books is spawning PC in the air +Bug #1440: Topic selection menu should be wider +Bug #1441: Dropping items on the rug makes them inaccessible +Bug #1442: When dropping and taking some looted items, bystanders consider that as a crime +Bug #1444: Arrows and bolts are not dropped where the cursor points +Bug #1445: Security trainers offering acrobatics instead +Bug #1447: Character dash not displayed, French edition +Bug #1448: When the player is killed by the guard while having a bounty on his head, the guard dialogue opens over and over instead of loading dialogue +Bug #1454: Script error in SkipTutorial +Bug #1456: Bad lighting when using certain Morrowind.ini generated by MGE +Bug #1457: Heart of Lorkan comes after you when attacking it +Bug #1458: Modified Keybindings are not remembered +Bug #1459: Dura Gra-Bol doesn't respond to PC attack +Bug #1462: Interior cells not loaded with Morrowind Patch active +Bug #1469: Item tooltip should show the base value, not real value +Bug #1477: Death count is not stored in savegame +Bug #1478: AiActivate does not trigger activate scripts +Bug #1481: Weapon not rendered when partially submerged in water +Bug #1483: Enemies are attacking even while dying +Bug #1486: ESM Error: Don't know what to do with INFO +Bug #1490: Arrows shot at PC can end up in inventory +Bug #1492: Monsters respawn on top of one another +Bug #1493: Dialogue box opens with follower NPC even if NPC is dead +Bug #1494: Paralysed cliffracers remain airbourne +Bug #1495: Dialogue box opens with follower NPC even the game is paused +Bug #1496: GUI messages are not cleared when loading another saved game +Bug #1499: Underwater sound sometimes plays when transitioning from interior. +Bug #1500: Targetted spells and water. +Bug #1502: Console error message on info refusal +Bug #1507: Bloodmoon MQ The Ritual of Beasts: Can't remove the arrow +Bug #1508: Bloodmoon: Fort Frostmoth, cant talk with Carnius Magius +Bug #1516: PositionCell doesn't move actors to current cell +Bug #1518: ForceGreeting broken for explicit references +Bug #1522: Crash after attempting to play non-music file +Bug #1523: World map empty after loading interior save +Bug #1524: Arrows in waiting/resting dialog act like minimum and maximum buttons +Bug #1525: Werewolf: Killed NPC's don't fill werewolfs hunger for blood +Bug #1527: Werewolf: Detect life detects wrong type of actor +Bug #1529: OpenMW crash during "the shrine of the dead" mission (tribunal) +Bug #1530: Selected text in the console has the same color as the background +Bug #1539: Barilzar's Mazed Band: Tribunal +Bug #1542: Looping taunts from NPC`s after death: Tribunal +Bug #1543: OpenCS crash when using drag&drop in script editor +Bug #1547: Bamz-Amschend: Centurion Archers combat problem +Bug #1548: The Missing Hand: Tribunal +Bug #1549: The Mad God: Tribunal, Dome of Serlyn +Bug #1557: A bounty is calculated from actual item cost +Bug #1562: Invisible terrain on top of Red Mountain +Bug #1564: Cave of the hidden music: Bloodmoon +Bug #1567: Editor: Deleting of referenceables does not work +Bug #1568: Picking up a stack of items and holding the enter key and moving your mouse around paints a bunch of garbage on screen. +Bug #1574: Solstheim: Drauger cant inflict damage on player +Bug #1578: Solstheim: Bonewolf running animation not working +Bug #1585: Particle effects on PC are stopped when paralyzed +Bug #1589: Tribunal: Crimson Plague quest does not update when Gedna Relvel is killed +Bug #1590: Failed to save game: compile error +Bug #1598: Segfault when making Drain/Fortify Skill spells +Bug #1599: Unable to switch to fullscreen +Bug #1613: Morrowind Rebirth duplicate objects / vanilla objects not removed +Feature #32: Periodic Cleanup/Refill +Feature #41: Precipitation and weather particles +Feature #568: Editor: Configuration setup +Feature #649: Editor: Threaded loading +Feature #930: Editor: Cell record saving +Feature #934: Editor: Body part table +Feature #935: Editor: Enchantment effect table +Feature #1162: Dialogue merging +Feature #1174: Saved Game: add missing creature state +Feature #1177: Saved Game: fog of war state +Feature #1312: Editor: Combat/Magic/Stealth values for creatures are not displayed +Feature #1314: Make NPCs and creatures fight each other +Feature #1315: Crime: Murder +Feature #1321: Sneak skill enhancements +Feature #1323: Handle restocking items +Feature #1332: Saved Game: levelled creatures +Feature #1347: modFactionReaction script instruction +Feature #1362: Animated main menu support +Feature #1433: Store walk/run toggle +Feature #1449: Use names instead of numbers for saved game files and folders +Feature #1453: Adding Delete button to the load menu +Feature #1460: Enable Journal screen while in dialogue +Feature #1480: Play Battle music when in combat +Feature #1501: Followers unable to fast travel with you +Feature #1520: Disposition and distance-based aggression/ShouldAttack +Feature #1595: Editor: Object rendering in cells +Task #940: Move license to locations where applicable +Task #1333: Remove cmake git tag reading +Task #1566: Editor: Object rendering refactoring + 0.30.0 Bug #416: Extreme shaking can occur during cell transitions while moving From 41d73377b0fa0fd6960b3d4be899bd2ffe2ccaca Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 4 Jul 2014 23:19:40 +0200 Subject: [PATCH 21/23] Use explicit C locale in Misc::StringUtils (Fixes #1216) --- components/misc/stringops.cpp | 55 +---------------------------------- components/misc/stringops.hpp | 32 ++++++-------------- 2 files changed, 10 insertions(+), 77 deletions(-) diff --git a/components/misc/stringops.cpp b/components/misc/stringops.cpp index 0bc8e290a1..0f801e5549 100644 --- a/components/misc/stringops.cpp +++ b/components/misc/stringops.cpp @@ -12,59 +12,6 @@ namespace Misc { -bool begins(const char* str1, const char* str2) -{ - while(*str2) - { - if(*str1 == 0 || *str1 != *str2) return false; - - str1++; - str2++; - } - return true; -} - -bool ends(const char* str1, const char* str2) -{ - int len1 = strlen(str1); - int len2 = strlen(str2); - - if(len1 < len2) return false; - - return strcmp(str2, str1+len1-len2) == 0; -} - -// True if the given chars match, case insensitive -static bool icmp(char a, char b) -{ - if(a >= 'A' && a <= 'Z') - a += 'a' - 'A'; - if(b >= 'A' && b <= 'Z') - b += 'a' - 'A'; - - return a == b; -} - -bool ibegins(const char* str1, const char* str2) -{ - while(*str2) - { - if(*str1 == 0 || !icmp(*str1,*str2)) return false; - - str1++; - str2++; - } - return true; -} - -bool iends(const char* str1, const char* str2) -{ - int len1 = strlen(str1); - int len2 = strlen(str2); - - if(len1 < len2) return false; - - return strcasecmp(str2, str1+len1-len2) == 0; -} +std::locale StringUtils::mLocale = std::locale::classic(); } diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index d41463cfce..04dedb0721 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -4,15 +4,18 @@ #include #include #include +#include namespace Misc { class StringUtils { + + static std::locale mLocale; struct ci { - bool operator()(int x, int y) const { - return std::tolower(x) < std::tolower(y); + bool operator()(char x, char y) const { + return std::tolower(x, StringUtils::mLocale) < std::tolower(y, StringUtils::mLocale); } }; @@ -28,7 +31,7 @@ public: std::string::const_iterator xit = x.begin(); std::string::const_iterator yit = y.begin(); for (; xit != x.end(); ++xit, ++yit) { - if (std::tolower(*xit) != std::tolower(*yit)) { + if (std::tolower(*xit, mLocale) != std::tolower(*yit, mLocale)) { return false; } } @@ -42,7 +45,7 @@ public: for(;xit != x.end() && yit != y.end() && len > 0;++xit,++yit,--len) { int res = *xit - *yit; - if(res != 0 && std::tolower(*xit) != std::tolower(*yit)) + if(res != 0 && std::tolower(*xit, mLocale) != std::tolower(*yit, mLocale)) return (res > 0) ? 1 : -1; } if(len > 0) @@ -57,12 +60,8 @@ public: /// Transforms input string to lower case w/o copy static std::string &toLower(std::string &inout) { - std::transform( - inout.begin(), - inout.end(), - inout.begin(), - (int (*)(int)) std::tolower - ); + for (unsigned int i=0; i Date: Sat, 5 Jul 2014 07:24:30 +0200 Subject: [PATCH 22/23] Fix travis --- .../components/misc/test_stringops.cpp | 65 ------------------- 1 file changed, 65 deletions(-) diff --git a/apps/openmw_test_suite/components/misc/test_stringops.cpp b/apps/openmw_test_suite/components/misc/test_stringops.cpp index 44587c445f..55fe0e0c27 100644 --- a/apps/openmw_test_suite/components/misc/test_stringops.cpp +++ b/apps/openmw_test_suite/components/misc/test_stringops.cpp @@ -12,68 +12,3 @@ struct StringOpsTest : public ::testing::Test { } }; - -TEST_F(StringOpsTest, begins_matching) -{ - ASSERT_TRUE(Misc::begins("abc", "a")); - ASSERT_TRUE(Misc::begins("abc", "ab")); - ASSERT_TRUE(Misc::begins("abc", "abc")); - ASSERT_TRUE(Misc::begins("abcd", "abc")); -} - -TEST_F(StringOpsTest, begins_not_matching) -{ - ASSERT_FALSE(Misc::begins("abc", "b")); - ASSERT_FALSE(Misc::begins("abc", "bc")); - ASSERT_FALSE(Misc::begins("abc", "bcd")); - ASSERT_FALSE(Misc::begins("abc", "abcd")); -} - -TEST_F(StringOpsTest, ibegins_matching) -{ - ASSERT_TRUE(Misc::ibegins("Abc", "a")); - ASSERT_TRUE(Misc::ibegins("aBc", "ab")); - ASSERT_TRUE(Misc::ibegins("abC", "abc")); - ASSERT_TRUE(Misc::ibegins("abcD", "abc")); -} - -TEST_F(StringOpsTest, ibegins_not_matching) -{ - ASSERT_FALSE(Misc::ibegins("abc", "b")); - ASSERT_FALSE(Misc::ibegins("abc", "bc")); - ASSERT_FALSE(Misc::ibegins("abc", "bcd")); - ASSERT_FALSE(Misc::ibegins("abc", "abcd")); -} - -TEST_F(StringOpsTest, ends_matching) -{ - ASSERT_TRUE(Misc::ends("abc", "c")); - ASSERT_TRUE(Misc::ends("abc", "bc")); - ASSERT_TRUE(Misc::ends("abc", "abc")); - ASSERT_TRUE(Misc::ends("abcd", "abcd")); -} - -TEST_F(StringOpsTest, ends_not_matching) -{ - ASSERT_FALSE(Misc::ends("abc", "b")); - ASSERT_FALSE(Misc::ends("abc", "ab")); - ASSERT_FALSE(Misc::ends("abc", "bcd")); - ASSERT_FALSE(Misc::ends("abc", "abcd")); -} - -TEST_F(StringOpsTest, iends_matching) -{ - ASSERT_TRUE(Misc::iends("Abc", "c")); - ASSERT_TRUE(Misc::iends("aBc", "bc")); - ASSERT_TRUE(Misc::iends("abC", "abc")); - ASSERT_TRUE(Misc::iends("abcD", "abcd")); -} - -TEST_F(StringOpsTest, iends_not_matching) -{ - ASSERT_FALSE(Misc::iends("abc", "b")); - ASSERT_FALSE(Misc::iends("abc", "ab")); - ASSERT_FALSE(Misc::iends("abc", "bcd")); - ASSERT_FALSE(Misc::iends("abc", "abcd")); -} - From f2799ea1d9c986ace162f69526612bfcfc7dc1e6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 7 Jul 2014 23:37:59 +0200 Subject: [PATCH 23/23] Reset item model when reference is reset (Fixes #1628) This caused crashes when the window was resized after the reference no longer exists (e.g. when a savegame is loaded) --- apps/openmw/mwgui/companionwindow.cpp | 5 +++++ apps/openmw/mwgui/companionwindow.hpp | 2 ++ apps/openmw/mwgui/container.cpp | 6 ++++++ apps/openmw/mwgui/container.hpp | 2 ++ apps/openmw/mwgui/tradewindow.cpp | 6 ++++++ apps/openmw/mwgui/tradewindow.hpp | 1 + 6 files changed, 22 insertions(+) diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index d0ac3e7c36..8d199e7275 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -153,6 +153,11 @@ void CompanionWindow::onReferenceUnavailable() MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); } +void CompanionWindow::resetReference() +{ + ReferenceInterface::resetReference(); + mItemView->setModel(NULL); +} } diff --git a/apps/openmw/mwgui/companionwindow.hpp b/apps/openmw/mwgui/companionwindow.hpp index 006d0a3c35..dc460e2fc2 100644 --- a/apps/openmw/mwgui/companionwindow.hpp +++ b/apps/openmw/mwgui/companionwindow.hpp @@ -20,6 +20,8 @@ namespace MWGui virtual void exit(); + virtual void resetReference(); + void open(const MWWorld::Ptr& npc); void onFrame (); diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 011feb4d35..8da3def5fa 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -258,6 +258,12 @@ namespace MWGui onTakeAllButtonClicked(mTakeButton); } + void ContainerWindow::resetReference() + { + ReferenceInterface::resetReference(); + mItemView->setModel(NULL); + } + void ContainerWindow::close() { WindowBase::close(); diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 5446a4ab73..79951f70e9 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -54,6 +54,8 @@ namespace MWGui void open(const MWWorld::Ptr& container, bool loot=false); virtual void close(); + virtual void resetReference(); + virtual void exit(); private: diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index c0a51311f5..19187cde1f 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -531,4 +531,10 @@ namespace MWGui sellerStats.setLastRestockTime(MWBase::Environment::get().getWorld()->getTimeStamp()); } } + + void TradeWindow::resetReference() + { + ReferenceInterface::resetReference(); + mItemView->setModel(NULL); + } } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index cc70f1ae96..b487a8870e 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -37,6 +37,7 @@ namespace MWGui virtual void exit(); + virtual void resetReference(); private: ItemView* mItemView;