Merge branch 'whoanotouchy' into 'master'

Properly calculate touch spell hit position (#6156)

Closes #6156

See merge request OpenMW/openmw!3920
fix-osga-rotate-wildly
psi29a 10 months ago
commit effb4fc383

@ -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

@ -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<MWWorld::Ptr> targetActors;
if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell)
stats.getAiSequence().getCombatTargets(targetActors);
const float fCombatDistance = mStore.get<ESM::GameSetting>().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<Ptr, osg::Vec3f> 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<float>::max();
float dist2 = std::numeric_limits<float>::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<ESM::GameSetting>().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);

Loading…
Cancel
Save