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/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/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; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index c003e0b227..e2cb8ba7af 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" @@ -53,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 @@ -108,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 @@ -193,18 +213,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? @@ -217,12 +225,25 @@ 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)); + +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0 } + + int skills[ESM::Skill::Length]; + for (int i=0; i spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); + for (std::vector::iterator it = spells.begin(); it != spells.end(); ++it) + npcStats.getSpells().add(*it); } } 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/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 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() 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; 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() { diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp new file mode 100644 index 0000000000..255decdf75 --- /dev/null +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -0,0 +1,232 @@ +#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::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(); + 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::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; + + 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.push_back(spell->mId); + + if (cap.mReachedLimit) + { + std::vector::iterator found = std::find(selectedSpells.begin(), selectedSpells.end(), cap.mWeakestSpell); + if (found != selectedSpells.end()) + selectedSpells.erase(found); + + cap.mMinCost = INT_MAX; + for (std::vector::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt) + { + const ESM::Spell* testSpell = spells.find(*weakIt); + + //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; + } + } + } + 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; + if (effect.mRange == ESM::RT_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..1912c75c42 --- /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::vector 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/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2c5d68ceb7..d4ddf53cd0 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) @@ -570,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); } @@ -1241,6 +1249,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 +1339,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 +1402,8 @@ void CharacterController::update(float duration) forcestateupdate = updateCreatureState() || forcestateupdate; refreshCurrentAnims(idlestate, movestate, forcestateupdate); + if (inJump) + mMovementAnimationControlled = false; if (!mSkipAnim) { @@ -1402,7 +1415,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 +1459,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; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 6e515142de..d7c3e2f56d 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,87 @@ 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::vector 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 (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.push_back(spell->mId); + + if (reachedLimit) + { + std::vector::iterator it = std::find(selectedSpells.begin(), selectedSpells.end(), weakestSpell->mId); + if (it != selectedSpells.end()) + selectedSpells.erase(it); + + minCost = INT_MAX; + for (std::vector::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt) + { + const ESM::Spell* testSpell = esmStore.get().find(*weakIt); + if (testSpell->mData.mCost < minCost) + { + 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::vector::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/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(); 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(); 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; } }; 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/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 diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 33405b4d86..3868348820 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) @@ -2649,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(); 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")); -} - 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 { 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 }; 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