mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-24 14:39:43 +00:00
Merge remote-tracking branch 'refs/remotes/OpenMW/master'
This commit is contained in:
commit
4d057febe7
14 changed files with 110 additions and 58 deletions
|
@ -264,7 +264,7 @@ namespace MWClass
|
||||||
|
|
||||||
if(Misc::Rng::roll0to99() >= hitchance)
|
if(Misc::Rng::roll0to99() >= hitchance)
|
||||||
{
|
{
|
||||||
victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false);
|
victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false);
|
||||||
MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
|
MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -318,15 +318,12 @@ namespace MWClass
|
||||||
if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength))
|
if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength))
|
||||||
damage = 0;
|
damage = 0;
|
||||||
|
|
||||||
if (damage > 0)
|
|
||||||
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
|
|
||||||
|
|
||||||
MWMechanics::diseaseContact(victim, ptr);
|
MWMechanics::diseaseContact(victim, ptr);
|
||||||
|
|
||||||
victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, true);
|
victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const
|
void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const
|
||||||
{
|
{
|
||||||
// NOTE: 'object' and/or 'attacker' may be empty.
|
// NOTE: 'object' and/or 'attacker' may be empty.
|
||||||
|
|
||||||
|
@ -342,10 +339,10 @@ namespace MWClass
|
||||||
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
|
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!object.isEmpty())
|
if (!object.isEmpty())
|
||||||
getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId());
|
getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId());
|
||||||
|
|
||||||
if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
||||||
{
|
{
|
||||||
const std::string &script = ptr.get<ESM::Creature>()->mBase->mScript;
|
const std::string &script = ptr.get<ESM::Creature>()->mBase->mScript;
|
||||||
/* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
|
/* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
|
||||||
|
@ -353,14 +350,14 @@ namespace MWClass
|
||||||
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!successful)
|
if (!successful)
|
||||||
{
|
{
|
||||||
// Missed
|
// Missed
|
||||||
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f);
|
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!object.isEmpty())
|
if (!object.isEmpty())
|
||||||
getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId());
|
getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId());
|
||||||
|
|
||||||
if (damage > 0.0f && !object.isEmpty())
|
if (damage > 0.0f && !object.isEmpty())
|
||||||
|
@ -391,7 +388,10 @@ namespace MWClass
|
||||||
if(ishealth)
|
if(ishealth)
|
||||||
{
|
{
|
||||||
if (!attacker.isEmpty())
|
if (!attacker.isEmpty())
|
||||||
|
{
|
||||||
damage = scaleDamage(damage, attacker, ptr);
|
damage = scaleDamage(damage, attacker, ptr);
|
||||||
|
MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition);
|
||||||
|
}
|
||||||
|
|
||||||
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
|
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ namespace MWClass
|
||||||
|
|
||||||
virtual void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const;
|
virtual void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const;
|
||||||
|
|
||||||
virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const;
|
virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const;
|
||||||
|
|
||||||
virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
|
virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
|
||||||
const MWWorld::Ptr& actor) const;
|
const MWWorld::Ptr& actor) const;
|
||||||
|
|
|
@ -591,7 +591,7 @@ namespace MWClass
|
||||||
|
|
||||||
if (Misc::Rng::roll0to99() >= hitchance)
|
if (Misc::Rng::roll0to99() >= hitchance)
|
||||||
{
|
{
|
||||||
othercls.onHit(victim, 0.0f, false, weapon, ptr, false);
|
othercls.onHit(victim, 0.0f, false, weapon, ptr, osg::Vec3f(), false);
|
||||||
MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
|
MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -646,15 +646,12 @@ namespace MWClass
|
||||||
if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength))
|
if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength))
|
||||||
damage = 0;
|
damage = 0;
|
||||||
|
|
||||||
if (healthdmg && damage > 0)
|
|
||||||
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
|
|
||||||
|
|
||||||
MWMechanics::diseaseContact(victim, ptr);
|
MWMechanics::diseaseContact(victim, ptr);
|
||||||
|
|
||||||
othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
|
othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const
|
void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const
|
||||||
{
|
{
|
||||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||||
|
|
||||||
|
@ -671,10 +668,10 @@ namespace MWClass
|
||||||
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
|
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!object.isEmpty())
|
if (!object.isEmpty())
|
||||||
getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId());
|
getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId());
|
||||||
|
|
||||||
if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
||||||
{
|
{
|
||||||
const std::string &script = ptr.getClass().getScript(ptr);
|
const std::string &script = ptr.getClass().getScript(ptr);
|
||||||
/* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
|
/* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
|
||||||
|
@ -682,14 +679,14 @@ namespace MWClass
|
||||||
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!successful)
|
if (!successful)
|
||||||
{
|
{
|
||||||
// Missed
|
// Missed
|
||||||
sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f);
|
sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!object.isEmpty())
|
if (!object.isEmpty())
|
||||||
getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId());
|
getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId());
|
||||||
|
|
||||||
|
|
||||||
|
@ -699,7 +696,7 @@ namespace MWClass
|
||||||
if (damage < 0.001f)
|
if (damage < 0.001f)
|
||||||
damage = 0;
|
damage = 0;
|
||||||
|
|
||||||
if(damage > 0.0f && !attacker.isEmpty())
|
if (damage > 0.0f && !attacker.isEmpty())
|
||||||
{
|
{
|
||||||
// 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
|
// 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
|
||||||
// something, alert the character controller, scripts, etc.
|
// something, alert the character controller, scripts, etc.
|
||||||
|
@ -725,7 +722,7 @@ namespace MWClass
|
||||||
else
|
else
|
||||||
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
|
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
|
||||||
|
|
||||||
if(damage > 0 && ishealth)
|
if (damage > 0 && ishealth)
|
||||||
{
|
{
|
||||||
// Hit percentages:
|
// Hit percentages:
|
||||||
// cuirass = 30%
|
// cuirass = 30%
|
||||||
|
@ -787,16 +784,18 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ishealth)
|
if (ishealth)
|
||||||
{
|
{
|
||||||
if (!attacker.isEmpty())
|
if (!attacker.isEmpty())
|
||||||
damage = scaleDamage(damage, attacker, ptr);
|
damage = scaleDamage(damage, attacker, ptr);
|
||||||
|
|
||||||
if(damage > 0.0f)
|
if (damage > 0.0f)
|
||||||
{
|
{
|
||||||
sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
|
sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
|
||||||
if (ptr == MWMechanics::getPlayer())
|
if (ptr == MWMechanics::getPlayer())
|
||||||
MWBase::Environment::get().getWindowManager()->activateHitOverlay();
|
MWBase::Environment::get().getWindowManager()->activateHitOverlay();
|
||||||
|
if (!attacker.isEmpty())
|
||||||
|
MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition);
|
||||||
}
|
}
|
||||||
MWMechanics::DynamicStat<float> health(getCreatureStats(ptr).getHealth());
|
MWMechanics::DynamicStat<float> health(getCreatureStats(ptr).getHealth());
|
||||||
health.setCurrent(health.getCurrent() - damage);
|
health.setCurrent(health.getCurrent() - damage);
|
||||||
|
|
|
@ -73,7 +73,7 @@ namespace MWClass
|
||||||
|
|
||||||
virtual void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const;
|
virtual void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const;
|
||||||
|
|
||||||
virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const;
|
virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const;
|
||||||
|
|
||||||
virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector<std::string>& models) const;
|
virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector<std::string>& models) const;
|
||||||
///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel().
|
///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel().
|
||||||
|
|
|
@ -69,7 +69,7 @@ namespace MWMechanics
|
||||||
mMovement()
|
mMovement()
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack);
|
void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
|
||||||
void updateCombatMove(float duration);
|
void updateCombatMove(float duration);
|
||||||
void stopCombatMove();
|
void stopCombatMove();
|
||||||
void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController,
|
void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController,
|
||||||
|
@ -242,7 +242,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (storage.mReadyToAttack)
|
if (storage.mReadyToAttack)
|
||||||
{
|
{
|
||||||
storage.startCombatMove(actorClass.isNpc(), isRangedCombat, distToTarget, rangeAttack);
|
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
|
||||||
// start new attack
|
// start new attack
|
||||||
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);
|
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);
|
||||||
|
|
||||||
|
@ -306,7 +306,6 @@ namespace MWMechanics
|
||||||
return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
|
return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
AiCombat *MWMechanics::AiCombat::clone() const
|
AiCombat *MWMechanics::AiCombat::clone() const
|
||||||
{
|
{
|
||||||
return new AiCombat(*this);
|
return new AiCombat(*this);
|
||||||
|
@ -323,18 +322,39 @@ namespace MWMechanics
|
||||||
sequence.mPackages.push_back(package);
|
sequence.mPackages.push_back(package);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiCombatStorage::startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack)
|
void AiCombatStorage::startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target)
|
||||||
{
|
{
|
||||||
if (mMovement.mPosition[0] || mMovement.mPosition[1])
|
if (mMovement.mPosition[0] || mMovement.mPosition[1])
|
||||||
{
|
{
|
||||||
mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
|
mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
|
||||||
mCombatMove = true;
|
mCombatMove = true;
|
||||||
}
|
}
|
||||||
// dodge movements (for NPCs only)
|
// dodge movements (for NPCs and bipedal creatures)
|
||||||
else if (isNpc && (!isDistantCombat || (distToTarget < rangeAttack / 2)))
|
else if (actor.getClass().isBipedal(actor))
|
||||||
{
|
{
|
||||||
//apply sideway movement (kind of dodging) with some probability
|
// get the range of the target's weapon
|
||||||
if (Misc::Rng::rollClosedProbability() < 0.25)
|
float rangeAttackOfTarget = 0.f;
|
||||||
|
bool isRangedCombat = false;
|
||||||
|
MWWorld::Ptr targetWeapon = MWWorld::Ptr();
|
||||||
|
const MWWorld::Class& targetClass = target.getClass();
|
||||||
|
|
||||||
|
if (targetClass.hasInventoryStore(target))
|
||||||
|
{
|
||||||
|
MWMechanics::WeaponType weapType = WeapType_None;
|
||||||
|
MWWorld::ContainerStoreIterator weaponSlot =
|
||||||
|
MWMechanics::getActiveWeapon(targetClass.getCreatureStats(target), targetClass.getInventoryStore(target), &weapType);
|
||||||
|
if (weapType != WeapType_PickProbe && weapType != WeapType_Spell && weapType != WeapType_None && weapType != WeapType_HandToHand)
|
||||||
|
targetWeapon = *weaponSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::shared_ptr<Action> targetWeaponAction (new ActionWeapon(targetWeapon));
|
||||||
|
|
||||||
|
if (targetWeaponAction.get())
|
||||||
|
rangeAttackOfTarget = targetWeaponAction->getCombatRange(isRangedCombat);
|
||||||
|
|
||||||
|
// apply sideway movement (kind of dodging) with some probability
|
||||||
|
// if actor is within range of target's weapon
|
||||||
|
if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25)
|
||||||
{
|
{
|
||||||
mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right
|
mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right
|
||||||
mTimerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability();
|
mTimerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability();
|
||||||
|
@ -342,6 +362,9 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Original engine behavior seems to be to back up during ranged combat
|
||||||
|
// according to fCombatDistance or opponent's weapon range, unless opponent
|
||||||
|
// is also using a ranged weapon
|
||||||
if (isDistantCombat && distToTarget < rangeAttack / 4)
|
if (isDistantCombat && distToTarget < rangeAttack / 4)
|
||||||
{
|
{
|
||||||
mMovement.mPosition[1] = -1;
|
mMovement.mPosition[1] = -1;
|
||||||
|
|
|
@ -462,8 +462,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
void ActionWeapon::prepare(const MWWorld::Ptr &actor)
|
void ActionWeapon::prepare(const MWWorld::Ptr &actor)
|
||||||
{
|
{
|
||||||
mIsNpc = actor.getClass().isNpc();
|
|
||||||
|
|
||||||
if (actor.getClass().hasInventoryStore(actor))
|
if (actor.getClass().hasInventoryStore(actor))
|
||||||
{
|
{
|
||||||
if (mWeapon.isEmpty())
|
if (mWeapon.isEmpty())
|
||||||
|
@ -490,19 +488,11 @@ namespace MWMechanics
|
||||||
static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fCombatDistance")->getFloat();
|
static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fCombatDistance")->getFloat();
|
||||||
|
|
||||||
if (mWeapon.isEmpty())
|
if (mWeapon.isEmpty())
|
||||||
{
|
|
||||||
if (!mIsNpc)
|
|
||||||
{
|
|
||||||
return fCombatDistance;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
static float fHandToHandReach =
|
static float fHandToHandReach =
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fHandToHandReach")->getFloat();
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fHandToHandReach")->getFloat();
|
||||||
|
|
||||||
return fHandToHandReach * fCombatDistance;
|
return fHandToHandReach * fCombatDistance;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const ESM::Weapon* weapon = mWeapon.get<ESM::Weapon>()->mBase;
|
const ESM::Weapon* weapon = mWeapon.get<ESM::Weapon>()->mBase;
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,6 @@ namespace MWMechanics
|
||||||
private:
|
private:
|
||||||
MWWorld::Ptr mAmmunition;
|
MWWorld::Ptr mAmmunition;
|
||||||
MWWorld::Ptr mWeapon;
|
MWWorld::Ptr mWeapon;
|
||||||
bool mIsNpc;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// \a weapon may be empty for hand-to-hand combat
|
/// \a weapon may be empty for hand-to-hand combat
|
||||||
|
|
|
@ -19,10 +19,13 @@
|
||||||
#include "actorutil.hpp"
|
#include "actorutil.hpp"
|
||||||
#include "coordinateconverter.hpp"
|
#include "coordinateconverter.hpp"
|
||||||
|
|
||||||
|
#include <osg/Quat>
|
||||||
|
|
||||||
MWMechanics::AiPackage::~AiPackage() {}
|
MWMechanics::AiPackage::~AiPackage() {}
|
||||||
|
|
||||||
MWMechanics::AiPackage::AiPackage() :
|
MWMechanics::AiPackage::AiPackage() :
|
||||||
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
|
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
|
||||||
|
mRotateOnTheRunChecks(0),
|
||||||
mIsShortcutting(false),
|
mIsShortcutting(false),
|
||||||
mShortcutProhibited(false), mShortcutFailPos()
|
mShortcutProhibited(false), mShortcutFailPos()
|
||||||
{
|
{
|
||||||
|
@ -104,6 +107,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr
|
||||||
if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path
|
if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path
|
||||||
{
|
{
|
||||||
mPathFinder.buildSyncedPath(start, dest, actor.getCell());
|
mPathFinder.buildSyncedPath(start, dest, actor.getCell());
|
||||||
|
mRotateOnTheRunChecks = 3;
|
||||||
|
|
||||||
// give priority to go directly on target if there is minimal opportunity
|
// give priority to go directly on target if there is minimal opportunity
|
||||||
if (destInLOS && mPathFinder.getPath().size() > 1)
|
if (destInLOS && mPathFinder.getPath().size() > 1)
|
||||||
|
@ -143,7 +147,13 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // run to target
|
if (mRotateOnTheRunChecks == 0
|
||||||
|
|| isReachableRotatingOnTheRun(actor, *mPathFinder.getPath().begin())) // to prevent circling around a path point
|
||||||
|
{
|
||||||
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // move to the target
|
||||||
|
if (mRotateOnTheRunChecks > 0) mRotateOnTheRunChecks--;
|
||||||
|
}
|
||||||
|
|
||||||
// handle obstacles on the way
|
// handle obstacles on the way
|
||||||
evadeObstacles(actor, duration, pos);
|
evadeObstacles(actor, duration, pos);
|
||||||
}
|
}
|
||||||
|
@ -292,3 +302,32 @@ bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest)
|
||||||
|
{
|
||||||
|
// get actor's shortest radius for moving in circle
|
||||||
|
float speed = actor.getClass().getSpeed(actor);
|
||||||
|
speed += speed * 0.1f; // 10% real speed inaccuracy
|
||||||
|
float radius = speed / MAX_VEL_ANGULAR_RADIANS;
|
||||||
|
|
||||||
|
// get radius direction to the center
|
||||||
|
const float* rot = actor.getRefData().getPosition().rot;
|
||||||
|
osg::Quat quatRot(rot[0], -osg::X_AXIS, rot[1], -osg::Y_AXIS, rot[2], -osg::Z_AXIS);
|
||||||
|
osg::Vec3f dir = quatRot * osg::Y_AXIS; // actor's orientation direction is a tangent to circle
|
||||||
|
osg::Vec3f radiusDir = dir ^ osg::Z_AXIS; // radius is perpendicular to a tangent
|
||||||
|
radiusDir.normalize();
|
||||||
|
radiusDir *= radius;
|
||||||
|
|
||||||
|
// pick up the nearest center candidate
|
||||||
|
osg::Vec3f dest_ = PathFinder::MakeOsgVec3(dest);
|
||||||
|
osg::Vec3f pos = actor.getRefData().getPosition().asVec3();
|
||||||
|
osg::Vec3f center1 = pos - radiusDir;
|
||||||
|
osg::Vec3f center2 = pos + radiusDir;
|
||||||
|
osg::Vec3f center = (center1 - dest_).length2() < (center2 - dest_).length2() ? center1 : center2;
|
||||||
|
|
||||||
|
float distToDest = (center - dest_).length();
|
||||||
|
|
||||||
|
// if pathpoint is reachable for the actor rotating on the run:
|
||||||
|
// no points of actor's circle should be farther from the center than destination point
|
||||||
|
return (radius <= distToDest);
|
||||||
|
}
|
||||||
|
|
|
@ -97,6 +97,9 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool isTargetMagicallyHidden(const MWWorld::Ptr& target);
|
bool isTargetMagicallyHidden(const MWWorld::Ptr& target);
|
||||||
|
|
||||||
|
/// Return if actor's rotation speed is sufficient to rotate to the destination pathpoint on the run. Otherwise actor should rotate while standing.
|
||||||
|
static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Handles path building and shortcutting with obstacles avoiding
|
/// Handles path building and shortcutting with obstacles avoiding
|
||||||
/** \return If the actor has arrived at his destination **/
|
/** \return If the actor has arrived at his destination **/
|
||||||
|
@ -123,6 +126,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
osg::Vec3f mLastActorPos;
|
osg::Vec3f mLastActorPos;
|
||||||
|
|
||||||
|
short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility
|
||||||
|
|
||||||
bool mIsShortcutting; // if shortcutting at the moment
|
bool mIsShortcutting; // if shortcutting at the moment
|
||||||
bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt
|
bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt
|
||||||
ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail
|
ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail
|
||||||
|
|
|
@ -1771,7 +1771,7 @@ void CharacterController::update(float duration)
|
||||||
float realHealthLost = static_cast<float>(healthLost * (1.0f - 0.25f * fatigueTerm));
|
float realHealthLost = static_cast<float>(healthLost * (1.0f - 0.25f * fatigueTerm));
|
||||||
health.setCurrent(health.getCurrent() - realHealthLost);
|
health.setCurrent(health.getCurrent() - realHealthLost);
|
||||||
cls.getCreatureStats(mPtr).setHealth(health);
|
cls.getCreatureStats(mPtr).setHealth(health);
|
||||||
cls.onHit(mPtr, realHealthLost, true, MWWorld::Ptr(), MWWorld::Ptr(), true);
|
cls.onHit(mPtr, realHealthLost, true, MWWorld::Ptr(), MWWorld::Ptr(), osg::Vec3f(), true);
|
||||||
|
|
||||||
const int acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics);
|
const int acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics);
|
||||||
if (healthLost > (acrobaticsSkill * fatigueTerm))
|
if (healthLost > (acrobaticsSkill * fatigueTerm))
|
||||||
|
|
|
@ -190,7 +190,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue))
|
if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue))
|
||||||
{
|
{
|
||||||
victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false);
|
victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, osg::Vec3f(), false);
|
||||||
MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker);
|
MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -218,9 +218,6 @@ namespace MWMechanics
|
||||||
if (weapon != projectile)
|
if (weapon != projectile)
|
||||||
appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition);
|
appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition);
|
||||||
|
|
||||||
if (damage > 0)
|
|
||||||
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
|
|
||||||
|
|
||||||
// Non-enchanted arrows shot at enemies have a chance to turn up in their inventory
|
// Non-enchanted arrows shot at enemies have a chance to turn up in their inventory
|
||||||
if (victim != getPlayer()
|
if (victim != getPlayer()
|
||||||
&& !appliedEnchantment)
|
&& !appliedEnchantment)
|
||||||
|
@ -230,7 +227,7 @@ namespace MWMechanics
|
||||||
victim.getClass().getContainerStore(victim).add(projectile, 1, victim);
|
victim.getClass().getContainerStore(victim).add(projectile, 1, victim);
|
||||||
}
|
}
|
||||||
|
|
||||||
victim.getClass().onHit(victim, damage, true, projectile, attacker, true);
|
victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
float getHitChance(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, int skillValue)
|
float getHitChance(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, int skillValue)
|
||||||
|
|
|
@ -569,7 +569,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
// Notify the target actor they've been hit
|
// Notify the target actor they've been hit
|
||||||
if (anyHarmfulEffect && target.getClass().isActor() && target != caster && !caster.isEmpty() && caster.getClass().isActor())
|
if (anyHarmfulEffect && target.getClass().isActor() && target != caster && !caster.isEmpty() && caster.getClass().isActor())
|
||||||
target.getClass().onHit(target, 0.f, true, MWWorld::Ptr(), caster, true);
|
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude)
|
bool CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude)
|
||||||
|
|
|
@ -98,7 +98,7 @@ namespace MWWorld
|
||||||
throw std::runtime_error("class cannot block");
|
throw std::runtime_error("class cannot block");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, bool successful) const
|
void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const
|
||||||
{
|
{
|
||||||
throw std::runtime_error("class cannot be hit");
|
throw std::runtime_error("class cannot be hit");
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,7 @@ namespace MWWorld
|
||||||
/// enums. ignored for creature attacks.
|
/// enums. ignored for creature attacks.
|
||||||
/// (default implementation: throw an exception)
|
/// (default implementation: throw an exception)
|
||||||
|
|
||||||
virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const;
|
virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const;
|
||||||
///< Alerts \a ptr that it's being hit for \a damage points to health if \a ishealth is
|
///< Alerts \a ptr that it's being hit for \a damage points to health if \a ishealth is
|
||||||
/// true (else fatigue) by \a object (sword, arrow, etc). \a attacker specifies the
|
/// true (else fatigue) by \a object (sword, arrow, etc). \a attacker specifies the
|
||||||
/// actor responsible for the attack, and \a successful specifies if the hit is
|
/// actor responsible for the attack, and \a successful specifies if the hit is
|
||||||
|
|
Loading…
Reference in a new issue