diff --git a/CHANGELOG.md b/CHANGELOG.md index ccccc92f66..953801b345 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Bug #6025: Subrecords cannot overlap records Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex Bug #6146: Lua command `actor:setEquipment` doesn't trigger mwscripts when equipping or unequipping a scripted item + Bug #6156: 1ft Charm or Sound magic effect vfx doesn't work properly Bug #6190: Unintuitive sun specularity time of day dependence Bug #6222: global map cell size can crash openmw if set to too high a value Bug #6313: Followers with high Fight can turn hostile diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index cbef6789f1..0468b36a2f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2939,89 +2939,75 @@ namespace MWWorld { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - // 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 != MWMechanics::getPlayer() && !manualSpell) - stats.getAiSequence().getCombatTargets(targetActors); - - const float fCombatDistance = mStore.get().find("fCombatDistance")->mValue.getFloat(); - - osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); - - // for player we can take faced object first + const bool casterIsPlayer = actor == MWMechanics::getPlayer(); MWWorld::Ptr target; - if (actor == MWMechanics::getPlayer()) - target = getFacedObject(); - - // if the faced object can not be activated, do not use it - if (!target.isEmpty() && !target.getClass().hasToolTip(target)) - target = nullptr; - - if (target.isEmpty()) + // For scripted spells we should not use hit contact + if (manualSpell) { - // For scripted spells we should not use hit contact - if (manualSpell) + if (!casterIsPlayer) { - if (actor != MWMechanics::getPlayer()) + for (const auto& package : stats.getAiSequence()) { - for (const auto& package : stats.getAiSequence()) + if (package->getTypeId() == MWMechanics::AiPackageTypeId::Cast) { - if (package->getTypeId() == MWMechanics::AiPackageTypeId::Cast) - { - target = package->getTarget(); - break; - } + target = package->getTarget(); + break; } } } - else + } + else + { + if (casterIsPlayer) + target = getFacedObject(); + + if (target.isEmpty() || !target.getClass().hasToolTip(target)) { // For actor targets, we want to use melee hit contact. // This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would - // be very hard to aim at otherwise. For object targets, we want the detailed shapes (rendering - // raycast). If we used the bounding boxes for static objects, then we would not be able to target e.g. + // be very hard to aim at otherwise. + // For object targets, we want the detailed shapes (rendering raycast). + // If we used the bounding boxes for static objects, then we would not be able to target e.g. // objects lying on a shelf. - const std::pair result1 = MWMechanics::getHitContact(actor, fCombatDistance); - - // Get the target to use for "on touch" effects, using the facing direction from Head node - osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); - - osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1, 0, 0)) - * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0, 0, -1)); - - osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); - float distance = getMaxActivationDistance(); - osg::Vec3f dest = origin + direction * distance; - - MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); - - float dist1 = std::numeric_limits::max(); - float dist2 = std::numeric_limits::max(); - - if (!result1.first.isEmpty() && result1.first.getClass().isActor()) - dist1 = (origin - result1.second).length(); - if (result2.mHit) - dist2 = (origin - result2.mHitPointWorld).length(); + const float fCombatDistance = mStore.get().find("fCombatDistance")->mValue.getFloat(); + target = MWMechanics::getHitContact(actor, fCombatDistance).first; - if (!result1.first.isEmpty() && result1.first.getClass().isActor()) + if (target.isEmpty()) { - target = result1.first; - hitPosition = result1.second; - if (dist1 > getMaxActivationDistance()) - target = nullptr; - } - else if (result2.mHit) - { - target = result2.mHitObject; - hitPosition = result2.mHitPointWorld; - if (dist2 > getMaxActivationDistance() && !target.isEmpty() - && !target.getClass().hasToolTip(target)) - target = nullptr; + // Get the target using the facing direction from Head node + const osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); + const osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1, 0, 0)) + * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0, 0, -1)); + const osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); + const osg::Vec3f dest = origin + direction * getMaxActivationDistance(); + const MWRender::RenderingManager::RayResult result = mRendering->castRay(origin, dest, true, true); + if (result.mHit) + target = result.mHitObject; } } } + osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); + if (!target.isEmpty()) + { + // Touch explosion placement doesn't depend on where the target was "touched". + // In Morrowind, it's at 0.7 of the actor's AABB height for actors + // or at 0.7 of the player's height for non-actors if the player is the caster + // This is probably meant to prevent the explosion from being too far above on large objects + // but it often puts the explosions way above small objects, so we'll deviate here + // and use the object's bounds when reasonable (it's $CURRENT_YEAR, we can afford that) + // Note collision object origin is intentionally not used + hitPosition = target.getRefData().getPosition().asVec3(); + constexpr float explosionHeight = 0.7f; + float targetHeight = getHalfExtents(target).z() * 2.f; + if (!target.getClass().isActor() && casterIsPlayer) + { + const float playerHeight = getHalfExtents(actor).z() * 2.f; + targetHeight = std::min(targetHeight, playerHeight); + } + hitPosition.z() += targetHeight * explosionHeight; + } + const ESM::RefId& selectedSpell = stats.getSpells().getSelectedSpell(); MWMechanics::CastSpell cast(actor, target, false, manualSpell);