From b89ce8675f4dd641f3b1388b3bd94fa65651c620 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 4 Mar 2025 00:08:33 +0300 Subject: [PATCH 1/2] Handle instant projectile impact before underwater logic (#7622) --- apps/openmw/mwworld/worldimp.cpp | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 7b2f178343..6ba11e5084 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3047,28 +3047,31 @@ namespace MWWorld // TODO: as a better solutuon we should handle projectiles during physics update, not during world update. const osg::Vec3f sourcePos = worldPos + orient * osg::Vec3f(0, -1, 0) * 64.f; - // Early out if the launch position is underwater - bool underwater = isUnderwater(MWMechanics::getPlayer().getCell(), worldPos); - if (underwater) - { - MWMechanics::projectileHit(actor, Ptr(), bow, projectile, worldPos, attackStrength); - mRendering->emitWaterRipple(worldPos); - return; - } - // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit // result. std::vector targetActors; if (!actor.isEmpty() && actor.getClass().isActor() && actor != MWMechanics::getPlayer()) actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); - // Check for impact, if yes, handle hit, if not, launch projectile + // Check for impact, if yes, handle hit MWPhysics::RayCastingResult result = mPhysics->castRay( sourcePos, worldPos, { actor }, targetActors, 0xff, MWPhysics::CollisionType_Projectile); + if (result.mHit) + { MWMechanics::projectileHit(actor, result.mHitObject, bow, projectile, result.mHitPos, attackStrength); - else - mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); + return; + } + + // Bail out if the launch position is underwater + if (isUnderwater(MWMechanics::getPlayer().getCell(), worldPos)) + { + MWMechanics::projectileHit(actor, Ptr(), bow, projectile, worldPos, attackStrength); + mRendering->emitWaterRipple(worldPos); + return; + } + + mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); } void World::launchMagicBolt( From e79ab6e5ff45a806a1c184bf57c053c401f15fcb Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 4 Mar 2025 00:42:56 +0300 Subject: [PATCH 2/2] Allow non-actor spell projectiles to fire underwater (#8303) --- apps/openmw/mwworld/projectilemanager.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index b019dabdfe..6ea510e1e2 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -274,8 +274,8 @@ namespace MWWorld pos.z() += mPhysics->getRenderingHalfExtents(caster).z() * 2 * Constants::TorsoHeight; } - if (MWBase::Environment::get().getWorld()->isUnderwater( - caster.getCell(), pos)) // Underwater casting not possible + // Actors can't cast target spells underwater + if (caster.getClass().isActor() && MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) return; osg::Quat orient; @@ -564,15 +564,19 @@ namespace MWWorld for (const auto& sound : magicBoltState.mSounds) sound->setPosition(pos); - if (projectile->isActive()) + const Ptr caster = magicBoltState.getCaster(); + + const MWBase::World& world = *MWBase::Environment::get().getWorld(); + const bool active = projectile->isActive(); + if (active && !world.isUnderwater(caster.getCell(), pos)) continue; - const auto target = projectile->getTarget(); - const auto caster = magicBoltState.getCaster(); + const Ptr target = !active ? projectile->getTarget() : Ptr(); + assert(target != caster); MWMechanics::CastSpell cast(caster, target); - cast.mHitPosition = Misc::Convert::toOsg(projectile->getHitPosition()); + cast.mHitPosition = !active ? Misc::Convert::toOsg(projectile->getHitPosition()) : pos; cast.mId = magicBoltState.mSpellId; cast.mSourceName = magicBoltState.mSourceName; cast.mItem = magicBoltState.mItem;