diff --git a/AUTHORS.md b/AUTHORS.md index 5b848fab4..c64b83dab 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -22,6 +22,7 @@ Programmers alexanderkjall Alexander Nadeau (wareya) Alexander Olofsson (Ace) + Alex Rice Alex S (docwest) Allofich Andrei Kortunov (akortunov) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf1f80705..da0aaa762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ Bug #4384: Resist Normal Weapons only checks ammunition for ranged weapons Bug #4411: Reloading a saved game while falling prevents damage in some cases Bug #4540: Rain delay when exiting water - Bug #4600: Crash when no sound output is available or --no-sound is used. + Bug #4600: Crash when no sound output is available or --no-sound is used. Bug #4639: Black screen after completing first mages guild mission + training Bug #4701: PrisonMarker record is not hardcoded like other markers Bug #4703: Editor: it's possible to preview levelled list records @@ -105,6 +105,7 @@ Bug #5038: Enchanting success chance calculations are blatantly wrong Bug #5047: # in cell names sets color Bug #5050: Invalid spell effects are not handled gracefully + Bug #5055: Mark, Recall, Intervention magic effect abilities have no effect when added and removed in the same frame Bug #5056: Calling Cast function on player doesn't equip the spell but casts it Bug #5060: Magic effect visuals stop when death animation begins instead of when it ends Bug #5063: Shape named "Tri Shadow" in creature mesh is visible if it isn't hidden @@ -112,8 +113,13 @@ Bug #5074: Paralyzed actors greet the player Bug #5075: Enchanting cast style can be changed if there's no object Bug #5082: Scrolling with controller in GUI mode is broken + Bug #5089: Swimming/Underwater creatures only swim around ground level Bug #5092: NPCs with enchanted weapons play sound when out of charges Bug #5093: Hand to hand sound plays on knocked out enemies + Bug #5099: Non-swimming enemies will enter water if player is water walking + Bug #5104: Black Dart's enchantment doesn't trigger at low Enchant levels + Bug #5105: NPCs start combat with werewolves from any distance + Bug #5110: ModRegion with a redundant numerical argument breaks script execution Feature #1774: Handle AvoidNode Feature #2229: Improve pathfinding AI Feature #3025: Analogue gamepad movement controls @@ -149,6 +155,8 @@ Feature #5036: Allow scripted faction leaving Feature #5046: Gamepad thumbstick cursor speed Feature #5051: Provide a separate textures for scrollbars + Feature #5094: Unix like console hotkeys + Feature #5098: Allow user controller bindings Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4695: Optimize Distant Terrain memory consumption Task #4789: Optimize cell transitions diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index a206fad6f..bfe907c19 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "intsetting.hpp" #include "doublesetting.hpp" @@ -415,7 +416,9 @@ CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble (const std::string& key, if (mCurrentCategory==mCategories.end()) throw std::logic_error ("no category for setting"); - setDefault(key, std::to_string(default_)); + std::ostringstream stream; + stream << default_; + setDefault(key, stream.str()); default_ = mSettings.getFloat (key, mCurrentCategory->second.getKey()); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 07127d28b..d5ab73213 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -515,8 +515,17 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) controllerFileName = "gamecontrollerdb.txt"; } + const std::string userdefault = mCfgMgr.getUserConfigPath().string() + "/" + controllerFileName; const std::string localdefault = mCfgMgr.getLocalPath().string() + "/" + controllerFileName; const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/" + controllerFileName; + + std::string userGameControllerdb; + if (boost::filesystem::exists(userdefault)){ + userGameControllerdb = userdefault; + } + else + userGameControllerdb = ""; + std::string gameControllerdb; if (boost::filesystem::exists(localdefault)) gameControllerdb = localdefault; @@ -525,7 +534,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) else gameControllerdb = ""; //if it doesn't exist, pass in an empty string - MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, gameControllerdb, mGrab); + MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); mEnvironment.setInputManager (input); std::string myguiResources = (mResDir / "mygui").string(); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 02f06c1df..87397efa2 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1181,7 +1181,7 @@ namespace MWClass MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); // Race weight should not affect 1st-person meshes, otherwise it will change hand proportions and can break aiming. - if (ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson()) + if (ptr == MWMechanics::getPlayer() && ptr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson()) { if (ref->mBase->isMale()) scale *= race->mData.mHeight.mMale; diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 64eecdb79..8f8ca4bb5 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -112,10 +112,7 @@ struct TypesetBookImpl : TypesetBook if (i->empty()) return Range (Utf8Point (nullptr), Utf8Point (nullptr)); - Utf8Point begin = &i->front (); - Utf8Point end = &i->front () + i->size (); - - return Range (begin, end); + return Range (i->data(), i->data() + i->size()); } size_t pageCount () const { return mPages.size (); } @@ -346,8 +343,8 @@ struct TypesetBookImpl::Typesetter : BookTypesetter assert (end <= mCurrentContent->size ()); assert (begin <= mCurrentContent->size ()); - Utf8Point begin_ = &mCurrentContent->front () + begin; - Utf8Point end_ = &mCurrentContent->front () + end ; + Utf8Point begin_ = mCurrentContent->data() + begin; + Utf8Point end_ = mCurrentContent->data() + end; writeImpl (static_cast (style), begin_, end_); } diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 1633b92db..df4bdec5b 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -1,6 +1,7 @@ #include "console.hpp" #include +#include #include #include @@ -224,11 +225,48 @@ namespace MWGui resetReference(); } + bool isWhitespace(char c) + { + return c == ' ' || c == '\t'; + } + void Console::keyPress(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char _char) { - if( key == MyGUI::KeyCode::Tab) + if(MyGUI::InputManager::getInstance().isControlPressed()) + { + if(key == MyGUI::KeyCode::W) + { + const auto& caption = mCommandLine->getCaption(); + if(caption.empty()) + return; + size_t max = mCommandLine->getTextCursor(); + while(max > 0 && (isWhitespace(caption[max - 1]) || caption[max - 1] == '>')) + max--; + while(max > 0 && !isWhitespace(caption[max - 1]) && caption[max - 1] != '>') + max--; + size_t length = mCommandLine->getTextCursor() - max; + if(length > 0) + { + std::string text = caption; + text.erase(max, length); + mCommandLine->setCaption(text); + mCommandLine->setTextCursor(max); + } + } + else if(key == MyGUI::KeyCode::U) + { + if(mCommandLine->getTextCursor() > 0) + { + std::string text = mCommandLine->getCaption(); + text.erase(0, mCommandLine->getTextCursor()); + mCommandLine->setCaption(text); + mCommandLine->setTextCursor(0); + } + } + } + else if(key == MyGUI::KeyCode::Tab) { std::vector matches; listNames(); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 581a96e7b..efe85a3bb 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -39,6 +39,7 @@ namespace MWInput osg::ref_ptr screenCaptureHandler, osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, const std::string& userFile, bool userFileExists, + const std::string& userControllerBindingsFile, const std::string& controllerBindingsFile, bool grab) : mWindow(window) , mWindowVisible(true) @@ -113,10 +114,14 @@ namespace MWInput // Load controller mappings #if SDL_VERSION_ATLEAST(2,0,2) - if(controllerBindingsFile!="") + if(!controllerBindingsFile.empty()) { SDL_GameControllerAddMappingsFromFile(controllerBindingsFile.c_str()); } + if(!userControllerBindingsFile.empty()) + { + SDL_GameControllerAddMappingsFromFile(userControllerBindingsFile.c_str()); + } #endif // Open all presently connected sticks diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index caf57681d..eaa081f99 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -76,6 +76,7 @@ namespace MWInput osg::ref_ptr screenCaptureHandler, osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, const std::string& userFile, bool userFileExists, + const std::string& userControllerBindingsFile, const std::string& controllerBindingsFile, bool grab); virtual ~InputManager(); diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 96ff0c308..167e13128 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -422,6 +422,11 @@ namespace MWMechanics return true; } + if (actor.getClass().isPureLandCreature(actor) && MWBase::Environment::get().getWorld()->isWalkingOnWater(enemy)) + { + return false; + } + if (actor.getClass().isPureFlyingCreature(actor) || actor.getClass().isPureLandCreature(actor)) { if (MWBase::Environment::get().getWorld()->isSwimming(enemy)) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 94a502544..598292fc3 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -301,7 +301,7 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoin return true; const float actorSpeed = actor.getClass().getSpeed(actor); - const float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability + const float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / getAngularVelocity(actorSpeed) * 2; // *2 - for reliability const float distToTarget = osg::Vec2f(endPoint.x(), endPoint.y()).length(); const float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2; @@ -363,7 +363,7 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act // get actor's shortest radius for moving in circle float speed = actor.getClass().getSpeed(actor); speed += speed * 0.1f; // 10% real speed inaccuracy - float radius = speed / MAX_VEL_ANGULAR_RADIANS; + float radius = speed / getAngularVelocity(speed); // get radius direction to the center const float* rot = actor.getRefData().getPosition().rot; diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 018756aea..d97edfe2a 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -88,6 +88,7 @@ namespace MWMechanics // that is the user's responsibility MWBase::Environment::get().getWorld()->moveObject(actor, mX, mY, mZ); actor.getClass().adjustPosition(actor, false); + reset(); } void AiTravel::writeState(ESM::AiSequence::AiSequence &sequence) const diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index d00bbeb5f..cd13b1820 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -448,7 +448,7 @@ namespace MWMechanics else { // have not yet reached the destination - evadeObstacles(actor, storage); + evadeObstacles(actor, duration, storage); } } @@ -479,8 +479,17 @@ namespace MWMechanics storage.setState(AiWanderStorage::Wander_IdleNow); } - void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage) + void AiWander::evadeObstacles(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage) { + if (mUsePathgrid) + { + const auto halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + const float actorTolerance = 2 * actor.getClass().getSpeed(actor) * duration + + 1.2 * std::max(halfExtents.x(), halfExtents.y()); + const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance); + mPathFinder.buildPathByNavMeshToNextPoint(actor, halfExtents, getNavigatorFlags(actor), pointTolerance); + } + if (mObstacleCheck.isEvading()) { // first check if we're walking into a door diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index d586bb0bc..0b967983f 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -137,7 +137,7 @@ namespace MWMechanics short unsigned getRandomIdle(); void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos); void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage); - void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); + void evadeObstacles(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage); void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 23f5ea01d..fd7ab63fe 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1785,8 +1785,8 @@ namespace MWMechanics if (ptr.getClass().isNpc()) disposition = getDerivedDisposition(ptr, true); - int fight = std::max(0, ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified() - + static_cast(getFightDistanceBias(ptr, target) + getFightDispositionBias(static_cast(disposition)))); + int fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified() + + static_cast(getFightDistanceBias(ptr, target) + getFightDispositionBias(static_cast(disposition))); if (ptr.getClass().isNpc() && target.getClass().isNpc()) { diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 4ff28e01a..900f97a67 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -73,6 +73,13 @@ namespace { return sqrDistance(osg::Vec2f(lhs.x(), lhs.y()), osg::Vec2f(rhs.x(), rhs.y())); } + + float getPathStepSize(const MWWorld::ConstPtr& actor) + { + const auto world = MWBase::Environment::get().getWorld(); + const auto realHalfExtents = world->getHalfExtents(actor); + return 2 * std::max(realHalfExtents.x(), realHalfExtents.y()); + } } namespace MWMechanics @@ -320,8 +327,7 @@ namespace MWMechanics try { const auto world = MWBase::Environment::get().getWorld(); - const auto realHalfExtents = world->getHalfExtents(actor); // This may differ from halfExtents argument - const auto stepSize = 2 * std::max(realHalfExtents.x(), realHalfExtents.y()); + const auto stepSize = getPathStepSize(actor); const auto navigator = world->getNavigator(); navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, out); } @@ -333,4 +339,39 @@ namespace MWMechanics << DetourNavigator::WriteFlags {flags} << ")"; } } + + void PathFinder::buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents, + const DetourNavigator::Flags flags, const float pointTolerance) + { + if (mPath.empty()) + return; + + const auto stepSize = getPathStepSize(actor); + const auto startPoint = actor.getRefData().getPosition().asVec3(); + + if (sqrDistanceIgnoreZ(mPath.front(), startPoint) <= 4 * stepSize * stepSize) + return; + + try + { + const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + std::deque prePath; + navigator->findPath(halfExtents, stepSize, startPoint, mPath.front(), flags, std::back_inserter(prePath)); + + while (!prePath.empty() && sqrDistanceIgnoreZ(prePath.front(), startPoint) < stepSize * stepSize) + prePath.pop_front(); + + while (!prePath.empty() && sqrDistanceIgnoreZ(prePath.back(), mPath.front()) < stepSize * stepSize) + prePath.pop_back(); + + std::copy(prePath.rbegin(), prePath.rend(), std::front_inserter(mPath)); + } + catch (const DetourNavigator::NavigatorException& exception) + { + Log(Debug::Debug) << "Build path by navigator exception: \"" << exception.what() + << "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase() + << ") from " << startPoint << " to " << mPath.front() << " with flags (" + << DetourNavigator::WriteFlags {flags} << ")"; + } + } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 1dc85c5e5..f762b6f18 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -84,6 +84,9 @@ namespace MWMechanics const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags); + void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents, + const DetourNavigator::Flags flags, const float pointTolerance); + /// Remove front point if exist and within tolerance void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 2c178a40e..d95d84210 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -823,18 +823,22 @@ namespace MWMechanics mStack = false; bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); + bool isProjectile = false; + if (item.getTypeName() == typeid(ESM::Weapon).name()) + { + const ESM::Weapon* ref = item.get()->mBase; + isProjectile = ref->mData.mType >= ESM::Weapon::MarksmanThrown; + } + int type = enchantment->mData.mType; // Check if there's enough charge left - if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + if (!godmode && (type == ESM::Enchantment::WhenUsed || (!isProjectile && type == ESM::Enchantment::WhenStrikes))) { int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), mCaster); if (item.getCellRef().getEnchantmentCharge() == -1) item.getCellRef().setEnchantmentCharge(static_cast(enchantment->mData.mCharge)); - if (godmode) - castCost = 0; - if (item.getCellRef().getEnchantmentCharge() < castCost) { if (mCaster == getPlayer()) @@ -862,17 +866,17 @@ namespace MWMechanics item.getCellRef().setEnchantmentCharge(item.getCellRef().getEnchantmentCharge() - castCost); } - if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) + if (type == ESM::Enchantment::WhenUsed) { if (mCaster == getPlayer()) mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1); } - else if (enchantment->mData.mType == ESM::Enchantment::CastOnce) + else if (type == ESM::Enchantment::CastOnce) { if (!godmode) item.getContainerStore()->remove(item, 1, mCaster); } - else if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + else if (type == ESM::Enchantment::WhenStrikes) { if (mCaster == getPlayer()) mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3); @@ -880,13 +884,6 @@ namespace MWMechanics inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); - bool isProjectile = false; - if (item.getTypeName() == typeid(ESM::Weapon).name()) - { - const MWWorld::LiveCellRef *ref = item.get(); - isProjectile = ref->mBase->mData.mType == ESM::Weapon::Arrow || ref->mBase->mData.mType == ESM::Weapon::Bolt || ref->mBase->mData.mType == ESM::Weapon::MarksmanThrown; - } - if (isProjectile || !mTarget.isEmpty()) inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); diff --git a/apps/openmw/mwmechanics/steering.cpp b/apps/openmw/mwmechanics/steering.cpp index 1ef46e1ae..b08a90220 100644 --- a/apps/openmw/mwmechanics/steering.cpp +++ b/apps/openmw/mwmechanics/steering.cpp @@ -32,7 +32,7 @@ bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, f if (absDiff < epsilonRadians) return true; - float limit = MAX_VEL_ANGULAR_RADIANS * MWBase::Environment::get().getFrameDuration(); + float limit = getAngularVelocity(actor.getClass().getSpeed(actor)) * MWBase::Environment::get().getFrameDuration(); if (absDiff > limit) diff = osg::sign(diff) * limit; diff --git a/apps/openmw/mwmechanics/steering.hpp b/apps/openmw/mwmechanics/steering.hpp index 632ab3611..f305a6961 100644 --- a/apps/openmw/mwmechanics/steering.hpp +++ b/apps/openmw/mwmechanics/steering.hpp @@ -3,6 +3,8 @@ #include +#include + namespace MWWorld { class Ptr; @@ -12,7 +14,12 @@ namespace MWMechanics { // Max rotating speed, radian/sec -const float MAX_VEL_ANGULAR_RADIANS(10); +inline float getAngularVelocity(const float actorSpeed) +{ + const float baseAngluarVelocity = 10; + const float baseSpeed = 200; + return baseAngluarVelocity * std::max(actorSpeed / baseSpeed, 1.0f); +} /// configure rotation settings for an actor to reach this target angle (eventually) /// @return have we reached the target angle? diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 118c0297f..37f37d4c7 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -102,8 +102,9 @@ namespace MWMechanics if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), actor); + float charge = item.getCellRef().getEnchantmentCharge(); - if (item.getCellRef().getEnchantmentCharge() == -1 || item.getCellRef().getEnchantmentCharge() >= castCost) + if (charge == -1 || charge >= castCost || weapon->mData.mType >= ESM::Weapon::MarksmanThrown) rating += rateEffects(enchantment->mEffects, actor, enemy); } } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index aa1da8b47..847fe0bfc 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -25,6 +25,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "ref.hpp" @@ -464,6 +465,16 @@ namespace MWScript { // Apply looping particles immediately for constant effects MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); + + // The spell may have an instant effect which must be handled immediately. + for (const auto& effect : creatureStats.getSpells().getMagicEffects()) + { + if (effect.second.getMagnitude() <= 0) + continue; + MWMechanics::CastSpell cast(ptr, ptr); + if (cast.applyInstantEffect(ptr, ptr, effect.first, effect.second.getMagnitude())) + creatureStats.getSpells().purgeEffect(effect.first.mId); + } } } }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3426e9115..ce253b8ba 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1401,7 +1401,7 @@ namespace MWWorld pos.z() += 20; // place slightly above. will snap down to ground with code below - if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && isActorCollisionEnabled(ptr))) + if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !isSwimming(ptr) && isActorCollisionEnabled(ptr))) { osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits); if (traced.z() < pos.z()) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 5b60f9d6d..7ccfb9285 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -340,7 +340,7 @@ namespace Compiler extensions.registerFunction ("getmasserphase", 'l', "", opcodeGetMasserPhase); extensions.registerFunction ("getsecundaphase", 'l', "", opcodeGetSecundaPhase); extensions.registerFunction ("getcurrentweather", 'l', "", opcodeGetCurrentWeather); - extensions.registerInstruction ("modregion", "S/llllllllll", opcodeModRegion); + extensions.registerInstruction ("modregion", "S/llllllllllX", opcodeModRegion); } } diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 0fdf4b440..b685c5662 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -160,9 +160,9 @@ public: { std::vector str(length + 1, 0); - inp->read(&str[0], length); + inp->read(str.data(), length); - return &str[0]; + return str.data(); } ///Read in a string of the length specified in the file std::string getString() @@ -181,34 +181,34 @@ public: void getUShorts(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, &vec.front(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getFloats(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, &vec.front(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getVector2s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec2f is 2 floats exactly */ - readLittleEndianDynamicBufferOfType(inp,(float*) &vec.front(), size*2); + readLittleEndianDynamicBufferOfType(inp,(float*)vec.data(), size*2); } void getVector3s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec3f is 3 floats exactly */ - readLittleEndianDynamicBufferOfType(inp, (float*) &vec.front(), size*3); + readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*3); } void getVector4s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec4f is 4 floats exactly */ - readLittleEndianDynamicBufferOfType(inp, (float*) &vec.front(), size*4); + readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*4); } void getQuaternions(std::vector &quat, size_t size) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 9b31a1b93..69840117d 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1034,11 +1034,10 @@ namespace NifOsg { const Nif::NiTriShapeData* data = triShape->data.getPtr(); - { - geometry->setVertexArray(new osg::Vec3Array(data->vertices.size(), &data->vertices[0])); - if (!data->normals.empty()) - geometry->setNormalArray(new osg::Vec3Array(data->normals.size(), &data->normals[0]), osg::Array::BIND_PER_VERTEX); - } + if (!data->vertices.empty()) + geometry->setVertexArray(new osg::Vec3Array(data->vertices.size(), data->vertices.data())); + if (!data->normals.empty()) + geometry->setNormalArray(new osg::Vec3Array(data->normals.size(), data->normals.data()), osg::Array::BIND_PER_VERTEX); int textureStage = 0; for (std::vector::const_iterator it = boundTextures.begin(); it != boundTextures.end(); ++it,++textureStage) @@ -1048,19 +1047,20 @@ namespace NifOsg { Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename; if (!data->uvlist.empty()) - geometry->setTexCoordArray(textureStage, new osg::Vec2Array(data->uvlist[0].size(), &data->uvlist[0][0]), osg::Array::BIND_PER_VERTEX); + geometry->setTexCoordArray(textureStage, new osg::Vec2Array(data->uvlist[0].size(), data->uvlist[0].data()), osg::Array::BIND_PER_VERTEX); continue; } - geometry->setTexCoordArray(textureStage, new osg::Vec2Array(data->uvlist[uvSet].size(), &data->uvlist[uvSet][0]), osg::Array::BIND_PER_VERTEX); + geometry->setTexCoordArray(textureStage, new osg::Vec2Array(data->uvlist[uvSet].size(), data->uvlist[uvSet].data()), osg::Array::BIND_PER_VERTEX); } if (!data->colors.empty()) - geometry->setColorArray(new osg::Vec4Array(data->colors.size(), &data->colors[0]), osg::Array::BIND_PER_VERTEX); + geometry->setColorArray(new osg::Vec4Array(data->colors.size(), data->colors.data()), osg::Array::BIND_PER_VERTEX); - geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, - data->triangles.size(), - (unsigned short*)&data->triangles[0])); + if (!data->triangles.empty()) + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, + data->triangles.size(), + (unsigned short*)data->triangles.data())); // osg::Material properties are handled here for two reasons: // - if there are no vertex colors, we need to disable colorMode. @@ -1115,7 +1115,7 @@ namespace NifOsg return morphGeom; // Note we are not interested in morph 0, which just contains the original vertices for (unsigned int i = 1; i < morphs.size(); ++i) - morphGeom->addMorphTarget(new osg::Vec3Array(morphs[i].mVertices.size(), &morphs[i].mVertices[0]), 0.f); + morphGeom->addMorphTarget(new osg::Vec3Array(morphs[i].mVertices.size(), morphs[i].mVertices.data()), 0.f); return morphGeom; } @@ -1282,7 +1282,7 @@ namespace NifOsg } unsigned char* data = new unsigned char[pixelData->data.size()]; - memcpy(data, &pixelData->data[0], pixelData->data.size()); + memcpy(data, pixelData->data.data(), pixelData->data.size()); image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); image->setMipmapLevels(mipmapVector);