|
|
|
@ -32,6 +32,7 @@
|
|
|
|
|
#include "../mwbase/world.hpp"
|
|
|
|
|
#include "../mwbase/soundmanager.hpp"
|
|
|
|
|
#include "../mwbase/windowmanager.hpp"
|
|
|
|
|
#include "../mwbase/statemanager.hpp"
|
|
|
|
|
|
|
|
|
|
#include "../mwworld/class.hpp"
|
|
|
|
|
#include "../mwworld/inventorystore.hpp"
|
|
|
|
@ -118,7 +119,7 @@ static const struct WeaponInfo {
|
|
|
|
|
{ WeapType_TwoWide, "2w", "weapontwowide" },
|
|
|
|
|
{ WeapType_BowAndArrow, "1h", "bowandarrow" },
|
|
|
|
|
{ WeapType_Crossbow, "crossbow", "crossbow" },
|
|
|
|
|
{ WeapType_ThowWeapon, "1h", "throwweapon" },
|
|
|
|
|
{ WeapType_Thrown, "1h", "throwweapon" },
|
|
|
|
|
{ WeapType_PickProbe, "1h", "pickprobe" },
|
|
|
|
|
{ WeapType_Spell, "spell", "spellcast" },
|
|
|
|
|
};
|
|
|
|
@ -156,7 +157,14 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
|
|
|
|
bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock();
|
|
|
|
|
if(mHitState == CharState_None)
|
|
|
|
|
{
|
|
|
|
|
if(knockdown)
|
|
|
|
|
if (mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0)
|
|
|
|
|
{
|
|
|
|
|
mHitState = CharState_KnockOut;
|
|
|
|
|
mCurrentHit = "knockout";
|
|
|
|
|
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, false, 1, "start", "stop", 0.0f, ~0ul);
|
|
|
|
|
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true);
|
|
|
|
|
}
|
|
|
|
|
else if(knockdown)
|
|
|
|
|
{
|
|
|
|
|
mHitState = CharState_KnockDown;
|
|
|
|
|
mCurrentHit = "knockdown";
|
|
|
|
@ -186,6 +194,12 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
|
|
|
|
mPtr.getClass().getCreatureStats(mPtr).setBlock(false);
|
|
|
|
|
mHitState = CharState_None;
|
|
|
|
|
}
|
|
|
|
|
else if (mHitState == CharState_KnockOut && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0)
|
|
|
|
|
{
|
|
|
|
|
mHitState = CharState_KnockDown;
|
|
|
|
|
mAnimation->disable(mCurrentHit);
|
|
|
|
|
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "loop stop", "stop", 0.0f, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType));
|
|
|
|
@ -301,12 +315,24 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
|
|
|
|
if(!mCurrentMovement.empty())
|
|
|
|
|
{
|
|
|
|
|
float vel, speedmult = 1.0f;
|
|
|
|
|
|
|
|
|
|
bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run);
|
|
|
|
|
|
|
|
|
|
if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 1.0f)
|
|
|
|
|
speedmult = mMovementSpeed / vel;
|
|
|
|
|
|
|
|
|
|
else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight)
|
|
|
|
|
speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed
|
|
|
|
|
else if (mMovementSpeed > 0.0f)
|
|
|
|
|
// The first person anims don't have any velocity to calculate a speed multiplier from.
|
|
|
|
|
// We use the third person velocities instead.
|
|
|
|
|
// FIXME: should be pulled from the actual animation, but it is not presently loaded.
|
|
|
|
|
speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f);
|
|
|
|
|
mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false,
|
|
|
|
|
speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
|
|
|
|
|
|
|
|
|
|
mMovementAnimVelocity = vel;
|
|
|
|
|
}
|
|
|
|
|
else mMovementAnimVelocity = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -367,7 +393,7 @@ MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::I
|
|
|
|
|
*weaptype = WeapType_Crossbow;
|
|
|
|
|
break;
|
|
|
|
|
case ESM::Weapon::MarksmanThrown:
|
|
|
|
|
*weaptype = WeapType_ThowWeapon;
|
|
|
|
|
*weaptype = WeapType_Thrown;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -403,6 +429,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
|
|
|
|
|
, mIdleState(CharState_None)
|
|
|
|
|
, mMovementState(CharState_None)
|
|
|
|
|
, mMovementSpeed(0.0f)
|
|
|
|
|
, mMovementAnimVelocity(0.0f)
|
|
|
|
|
, mDeathState(CharState_None)
|
|
|
|
|
, mHitState(CharState_None)
|
|
|
|
|
, mUpperBodyState(UpperCharState_Nothing)
|
|
|
|
@ -479,7 +506,14 @@ bool CharacterController::updateCreatureState()
|
|
|
|
|
{
|
|
|
|
|
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
|
|
|
|
|
|
|
|
|
|
determineAttackType();
|
|
|
|
|
// These are unique animations and not linked to movement type. Just pick one randomly.
|
|
|
|
|
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 3; // [0, 2]
|
|
|
|
|
if (roll == 0)
|
|
|
|
|
mCurrentWeapon = "attack1";
|
|
|
|
|
else if (roll == 1)
|
|
|
|
|
mCurrentWeapon = "attack2";
|
|
|
|
|
else
|
|
|
|
|
mCurrentWeapon = "attack3";
|
|
|
|
|
|
|
|
|
|
mAnimation->play(mCurrentWeapon, Priority_Weapon,
|
|
|
|
|
MWRender::Animation::Group_All, true,
|
|
|
|
@ -527,6 +561,7 @@ bool CharacterController::updateWeaponState()
|
|
|
|
|
{
|
|
|
|
|
getWeaponGroup(weaptype, weapgroup);
|
|
|
|
|
mAnimation->showWeapons(false);
|
|
|
|
|
mAnimation->setWeaponGroup(weapgroup);
|
|
|
|
|
|
|
|
|
|
mAnimation->play(weapgroup, Priority_Weapon,
|
|
|
|
|
MWRender::Animation::Group_UpperBody, true,
|
|
|
|
@ -581,6 +616,19 @@ bool CharacterController::updateWeaponState()
|
|
|
|
|
if(isWeapon)
|
|
|
|
|
weapSpeed = weapon->get<ESM::Weapon>()->mBase->mData.mSpeed;
|
|
|
|
|
|
|
|
|
|
// Cancel attack if we no longer have ammunition
|
|
|
|
|
bool ammunition = true;
|
|
|
|
|
MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
|
|
|
|
if (mWeaponType == WeapType_Crossbow)
|
|
|
|
|
ammunition = (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt);
|
|
|
|
|
else if (mWeaponType == WeapType_BowAndArrow)
|
|
|
|
|
ammunition = (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Arrow);
|
|
|
|
|
if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped)
|
|
|
|
|
{
|
|
|
|
|
mAnimation->disable(mCurrentWeapon);
|
|
|
|
|
mUpperBodyState = UpperCharState_WeapEquiped;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float complete;
|
|
|
|
|
bool animPlaying;
|
|
|
|
|
if(stats.getAttackingOrSpell())
|
|
|
|
@ -685,14 +733,15 @@ bool CharacterController::updateWeaponState()
|
|
|
|
|
if(item.getRefData().getCount())
|
|
|
|
|
MWBase::Environment::get().getWindowManager()->setSelectedWeapon(item);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
else if (ammunition)
|
|
|
|
|
{
|
|
|
|
|
if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow ||
|
|
|
|
|
mWeaponType == WeapType_ThowWeapon)
|
|
|
|
|
mWeaponType == WeapType_Thrown)
|
|
|
|
|
mAttackType = "shoot";
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if(isWeapon && Settings::Manager::getBool("best attack", "Game"))
|
|
|
|
|
if(isWeapon && mPtr.getRefData().getHandle() == "player" &&
|
|
|
|
|
Settings::Manager::getBool("best attack", "Game"))
|
|
|
|
|
mAttackType = getBestAttack(weapon->get<ESM::Weapon>()->mBase);
|
|
|
|
|
else
|
|
|
|
|
determineAttackType();
|
|
|
|
@ -751,12 +800,59 @@ bool CharacterController::updateWeaponState()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mAnimation->setPitchFactor(0.f);
|
|
|
|
|
if (mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown)
|
|
|
|
|
{
|
|
|
|
|
switch (mUpperBodyState)
|
|
|
|
|
{
|
|
|
|
|
case UpperCharState_StartToMinAttack:
|
|
|
|
|
mAnimation->setPitchFactor(complete);
|
|
|
|
|
break;
|
|
|
|
|
case UpperCharState_MinAttackToMaxAttack:
|
|
|
|
|
case UpperCharState_MaxAttackToMinHit:
|
|
|
|
|
case UpperCharState_MinHitToHit:
|
|
|
|
|
mAnimation->setPitchFactor(1.f);
|
|
|
|
|
break;
|
|
|
|
|
case UpperCharState_FollowStartToFollowStop:
|
|
|
|
|
if (animPlaying)
|
|
|
|
|
mAnimation->setPitchFactor(1.f-complete);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (mWeaponType == WeapType_Crossbow)
|
|
|
|
|
{
|
|
|
|
|
switch (mUpperBodyState)
|
|
|
|
|
{
|
|
|
|
|
case UpperCharState_EquipingWeap:
|
|
|
|
|
mAnimation->setPitchFactor(complete);
|
|
|
|
|
break;
|
|
|
|
|
case UpperCharState_UnEquipingWeap:
|
|
|
|
|
mAnimation->setPitchFactor(1.f-complete);
|
|
|
|
|
break;
|
|
|
|
|
case UpperCharState_WeapEquiped:
|
|
|
|
|
case UpperCharState_StartToMinAttack:
|
|
|
|
|
case UpperCharState_MinAttackToMaxAttack:
|
|
|
|
|
case UpperCharState_MaxAttackToMinHit:
|
|
|
|
|
case UpperCharState_MinHitToHit:
|
|
|
|
|
case UpperCharState_FollowStartToFollowStop:
|
|
|
|
|
mAnimation->setPitchFactor(1.f);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!animPlaying)
|
|
|
|
|
{
|
|
|
|
|
if(mUpperBodyState == UpperCharState_EquipingWeap ||
|
|
|
|
|
mUpperBodyState == UpperCharState_FollowStartToFollowStop ||
|
|
|
|
|
mUpperBodyState == UpperCharState_CastingSpell)
|
|
|
|
|
{
|
|
|
|
|
if (ammunition && mWeaponType == WeapType_Crossbow)
|
|
|
|
|
mAnimation->attachArrow();
|
|
|
|
|
|
|
|
|
|
mUpperBodyState = UpperCharState_WeapEquiped;
|
|
|
|
|
//don't allow to continue playing hit animation on UpperBody after actor had attacked during it
|
|
|
|
|
if(mHitState == CharState_Hit)
|
|
|
|
@ -1136,18 +1232,17 @@ void CharacterController::update(float duration)
|
|
|
|
|
|
|
|
|
|
if (!mSkipAnim)
|
|
|
|
|
{
|
|
|
|
|
if(mHitState != CharState_KnockDown)
|
|
|
|
|
rot *= Ogre::Math::RadiansToDegrees(1.0f);
|
|
|
|
|
if(mHitState != CharState_KnockDown && mHitState != CharState_KnockOut)
|
|
|
|
|
{
|
|
|
|
|
rot *= duration * Ogre::Math::RadiansToDegrees(1.0f);
|
|
|
|
|
world->rotateObject(mPtr, rot.x, rot.y, rot.z, true);
|
|
|
|
|
}
|
|
|
|
|
else //avoid z-rotating for knockdown
|
|
|
|
|
world->rotateObject(mPtr, rot.x, rot.y, 0.0f, true);
|
|
|
|
|
|
|
|
|
|
// all actual movement in 3rd person controlled by animations, except for jump
|
|
|
|
|
// !mAnimation->hasAnimation("death1") identifies 1st person mode
|
|
|
|
|
if(mJumpState != JumpState_None || vec.z > 0
|
|
|
|
|
|| (mPtr.getRefData().getHandle() == "player" && !mAnimation->hasAnimation("death1")))
|
|
|
|
|
// always control actual movement by animation unless this:
|
|
|
|
|
// FIXME: actor falling/landing should be controlled by physics engine
|
|
|
|
|
if(mMovementAnimVelocity == 0.0f && (vec.length() > 0.0f || mJumpState != JumpState_None))
|
|
|
|
|
{
|
|
|
|
|
world->queueMovement(mPtr, vec);
|
|
|
|
|
}
|
|
|
|
@ -1158,7 +1253,6 @@ void CharacterController::update(float duration)
|
|
|
|
|
}
|
|
|
|
|
else if(cls.getCreatureStats(mPtr).isDead())
|
|
|
|
|
{
|
|
|
|
|
MWBase::Environment::get().getWorld()->enableActorCollision(mPtr, false);
|
|
|
|
|
world->queueMovement(mPtr, Ogre::Vector3(0.0f));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -1260,10 +1354,17 @@ void CharacterController::forceStateUpdate()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CharacterController::kill()
|
|
|
|
|
bool CharacterController::kill()
|
|
|
|
|
{
|
|
|
|
|
if(mDeathState != CharState_None)
|
|
|
|
|
return;
|
|
|
|
|
if( isDead() )
|
|
|
|
|
{
|
|
|
|
|
//player's death animation is over
|
|
|
|
|
if( mPtr.getRefData().getHandle()=="player" && !isAnimPlaying(mCurrentDeath) )
|
|
|
|
|
{
|
|
|
|
|
MWBase::Environment::get().getStateManager()->askLoadRecent();
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
playRandomDeath();
|
|
|
|
|
|
|
|
|
@ -1274,6 +1375,8 @@ void CharacterController::kill()
|
|
|
|
|
|
|
|
|
|
mIdleState = CharState_None;
|
|
|
|
|
mCurrentIdle.clear();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CharacterController::resurrect()
|
|
|
|
@ -1338,15 +1441,6 @@ void CharacterController::determineAttackType()
|
|
|
|
|
else
|
|
|
|
|
mAttackType = "chop";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (move[0] && !move[1]) //sideway
|
|
|
|
|
mCurrentWeapon = "attack2";
|
|
|
|
|
else if (move[1]) //forward
|
|
|
|
|
mCurrentWeapon = "attack3";
|
|
|
|
|
else
|
|
|
|
|
mCurrentWeapon = "attack1";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|