1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-16 11:49:56 +00:00
openmw-tes3mp/apps/openmw/mwvr/realisticcombat.cpp

342 lines
12 KiB
C++
Raw Normal View History

2020-03-15 14:31:38 +00:00
#include "realisticcombat.hpp"
2020-03-15 14:31:38 +00:00
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
2020-03-23 22:32:47 +00:00
#include "../mwmechanics/weapontype.hpp"
#include <components/debug/debuglog.hpp>
2020-03-23 22:32:47 +00:00
#include <iomanip>
2020-03-15 14:31:38 +00:00
namespace MWVR {
namespace RealisticCombat {
2020-03-15 14:31:38 +00:00
static const char* stateToString(SwingState florida)
{
switch (florida)
{
case SwingState_Cooldown:
return "Cooldown";
case SwingState_Impact:
return "Impact";
case SwingState_Ready:
return "Ready";
case SwingState_Swing:
return "Swing";
case SwingState_Launch:
return "Launch";
}
return "Error, invalid enum";
}
static const char* swingTypeToString(int type)
{
switch (type)
{
case ESM::Weapon::AT_Chop:
return "Chop";
case ESM::Weapon::AT_Slash:
return "Slash";
case ESM::Weapon::AT_Thrust:
return "Thrust";
case -1:
return "Fail";
default:
return "Invalid";
}
}
2020-03-23 22:32:47 +00:00
2020-06-28 09:33:01 +00:00
StateMachine::StateMachine(MWWorld::Ptr ptr)
: mPtr(ptr)
, mMinVelocity(Settings::Manager::getFloat("realistic combat minimum swing velocity", "VR"))
, mMaxVelocity(Settings::Manager::getFloat("realistic combat maximum swing velocity", "VR"))
{
Log(Debug::Verbose) << "realistic combat minimum swing velocity: " << mMinVelocity;
Log(Debug::Verbose) << "realistic combat maximum swing velocity: " << mMaxVelocity;
}
2020-03-15 14:31:38 +00:00
bool StateMachine::canSwing()
{
2020-06-28 09:33:01 +00:00
if (mSwingType >= 0)
if (mVelocity >= mMinVelocity)
if (mSwingType != ESM::Weapon::AT_Thrust || mThrustVelocity >= 0.f)
return true;
return false;
}
2020-03-15 14:31:38 +00:00
// Actions common to all transitions
void StateMachine::transition(
SwingState newState)
{
2020-06-28 09:33:01 +00:00
Log(Debug::Verbose) << "Transition:" << stateToString(mState) << "->" << stateToString(newState);
mMaxSwingVelocity = 0.f;
mTimeSinceEnteredState = 0.f;
mMovementSinceEnteredState = 0.f;
mState = newState;
}
2020-03-23 22:32:47 +00:00
void StateMachine::reset()
{
2020-06-28 09:33:01 +00:00
mMaxSwingVelocity = 0.f;
mTimeSinceEnteredState = 0.f;
mVelocity = 0.f;
mPreviousPosition = osg::Vec3(0.f, 0.f, 0.f);
mState = SwingState_Ready;
}
2020-03-23 22:32:47 +00:00
static bool isMeleeWeapon(int type)
{
if (MWMechanics::getWeaponType(type)->mWeaponClass != ESM::WeaponType::Melee)
return false;
if (type == ESM::Weapon::HandToHand)
return true;
if (type >= 0)
return true;
2020-03-28 15:35:49 +00:00
return false;
}
2020-03-23 22:32:47 +00:00
static bool isSideSwingValidForWeapon(int type)
{
switch (type)
{
case ESM::Weapon::HandToHand:
case ESM::Weapon::BluntOneHand:
case ESM::Weapon::BluntTwoClose:
case ESM::Weapon::BluntTwoWide:
case ESM::Weapon::SpearTwoWide:
return true;
case ESM::Weapon::ShortBladeOneHand:
case ESM::Weapon::LongBladeOneHand:
case ESM::Weapon::LongBladeTwoHand:
case ESM::Weapon::AxeOneHand:
case ESM::Weapon::AxeTwoHand:
default:
return false;
}
}
2020-03-15 14:31:38 +00:00
void StateMachine::update(float dt, bool enabled)
{
auto* session = Environment::get().getSession();
auto* world = MWBase::Environment::get().getWorld();
auto& predictedPoses = session->predictedPoses(VRSession::FramePhase::Update);
auto& handPose = predictedPoses.hands[(int)MWVR::Side::RIGHT_SIDE];
auto weaponType = world->getActiveWeaponType();
2020-03-15 14:31:38 +00:00
enabled = enabled && isMeleeWeapon(weaponType);
2020-03-15 14:31:38 +00:00
if (mEnabled != enabled)
{
reset();
mEnabled = enabled;
}
if (!enabled)
return;
2020-03-15 14:31:38 +00:00
2020-06-28 09:33:01 +00:00
mTimeSinceEnteredState += dt;
2020-03-15 14:31:38 +00:00
2020-03-23 22:32:47 +00:00
// First determine direction of different swing types
2020-03-15 14:31:38 +00:00
// Discover orientation of weapon
osg::Quat weaponDir = handPose.orientation;
2020-03-15 14:31:38 +00:00
// Morrowind models do not hold weapons at a natural angle, so i rotate the hand forward
// to get a more natural angle on the weapon to allow more comfortable combat.
if (weaponType != ESM::Weapon::HandToHand)
weaponDir = osg::Quat(osg::PI_4, osg::Vec3{ 1,0,0 }) * weaponDir;
2020-03-23 22:32:47 +00:00
// Thrust means stabbing in the direction of the weapon
osg::Vec3 thrustDirection = weaponDir * osg::Vec3{ 0,1,0 };
2020-03-15 14:31:38 +00:00
// Slash and Chop are vertical, relative to the orientation of the weapon (direction of the sharp edge / hammer)
osg::Vec3 slashChopDirection = weaponDir * osg::Vec3{ 0,0,1 };
2020-03-15 14:31:38 +00:00
// Side direction of the weapon (i.e. The blunt side of the sword)
osg::Vec3 sideDirection = weaponDir * osg::Vec3{ 1,0,0 };
2020-03-15 14:31:38 +00:00
// Next determine current hand movement
2020-03-15 14:31:38 +00:00
// If tracking is lost, openxr will return a position of 0
// So i reset position when tracking is re-acquired to avoid a superspeed strike.
// Theoretically, the player's hand really could be at 0,0,0
// but that's a super rare case so whatever.
2020-06-28 09:33:01 +00:00
if (mPreviousPosition == osg::Vec3(0.f, 0.f, 0.f))
mPreviousPosition = handPose.position;
2020-03-28 15:35:49 +00:00
2020-06-28 09:33:01 +00:00
osg::Vec3 movement = handPose.position - mPreviousPosition;
mMovementSinceEnteredState += movement.length();
mPreviousPosition = handPose.position;
osg::Vec3 swingVector = movement / dt;
osg::Vec3 swingDirection = swingVector;
swingDirection.normalize();
2020-03-28 15:35:49 +00:00
// Compute swing velocities
2020-03-28 15:35:49 +00:00
// Thrust follows the orientation of the weapon. Negative thrust = no attack.
2020-06-28 09:33:01 +00:00
mThrustVelocity = swingVector * thrustDirection;
mVelocity = swingVector.length();
2020-03-28 15:35:49 +00:00
2020-03-15 14:31:38 +00:00
if (isSideSwingValidForWeapon(weaponType))
{
// Compute velocity in the plane normal to the thrust direction.
2020-06-28 09:33:01 +00:00
float thrustComponent = std::abs(mThrustVelocity / mVelocity);
float planeComponent = std::sqrt(1 - thrustComponent * thrustComponent);
2020-06-28 09:33:01 +00:00
mSlashChopVelocity = mVelocity * planeComponent;
mSideVelocity = -1000.f;
}
else
{
// If side swing is not valid for the weapon, count slash/chop only along in
// the direction of the weapon's edge.
2020-06-28 09:33:01 +00:00
mSlashChopVelocity = std::abs(swingVector * slashChopDirection);
mSideVelocity = std::abs(swingVector * sideDirection);
}
float orientationVerticality = std::abs(thrustDirection * osg::Vec3{ 0,0,1 });
float swingVerticality = std::abs(swingDirection * osg::Vec3{ 0,0,1 });
// Pick swing type based on greatest current velocity
// Note i use abs() of thrust velocity to prevent accidentally triggering
// chop/slash when player is withdrawing the weapon.
2020-06-28 09:33:01 +00:00
if (mSideVelocity > std::abs(mThrustVelocity) && mSideVelocity > mSlashChopVelocity)
{
// Player is swinging with the "blunt" side of a weapon that
// cannot be used that way.
2020-06-28 09:33:01 +00:00
mSwingType = -1;
}
2020-06-28 09:33:01 +00:00
else if (std::abs(mThrustVelocity) > mSlashChopVelocity)
{
2020-06-28 09:33:01 +00:00
mSwingType = ESM::Weapon::AT_Thrust;
}
2020-03-28 15:35:49 +00:00
else
{
// First check if the weapon is pointing upwards. In which case slash is not
// applicable, and the attack must be a chop.
if (orientationVerticality > 0.707)
2020-06-28 09:33:01 +00:00
mSwingType = ESM::Weapon::AT_Chop;
else
{
// Next check if the swing is more horizontal or vertical. A slash
// would be more horizontal.
if (swingVerticality > 0.707)
2020-06-28 09:33:01 +00:00
mSwingType = ESM::Weapon::AT_Chop;
else
2020-06-28 09:33:01 +00:00
mSwingType = ESM::Weapon::AT_Slash;
}
}
2020-06-28 09:33:01 +00:00
switch (mState)
{
case SwingState_Cooldown:
return update_cooldownState();
case SwingState_Ready:
return update_readyState();
case SwingState_Swing:
return update_swingState();
case SwingState_Impact:
return update_impactState();
case SwingState_Launch:
return update_launchState();
default:
2020-06-28 09:33:01 +00:00
throw std::logic_error(std::string("You forgot to implement state ") + stateToString(mState) + " ya dingus");
}
2020-03-28 15:35:49 +00:00
}
2020-03-15 14:31:38 +00:00
void StateMachine::update_cooldownState()
{
2020-06-28 09:33:01 +00:00
if (mTimeSinceEnteredState >= mMinimumPeriod)
transition_cooldownToReady();
}
2020-03-15 14:31:38 +00:00
void StateMachine::transition_cooldownToReady()
{
transition(SwingState_Ready);
}
2020-03-23 22:32:47 +00:00
void StateMachine::update_readyState()
{
if (canSwing())
return transition_readyToLaunch();
}
2020-03-23 22:32:47 +00:00
void StateMachine::transition_readyToLaunch()
{
transition(SwingState_Launch);
}
2020-03-15 14:31:38 +00:00
void StateMachine::playSwish()
{
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
2020-03-15 14:31:38 +00:00
std::string sound = "Weapon Swish";
2020-06-28 09:33:01 +00:00
if (mStrength < 0.5f)
sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack
if (mStrength < 1.0f)
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack
else
2020-06-28 09:33:01 +00:00
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack
2020-03-15 14:31:38 +00:00
2020-06-28 09:33:01 +00:00
Log(Debug::Verbose) << "Swing: " << swingTypeToString(mSwingType);
}
2020-03-28 15:35:49 +00:00
void StateMachine::update_launchState()
{
2020-06-28 09:33:01 +00:00
if (mMovementSinceEnteredState > mMinimumPeriod)
transition_launchToSwing();
if (!canSwing())
return transition_launchToReady();
}
2020-03-15 14:31:38 +00:00
void StateMachine::transition_launchToReady()
{
transition(SwingState_Ready);
}
2020-03-15 14:31:38 +00:00
void StateMachine::transition_launchToSwing()
{
playSwish();
transition(SwingState_Swing);
2020-03-15 14:31:38 +00:00
// As a special case, update the new state immediately to allow
// same-frame impacts.
update_swingState();
}
void StateMachine::update_swingState()
{
2020-06-28 09:33:01 +00:00
mMaxSwingVelocity = std::max(mVelocity, mMaxSwingVelocity);
mStrength = std::min(1.f, (mMaxSwingVelocity - mMinVelocity) / mMaxVelocity);
// When velocity falls below minimum, transition to register the miss
if (!canSwing())
return transition_swingingToImpact();
// Call hit with simulated=true to check for hit without actually causing an impact
2020-06-28 09:33:01 +00:00
if (mPtr.getClass().hit(mPtr, mStrength, mSwingType, true))
return transition_swingingToImpact();
}
void StateMachine::transition_swingingToImpact()
{
2020-06-28 09:33:01 +00:00
mPtr.getClass().hit(mPtr, mStrength, mSwingType, false);
transition(SwingState_Impact);
}
void StateMachine::update_impactState()
{
2020-06-28 09:33:01 +00:00
if (mVelocity < mMinVelocity)
return transition_impactToCooldown();
}
2020-03-15 14:31:38 +00:00
void StateMachine::transition_impactToCooldown()
{
transition(SwingState_Cooldown);
}
2020-03-15 14:31:38 +00:00
}
2020-03-15 14:31:38 +00:00
}