diff --git a/AUTHORS.md b/AUTHORS.md index 75302908e..5ade21d41 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -222,6 +222,7 @@ Programmers Yuri Krupenin zelurker Noah Gooder + Andrew Appuhamy (andrew-app) Documentation ------------- diff --git a/CHANGELOG.md b/CHANGELOG.md index 88d6049f1..8dacef572 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -131,6 +131,7 @@ Bug #6043: Actor can have torch missing when torch animation is played Bug #6047: Mouse bindings can be triggered during save loading Bug #6136: Game freezes when NPCs try to open doors that are about to be closed + Bug #6294: Game crashes with empty pathgrid Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu @@ -160,6 +161,7 @@ Feature #5519: Code Patch tab in launcher Feature #5524: Resume failed script execution after reload Feature #5545: Option to allow stealing from an unconscious NPC during combat + Feature #5551: Do not reboot PC after OpenMW installation on Windows Feature #5563: Run physics update in background thread Feature #5579: MCP SetAngle enhancement Feature #5580: Service refusal filtering @@ -174,6 +176,7 @@ Feature #5814: Bsatool should be able to create BSA archives, not only to extract it Feature #5828: Support more than 8 lights Feature #5910: Fall back to delta time when physics can't keep up + Feature #5980: Support Bullet with double precision instead of one with single precision Feature #6024: OpenMW-CS: Selecting terrain in "Terrain land editing" should support "Add to selection" and "Remove from selection" modes Feature #6033: Include pathgrid to navigation mesh Feature #6034: Find path based on area cost depending on NPC stats diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 03d492c9c..abb214710 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -98,8 +98,8 @@ namespace MWInput // We keep track of our own mouse position, so that moving the mouse while in // game mode does not move the position of the GUI cursor float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); - float xMove = xAxis * dt * 1500.0f / uiScale; - float yMove = yAxis * dt * 1500.0f / uiScale; + float xMove = xAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed; + float yMove = yAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed; float mouseWheelMove = -zAxis * dt * 1500.0f; if (xMove != 0 || yMove != 0 || mouseWheelMove != 0) diff --git a/apps/openmw/mwmechanics/spellabsorption.cpp b/apps/openmw/mwmechanics/spellabsorption.cpp index bab290fda..1a2dfe65a 100644 --- a/apps/openmw/mwmechanics/spellabsorption.cpp +++ b/apps/openmw/mwmechanics/spellabsorption.cpp @@ -44,14 +44,14 @@ namespace MWMechanics } }; - bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) + int getAbsorbChance(const MWWorld::Ptr& caster, const MWWorld::Ptr& target) { - if (spellId.empty() || target.isEmpty() || caster == target || !target.getClass().isActor()) - return false; + if(target.isEmpty() || caster == target || !target.getClass().isActor()) + return 0; CreatureStats& stats = target.getClass().getCreatureStats(target); if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f) - return false; + return 0; GetAbsorptionProbability check; stats.getActiveSpells().visitEffectSources(check); @@ -59,9 +59,12 @@ namespace MWMechanics if (target.getClass().hasInventoryStore(target)) target.getClass().getInventoryStore(target).visitEffectSources(check); - int chance = check.mProbability * 100; - if (Misc::Rng::roll0to99() >= chance) - return false; + return check.mProbability * 100; + } + + void absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) + { + CreatureStats& stats = target.getClass().getCreatureStats(target); const auto& esmStore = MWBase::Environment::get().getWorld()->getStore(); const ESM::Static* absorbStatic = esmStore.get().find("VFX_Absorb"); @@ -85,7 +88,6 @@ namespace MWMechanics DynamicStat magicka = stats.getMagicka(); magicka.setCurrent(magicka.getCurrent() + spellCost); stats.setMagicka(magicka); - return true; } } diff --git a/apps/openmw/mwmechanics/spellabsorption.hpp b/apps/openmw/mwmechanics/spellabsorption.hpp index 0fe501df9..5537483a4 100644 --- a/apps/openmw/mwmechanics/spellabsorption.hpp +++ b/apps/openmw/mwmechanics/spellabsorption.hpp @@ -10,8 +10,9 @@ namespace MWWorld namespace MWMechanics { - // Try to absorb a spell based on the magnitude of every Spell Absorption effect source on the target. - bool absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target); + void absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target); + // Calculate the chance to absorb a spell based on the magnitude of every Spell Absorption effect source on the target. + int getAbsorbChance(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); } #endif diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 58d83fee1..67e64dffc 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -136,8 +136,7 @@ namespace MWMechanics // throughout the iteration of this spell's // effects, we display a "can't re-cast" message - // Try absorbing the spell. Some handling must still happen for absorbed effects. - bool absorbed = absorbSpell(mId, caster, target); + int absorbChance = getAbsorbChance(caster, target); int currentEffectIndex = 0; for (std::vector::const_iterator effectIt (effects.mList.begin()); @@ -159,6 +158,13 @@ namespace MWMechanics } canCastAnEffect = true; + // Try absorbing the effect + if(absorbChance && Misc::Rng::roll0to99() < absorbChance) + { + absorbSpell(mId, caster, target); + continue; + } + if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer)) continue; @@ -172,10 +178,6 @@ namespace MWMechanics if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful) target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true); - // Avoid proceeding further for absorbed spells. - if (absorbed) - continue; - // Reflect harmful effects if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects)) continue; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index fd3854bb7..8265fef9b 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -644,7 +644,7 @@ namespace MWPhysics mTaskScheduler->convexSweepTest(projectile->getConvexShape(), from_, to_, resultCallback); - const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(resultCallback.m_hitPointWorld); + const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(projectile->getHitPosition()); projectile->setPosition(newpos); mTaskScheduler->updateSingleAabb(foundProjectile->second); } @@ -717,7 +717,7 @@ namespace MWPhysics mActors.emplace(ptr, std::move(actor)); } - int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater) + int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius) { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); assert(shapeInstance); @@ -725,7 +725,7 @@ namespace MWPhysics mProjectileId++; - auto projectile = std::make_shared(caster, position, radius, canTraverseWater, mTaskScheduler.get(), this); + auto projectile = std::make_shared(caster, position, radius, mTaskScheduler.get(), this); mProjectiles.emplace(mProjectileId, std::move(projectile)); return mProjectileId; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 2e735b7c1..a746dc01a 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -125,7 +125,7 @@ namespace MWPhysics void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); - int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater); + int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius); void setCaster(int projectileId, const MWWorld::Ptr& caster); void updateProjectile(const int projectileId, const osg::Vec3f &position) const; void removeProjectile(const int projectileId); diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 0e233562c..0c7c99db3 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -13,12 +13,10 @@ namespace MWPhysics { -Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) - : mCanCrossWaterSurface(canCrossWaterSurface) - , mCrossedWaterSurface(false) +Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) + : mHitWater(false) , mActive(true) , mCaster(caster) - , mWaterHitPosition(std::nullopt) , mPhysics(physicssystem) , mTaskScheduler(scheduler) { @@ -72,11 +70,6 @@ osg::Vec3f Projectile::getPosition() const return mPosition; } -bool Projectile::canTraverseWater() const -{ - return mCanCrossWaterSurface; -} - void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal) { if (!mActive.load(std::memory_order_acquire)) @@ -127,17 +120,4 @@ bool Projectile::isValidTarget(const MWWorld::Ptr& target) const return validTarget; } -std::optional Projectile::getWaterHitPosition() -{ - return std::exchange(mWaterHitPosition, std::nullopt); -} - -void Projectile::setWaterHitPosition(btVector3 pos) -{ - if (mCrossedWaterSurface) - return; - mCrossedWaterSurface = true; - mWaterHitPosition = pos; -} - } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index 2ce1a72d5..c14b201d8 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include @@ -32,7 +31,7 @@ namespace MWPhysics class Projectile final : public PtrHolder { public: - Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); + Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } @@ -61,15 +60,25 @@ namespace MWPhysics MWWorld::Ptr getCaster() const; void setCaster(MWWorld::Ptr caster); - bool canTraverseWater() const; + void setHitWater() + { + mHitWater = true; + } + + bool getHitWater() const + { + return mHitWater; + } void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal); void setValidTargets(const std::vector& targets); bool isValidTarget(const MWWorld::Ptr& target) const; - std::optional getWaterHitPosition(); - void setWaterHitPosition(btVector3 pos); + btVector3 getHitPosition() const + { + return mHitPosition; + } private: @@ -78,12 +87,10 @@ namespace MWPhysics std::unique_ptr mCollisionObject; bool mTransformUpdatePending; - bool mCanCrossWaterSurface; - bool mCrossedWaterSurface; + bool mHitWater; std::atomic mActive; MWWorld::Ptr mCaster; MWWorld::Ptr mHitTarget; - std::optional mWaterHitPosition; osg::Vec3f mPosition; btVector3 mHitPosition; btVector3 mHitNormal; diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp index b803c4400..1e19937db 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.cpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -47,9 +47,7 @@ namespace MWPhysics } case CollisionType_Water: { - mProjectile->setWaterHitPosition(m_hitPointWorld); - if (mProjectile->canTraverseWater()) - return 1.f; + mProjectile->setHitWater(); mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); break; } diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 53b1b364b..753ee69e8 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -320,7 +320,7 @@ namespace MWWorld // in case there are multiple effects, the model is a dummy without geometry. Use the second effect for physics shape if (state.mIdMagic.size() > 1) model = "meshes\\" + MWBase::Environment::get().getWorld()->getStore().get().find(state.mIdMagic.at(1))->mModel; - state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true, false); + state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true); state.mToDelete = false; mMagicBolts.push_back(state); } @@ -345,7 +345,7 @@ namespace MWWorld if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); - state.mProjectileId = mPhysics->addProjectile(actor, pos, model, false, true); + state.mProjectileId = mPhysics->addProjectile(actor, pos, model, false); state.mToDelete = false; mProjectiles.push_back(state); } @@ -496,9 +496,6 @@ namespace MWWorld auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); - if (const auto hitWaterPos = projectile->getWaterHitPosition()) - mRendering->emitWaterRipple(Misc::Convert::toOsg(*hitWaterPos)); - const auto pos = projectile->getPosition(); projectileState.mNode->setPosition(pos); @@ -522,6 +519,8 @@ namespace MWWorld if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId)) bow = *invIt; } + if (projectile->getHitWater()) + mRendering->emitWaterRipple(pos); MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); cleanupProjectile(projectileState); @@ -654,7 +653,7 @@ namespace MWWorld int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false, true); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false); } catch(...) { @@ -707,7 +706,7 @@ namespace MWWorld osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true, false); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (const std::string &soundid : state.mSoundIds) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 016a39b3e..dabd7719b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3672,6 +3672,7 @@ namespace MWWorld bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), worldPos); if (underwater) { + MWMechanics::projectileHit(actor, Ptr(), bow, projectile, worldPos, attackStrength); mRendering->emitWaterRipple(worldPos); return; }