1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-03-29 04:20:29 +00:00

Improve hit detection emulation (#7900)

This commit is contained in:
Alexei Kotov 2024-03-25 15:53:26 +03:00
parent 6f8123998c
commit 86b6eee62b

View File

@ -587,18 +587,20 @@ namespace MWMechanics
MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::World* world = MWBase::Environment::get().getWorld();
const MWWorld::Store<ESM::GameSetting>& store = world->getStore().get<ESM::GameSetting>(); const MWWorld::Store<ESM::GameSetting>& store = world->getStore().get<ESM::GameSetting>();
// These GMSTs are not in degrees. They're tolerance angle sines multiplied by 90.
// With the default values of 60, the actual tolerance angles are roughly 41.8 degrees.
// Don't think too hard about it. In this place, thinking can cause permanent damage to your mental health.
const float fCombatAngleXY = store.find("fCombatAngleXY")->mValue.getFloat() / 90.f;
const float fCombatAngleZ = store.find("fCombatAngleZ")->mValue.getFloat() / 90.f;
const ESM::Position& posdata = actor.getRefData().getPosition(); const ESM::Position& posdata = actor.getRefData().getPosition();
const osg::Vec3f actorPos(posdata.asVec3()); const osg::Vec3f actorPos(posdata.asVec3());
const osg::Vec3f actorDirXY = osg::Quat(posdata.rot[2], osg::Vec3(0, 0, -1)) * osg::Vec3f(0, 1, 0);
// Morrowind uses body orientation or camera orientation if available // Only the player can look up, apparently.
// The difference between that and this is subtle const float actorVerticalAngle = actor == getPlayer() ? -std::sin(posdata.rot[0]) : 0.f;
osg::Quat actorRot const float actorEyeLevel = world->getHalfExtents(actor, true).z() * 2.f * 0.85f;
= osg::Quat(posdata.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0, 0, -1)); const osg::Vec3f actorEyePos{ actorPos.x(), actorPos.y(), actorPos.z() + actorEyeLevel };
const bool canMoveByZ = canActorMoveByZAxis(actor);
const float fCombatAngleXY = store.find("fCombatAngleXY")->mValue.getFloat();
const float fCombatAngleZ = store.find("fCombatAngleZ")->mValue.getFloat();
const float combatAngleXYcos = std::cos(osg::DegreesToRadians(fCombatAngleXY));
const float combatAngleZcos = std::cos(osg::DegreesToRadians(fCombatAngleZ));
// The player can target any active actor, non-playable actors only target their targets // The player can target any active actor, non-playable actors only target their targets
std::vector<MWWorld::Ptr> targets; std::vector<MWWorld::Ptr> targets;
@ -612,26 +614,40 @@ namespace MWMechanics
{ {
if (actor == target || target.getClass().getCreatureStats(target).isDead()) if (actor == target || target.getClass().getCreatureStats(target).isDead())
continue; continue;
float dist = getDistanceToBounds(actor, target); const float dist = getDistanceToBounds(actor, target);
osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3());
osg::Vec3f dirToTarget = targetPos - actorPos; if (dist >= reach || dist >= minDist || std::abs(targetPos.z() - actorPos.z()) >= reach)
if (dist >= reach || dist >= minDist || std::abs(dirToTarget.z()) >= reach)
continue; continue;
dirToTarget.normalize(); // Horizontal angle checks.
osg::Vec2f actorToTargetXY{ targetPos.x() - actorPos.x(), targetPos.y() - actorPos.y() };
actorToTargetXY.normalize();
// The idea is to use fCombatAngleXY and fCombatAngleZ as tolerance angles // Use dot product to check if the target is behind first...
// in XY and YZ planes of the coordinate system where the actor's orientation if (actorToTargetXY.x() * actorDirXY.x() + actorToTargetXY.y() * actorDirXY.y() <= 0.f)
// corresponds to (0, 1, 0) vector. This is not exactly what Morrowind does
// but Morrowind does something (even more) stupid here
osg::Vec3f hitDir = actorRot.inverse() * dirToTarget;
if (combatAngleXYcos * std::abs(hitDir.x()) > hitDir.y())
continue; continue;
// Nice cliff racer hack Todd // And then perp dot product to calculate the hit angle sine.
if (combatAngleZcos * std::abs(hitDir.z()) > hitDir.y() && !MWMechanics::canActorMoveByZAxis(target)) // This gives us a horizontal hit range of [-asin(fCombatAngleXY / 90); asin(fCombatAngleXY / 90)]
if (std::abs(actorToTargetXY.x() * actorDirXY.y() - actorToTargetXY.y() * actorDirXY.x()) > fCombatAngleXY)
continue; continue;
// Vertical angle checks. Nice cliff racer hack, Todd.
if (!canMoveByZ)
{
// The idea is that the body should always be possible to hit.
// fCombatAngleZ is the tolerance for hitting the target's feet or head.
osg::Vec3f actorToTargetFeet = targetPos - actorEyePos;
osg::Vec3f actorToTargetHead = actorToTargetFeet;
actorToTargetFeet.normalize();
actorToTargetHead.z() += world->getHalfExtents(target, true).z() * 2.f;
actorToTargetHead.normalize();
if (actorVerticalAngle - actorToTargetHead.z() > fCombatAngleZ
|| actorVerticalAngle - actorToTargetFeet.z() < -fCombatAngleZ)
continue;
}
// Gotta use physics somehow! // Gotta use physics somehow!
if (!world->getLOS(actor, target)) if (!world->getLOS(actor, target))
continue; continue;